Skip to content

Commit 70b96ce

Browse files
authored
Merge pull request #782 from zrezke/cam_test_gui
Cam test gui
2 parents 1f162f1 + e914e27 commit 70b96ce

File tree

4 files changed

+533
-58
lines changed

4 files changed

+533
-58
lines changed

utilities/README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,28 @@ pyinstaller --onefile -w --icon=assets/icon.ico --add-data="assets/icon.ico;asse
2424
```
2525

2626
Optionally, append `--runtime-tmpdir [path or .]` to modify where the temporary directory should be created when launched.
27+
28+
29+
## Cam Test
30+
Run:
31+
```sh
32+
python3 cam_test.py
33+
```
34+
To start cam test with GUI.
35+
Run cam_test.py with args to start cam test without GUI:
36+
37+
### Bundled executable
38+
Requirements:
39+
```
40+
# Linux/macOS
41+
python3 -m pip install pyinstaller
42+
# Windows
43+
python -m pip install pyinstaller
44+
```
45+
46+
To build a bundled executable issue the following command:
47+
```sh
48+
pyinstaller -w cam_test.py --hidden-import PyQt5.sip
49+
```
50+
51+
The executable will be located in `dist/cam_test` folder.

utilities/cam_test.py

Lines changed: 128 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -37,22 +37,29 @@
3737

3838
import cv2
3939
import argparse
40-
import depthai as dai
4140
import collections
4241
import time
4342
from itertools import cycle
4443
from pathlib import Path
44+
import sys
45+
import cam_test_gui
46+
import signal
47+
4548

4649
def socket_type_pair(arg):
4750
socket, type = arg.split(',')
48-
if not (socket in ['rgb', 'left', 'right', 'camd']): raise ValueError("")
49-
if not (type in ['m', 'mono', 'c', 'color']): raise ValueError("")
51+
if not (socket in ['rgb', 'left', 'right', 'camd']):
52+
raise ValueError("")
53+
if not (type in ['m', 'mono', 'c', 'color']):
54+
raise ValueError("")
5055
is_color = True if type in ['c', 'color'] else False
5156
return [socket, is_color]
5257

58+
5359
parser = argparse.ArgumentParser()
5460
parser.add_argument('-cams', '--cameras', type=socket_type_pair, nargs='+',
55-
default=[['rgb', True], ['left', False], ['right', False], ['camd', True]],
61+
default=[['rgb', True], ['left', False],
62+
['right', False], ['camd', True]],
5663
help="Which camera sockets to enable, and type: c[olor] / m[ono]. "
5764
"E.g: -cams rgb,m right,c . Default: rgb,c left,m right,m camd,c")
5865
parser.add_argument('-mres', '--mono-resolution', type=int, default=800, choices={480, 400, 720, 800},
@@ -71,8 +78,25 @@ def socket_type_pair(arg):
7178
help="Make OpenCV windows resizable. Note: may introduce some artifacts")
7279
parser.add_argument('-tun', '--camera-tuning', type=Path,
7380
help="Path to custom camera tuning database")
81+
parser.add_argument('-d', '--device', default="", type=str,
82+
help="Optional MX ID of the device to connect to.")
83+
84+
parser.add_argument('-ctimeout', '--connection-timeout', default=30000,
85+
help="Connection timeout in ms. Default: %(default)s (sets DEPTHAI_CONNECTION_TIMEOUT environment variable)")
86+
87+
parser.add_argument('-btimeout', '--boot-timeout', default=30000,
88+
help="Boot timeout in ms. Default: %(default)s (sets DEPTHAI_BOOT_TIMEOUT environment variable)")
89+
7490
args = parser.parse_args()
7591

92+
# Set timeouts before importing depthai
93+
os.environ["DEPTHAI_CONNECTION_TIMEOUT"] = str(args.connection_timeout)
94+
os.environ["DEPTHAI_BOOT_TIMEOUT"] = str(args.boot_timeout)
95+
import depthai as dai
96+
97+
if len(sys.argv) == 1:
98+
cam_test_gui.main()
99+
76100
cam_list = []
77101
cam_type_color = {}
78102
print("Enabled cameras:")
@@ -85,24 +109,24 @@ def socket_type_pair(arg):
85109
print("DepthAI path:", dai.__file__)
86110

87111
cam_socket_opts = {
88-
'rgb' : dai.CameraBoardSocket.RGB, # Or CAM_A
89-
'left' : dai.CameraBoardSocket.LEFT, # Or CAM_B
90-
'right': dai.CameraBoardSocket.RIGHT, # Or CAM_C
91-
'camd' : dai.CameraBoardSocket.CAM_D,
112+
'rgb': dai.CameraBoardSocket.RGB, # Or CAM_A
113+
'left': dai.CameraBoardSocket.LEFT, # Or CAM_B
114+
'right': dai.CameraBoardSocket.RIGHT, # Or CAM_C
115+
'camd': dai.CameraBoardSocket.CAM_D,
92116
}
93117

94118
cam_socket_to_name = {
95-
'RGB' : 'rgb',
96-
'LEFT' : 'left',
119+
'RGB': 'rgb',
120+
'LEFT': 'left',
97121
'RIGHT': 'right',
98122
'CAM_D': 'camd',
99123
}
100124

101125
rotate = {
102-
'rgb' : args.rotate in ['all', 'rgb'],
103-
'left' : args.rotate in ['all', 'mono'],
126+
'rgb': args.rotate in ['all', 'rgb'],
127+
'left': args.rotate in ['all', 'mono'],
104128
'right': args.rotate in ['all', 'mono'],
105-
'camd' : args.rotate in ['all', 'rgb'],
129+
'camd': args.rotate in ['all', 'rgb'],
106130
}
107131

108132
mono_res_opts = {
@@ -134,9 +158,11 @@ def __init__(self, window_size=30):
134158
self.fps = 0
135159

136160
def update(self, timestamp=None):
137-
if timestamp == None: timestamp = time.monotonic()
161+
if timestamp == None:
162+
timestamp = time.monotonic()
138163
count = len(self.dq)
139-
if count > 0: self.fps = count / (timestamp - self.dq[0])
164+
if count > 0:
165+
self.fps = count / (timestamp - self.dq[0])
140166
self.dq.append(timestamp)
141167

142168
def get(self):
@@ -145,7 +171,7 @@ def get(self):
145171
# Start defining a pipeline
146172
pipeline = dai.Pipeline()
147173
# Uncomment to get better throughput
148-
#pipeline.setXLinkChunkSize(0)
174+
# pipeline.setXLinkChunkSize(0)
149175

150176
control = pipeline.createXLinkIn()
151177
control.setStreamName('control')
@@ -159,21 +185,21 @@ def get(self):
159185
cam[c] = pipeline.createColorCamera()
160186
cam[c].setResolution(color_res_opts[args.color_resolution])
161187
cam[c].setIspScale(1, args.isp_downscale)
162-
#cam[c].initialControl.setManualFocus(85) # TODO
188+
# cam[c].initialControl.setManualFocus(85) # TODO
163189
cam[c].isp.link(xout[c].input)
164190
else:
165191
cam[c] = pipeline.createMonoCamera()
166192
cam[c].setResolution(mono_res_opts[args.mono_resolution])
167193
cam[c].out.link(xout[c].input)
168194
cam[c].setBoardSocket(cam_socket_opts[c])
169195
# Num frames to capture on trigger, with first to be discarded (due to degraded quality)
170-
#cam[c].initialControl.setExternalTrigger(2, 1)
171-
#cam[c].initialControl.setStrobeExternal(48, 1)
172-
#cam[c].initialControl.setFrameSyncMode(dai.CameraControl.FrameSyncMode.INPUT)
196+
# cam[c].initialControl.setExternalTrigger(2, 1)
197+
# cam[c].initialControl.setStrobeExternal(48, 1)
198+
# cam[c].initialControl.setFrameSyncMode(dai.CameraControl.FrameSyncMode.INPUT)
173199

174-
#cam[c].initialControl.setManualExposure(15000, 400) # exposure [us], iso
200+
# cam[c].initialControl.setManualExposure(15000, 400) # exposure [us], iso
175201
# When set, takes effect after the first 2 frames
176-
#cam[c].initialControl.setManualWhiteBalance(4000) # light temperature in K, 1000..12000
202+
# cam[c].initialControl.setManualWhiteBalance(4000) # light temperature in K, 1000..12000
177203
control.out.link(cam[c].inputControl)
178204
if rotate[c]:
179205
cam[c].setImageOrientation(dai.CameraImageOrientation.ROTATE_180_DEG)
@@ -183,13 +209,26 @@ def get(self):
183209
if args.camera_tuning:
184210
pipeline.setCameraTuningBlobPath(str(args.camera_tuning))
185211

212+
def exit_cleanly(signum, frame):
213+
print("Exiting cleanly")
214+
cv2.destroyAllWindows()
215+
sys.exit(0)
216+
217+
signal.signal(signal.SIGINT, exit_cleanly)
218+
219+
186220
# Pipeline is defined, now we can connect to the device
187-
with dai.Device(pipeline) as device:
188-
#print('Connected cameras:', [c.name for c in device.getConnectedCameras()])
221+
device = dai.Device.getDeviceByMxId(args.device)
222+
dai_device_args = [pipeline]
223+
if device[0]:
224+
dai_device_args.append(device[1])
225+
with dai.Device(*dai_device_args) as device:
226+
# print('Connected cameras:', [c.name for c in device.getConnectedCameras()])
189227
print('Connected cameras:')
190228
cam_name = {}
191229
for p in device.getConnectedCameraFeatures():
192-
print(f' -socket {p.socket.name:6}: {p.sensorName:6} {p.width:4} x {p.height:4} focus:', end='')
230+
print(
231+
f' -socket {p.socket.name:6}: {p.sensorName:6} {p.width:4} x {p.height:4} focus:', end='')
193232
print('auto ' if p.hasAutofocus else 'fixed', '- ', end='')
194233
print(*[type.name for type in p.supportedTypes])
195234
cam_name[cam_socket_to_name[p.socket.name]] = p.sensorName
@@ -237,9 +276,12 @@ def get(self):
237276
dotIntensity = 0
238277
floodIntensity = 0
239278

240-
awb_mode = cycle([item for name, item in vars(dai.CameraControl.AutoWhiteBalanceMode).items() if name.isupper()])
241-
anti_banding_mode = cycle([item for name, item in vars(dai.CameraControl.AntiBandingMode).items() if name.isupper()])
242-
effect_mode = cycle([item for name, item in vars(dai.CameraControl.EffectMode).items() if name.isupper()])
279+
awb_mode = cycle([item for name, item in vars(
280+
dai.CameraControl.AutoWhiteBalanceMode).items() if name.isupper()])
281+
anti_banding_mode = cycle([item for name, item in vars(
282+
dai.CameraControl.AntiBandingMode).items() if name.isupper()])
283+
effect_mode = cycle([item for name, item in vars(
284+
dai.CameraControl.EffectMode).items() if name.isupper()])
243285

244286
ae_comp = 0
245287
ae_lock = False
@@ -252,34 +294,43 @@ def get(self):
252294
chroma_denoise = 0
253295
control = 'none'
254296

255-
print("Cam:", *[' ' + c.ljust(8) for c in cam_list], "[host | capture timestamp]")
297+
print("Cam:", *[' ' + c.ljust(8)
298+
for c in cam_list], "[host | capture timestamp]")
256299

257300
capture_list = []
258301
while True:
259302
for c in cam_list:
260-
pkt = q[c].tryGet()
303+
try:
304+
pkt = q[c].tryGet()
305+
except Exception as e:
306+
print(e)
307+
exit_cleanly(0, 0)
261308
if pkt is not None:
262309
fps_host[c].update()
263310
fps_capt[c].update(pkt.getTimestamp().total_seconds())
264311
frame = pkt.getCvFrame()
265312
if c in capture_list:
266313
width, height = pkt.getWidth(), pkt.getHeight()
267314
capture_file_name = ('capture_' + c + '_' + cam_name[c]
268-
+ '_' + str(width) + 'x' + str(height)
269-
+ '_exp_' + str(int(pkt.getExposureTime().total_seconds()*1e6))
270-
+ '_iso_' + str(pkt.getSensitivity())
271-
+ '_lens_' + str(pkt.getLensPosition())
272-
+ '_' + capture_time
273-
+ '_' + str(pkt.getSequenceNum())
274-
+ ".png"
275-
)
315+
+ '_' + str(width) + 'x' + str(height)
316+
+ '_exp_' +
317+
str(int(
318+
pkt.getExposureTime().total_seconds()*1e6))
319+
+ '_iso_' + str(pkt.getSensitivity())
320+
+ '_lens_' +
321+
str(pkt.getLensPosition())
322+
+ '_' + capture_time
323+
+ '_' + str(pkt.getSequenceNum())
324+
+ ".png"
325+
)
276326
print("\nSaving:", capture_file_name)
277327
cv2.imwrite(capture_file_name, frame)
278328
capture_list.remove(c)
279329

280330
cv2.imshow(c, frame)
281331
print("\rFPS:",
282-
*["{:6.2f}|{:6.2f}".format(fps_host[c].get(), fps_capt[c].get()) for c in cam_list],
332+
*["{:6.2f}|{:6.2f}".format(fps_host[c].get(),
333+
fps_capt[c].get()) for c in cam_list],
283334
end='', flush=True)
284335

285336
key = cv2.waitKey(1)
@@ -297,26 +348,33 @@ def get(self):
297348
elif key == ord('f'):
298349
print("Autofocus enable, continuous")
299350
ctrl = dai.CameraControl()
300-
ctrl.setAutoFocusMode(dai.CameraControl.AutoFocusMode.CONTINUOUS_VIDEO)
351+
ctrl.setAutoFocusMode(
352+
dai.CameraControl.AutoFocusMode.CONTINUOUS_VIDEO)
301353
controlQueue.send(ctrl)
302354
elif key == ord('e'):
303355
print("Autoexposure enable")
304356
ctrl = dai.CameraControl()
305357
ctrl.setAutoExposureEnable()
306358
controlQueue.send(ctrl)
307359
elif key in [ord(','), ord('.')]:
308-
if key == ord(','): lensPos -= LENS_STEP
309-
if key == ord('.'): lensPos += LENS_STEP
360+
if key == ord(','):
361+
lensPos -= LENS_STEP
362+
if key == ord('.'):
363+
lensPos += LENS_STEP
310364
lensPos = clamp(lensPos, lensMin, lensMax)
311365
print("Setting manual focus, lens position: ", lensPos)
312366
ctrl = dai.CameraControl()
313367
ctrl.setManualFocus(lensPos)
314368
controlQueue.send(ctrl)
315369
elif key in [ord('i'), ord('o'), ord('k'), ord('l')]:
316-
if key == ord('i'): expTime -= EXP_STEP
317-
if key == ord('o'): expTime += EXP_STEP
318-
if key == ord('k'): sensIso -= ISO_STEP
319-
if key == ord('l'): sensIso += ISO_STEP
370+
if key == ord('i'):
371+
expTime -= EXP_STEP
372+
if key == ord('o'):
373+
expTime += EXP_STEP
374+
if key == ord('k'):
375+
sensIso -= ISO_STEP
376+
if key == ord('l'):
377+
sensIso += ISO_STEP
320378
expTime = clamp(expTime, expMin, expMax)
321379
sensIso = clamp(sensIso, sensMin, sensMax)
322380
print("Setting manual exposure, time: ", expTime, "iso: ", sensIso)
@@ -356,21 +414,33 @@ def get(self):
356414
floodIntensity = 0
357415
device.setIrFloodLightBrightness(floodIntensity)
358416
elif key >= 0 and chr(key) in '34567890[]':
359-
if key == ord('3'): control = 'awb_mode'
360-
elif key == ord('4'): control = 'ae_comp'
361-
elif key == ord('5'): control = 'anti_banding_mode'
362-
elif key == ord('6'): control = 'effect_mode'
363-
elif key == ord('7'): control = 'brightness'
364-
elif key == ord('8'): control = 'contrast'
365-
elif key == ord('9'): control = 'saturation'
366-
elif key == ord('0'): control = 'sharpness'
367-
elif key == ord('['): control = 'luma_denoise'
368-
elif key == ord(']'): control = 'chroma_denoise'
417+
if key == ord('3'):
418+
control = 'awb_mode'
419+
elif key == ord('4'):
420+
control = 'ae_comp'
421+
elif key == ord('5'):
422+
control = 'anti_banding_mode'
423+
elif key == ord('6'):
424+
control = 'effect_mode'
425+
elif key == ord('7'):
426+
control = 'brightness'
427+
elif key == ord('8'):
428+
control = 'contrast'
429+
elif key == ord('9'):
430+
control = 'saturation'
431+
elif key == ord('0'):
432+
control = 'sharpness'
433+
elif key == ord('['):
434+
control = 'luma_denoise'
435+
elif key == ord(']'):
436+
control = 'chroma_denoise'
369437
print("Selected control:", control)
370438
elif key in [ord('-'), ord('_'), ord('+'), ord('=')]:
371439
change = 0
372-
if key in [ord('-'), ord('_')]: change = -1
373-
if key in [ord('+'), ord('=')]: change = 1
440+
if key in [ord('-'), ord('_')]:
441+
change = -1
442+
if key in [ord('+'), ord('=')]:
443+
change = 1
374444
ctrl = dai.CameraControl()
375445
if control == 'none':
376446
print("Please select a control first using keys 3..9 0 [ ]")

0 commit comments

Comments
 (0)