Skip to content

Commit 9aef1f9

Browse files
committed
Merge branch 'RobotAndPiSideCode'
2 parents 46966ad + 5d2b4f6 commit 9aef1f9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+4700
-3784
lines changed

PiSideCode/Calibration/calibration.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ def getReprojectionError(self) -> float:
296296

297297
return totalError / len(self.objPoints)
298298

299-
# Load a calibration file
299+
# Load a calibration file
300300
def loadCalibration(self, file: str):
301301
with open(file, "r") as f:
302302
self.calibrationData = json.load(f)

PiSideCode/Camera/BootUp.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ def parseConfig(camPath):
1313
print("File Not Found")
1414
fileDump = {
1515
"Resolution": None,
16-
"Gain": None,
16+
"Brightness": None,
1717
"Exposure": None,
1818
}
1919

@@ -23,8 +23,13 @@ def parseConfig(camPath):
2323
json.dump(fileDump, outFile)
2424
return fileDump
2525

26+
2627
# Write config data to a file following naming conventions
27-
def writeConfig(camPath, resolution, gain, exposure):
28+
def writeConfig(camPath, resolution, brightness, exposure):
2829
with open(f"./Camera/CameraConfigs/ConfigSettings_{camPath}.json", "w") as data:
29-
fileDump = {"Resolution": resolution, "Gain": gain, "Exposure": exposure}
30+
fileDump = {
31+
"Resolution": resolution,
32+
"Brightness": brightness,
33+
"Exposure": exposure,
34+
}
3035
json.dump(fileDump, data)
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"Resolution": [640, 480], "Gain": 1.0, "Exposure": -6.0}
1+
{"Resolution": [640, 480], "Brightness": 1.0, "Exposure": -6.0}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"Resolution": [640, 480], "Gain": -1.0, "Exposure": 54.0}
1+
{"Resolution": [1600, 1300], "Brightness": 10.0, "Exposure": 105.0}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"Resolution": [1600, 1200], "Gain": 3.0, "Exposure": 3.0}
1+
{"Resolution": [1600, 1200], "Brightness": 3.0, "Exposure": 3.0}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"Resolution": [1600, 1300], "Brightness": -1.0, "Exposure": 156.0}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"Resolution": [960, 544], "Gain": -1.0, "Exposure": 156.0}
1+
{"Resolution": [960, 544], "Brightness": -1.0, "Exposure": 156.0}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"Resolution": [640, 480], "Gain": -1.0, "Exposure": 45.0}
1+
{"Resolution": [640, 480], "Brightness": -1.0, "Exposure": 45.0}

PiSideCode/Camera/CameraInfo.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ def __init__(
88
self.identifier = identifier
99
self.resolution = resolution
1010
self.supportedResolutions = supportedResolutions
11+
self.exposureRange = (0, 100, 1)
12+
self.brightnessRange = (0, 240, 1)
13+
self.validFormats = []
1114

1215
# Calibration
1316
self.K = K

PiSideCode/Camera/camera.py

Lines changed: 123 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -38,34 +38,84 @@ def __init__(self):
3838
Cameras.logger.info(f"Camera found: {camPath}")
3939

4040
# Get supported resolutions using v4l2-ctl and a little regex
41-
supportedResolutions = sorted( # Sort values
41+
formatParams = subprocess.run(
42+
[
43+
"v4l2-ctl",
44+
"-d",
45+
path,
46+
"--list-formats-ext",
47+
],
48+
capture_output=True,
49+
).stdout.decode("utf-8")
50+
51+
supportedResolutions = sorted( # Sort values
4252
list(
43-
set( # Unique values
53+
set( # Unique values
4454
map(
4555
lambda x: (
4656
int(x.split("x")[0]),
4757
int(x.split("x")[1]),
4858
),
4959
re.findall(
5060
"[0-9]+x[0-9]+",
51-
subprocess.run(
52-
[
53-
"v4l2-ctl",
54-
"-d",
55-
path,
56-
"--list-formats-ext",
57-
],
58-
capture_output=True,
59-
).stdout.decode("utf-8"),
61+
formatParams,
6062
),
6163
)
6264
)
6365
)
6466
)
6567

68+
formats = set(
69+
map(
70+
lambda x: re.search("'....'", x).group().strip("'"),
71+
re.findall(
72+
": '....'",
73+
formatParams,
74+
),
75+
)
76+
)
77+
78+
settingParams = subprocess.run(
79+
["v4l2-ctl", "-d", path, "--list-ctrls-menus"],
80+
capture_output=True,
81+
).stdout.decode("utf-8")
82+
83+
exposureRange = tuple(
84+
map(
85+
lambda x: int(x.split("=")[-1]),
86+
re.search(
87+
"exposure_absolute .* min=[0-9]+ max=[0-9]+ step=[0-9]+",
88+
settingParams,
89+
)
90+
.group()
91+
.split()[-3:],
92+
)
93+
)
94+
95+
brightnessRange = tuple(
96+
map(
97+
lambda x: int(x.split("=")[-1]),
98+
re.search(
99+
"brightness .* min=[0-9]+ max=[0-9]+ step=[0-9]+",
100+
settingParams,
101+
)
102+
.group()
103+
.split()[-3:],
104+
)
105+
)
106+
66107
Cameras.logger.info(
67108
f"Supported resolutions: {supportedResolutions}"
68109
)
110+
Cameras.logger.info(
111+
f"Supported formats: {formats}"
112+
)
113+
Cameras.logger.info(
114+
f"Supported exposures (min, max, step): {exposureRange}"
115+
)
116+
Cameras.logger.info(
117+
f"Supported brightnesses (min, max, step): {brightnessRange}"
118+
)
69119

70120
# Disable buffer so we always pull the latest image
71121
cam.set(cv2.CAP_PROP_BUFFERSIZE, 1)
@@ -81,6 +131,9 @@ def __init__(self):
81131
# Initialize CameraInfo object
82132
self.info[camPath] = CameraInfo(cam, camPath, supportedResolutions)
83133
self.info[camPath].resolution = self.getResolutions()[camPath]
134+
self.info[camPath].exposureRange = exposureRange
135+
self.info[camPath].brightnessRange = brightnessRange
136+
self.info[camPath].validFormats = formats
84137

85138
# Attempt to import config from file
86139
self.importConfig(camPath)
@@ -89,13 +142,13 @@ def __init__(self):
89142
writeConfig(
90143
self.cleanIdentifier(camPath),
91144
self.getResolutions()[camPath],
92-
self.getGains()[camPath],
145+
self.getBrightnesss()[camPath],
93146
self.getExposures()[camPath],
94147
)
95148

96149
else:
97150
Cameras.logger.error("Unknown platform!")
98-
151+
99152
def setCalibration(self, identifier, K, D):
100153
self.info[identifier].K = K
101154
self.info[identifier].D = D
@@ -118,6 +171,16 @@ def getFrames(self):
118171
frames[identifier] = img
119172
return frames
120173

174+
# Grab frames from each camera specifically for processing
175+
def getFramesForProcessing(self):
176+
frames = {}
177+
connections = {}
178+
for identifier, camInfo in self.info.items():
179+
flag, img = camInfo.cam.read()
180+
frames[identifier] = img
181+
connections[identifier] = True if flag else False
182+
return (connections, frames)
183+
121184
# Sets resolution, video format, and FPS
122185
def setResolution(self, identifier, resolution):
123186
if resolution is None:
@@ -127,8 +190,10 @@ def setResolution(self, identifier, resolution):
127190
# os.system(f"v4l2-ctl -d /dev/v4l/by-path/{identifier} --set-fmt-video=width={resolution[0]},height={resolution[1]}")
128191
self.info[identifier].cam.set(cv2.CAP_PROP_FRAME_HEIGHT, resolution[1])
129192
self.info[identifier].cam.set(cv2.CAP_PROP_FRAME_WIDTH, resolution[0])
130-
self.info[identifier].cam.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(*"MJPG"))
131-
self.info[identifier].cam.set(cv2.CAP_PROP_FPS, 30)
193+
self.info[identifier].cam.set(
194+
cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(*("MJPG" if "MJPG" in self.info[identifier].validFormats else "GREY"))
195+
)
196+
self.info[identifier].cam.set(cv2.CAP_PROP_FPS, 30) # Lower can be better
132197
resolution = tuple(resolution)
133198

134199
# Test if resolution got set
@@ -145,31 +210,35 @@ def setResolution(self, identifier, resolution):
145210
writeConfig(
146211
self.cleanIdentifier(identifier),
147212
resolution,
148-
self.getGains()[identifier],
213+
self.getBrightnesss()[identifier],
149214
self.getExposures()[identifier],
150215
)
151216

152217
Cameras.logger.info(f"Resolution set to {resolution} for {identifier}")
153218
return True
154219

155-
def setGain(self, identifier, gain):
156-
if gain is None:
157-
Cameras.logger.info("Gain not set")
220+
def setBrightness(self, identifier, brightness):
221+
if brightness is None:
222+
Cameras.logger.info("Brightness not set")
158223
return False
159224

160-
# Set gain through command line
161-
os.system(f"v4l2-ctl -d /dev/v4l/by-path/{identifier} --set-ctrl gain={gain}")
225+
# Set brightness through command line
226+
returned = os.system(
227+
f"v4l2-ctl -d /dev/v4l/by-path/{identifier} --set-ctrl brightness={brightness}"
228+
)
162229

163230
# Check if it set, if so write it to a file
164-
if self.info[identifier].cam.get(cv2.CAP_PROP_GAIN) != gain:
165-
Cameras.logger.warning(f"Gain not set: {gain} not accepted")
231+
if returned != 0:
232+
Cameras.logger.warning(
233+
f"Brightness not set: {brightness} not accepted on camera {identifier}"
234+
)
166235
return False
167236
else:
168-
Cameras.logger.info(f"Gain set to {gain}")
237+
Cameras.logger.info(f"Brightness set to {brightness}")
169238
writeConfig(
170239
self.cleanIdentifier(identifier),
171240
self.getResolutions()[identifier],
172-
gain,
241+
brightness,
173242
self.getExposures()[identifier],
174243
)
175244
return True
@@ -180,38 +249,40 @@ def setExposure(self, identifier, exposure):
180249
return False
181250

182251
# Set exposure with a command
183-
os.system(
184-
f"v4l2-ctl -d /dev/v4l/by-path/{identifier} --set-ctrl exposure_auto=1 --set-ctrl exposure_absolute={exposure}"
252+
returned = os.system(
253+
f"v4l2-ctl -d /dev/v4l/by-path/{identifier} --set-ctrl exposure_absolute={exposure}" # --set-ctrl exposure_auto=1
185254
)
186255

187256
# Check if it set, if so write it to a file
188-
if self.info[identifier].cam.get(cv2.CAP_PROP_EXPOSURE) != exposure:
189-
Cameras.logger.warning(f"Exposure not set: {exposure} not accepted")
257+
if returned != 0:
258+
Cameras.logger.warning(
259+
f"Exposure not set: {exposure} not accepted on camera {identifier}"
260+
)
190261
return False
191262
else:
192263
Cameras.logger.info(f"Exposure set to {exposure}")
193264

194265
writeConfig(
195266
self.cleanIdentifier(identifier),
196267
self.getResolutions()[identifier],
197-
self.getGains()[identifier],
268+
self.getBrightnesss()[identifier],
198269
exposure,
199270
)
200271
return True
201272

202273
# Return a dictionary of all camera exposures
203274
def getExposures(self):
204-
exposure = {}
205-
for identifier, camInfo in self.info.items():
206-
exposure[identifier] = camInfo.cam.get(cv2.CAP_PROP_EXPOSURE)
207-
return exposure
208-
209-
# Return a dictionary of all camera gains
210-
def getGains(self):
211-
gain = {}
212-
for identifier, camInfo in self.info.items():
213-
gain[identifier] = camInfo.cam.get(cv2.CAP_PROP_GAIN)
214-
return gain
275+
return {
276+
identifier: camInfo.cam.get(cv2.CAP_PROP_EXPOSURE)
277+
for identifier, camInfo in self.info.items()
278+
}
279+
280+
# Return a dictionary of all camera brightnesss
281+
def getBrightnesss(self):
282+
return {
283+
identifier: camInfo.cam.get(cv2.CAP_PROP_BRIGHTNESS)
284+
for identifier, camInfo in self.info.items()
285+
}
215286

216287
# Return a dictionary of all camera resolutions
217288
def getResolutions(self):
@@ -222,7 +293,13 @@ def getResolutions(self):
222293
int(camInfo.cam.get(cv2.CAP_PROP_FRAME_HEIGHT)),
223294
)
224295

225-
return resolution
296+
return {
297+
identifier: (
298+
int(camInfo.cam.get(cv2.CAP_PROP_FRAME_WIDTH)),
299+
int(camInfo.cam.get(cv2.CAP_PROP_FRAME_HEIGHT)),
300+
)
301+
for identifier, camInfo in self.info.items()
302+
}
226303

227304
# Find a calibration for the camera
228305
def importCalibration(self, identifier):
@@ -246,7 +323,7 @@ def importCalibration(self, identifier):
246323
f"Calibration not found for camera {identifier} at resolution {resolution}"
247324
)
248325
return False
249-
326+
250327
def importConfig(self, camPath):
251328
# Attempt to import config from file
252329
Cameras.logger.info(f"Attempting to import config for {camPath}")
@@ -267,15 +344,13 @@ def importConfig(self, camPath):
267344
camPath, config["Resolution"]
268345
): # Calls self.importCalibration iff resolution was set
269346
self.importCalibration(camPath)
270-
self.setGain(camPath, config["Gain"])
347+
self.setBrightness(camPath, config["Brightness"])
271348
self.setExposure(camPath, config["Exposure"])
272349

273350
else:
274351
self.importCalibration(camPath)
275-
Cameras.logger.warning(
276-
f"Camera config not found for camera {camPath}"
277-
)
278-
352+
Cameras.logger.warning(f"Camera config not found for camera {camPath}")
353+
279354
return config
280355

281356
@staticmethod

0 commit comments

Comments
 (0)