Skip to content

Commit 256bb8d

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 675c192 commit 256bb8d

File tree

5 files changed

+368
-11
lines changed

5 files changed

+368
-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: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
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+
select PIXEL
7+
help
8+
Enable a software-based frame statistics video device, providing information that can be
9+
by an Image Processing Algorithm (IPA) via the video stats API. This allows applications
10+
to tune the image colors through the video control API.
11+
12+
config VIDEO_SW_STATS_NUM_SAMPLES
13+
int "Number of pixels to sample on every frame"
14+
default 400
15+
range 0 65535
16+
help
17+
The more pixels are sample, the most accurate the statistics may be, but the slowest they
18+
would be collected. The default value is estimated good enough not to slow-down most
19+
systems. Note that the performance of statistics collection does not depend on the frame
20+
size and only the number of samples, but accuracy does.

drivers/video/video_sw_stats.c

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

0 commit comments

Comments
 (0)