|
| 1 | +/* |
| 2 | + * Copyright 2024 NXP |
| 3 | + * |
| 4 | + * SPDX-License-Identifier: Apache-2.0 |
| 5 | + */ |
| 6 | + |
| 7 | +#ifndef TEST_PATTERN_CHECK_H_ |
| 8 | +#define TEST_PATTERN_CHECK_H_ |
| 9 | + |
| 10 | +#include <math.h> |
| 11 | + |
| 12 | +#include <zephyr/drivers/video.h> |
| 13 | + |
| 14 | +#define LAB_THRESHOLD 10.0 |
| 15 | + |
| 16 | +#define BARS_NUM 8 |
| 17 | +#define PIXELS_NUM 5 |
| 18 | + |
| 19 | +typedef struct { |
| 20 | + double L; |
| 21 | + double a; |
| 22 | + double b; |
| 23 | +} CIELAB; |
| 24 | + |
| 25 | +/* |
| 26 | + * This is measured on a real 8-colorbar pattern generated by an ov5640 camera sensor. |
| 27 | + * For other sensors, it can be slightly different. If it doesn't fit anymore, either |
| 28 | + * this array or the LAB_THRESHOLD can be modified. |
| 29 | + * |
| 30 | + * {White, Yellow, Cyan, Green, Magenta, Red, Blue, Black} |
| 31 | + */ |
| 32 | +static const CIELAB colorbars_target[] = { |
| 33 | + {100.0, 0.0053, -0.0104}, {97.1804, -21.2151, 91.3538}, {90.1352, -58.4675, 6.0570}, |
| 34 | + {87.7630, -85.9469, 83.2128}, {56.6641, 95.0182, -66.9129}, {46.6937, 72.7494, 49.5801}, |
| 35 | + {27.6487, 71.5662, -97.4712}, {1.3726, -2.8040, 2.0043}}; |
| 36 | + |
| 37 | +static inline CIELAB rgb888_to_lab(const uint8_t r, const uint8_t g, const uint8_t b) |
| 38 | +{ |
| 39 | + CIELAB lab; |
| 40 | + |
| 41 | + double r_lin = r / 255.0; |
| 42 | + double g_lin = g / 255.0; |
| 43 | + double b_lin = b / 255.0; |
| 44 | + |
| 45 | + r_lin = r_lin > 0.04045 ? pow((r_lin + 0.055) / 1.055, 2.4) : r_lin / 12.92; |
| 46 | + g_lin = g_lin > 0.04045 ? pow((g_lin + 0.055) / 1.055, 2.4) : g_lin / 12.92; |
| 47 | + b_lin = b_lin > 0.04045 ? pow((b_lin + 0.055) / 1.055, 2.4) : b_lin / 12.92; |
| 48 | + |
| 49 | + double x = r_lin * 0.4124 + g_lin * 0.3576 + b_lin * 0.1805; |
| 50 | + double y = r_lin * 0.2126 + g_lin * 0.7152 + b_lin * 0.0722; |
| 51 | + double z = r_lin * 0.0193 + g_lin * 0.1192 + b_lin * 0.9505; |
| 52 | + |
| 53 | + x /= 0.95047; |
| 54 | + z /= 1.08883; |
| 55 | + |
| 56 | + x = x > 0.008856 ? pow(x, 1.0 / 3.0) : (7.787 * x) + (16.0 / 116.0); |
| 57 | + y = y > 0.008856 ? pow(y, 1.0 / 3.0) : (7.787 * y) + (16.0 / 116.0); |
| 58 | + z = z > 0.008856 ? pow(z, 1.0 / 3.0) : (7.787 * z) + (16.0 / 116.0); |
| 59 | + |
| 60 | + lab.L = 116.0 * y - 16.0; |
| 61 | + lab.a = 500.0 * (x - y); |
| 62 | + lab.b = 200.0 * (y - z); |
| 63 | + |
| 64 | + return lab; |
| 65 | +} |
| 66 | + |
| 67 | +static inline CIELAB xrgb32_to_lab(const uint32_t color) |
| 68 | +{ |
| 69 | + uint8_t r = (color >> 16) & 0xFF; |
| 70 | + uint8_t g = (color >> 8) & 0xFF; |
| 71 | + uint8_t b = color & 0xFF; |
| 72 | + |
| 73 | + return rgb888_to_lab(r, g, b); |
| 74 | +} |
| 75 | + |
| 76 | +static inline CIELAB rgb565_to_lab(const uint16_t color) |
| 77 | +{ |
| 78 | + uint8_t r5 = (color >> 11) & 0x1F; |
| 79 | + uint8_t g6 = (color >> 5) & 0x3F; |
| 80 | + uint8_t b5 = color & 0x1F; |
| 81 | + |
| 82 | + /* Convert RGB565 to RGB888 */ |
| 83 | + uint8_t r = (r5 * 255) / 31; |
| 84 | + uint8_t g = (g6 * 255) / 63; |
| 85 | + uint8_t b = (b5 * 255) / 31; |
| 86 | + |
| 87 | + return rgb888_to_lab(r, g, b); |
| 88 | +} |
| 89 | + |
| 90 | +static inline void sum_lab(CIELAB *sum, const CIELAB lab) |
| 91 | +{ |
| 92 | + sum->L += lab.L; |
| 93 | + sum->a += lab.a; |
| 94 | + sum->b += lab.b; |
| 95 | +} |
| 96 | + |
| 97 | +static inline void average_lab(CIELAB *lab, const uint32_t count) |
| 98 | +{ |
| 99 | + if (count > 0) { |
| 100 | + lab->L /= count; |
| 101 | + lab->a /= count; |
| 102 | + lab->b /= count; |
| 103 | + } |
| 104 | +} |
| 105 | + |
| 106 | +static inline double deltaE(const CIELAB lab1, const CIELAB lab2) |
| 107 | +{ |
| 108 | + return sqrt(pow(lab1.L - lab2.L, 2) + pow(lab1.a - lab2.a, 2) + pow(lab1.b - lab2.b, 2)); |
| 109 | +} |
| 110 | + |
| 111 | +/* |
| 112 | + * As color values may vary near the boundary of each bar and also, for computational |
| 113 | + * efficiency, check only a small number of pixels (PIXELS_NUM) in the middle of each bar. |
| 114 | + */ |
| 115 | +static inline bool is_colorbar_ok(const uint8_t *const buf, const struct video_format fmt) |
| 116 | +{ |
| 117 | + int i; |
| 118 | + int bw = fmt.width / BARS_NUM; |
| 119 | + CIELAB colorbars[BARS_NUM] = {0}; |
| 120 | + |
| 121 | + for (int h = 0; h < fmt.height; h++) { |
| 122 | + for (i = 0; i < BARS_NUM; i++) { |
| 123 | + if (fmt.pixelformat == VIDEO_PIX_FMT_XRGB32) { |
| 124 | + uint32_t *pixel = |
| 125 | + (uint32_t *)&buf[4 * (h * fmt.width + bw / 2 + i * bw)]; |
| 126 | + |
| 127 | + for (int j = -PIXELS_NUM / 2; j <= PIXELS_NUM / 2; j++) { |
| 128 | + sum_lab(&colorbars[i], xrgb32_to_lab(*(pixel + j))); |
| 129 | + } |
| 130 | + } else if (fmt.pixelformat == VIDEO_PIX_FMT_RGB565) { |
| 131 | + uint16_t *pixel = |
| 132 | + (uint16_t *)&buf[2 * (h * fmt.width + bw / 2 + i * bw)]; |
| 133 | + |
| 134 | + for (int j = -PIXELS_NUM / 2; j <= PIXELS_NUM / 2; j++) { |
| 135 | + sum_lab(&colorbars[i], rgb565_to_lab(*(pixel + j))); |
| 136 | + } |
| 137 | + } else { |
| 138 | + printk("Format %d is not supported", fmt.pixelformat); |
| 139 | + return false; |
| 140 | + } |
| 141 | + } |
| 142 | + } |
| 143 | + |
| 144 | + for (i = 0; i < BARS_NUM; i++) { |
| 145 | + average_lab(&colorbars[i], PIXELS_NUM * fmt.height); |
| 146 | + if (deltaE(colorbars[i], colorbars_target[i]) > LAB_THRESHOLD) { |
| 147 | + return false; |
| 148 | + } |
| 149 | + } |
| 150 | + |
| 151 | + return true; |
| 152 | +} |
| 153 | + |
| 154 | +#endif /* TEST_PATTERN_CHECK_H_ */ |
0 commit comments