drivers/sensors: Add calibration ioctl for the genx320.

This commit is contained in:
Kwabena W. Agyeman 2025-07-02 22:35:46 -07:00
parent b200ec4b1f
commit 3908807509
5 changed files with 240 additions and 0 deletions

View File

@ -236,6 +236,7 @@ typedef enum {
OMV_CSI_IOCTL_GENX320_SET_AFK = 0x22,
OMV_CSI_IOCTL_GENX320_SET_MODE = 0x23,
OMV_CSI_IOCTL_GENX320_READ_EVENTS = 0x24,
OMV_CSI_IOCTL_GENX320_CALIBRATE = 0x25,
OMV_CSI_IOCTL_UPDATE_AGC_AEC = 0x7F
} omv_csi_ioctl_t;

View File

@ -233,6 +233,68 @@ static int set_vflip(omv_csi_t *csi, int enable) {
return 0;
}
static int disable_hot_pixels(omv_csi_t *csi, uint8_t *histogram, float sigma) {
// Compute average
int32_t avg = 0;
for (uint32_t i = 0; i < ACTIVE_SENSOR_SIZE; i++) {
avg += histogram[i];
}
avg /= ACTIVE_SENSOR_SIZE;
// Compute std
int64_t std = 0;
for (uint32_t i = 0; i < ACTIVE_SENSOR_SIZE; i++) {
int32_t diff = histogram[i] - avg;
std += diff * diff;
}
std = fast_sqrtf(std / ((float) ACTIVE_SENSOR_SIZE));
int32_t threshold = fast_roundf(avg + (std * sigma));
int ret = 0;
for (uint32_t y = 0; y < ACTIVE_SENSOR_HEIGHT; y++) {
// Reset all blocks
for (uint32_t i = 0; i < (ACTIVE_SENSOR_WIDTH / UINT32_T_BITS); i++) {
psee_write_ROI_X(csi, i * sizeof(uint32_t), 0);
}
// Select line
uint32_t offset = y / UINT32_T_BITS;
psee_write_ROI_Y(csi, offset * sizeof(uint32_t), 1 << (y % UINT32_T_BITS));
// Trigger shadow
psee_write_ROI_CTRL(csi, ROI_CTRL_PX_SW_RSTN | ROI_CTRL_TD_SHADOW_TRIGGER);
uint32_t tmp[ACTIVE_SENSOR_WIDTH / UINT32_T_BITS] = {};
for (uint32_t x = 0; x < ACTIVE_SENSOR_WIDTH; x++) {
if (histogram[(y * ACTIVE_SENSOR_WIDTH) + x] > threshold) {
tmp[x / UINT32_T_BITS] |= 1 << (x % UINT32_T_BITS);
ret += 1;
}
}
// Write x values to disable
for (uint32_t i = 0; i < (ACTIVE_SENSOR_WIDTH / UINT32_T_BITS); i++) {
psee_write_ROI_X(csi, i * sizeof(uint32_t), tmp[i]);
}
// Activate block
psee_write_ROI_CTRL(csi, ROI_CTRL_PX_SW_RSTN | ROI_CTRL_TD_SHADOW_TRIGGER | ROI_CTRL_TD_EN);
// Disable roi block
psee_write_ROI_CTRL(csi, ROI_CTRL_PX_SW_RSTN);
psee_write_ROI_Y(csi, offset * sizeof(uint32_t), 0);
mp_printf(MP_PYTHON_PRINTER, "CSI: Calibrating - %d%%\n", ((y * 50) / ACTIVE_SENSOR_HEIGHT) + 50);
}
return ret;
}
static int ioctl(omv_csi_t *csi, int request, va_list ap) {
genx_state_t *genx = csi->priv;
int ret = 0;
@ -403,6 +465,69 @@ static int ioctl(omv_csi_t *csi, int request, va_list ap) {
ret = omv_csi_snapshot(csi, &image, 0);
break;
}
case OMV_CSI_IOCTL_GENX320_CALIBRATE: {
uint32_t event_count = va_arg(ap, uint32_t);
float sigma = va_arg(ap, double);
if (omv_csi_get_cropped(csi)) {
return OMV_CSI_ERROR_CAPTURE_FAILED;
}
if (csi->transpose) {
return OMV_CSI_ERROR_CAPTURE_FAILED;
}
uint8_t *histogram = fb_alloc0(ACTIVE_SENSOR_SIZE, FB_ALLOC_NO_HINT);
// Collect events to calibrate hot pixels.
for (uint32_t i = 0; i < event_count; ) {
// Print something to prevent the user from thinking the camera is stuck.
mp_printf(MP_PYTHON_PRINTER, "CSI: Calibrating - %d%%\n", ((i * 50) / event_count));
image_t image;
ret = omv_csi_snapshot(csi, &image, OMV_CSI_FLAG_NO_POST);
if (ret < 0) {
return ret;
}
if (genx->mode == OMV_CSI_GENX320_MODE_HISTO) {
// Invalidate frame.
csi->fb->pixfmt = PIXFORMAT_INVALID;
for (uint32_t j = 0; j < ACTIVE_SENSOR_SIZE; j++) {
uint32_t val = abs(((int8_t *) image.data)[j]);
histogram[j] = __USAT(histogram[j] + val, UINT8_T_BITS);
i += val;
}
} else {
uint32_t len = resolution[csi->framesize][0] *
(resolution[csi->framesize][1] / sizeof(uint32_t));
for (uint32_t j = 0; j < len; j++) {
uint32_t val = ((uint32_t *) image.data)[j];
switch (__EVT20_TYPE(val)) {
case TD_LOW:
case TD_HIGH: {
uint32_t x = __EVT20_X(val);
uint32_t y = __EVT20_Y(val);
if ((x < ACTIVE_SENSOR_WIDTH) && (y < ACTIVE_SENSOR_HEIGHT)) {
uint32_t index = (y * ACTIVE_SENSOR_WIDTH) + x;
histogram[index] = __USAT(histogram[index] + 1, UINT8_T_BITS);
i++;
}
break;
}
default: {
break;
}
}
}
}
}
ret = disable_hot_pixels(csi, histogram, sigma);
fb_free();
break;
}
default: {
ret = -1;
break;

View File

@ -1221,6 +1221,16 @@ static mp_obj_t py_csi_ioctl(size_t n_args, const mp_obj_t *args) {
}
break;
}
case OMV_CSI_IOCTL_GENX320_CALIBRATE: {
if (n_args == 2) {
error = omv_csi_ioctl(self->csi, request, mp_obj_get_int(args[0]),
(double) mp_obj_get_float(args[1]));
if (error > 0) {
ret_obj = mp_obj_new_int(error);
}
}
break;
}
#endif // (OMV_GENX320_ENABLE == 1)
default: {
@ -1557,6 +1567,7 @@ static const mp_rom_map_elem_t globals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_GENX320_MODE_HISTO), MP_ROM_INT(OMV_CSI_GENX320_MODE_HISTO) },
{ MP_ROM_QSTR(MP_QSTR_GENX320_MODE_EVENT), MP_ROM_INT(OMV_CSI_GENX320_MODE_EVENT) },
{ MP_ROM_QSTR(MP_QSTR_IOCTL_GENX320_READ_EVENTS), MP_ROM_INT(OMV_CSI_IOCTL_GENX320_READ_EVENTS)},
{ MP_ROM_QSTR(MP_QSTR_IOCTL_GENX320_CALIBRATE), MP_ROM_INT(OMV_CSI_IOCTL_GENX320_CALIBRATE)},
{ MP_ROM_QSTR(MP_QSTR_PIX_OFF_EVENT), MP_ROM_INT(EC_PIX_OFF_EVENT)},
{ MP_ROM_QSTR(MP_QSTR_PIX_ON_EVENT), MP_ROM_INT(EC_PIX_ON_EVENT)},
{ MP_ROM_QSTR(MP_QSTR_RST_TRIGGER_RISING), MP_ROM_INT(EC_RST_TRIGGER_RISING)},

View File

@ -0,0 +1,37 @@
# This work is licensed under the MIT license.
# Copyright (c) 2013-2024 OpenMV LLC. All rights reserved.
# https://github.com/openmv/openmv/blob/master/LICENSE
#
# This example shows off hot pixel calibration
# using the genx320 event camera from Prophesee.
import csi
import time
csi0 = csi.CSI(cid=csi.GENX320)
csi0.reset()
csi0.pixformat(csi.GRAYSCALE) # Must always be grayscale.
csi0.framesize(csi.B320X320) # Must always be 320x320.
csi0.brightness(128) # Leave at 128 generally (this is the default).
csi0.contrast(16) # Increase to make the image pop.
# The default frame rate is 50 FPS. You can change it between ~20 FPS and ~350 FPS.
csi0.framerate(50)
# Show uncalibrated image first.
csi0.snapshot(time=5000)
CAL_EVENT_COUNT = 10000 # Number of events to collect for calibration.
CAL_SIGMA = 0.5 # Standard deviation for hot pixel detection.
disabled_pixels = csi0.ioctl(csi.IOCTL_GENX320_CALIBRATE, CAL_EVENT_COUNT, CAL_SIGMA)
print(f'Disabled {disabled_pixels} hot pixels.')
clock = time.clock()
while True:
clock.tick()
img = csi0.snapshot()
# img.median(1) # noise cleanup.
print(clock.fps())

View File

@ -0,0 +1,66 @@
# This work is licensed under the MIT license.
# Copyright (c) 2013-2025 OpenMV LLC. All rights reserved.
# https://github.com/openmv/openmv/blob/master/LICENSE
#
# This example shows off using the genx320 event camera from Prophesee
# using event streaming mode and calibrating the camera.
import csi
import image
import time
# https://micropython-ulab.readthedocs.io/en/latest/index.html
from ulab import numpy as np
CAL_EVENT_COUNT = 10000 # Number of events to collect for calibration.
CAL_SIGMA = 0.5 # Standard deviation for hot pixel detection.
# Surface to draw the histogram image on.
img = image.Image(320, 320, image.GRAYSCALE)
# Stores camera events
# Shape: (EVT_res, 6) where EVT_res is the event resolution
# Columns:
# [0] Event type
# [1] Seconds timestamp
# [2] Milliseconds timestamp
# [3] Microseconds timestamp
# [4] X coordinate 0 to 319 for GENX320
# [5] Y coordinate 0 to 319 for GENX320
events = np.zeros((2048, 6), dtype=np.uint16)
# Initialize the sensor.
csi0 = csi.CSI(cid=csi.GENX320)
csi0.reset()
csi0.ioctl(csi.IOCTL_GENX320_SET_MODE, csi.GENX320_MODE_EVENT)
clock = time.clock()
t = time.ticks_ms()
calibrated = False
while True:
clock.tick()
# Reads 2048 events from the camera.
# Returns the number of valid events (0-2048) or a negative error code.
event_count = csi0.ioctl(csi.IOCTL_GENX320_READ_EVENTS, events)
# Render events into a histogram image.
# If clear=True, the image is reset to "brightness" before drawing.
# For each PIX_ON_EVENT, add "contrast" to the bin value;
# for each PIX_OFF_EVENT, subtract it and clamp to [0, 255].
# If clear=False, histogram accumulates over multiple calls.
img.draw_event_histogram(events[:event_count], clear=True, brightness=128, contrast=64)
# Push the image to the jpeg buffer for the IDE to pull and display.
# The IDE pulls frames off the camera at a much lower rate than the
# onboard camera frame rate printed below.
img.flush()
# Show uncalibrated image first.
if not calibrated and time.ticks_diff(time.ticks_ms(), t) > 5000:
disabled_pixels = csi0.ioctl(csi.IOCTL_GENX320_CALIBRATE, CAL_EVENT_COUNT, CAL_SIGMA)
print(f'Disabled {disabled_pixels} hot pixels.')
calibrated = True
print(event_count, clock.fps())