Skip to content

Commit c163b71

Browse files
committed
drivers: video: sw_stats: introduce software-based image statistics
Add a new driver computing software statistics over a frame passed to it. This allows collecting channel averges or histogram of an input image using the video statistics API even when no hardware provides it. Signed-off-by: Josuah Demangeon <me@josuah.net>
1 parent 2e82a04 commit c163b71

File tree

5 files changed

+352
-11
lines changed

5 files changed

+352
-11
lines changed

drivers/video/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ zephyr_library_sources_ifdef(CONFIG_VIDEO_MCUX_CSI video_mcux_csi.c)
88
zephyr_library_sources_ifdef(CONFIG_VIDEO_MCUX_MIPI_CSI2RX video_mcux_mipi_csi2rx.c)
99
zephyr_library_sources_ifdef(CONFIG_VIDEO_SW_GENERATOR video_sw_generator.c)
1010
zephyr_library_sources_ifdef(CONFIG_VIDEO_SW_ISP video_sw_isp.c)
11+
zephyr_library_sources_ifdef(CONFIG_VIDEO_SW_STATS video_sw_stats.c)
1112
zephyr_library_sources_ifdef(CONFIG_VIDEO_MT9M114 mt9m114.c)
1213
zephyr_library_sources_ifdef(CONFIG_VIDEO_OV7725 ov7725.c)
1314
zephyr_library_sources_ifdef(CONFIG_VIDEO_OV2640 ov2640.c)

drivers/video/Kconfig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ source "drivers/video/Kconfig.sw_generator"
6060

6161
source "drivers/video/Kconfig.sw_isp"
6262

63+
source "drivers/video/Kconfig.sw_stats"
64+
6365
source "drivers/video/Kconfig.mt9m114"
6466

6567
source "drivers/video/Kconfig.ov7725"

drivers/video/Kconfig.sw_stats

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Copyright (c) 2025 tinyVision.ai Inc.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
config VIDEO_SW_STATS
5+
bool "Video Software Statistics"
6+
help
7+
Enable a software-based frame statistics video device, providing information that can be
8+
by an Image Processing Algorithm (IPA) via the video stats API. This allows applications
9+
to tune the image colors through the video control API.
10+
11+
config VIDEO_SW_STATS_NUM_SAMPLES
12+
int "Number of pixels to sample on every frame"
13+
default 400
14+
range 0 65535
15+
help
16+
The more pixels are sample, the most accurate the statistics may be, but the slowest they
17+
would be collected. The default value is estimated good enough not to slow-down most
18+
systems. Note that the performance of statistics collection does not depend on the frame
19+
size and only the number of samples, but accuracy does.

drivers/video/video_sw_stats.c

Lines changed: 323 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,323 @@
1+
/*
2+
* Copyright (c) 2025, tinyVision.ai Inc.
3+
* Copyright (c) 2019, Linaro Limited
4+
*
5+
* SPDX-License-Identifier: Apache-2.0
6+
*/
7+
8+
#define DT_DRV_COMPAT zephyr_sw_stats
9+
10+
#include <zephyr/kernel.h>
11+
#include <zephyr/drivers/video.h>
12+
#include <zephyr/drivers/video-controls.h>
13+
#include <zephyr/sys/util.h>
14+
#include <zephyr/sys/byteorder.h>
15+
#include <zephyr/pixel/pixel.h>
16+
#include <zephyr/logging/log.h>
17+
18+
LOG_MODULE_REGISTER(video_sw_stats, CONFIG_VIDEO_LOG_LEVEL);
19+
20+
#define NVAL CONFIG_VIDEO_SW_STATS_NUM_SAMPLES
21+
22+
struct video_sw_stats_data {
23+
const struct device *dev;
24+
struct video_format fmt;
25+
struct video_buffer *vbuf;
26+
uint16_t frame_counter;
27+
};
28+
29+
#define VIDEO_SW_STATS_FORMAT_CAP(pixfmt) \
30+
{ \
31+
.pixelformat = pixfmt, \
32+
.width_min = 2, \
33+
.width_max = UINT16_MAX, \
34+
.height_min = 2, \
35+
.height_max = UINT16_MAX, \
36+
.width_step = 2, \
37+
.height_step = 2, \
38+
}
39+
40+
static const struct video_format_cap fmts[] = {
41+
VIDEO_SW_STATS_FORMAT_CAP(VIDEO_PIX_FMT_RGB24),
42+
VIDEO_SW_STATS_FORMAT_CAP(VIDEO_PIX_FMT_RGGB8),
43+
VIDEO_SW_STATS_FORMAT_CAP(VIDEO_PIX_FMT_GRBG8),
44+
VIDEO_SW_STATS_FORMAT_CAP(VIDEO_PIX_FMT_BGGR8),
45+
VIDEO_SW_STATS_FORMAT_CAP(VIDEO_PIX_FMT_GBRG8),
46+
{0},
47+
};
48+
49+
static int video_sw_stats_set_fmt(const struct device *dev, enum video_endpoint_id ep,
50+
struct video_format *fmt)
51+
{
52+
struct video_sw_stats_data *data = dev->data;
53+
int i;
54+
55+
if (ep != VIDEO_EP_IN && ep != VIDEO_EP_ALL) {
56+
return -EINVAL;
57+
}
58+
59+
for (i = 0; fmts[i].pixelformat != 0; ++i) {
60+
if (fmt->pixelformat == fmts[i].pixelformat &&
61+
IN_RANGE(fmt->width, fmts[i].width_min, fmts[i].width_max) &&
62+
IN_RANGE(fmt->height, fmts[i].height_min, fmts[i].height_max)) {
63+
break;
64+
}
65+
}
66+
67+
if (fmts[i].pixelformat == 0) {
68+
LOG_ERR("Unsupported pixel format or resolution");
69+
return -ENOTSUP;
70+
}
71+
72+
data->fmt = *fmt;
73+
74+
return 0;
75+
}
76+
77+
static int video_sw_stats_get_fmt(const struct device *dev, enum video_endpoint_id ep,
78+
struct video_format *fmt)
79+
{
80+
struct video_sw_stats_data *data = dev->data;
81+
82+
if (ep != VIDEO_EP_IN && ep != VIDEO_EP_ALL) {
83+
return -EINVAL;
84+
}
85+
86+
*fmt = data->fmt;
87+
88+
return 0;
89+
}
90+
91+
static int video_sw_stats_set_stream(const struct device *dev, bool enable)
92+
{
93+
return 0;
94+
}
95+
96+
static void video_sw_stats_histogram_y(const struct device *const dev, struct video_buffer *vbuf,
97+
struct video_stats *stats)
98+
{
99+
struct video_sw_stats_data *data = dev->data;
100+
struct video_stats_histogram *hist = (void *)stats;
101+
uint8_t depth = LOG2(hist->num_buckets);
102+
103+
LOG_DBG("%u buckets configured", hist->num_buckets);
104+
105+
memset(buckets, 0x00, hist->num_buckets * sizeof(uint16_t));
106+
107+
hist->num_buckets = 1 << depth;
108+
hist->num_values = NVAL;
109+
base->flags = VIDEO_STATS_HISTOGRAM_Y;
110+
111+
switch (data->fmt.pixelformat) {
112+
case VIDEO_PIX_FMT_RGB24:
113+
pixel_rgb24frame_to_y8hist(vbuf->buffer, vbuf->bytesused, buckets, depth, NVAL);
114+
break;
115+
case VIDEO_PIX_FMT_RGGB8:
116+
pixel_rggb8frame_to_y8hist(vbuf->buffer, vbuf->bytesused, data->fmt.width,
117+
hist->buckets, depth, NVAL);
118+
break;
119+
case VIDEO_PIX_FMT_GBRG8:
120+
pixel_gbrg8frame_to_y8hist(vbuf->buffer, vbuf->bytesused, data->fmt.width,
121+
hist->buckets, depth, NVAL);
122+
break;
123+
case VIDEO_PIX_FMT_BGGR8:
124+
pixel_bggr8frame_to_y8hist(vbuf->buffer, vbuf->bytesused, data->fmt.width,
125+
hist->buckets, depth, NVAL);
126+
break;
127+
case VIDEO_PIX_FMT_GRBG8:
128+
pixel_grbg8frame_to_y8hist(vbuf->buffer, vbuf->bytesused, data->fmt.width,
129+
hist->buckets, depth, NVAL);
130+
break;
131+
default:
132+
CODE_UNREACHABLE;
133+
}
134+
}
135+
136+
static void video_sw_stats_histogram_rgb(const struct device *const dev, struct video_buffer *vbuf,
137+
struct video_stats *base)
138+
{
139+
struct video_sw_stats_data *data = dev->data;
140+
struct video_stats_histogram *stats = (void *)base;
141+
uint8_t depth = LOG2(stats->num_buckets);
142+
143+
LOG_DBG("%u buckets configured", stats->num_buckets);
144+
145+
stats->num_buckets = 1 << depth;
146+
stats->num_values = NVAL;
147+
base->flags = VIDEO_STATS_HISTOGRAM_RGB;
148+
149+
memset(&d[0], 0x00, stats->num_buckets * sizeof(uint16_t));
150+
memset(&d[1], 0x00, stats->num_buckets * sizeof(uint16_t));
151+
memset(&d[2], 0x00, stats->num_buckets * sizeof(uint16_t));
152+
153+
switch (data->fmt.pixelformat) {
154+
case VIDEO_PIX_FMT_RGB24:
155+
pixel_rgb24frame_to_rgb24hist(vbuf->buffer, vbuf->bytesused, hist->buckets, depth,
156+
NVAL);
157+
break;
158+
case VIDEO_PIX_FMT_RGGB8:
159+
pixel_rggb8frame_to_rgb24hist(vbuf->buffer, vbuf->bytesused, data->fmt.width,
160+
hist->buckets, depth, NVAL);
161+
break;
162+
case VIDEO_PIX_FMT_GBRG8:
163+
pixel_gbrg8frame_to_rgb24hist(vbuf->buffer, vbuf->bytesused, data->fmt.width,
164+
hist->buckets, depth, NVAL);
165+
break;
166+
case VIDEO_PIX_FMT_BGGR8:
167+
pixel_bggr8frame_to_rgb24hist(vbuf->buffer, vbuf->bytesused, data->fmt.width,
168+
hist->buckets, depth, NVAL);
169+
break;
170+
case VIDEO_PIX_FMT_GRBG8:
171+
pixel_grbg8frame_to_rgb24hist(vbuf->buffer, vbuf->bytesused, data->fmt.width,
172+
hist->buckets, depth, NVAL);
173+
break;
174+
default:
175+
CODE_UNREACHABLE;
176+
}
177+
}
178+
179+
static void video_sw_stats_channels(const struct device *const dev, struct video_buffer *vbuf,
180+
struct video_stats *base)
181+
{
182+
struct video_sw_stats_data *data = dev->data;
183+
struct video_stats_channels *stats = (void *)base;
184+
uint16_t w = data->fmt.width;
185+
186+
base->flags = VIDEO_STATS_CHANNELS_RGB;
187+
stats->rgb[0] = stats->rgb[1] = stats->rgb[2] = stats->y = 0x00;
188+
189+
switch (data->fmt.pixelformat) {
190+
case VIDEO_PIX_FMT_RGB24:
191+
pixel_rgb24frame_to_rgb24avg(vbuf->buffer, vbuf->bytesused, stats->rgb, NVAL);
192+
break;
193+
case VIDEO_PIX_FMT_RGGB8:
194+
pixel_rggb8frame_to_rgb24avg(vbuf->buffer, vbuf->bytesused, data->fmt.width,
195+
stats->rgb, NVAL);
196+
break;
197+
case VIDEO_PIX_FMT_GBRG8:
198+
pixel_gbrg8frame_to_rgb24avg(vbuf->buffer, vbuf->bytesused, data->fmt.width,
199+
stats->rgb, NVAL);
200+
break;
201+
case VIDEO_PIX_FMT_BGGR8:
202+
pixel_bggr8frame_to_rgb24avg(vbuf->buffer, vbuf->bytesused, data->fmt.width,
203+
stats->rgb, NVAL);
204+
break;
205+
case VIDEO_PIX_FMT_GRBG8:
206+
pixel_grbg8frame_to_rgb24avg(vbuf->buffer, vbuf->bytesused, data->fmt.width,
207+
stats->rgb, NVAL);
208+
break;
209+
default:
210+
CODE_UNREACHABLE;
211+
}
212+
}
213+
214+
static int video_sw_stats_enqueue(const struct device *dev, enum video_endpoint_id ep,
215+
struct video_buffer *vbuf)
216+
{
217+
struct video_sw_stats_data *data = dev->data;
218+
219+
if (ep != VIDEO_EP_IN && ep != VIDEO_EP_ALL) {
220+
return -EINVAL;
221+
}
222+
223+
if (data->vbuf != NULL) {
224+
LOG_ERR("Buffer already loaded: %p, dequeue it first", data->vbuf);
225+
return -ENOTSUP;
226+
}
227+
228+
data->frame_counter++;
229+
data->vbuf = vbuf;
230+
231+
return 0;
232+
}
233+
234+
static int video_sw_stats_get_stats(const struct device *dev, enum video_endpoint_id ep,
235+
struct video_stats *stats)
236+
{
237+
struct video_sw_stats_data *data = dev->data;
238+
239+
if (ep != VIDEO_EP_IN && ep != VIDEO_EP_ALL) {
240+
return -ENOTSUP;
241+
}
242+
243+
/* Wait that a frame is enqueued so that statistics can be performed on it */
244+
if (data->vbuf == NULL) {
245+
LOG_DBG("No frame is currently loaded, canot perform statistics");
246+
return -EAGAIN;
247+
}
248+
249+
LOG_DBG("Getting statistics out of %p (%u bytes)", data->vbuf->buffer,
250+
data->vbuf->bytesused);
251+
252+
if (stats->flags & VIDEO_STATS_CHANNELS) {
253+
video_sw_stats_channels(dev, data->vbuf, stats);
254+
} else if (stats->flags & VIDEO_STATS_HISTOGRAM_Y) {
255+
video_sw_stats_histogram_y(dev, data->vbuf, stats);
256+
} else if (stats->flags & VIDEO_STATS_HISTOGRAM_RGB) {
257+
video_sw_stats_histogram_rgb(dev, data->vbuf, stats);
258+
}
259+
260+
stats->frame_counter = data->frame_counter;
261+
262+
return 0;
263+
}
264+
265+
static int video_sw_stats_dequeue(const struct device *dev, enum video_endpoint_id ep,
266+
struct video_buffer **vbuf, k_timeout_t timeout)
267+
{
268+
struct video_sw_stats_data *data = dev->data;
269+
270+
if (ep != VIDEO_EP_IN && ep != VIDEO_EP_ALL) {
271+
return -EINVAL;
272+
}
273+
274+
*vbuf = data->vbuf;
275+
if (*vbuf == NULL) {
276+
return -EAGAIN;
277+
}
278+
279+
data->vbuf = NULL;
280+
281+
return 0;
282+
}
283+
284+
static int video_sw_stats_get_caps(const struct device *dev, enum video_endpoint_id ep,
285+
struct video_caps *caps)
286+
{
287+
caps->format_caps = fmts;
288+
caps->min_vbuf_count = 0;
289+
290+
/* SW stats processes full frames */
291+
caps->min_line_count = caps->max_line_count = LINE_COUNT_HEIGHT;
292+
293+
return 0;
294+
}
295+
296+
static DEVICE_API(video, video_sw_stats_driver_api) = {
297+
.set_format = video_sw_stats_set_fmt,
298+
.get_format = video_sw_stats_get_fmt,
299+
.set_stream = video_sw_stats_set_stream,
300+
.enqueue = video_sw_stats_enqueue,
301+
.dequeue = video_sw_stats_dequeue,
302+
.get_caps = video_sw_stats_get_caps,
303+
.get_stats = video_sw_stats_get_stats,
304+
};
305+
306+
static struct video_sw_stats_data video_sw_stats_data_0 = {
307+
.fmt.width = 320,
308+
.fmt.height = 160,
309+
.fmt.pitch = 320 * 2,
310+
.fmt.pixelformat = VIDEO_PIX_FMT_RGB565,
311+
};
312+
313+
static int video_sw_stats_init(const struct device *dev)
314+
{
315+
struct video_sw_stats_data *data = dev->data;
316+
317+
data->dev = dev;
318+
319+
return 0;
320+
}
321+
322+
DEVICE_DEFINE(video_sw_stats, "VIDEO_SW_STATS", &video_sw_stats_init, NULL, &video_sw_stats_data_0,
323+
NULL, POST_KERNEL, CONFIG_VIDEO_INIT_PRIORITY, &video_sw_stats_driver_api);

0 commit comments

Comments
 (0)