1 /*
2 * Copyright (c) 2019 Linaro Limited
3 * Copyright 2025 NXP
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 */
7
8 #include <math.h>
9
10 #include <zephyr/device.h>
11 #include <zephyr/drivers/display.h>
12 #include <zephyr/drivers/video-controls.h>
13 #include <zephyr/drivers/video.h>
14 #include <zephyr/kernel.h>
15 #include <zephyr/logging/log.h>
16 #include <zephyr/ztest.h>
17
18 LOG_MODULE_REGISTER(test_pattern, LOG_LEVEL_INF);
19
20 #define LAB_THRESHOLD ((double)CONFIG_TEST_LAB_THRESHOLD)
21
22 #define BARS_NUM 8
23 #define PIXELS_NUM 5
24
25 typedef struct {
26 double L;
27 double a;
28 double b;
29 } CIELAB;
30
31 /*
32 * This is measured on a real 8-colorbar pattern generated by an ov5640 camera sensor.
33 * For other sensors, it can be slightly different. If it doesn't fit anymore, either
34 * this array or the LAB_THRESHOLD can be modified.
35 */
36 static const CIELAB colorbars_target[] = {
37 {100.0, 0.0053, -0.0104}, /* White */
38 {97.1804, -21.2151, 91.3538}, /* Yellow */
39 {90.1352, -58.4675, 6.0570}, /* Cyan */
40 {87.7630, -85.9469, 83.2128}, /* Green */
41 {56.6641, 95.0182, -66.9129}, /* Magenta */
42 {46.6937, 72.7494, 49.5801}, /* Red */
43 {27.6487, 71.5662, -97.4712}, /* Blue */
44 {1.3726, -2.8040, 2.0043}, /* Black */
45 };
46
rgb888_to_lab(const uint8_t r,const uint8_t g,const uint8_t b)47 static inline CIELAB rgb888_to_lab(const uint8_t r, const uint8_t g, const uint8_t b)
48 {
49 CIELAB lab;
50
51 double r_lin = r / 255.0;
52 double g_lin = g / 255.0;
53 double b_lin = b / 255.0;
54
55 r_lin = r_lin > 0.04045 ? pow((r_lin + 0.055) / 1.055, 2.4) : r_lin / 12.92;
56 g_lin = g_lin > 0.04045 ? pow((g_lin + 0.055) / 1.055, 2.4) : g_lin / 12.92;
57 b_lin = b_lin > 0.04045 ? pow((b_lin + 0.055) / 1.055, 2.4) : b_lin / 12.92;
58
59 double x = r_lin * 0.4124 + g_lin * 0.3576 + b_lin * 0.1805;
60 double y = r_lin * 0.2126 + g_lin * 0.7152 + b_lin * 0.0722;
61 double z = r_lin * 0.0193 + g_lin * 0.1192 + b_lin * 0.9505;
62
63 x /= 0.95047;
64 z /= 1.08883;
65
66 x = x > 0.008856 ? pow(x, 1.0 / 3.0) : (7.787 * x) + (16.0 / 116.0);
67 y = y > 0.008856 ? pow(y, 1.0 / 3.0) : (7.787 * y) + (16.0 / 116.0);
68 z = z > 0.008856 ? pow(z, 1.0 / 3.0) : (7.787 * z) + (16.0 / 116.0);
69
70 lab.L = 116.0 * y - 16.0;
71 lab.a = 500.0 * (x - y);
72 lab.b = 200.0 * (y - z);
73
74 return lab;
75 }
76
xrgb32_to_lab(const uint32_t color)77 static inline CIELAB xrgb32_to_lab(const uint32_t color)
78 {
79 uint8_t r = (color >> 16) & 0xFF;
80 uint8_t g = (color >> 8) & 0xFF;
81 uint8_t b = color & 0xFF;
82
83 return rgb888_to_lab(r, g, b);
84 }
85
rgb565_to_lab(const uint16_t color)86 static inline CIELAB rgb565_to_lab(const uint16_t color)
87 {
88 uint8_t r5 = (color >> 11) & 0x1F;
89 uint8_t g6 = (color >> 5) & 0x3F;
90 uint8_t b5 = color & 0x1F;
91
92 /* Convert RGB565 to RGB888 */
93 uint8_t r = (r5 * 255) / 31;
94 uint8_t g = (g6 * 255) / 63;
95 uint8_t b = (b5 * 255) / 31;
96
97 return rgb888_to_lab(r, g, b);
98 }
99
sum_lab(CIELAB * sum,const CIELAB lab)100 static inline void sum_lab(CIELAB *sum, const CIELAB lab)
101 {
102 sum->L += lab.L;
103 sum->a += lab.a;
104 sum->b += lab.b;
105 }
106
average_lab(CIELAB * lab,const uint32_t count)107 static inline void average_lab(CIELAB *lab, const uint32_t count)
108 {
109 if (count > 0) {
110 lab->L /= count;
111 lab->a /= count;
112 lab->b /= count;
113 }
114 }
115
deltaE(const CIELAB lab1,const CIELAB lab2)116 static inline double deltaE(const CIELAB lab1, const CIELAB lab2)
117 {
118 return sqrt(pow(lab1.L - lab2.L, 2) + pow(lab1.a - lab2.a, 2) + pow(lab1.b - lab2.b, 2));
119 }
120
121 /*
122 * As color values may vary near the boundary of each bar and also, for computational
123 * efficiency, check only a small number of pixels (PIXELS_NUM) in the middle of each bar.
124 */
is_colorbar_ok(const uint8_t * const buf,const struct video_format * fmt)125 static inline bool is_colorbar_ok(const uint8_t *const buf, const struct video_format *fmt)
126 {
127 int i;
128 int bw = fmt->width / BARS_NUM;
129 CIELAB colorbars[BARS_NUM] = {0};
130
131 for (int h = 0; h < fmt->height; h++) {
132 for (i = 0; i < BARS_NUM; i++) {
133 if (fmt->pixelformat == VIDEO_PIX_FMT_XRGB32) {
134 uint32_t *pixel =
135 (uint32_t *)&buf[4 * (h * fmt->width + bw / 2 + i * bw)];
136
137 for (int j = -PIXELS_NUM / 2; j <= PIXELS_NUM / 2; j++) {
138 sum_lab(&colorbars[i], xrgb32_to_lab(*(pixel + j)));
139 }
140 } else if (fmt->pixelformat == VIDEO_PIX_FMT_RGB565) {
141 uint16_t *pixel =
142 (uint16_t *)&buf[2 * (h * fmt->width + bw / 2 + i * bw)];
143
144 for (int j = -PIXELS_NUM / 2; j <= PIXELS_NUM / 2; j++) {
145 sum_lab(&colorbars[i], rgb565_to_lab(*(pixel + j)));
146 }
147 } else {
148 printk("Format %d is not supported", fmt->pixelformat);
149 return false;
150 }
151 }
152 }
153
154 for (i = 0; i < BARS_NUM; i++) {
155 average_lab(&colorbars[i], PIXELS_NUM * fmt->height);
156 if (deltaE(colorbars[i], colorbars_target[i]) > LAB_THRESHOLD) {
157 return false;
158 }
159 }
160
161 return true;
162 }
163
164 static const struct device *const video_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_camera));
165 struct video_format fmt;
166
test_pattern_setup(void)167 static void *test_pattern_setup(void)
168 {
169 struct video_buffer *vbuf = &(struct video_buffer){};
170 struct video_caps caps = {
171 .type = VIDEO_BUF_TYPE_OUTPUT,
172 };
173 struct video_control ctrl = {
174 .id = VIDEO_CID_TEST_PATTERN, .val = CONFIG_TEST_PATTERN_CTRL,
175 };
176 int ret;
177
178 zassert(device_is_ready(video_dev), "device initialization failed");
179
180 ret = video_get_caps(video_dev, &caps);
181 zassert_ok(ret, "getting video capabilities failed");
182
183 fmt.type = VIDEO_BUF_TYPE_OUTPUT;
184 ret = video_get_format(video_dev, &fmt);
185 zassert_ok(ret, "getting default video format failed");
186
187 if (CONFIG_TEST_FRAME_HEIGHT > 0) {
188 fmt.height = CONFIG_TEST_FRAME_HEIGHT;
189 }
190 if (CONFIG_TEST_FRAME_WIDTH > 0) {
191 fmt.width = CONFIG_TEST_FRAME_WIDTH;
192 }
193 if (strcmp(CONFIG_TEST_PIXEL_FORMAT, "") != 0) {
194 fmt.pixelformat = VIDEO_FOURCC_FROM_STR(CONFIG_TEST_PIXEL_FORMAT);
195 }
196
197 LOG_INF("Video format: %s %ux%u",
198 VIDEO_FOURCC_TO_STR(fmt.pixelformat), fmt.width, fmt.height);
199
200 ret = video_set_format(video_dev, &fmt);
201 zassert_ok(ret, "setting video format failed");
202
203 ret = video_set_ctrl(video_dev, &ctrl);
204 zassert_ok(ret, "setting test pattern");
205
206 /* Alloc video buffers and enqueue for capture */
207 zassert(caps.min_vbuf_count <= CONFIG_VIDEO_BUFFER_POOL_NUM_MAX,
208 "not enough buffers");
209 zassert(fmt.size <= (CONFIG_VIDEO_BUFFER_POOL_HEAP_SIZE / CONFIG_VIDEO_BUFFER_POOL_NUM_MAX),
210 "buffers too large");
211
212 for (int i = 0; i < CONFIG_VIDEO_BUFFER_POOL_NUM_MAX; i++) {
213 vbuf = video_buffer_aligned_alloc(fmt.size, CONFIG_VIDEO_BUFFER_POOL_ALIGN,
214 K_NO_WAIT);
215 zassert_not_null(vbuf);
216
217 vbuf->type = VIDEO_BUF_TYPE_OUTPUT;
218
219 ret = video_enqueue(video_dev, vbuf);
220 zassert_ok(ret);
221 }
222
223 LOG_INF("Device %s configured starting capture", video_dev->name);
224
225 ret = video_stream_start(video_dev, VIDEO_BUF_TYPE_OUTPUT);
226 zassert_ok(ret);
227
228 return NULL;
229 }
230
test_pattern_after(void *)231 void test_pattern_after(void *)
232 {
233 int ret;
234
235 ret = video_stream_stop(video_dev, VIDEO_BUF_TYPE_OUTPUT);
236 zassert_ok(ret);
237 }
238
ZTEST(test_pattern,test_pattern_frames)239 ZTEST(test_pattern, test_pattern_frames)
240 {
241 struct video_buffer *vbuf = &(struct video_buffer){
242 .type = VIDEO_BUF_TYPE_OUTPUT
243 };
244 size_t valid = 0;
245 int ret;
246
247 for (size_t i = 0; i < CONFIG_TEST_FRAMES_TOTAL; i++) {
248 ret = video_dequeue(video_dev, &vbuf, K_FOREVER);
249 zassert_ok(ret);
250
251 LOG_INF("Got frame, testing color bars");
252
253 valid += is_colorbar_ok(vbuf->buffer, &fmt);
254 if (valid >= CONFIG_TEST_FRAMES_VALID) {
255 LOG_INF("Got %u valid frames out of %u, stopping the test", valid, i + 1);
256 break;
257 }
258
259 ret = video_enqueue(video_dev, vbuf);
260 zassert_ok(ret);
261 }
262
263 zassert_equal(valid, CONFIG_TEST_FRAMES_VALID,
264 "there should be at least %u valid frames out of %u",
265 CONFIG_TEST_FRAMES_VALID, CONFIG_TEST_FRAMES_TOTAL);
266 }
267
268 ZTEST_SUITE(test_pattern, NULL, test_pattern_setup, NULL, test_pattern_after, NULL);
269