Skip to content

Commit

Permalink
Add gen2-mono-raw-controls app
Browse files Browse the repository at this point in the history
  • Loading branch information
alex-luxonis committed May 21, 2021
1 parent f6e909b commit 551cba5
Show file tree
Hide file tree
Showing 2 changed files with 299 additions and 0 deletions.
293 changes: 293 additions & 0 deletions gen2-mono-raw-controls/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,293 @@
#!/usr/bin/env python3

''' Control keys:
'IOKL' for manual exposure/iso:
Control: key[dec/inc] min..max
exposure time: I O 20..33000 [us]
sensitivity iso: K L 100..1600
To go back to auto controls:
'E' - autoexposure
Other camera controls:
'1' - AWB lock (true / false)
'2' - AE lock (true / false)
'3' - Select control: AWB mode
'4' - Select control: AE compensation
'5' - Select control: anti-banding/flicker mode
'6' - Select control: effect mode
'7' - Select control: brightness
'8' - Select control: contrast
'9' - Select control: saturation
'0' - Select control: sharpness
'[' - Select control: luma denoise
For the 'Select control: ...' options, use these keys to modify the value:
'-' or '_' to decrease
'+' or '=' to increase
'C' - capture a set of frames (PNG and unprocessed)
'Q' - quit
'''

import cv2
import numpy as np
import numba as nb
import depthai as dai

streams = []
# Enable one or both streams
streams.append('isp')
streams.append('raw')

''' Packing scheme for RAW10 - MIPI CSI-2
- 4 pixels: p0[9:0], p1[9:0], p2[9:0], p3[9:0]
- stored on 5 bytes (byte0..4) as:
| byte0[7:0] | byte1[7:0] | byte2[7:0] | byte3[7:0] | byte4[7:0] |
| p0[9:2] | p1[9:2] | p2[9:2] | p3[9:2] | p3[1:0],p2[1:0],p1[1:0],p0[1:0] |
'''
# Optimized with 'numba' as otherwise would be extremely slow (55 seconds per frame!)
@nb.njit(nb.uint16[::1] (nb.uint8[::1], nb.uint16[::1], nb.boolean), parallel=True, cache=True)
def unpack_raw10(input, out, expand16bit):
lShift = 6 if expand16bit else 0

#for i in np.arange(input.size // 5): # around 25ms per frame (with numba)
for i in nb.prange(input.size // 5): # around 5ms per frame
b4 = input[i * 5 + 4]
out[i * 4] = ((input[i * 5] << 2) | ( b4 & 0x3)) << lShift
out[i * 4 + 1] = ((input[i * 5 + 1] << 2) | ((b4 >> 2) & 0x3)) << lShift
out[i * 4 + 2] = ((input[i * 5 + 2] << 2) | ((b4 >> 4) & 0x3)) << lShift
out[i * 4 + 3] = ((input[i * 5 + 3] << 2) | (b4 >> 6) ) << lShift

return out

print("depthai:", dai.__version__, dai.__commit_datetime__)
pipeline = dai.Pipeline()

cam = pipeline.createMonoCamera()
cam.setBoardSocket(dai.CameraBoardSocket.LEFT) # or RIGHT
cam.setResolution(dai.MonoCameraProperties.SensorResolution.THE_800_P)
# Uncomment to be able to set a larger manual exposure, e.g: 10fps / 100ms
# cam.setFps(10)

# Camera control input
control = pipeline.createXLinkIn()
control.setStreamName('control')
control.out.link(cam.inputControl)

if 'isp' in streams:
xout_isp = pipeline.createXLinkOut()
xout_isp.setStreamName('isp')
cam.out.link(xout_isp.input)

if 'raw' in streams:
xout_raw = pipeline.createXLinkOut()
xout_raw.setStreamName('raw')
cam.raw.link(xout_raw.input)

device = dai.Device(pipeline)
device.startPipeline()

q_list = []
for s in streams:
q = device.getOutputQueue(name=s, maxSize=3, blocking=False)
q_list.append(q)
# Make window resizable, and configure initial size
cv2.namedWindow(s, cv2.WINDOW_NORMAL)
cv2.resizeWindow(s, (1280, 800))

controlQueue = device.getInputQueue('control')

# Manual exposure set step, configurable
EXP_STEP = 500 # us
ISO_STEP = 50

# Defaults and limits for manual focus/exposure controls

exp_time = 20000
exp_min = 20
# Note: need to reduce FPS (see .setFps) to be able to set higher exposure time
exp_max = 33000 # 1000000

sens_iso = 800
sens_min = 100
sens_max = 1600

# TODO make automatically iterable
awb_mode_idx = -1
awb_mode_list = [
dai.CameraControl.AutoWhiteBalanceMode.OFF,
dai.CameraControl.AutoWhiteBalanceMode.AUTO,
dai.CameraControl.AutoWhiteBalanceMode.INCANDESCENT,
dai.CameraControl.AutoWhiteBalanceMode.FLUORESCENT,
dai.CameraControl.AutoWhiteBalanceMode.WARM_FLUORESCENT,
dai.CameraControl.AutoWhiteBalanceMode.DAYLIGHT,
dai.CameraControl.AutoWhiteBalanceMode.CLOUDY_DAYLIGHT,
dai.CameraControl.AutoWhiteBalanceMode.TWILIGHT,
dai.CameraControl.AutoWhiteBalanceMode.SHADE,
]

anti_banding_mode_idx = -1
anti_banding_mode_list = [
dai.CameraControl.AntiBandingMode.OFF,
dai.CameraControl.AntiBandingMode.MAINS_50_HZ,
dai.CameraControl.AntiBandingMode.MAINS_60_HZ,
dai.CameraControl.AntiBandingMode.AUTO,
]

effect_mode_idx = -1
effect_mode_list = [
dai.CameraControl.EffectMode.OFF,
dai.CameraControl.EffectMode.MONO,
dai.CameraControl.EffectMode.NEGATIVE,
dai.CameraControl.EffectMode.SOLARIZE,
dai.CameraControl.EffectMode.SEPIA,
dai.CameraControl.EffectMode.POSTERIZE,
dai.CameraControl.EffectMode.WHITEBOARD,
dai.CameraControl.EffectMode.BLACKBOARD,
dai.CameraControl.EffectMode.AQUA,
]

ae_comp = 0 # Valid: -9 .. +9
ae_lock = False
awb_lock = False
saturation = 0
contrast = 0
brightness = 0
sharpness = 0
luma_denoise = 0
control = 'none'

def clamp(num, v0, v1): return max(v0, min(num, v1))

capture_flag = False
while True:
for q in q_list:
name = q.getName()
data = q.get()
width, height = data.getWidth(), data.getHeight()
payload = data.getData()
capture_file_info_str = ('capture_' + name
+ '_' + str(width) + 'x' + str(height)
+ '_' + str(data.getSequenceNum())
)
if name == 'isp':
if capture_flag:
filename = capture_file_info_str + '_P400.yuv'
print("Saving to file:", filename)
payload.tofile(filename)
shape = (height, width)
bgr = payload.reshape(shape).astype(np.uint8)
if name == 'raw':
# Preallocate the output buffer
unpacked = np.empty(payload.size * 4 // 5, dtype=np.uint16)
if capture_flag:
# Save to capture file on bits [9:0] of the 16-bit pixels
unpack_raw10(payload, unpacked, expand16bit=False)
filename = capture_file_info_str + '_10bit.bw'
print("Saving to file:", filename)
unpacked.tofile(filename)
# Full range for display, use bits [15:6] of the 16-bit pixels
unpack_raw10(payload, unpacked, expand16bit=True)
shape = (height, width)
bgr = unpacked.reshape(shape).astype(np.uint16)
if capture_flag: # Save to disk if `c` was pressed
filename = capture_file_info_str + '.png'
print("Saving to file:", filename)
bgr = np.ascontiguousarray(bgr) # just in case
cv2.imwrite(filename, bgr)
bgr = np.ascontiguousarray(bgr) # just in case
cv2.imshow(name, bgr)
# Reset capture_flag after iterating through all streams
capture_flag = False
key = cv2.waitKey(1)
if key == ord('c'):
capture_flag = True
elif key == ord('e'):
print("Autoexposure enable")
ctrl = dai.CameraControl()
ctrl.setAutoExposureEnable()
controlQueue.send(ctrl)
elif key in [ord('i'), ord('o'), ord('k'), ord('l')]:
if key == ord('i'): exp_time -= EXP_STEP
if key == ord('o'): exp_time += EXP_STEP
if key == ord('k'): sens_iso -= ISO_STEP
if key == ord('l'): sens_iso += ISO_STEP
exp_time = clamp(exp_time, exp_min, exp_max)
sens_iso = clamp(sens_iso, sens_min, sens_max)
print("Setting manual exposure, time:", exp_time, "iso:", sens_iso)
ctrl = dai.CameraControl()
ctrl.setManualExposure(exp_time, sens_iso)
controlQueue.send(ctrl)
elif key == ord('1'):
awb_lock = not awb_lock
print("Auto white balance lock:", awb_lock)
ctrl = dai.CameraControl()
ctrl.setAutoWhiteBalanceLock(awb_lock)
controlQueue.send(ctrl)
elif key == ord('2'):
ae_lock = not ae_lock
print("Auto exposure lock:", ae_lock)
ctrl = dai.CameraControl()
ctrl.setAutoExposureLock(ae_lock)
controlQueue.send(ctrl)
elif key >= 0 and chr(key) in '34567890[]':
if key == ord('3'): control = 'awb_mode'
elif key == ord('4'): control = 'ae_comp'
elif key == ord('5'): control = 'anti_banding_mode'
elif key == ord('6'): control = 'effect_mode'
elif key == ord('7'): control = 'brightness'
elif key == ord('8'): control = 'contrast'
elif key == ord('9'): control = 'saturation'
elif key == ord('0'): control = 'sharpness'
elif key == ord('['): control = 'luma_denoise'
print("Selected control:", control)
elif key in [ord('-'), ord('_'), ord('+'), ord('=')]:
change = 0
if key in [ord('-'), ord('_')]: change = -1
if key in [ord('+'), ord('=')]: change = 1
ctrl = dai.CameraControl()
if control == 'none':
print("Please select a control first using keys 3..9 0 [ ]")
elif control == 'ae_comp':
ae_comp = clamp(ae_comp + change, -9, 9)
print("Auto exposure compensation:", ae_comp)
ctrl.setAutoExposureCompensation(ae_comp)
elif control == 'anti_banding_mode':
cnt = len(anti_banding_mode_list)
anti_banding_mode_idx = (anti_banding_mode_idx + cnt + change) % cnt
anti_banding_mode = anti_banding_mode_list[anti_banding_mode_idx]
print("Anti-banding mode:", anti_banding_mode)
ctrl.setAntiBandingMode(anti_banding_mode)
elif control == 'awb_mode':
cnt = len(awb_mode_list)
awb_mode_idx = (awb_mode_idx + cnt + change) % cnt
awb_mode = awb_mode_list[awb_mode_idx]
print("Auto white balance mode:", awb_mode)
ctrl.setAutoWhiteBalanceMode(awb_mode)
elif control == 'effect_mode':
cnt = len(effect_mode_list)
effect_mode_idx = (effect_mode_idx + cnt + change) % cnt
effect_mode = effect_mode_list[effect_mode_idx]
print("Effect mode:", effect_mode)
ctrl.setEffectMode(effect_mode)
elif control == 'brightness':
brightness = clamp(brightness + change, -10, 10)
print("Brightness:", brightness)
ctrl.setBrightness(brightness)
elif control == 'contrast':
contrast = clamp(contrast + change, -10, 10)
print("Contrast:", contrast)
ctrl.setContrast(contrast)
elif control == 'saturation':
saturation = clamp(saturation + change, -10, 10)
print("Saturation:", saturation)
ctrl.setSaturation(saturation)
elif control == 'sharpness':
sharpness = clamp(sharpness + change, 0, 4)
print("Sharpness:", sharpness)
ctrl.setSharpness(sharpness)
elif control == 'luma_denoise':
luma_denoise = clamp(luma_denoise + change, 0, 4)
print("Luma denoise:", luma_denoise)
ctrl.setLumaDenoise(luma_denoise)
controlQueue.send(ctrl)
elif key == ord('q'):
break
6 changes: 6 additions & 0 deletions gen2-mono-raw-controls/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
numpy
numba
opencv-python

--extra-index-url https://artifacts.luxonis.com/artifactory/luxonis-python-snapshot-local/
depthai==2.3.0.0.dev+642b687ac42a03ae3ef0dc64d81c969fb60eb06c

4 comments on commit 551cba5

@chris-piekarski
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @alex-luxonis, is this the correct depthai artifact? I have the frame showing up but none of the controls work. A pip install of requirements.txt fails due to snapshot-local url. Anything I can test here?

@alex-luxonis
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@chris-piekarski Yes, this should work. Being focused on one of the windows, and pressing 6, followed by = 3 times, the "isp" stream gets inverted, and I get these logs:

depthai: 2.3.0.0.dev+642b687ac42a03ae3ef0dc64d81c969fb60eb06c 2021-05-21 21:13:20 +0300
Selected control: effect_mode
Effect mode: EffectMode.OFF
Effect mode: EffectMode.MONO
Effect mode: EffectMode.NEGATIVE

It's also possible to use the latest release, as this feature was included there:

python3 -m pip uninstall depthai -y
python3 -m pip install depthai

Then the version printed is:

depthai: 2.4.0.0 2021-05-24 22:56:03 +0300

@chris-piekarski
Copy link
Contributor

@chris-piekarski chris-piekarski commented on 551cba5 May 26, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @alex-luxonis. I did get the effects working. However I am unable to see any changes for contrast, saturation and brightness. Also not seeing any changes for AWB modes. Should those be functional on most recent version?

@alex-luxonis
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for testing @chris-piekarski. These controls don't seem to work on MonoCamera. It's unclear where the problem is, as they get properly forwarded to the camera 3A algorithms (where we don't have visibility). We'll investigate further.

Please sign in to comment.