1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
#include "test_precomp.hpp"
using namespace cv;
using namespace std;
#undef RGB
#undef YUV
typedef Vec3b YUV;
typedef Vec3b RGB;
int countOfDifferencies(const Mat& gold, const Mat& result, int maxAllowedDifference = 1)
{
Mat diff;
absdiff(gold, result, diff);
return countNonZero(diff.reshape(1) > maxAllowedDifference);
}
class YUVreader
{
public:
virtual ~YUVreader() {}
virtual YUV read(const Mat& yuv, int row, int col) = 0;
virtual int channels() = 0;
virtual Size size(Size imgSize) = 0;
virtual bool requiresEvenHeight() { return true; }
virtual bool requiresEvenWidth() { return true; }
static YUVreader* getReader(int code);
};
class RGBwriter
{
public:
virtual ~RGBwriter() {}
virtual void write(Mat& rgb, int row, int col, const RGB& val) = 0;
virtual int channels() = 0;
static RGBwriter* getWriter(int code);
};
class GRAYwriter
{
public:
virtual ~GRAYwriter() {}
virtual void write(Mat& gray, int row, int col, const uchar& val)
{
gray.at<uchar>(row, col) = val;
}
virtual int channels() { return 1; }
static GRAYwriter* getWriter(int code);
};
class RGB888Writer : public RGBwriter
{
void write(Mat& rgb, int row, int col, const RGB& val)
{
rgb.at<Vec3b>(row, col) = val;
}
int channels() { return 3; }
};
class BGR888Writer : public RGBwriter
{
void write(Mat& rgb, int row, int col, const RGB& val)
{
Vec3b tmp(val[2], val[1], val[0]);
rgb.at<Vec3b>(row, col) = tmp;
}
int channels() { return 3; }
};
class RGBA8888Writer : public RGBwriter
{
void write(Mat& rgb, int row, int col, const RGB& val)
{
Vec4b tmp(val[0], val[1], val[2], 255);
rgb.at<Vec4b>(row, col) = tmp;
}
int channels() { return 4; }
};
class BGRA8888Writer : public RGBwriter
{
void write(Mat& rgb, int row, int col, const RGB& val)
{
Vec4b tmp(val[2], val[1], val[0], 255);
rgb.at<Vec4b>(row, col) = tmp;
}
int channels() { return 4; }
};
class YUV420Reader: public YUVreader
{
int channels() { return 1; }
Size size(Size imgSize) { return Size(imgSize.width, imgSize.height * 3 / 2); }
};
class YUV422Reader: public YUVreader
{
int channels() { return 2; }
Size size(Size imgSize) { return imgSize; }
bool requiresEvenHeight() { return false; }
};
class NV21Reader: public YUV420Reader
{
YUV read(const Mat& yuv, int row, int col)
{
uchar y = yuv.ptr<uchar>(row)[col];
uchar u = yuv.ptr<uchar>(yuv.rows * 2 / 3 + row/2)[(col/2)*2 + 1];
uchar v = yuv.ptr<uchar>(yuv.rows * 2 / 3 + row/2)[(col/2)*2];
return YUV(y, u, v);
}
};
struct NV12Reader: public YUV420Reader
{
YUV read(const Mat& yuv, int row, int col)
{
uchar y = yuv.ptr<uchar>(row)[col];
uchar u = yuv.ptr<uchar>(yuv.rows * 2 / 3 + row/2)[(col/2)*2];
uchar v = yuv.ptr<uchar>(yuv.rows * 2 / 3 + row/2)[(col/2)*2 + 1];
return YUV(y, u, v);
}
};
class YV12Reader: public YUV420Reader
{
YUV read(const Mat& yuv, int row, int col)
{
int h = yuv.rows * 2 / 3;
uchar y = yuv.ptr<uchar>(row)[col];
uchar u = yuv.ptr<uchar>(h + (row/2 + h/2)/2)[col/2 + ((row/2 + h/2) % 2) * (yuv.cols/2)];
uchar v = yuv.ptr<uchar>(h + row/4)[col/2 + ((row/2) % 2) * (yuv.cols/2)];
return YUV(y, u, v);
}
};
class IYUVReader: public YUV420Reader
{
YUV read(const Mat& yuv, int row, int col)
{
int h = yuv.rows * 2 / 3;
uchar y = yuv.ptr<uchar>(row)[col];
uchar u = yuv.ptr<uchar>(h + row/4)[col/2 + ((row/2) % 2) * (yuv.cols/2)];
uchar v = yuv.ptr<uchar>(h + (row/2 + h/2)/2)[col/2 + ((row/2 + h/2) % 2) * (yuv.cols/2)];
return YUV(y, u, v);
}
};
class UYVYReader: public YUV422Reader
{
YUV read(const Mat& yuv, int row, int col)
{
uchar y = yuv.ptr<Vec2b>(row)[col][1];
uchar u = yuv.ptr<Vec2b>(row)[(col/2)*2][0];
uchar v = yuv.ptr<Vec2b>(row)[(col/2)*2 + 1][0];
return YUV(y, u, v);
}
};
class YUY2Reader: public YUV422Reader
{
YUV read(const Mat& yuv, int row, int col)
{
uchar y = yuv.ptr<Vec2b>(row)[col][0];
uchar u = yuv.ptr<Vec2b>(row)[(col/2)*2][1];
uchar v = yuv.ptr<Vec2b>(row)[(col/2)*2 + 1][1];
return YUV(y, u, v);
}
};
class YVYUReader: public YUV422Reader
{
YUV read(const Mat& yuv, int row, int col)
{
uchar y = yuv.ptr<Vec2b>(row)[col][0];
uchar u = yuv.ptr<Vec2b>(row)[(col/2)*2 + 1][1];
uchar v = yuv.ptr<Vec2b>(row)[(col/2)*2][1];
return YUV(y, u, v);
}
};
class YUV888Reader : public YUVreader
{
YUV read(const Mat& yuv, int row, int col)
{
return yuv.at<YUV>(row, col);
}
int channels() { return 3; }
Size size(Size imgSize) { return imgSize; }
bool requiresEvenHeight() { return false; }
bool requiresEvenWidth() { return false; }
};
class YUV2RGB_Converter
{
public:
RGB convert(YUV yuv)
{
int y = std::max(0, yuv[0] - 16);
int u = yuv[1] - 128;
int v = yuv[2] - 128;
uchar r = saturate_cast<uchar>(1.164f * y + 1.596f * v);
uchar g = saturate_cast<uchar>(1.164f * y - 0.813f * v - 0.391f * u);
uchar b = saturate_cast<uchar>(1.164f * y + 2.018f * u);
return RGB(r, g, b);
}
};
class YUV2GRAY_Converter
{
public:
uchar convert(YUV yuv)
{
return yuv[0];
}
};
YUVreader* YUVreader::getReader(int code)
{
switch(code)
{
case CV_YUV2RGB_NV12:
case CV_YUV2BGR_NV12:
case CV_YUV2RGBA_NV12:
case CV_YUV2BGRA_NV12:
return new NV12Reader();
case CV_YUV2RGB_NV21:
case CV_YUV2BGR_NV21:
case CV_YUV2RGBA_NV21:
case CV_YUV2BGRA_NV21:
return new NV21Reader();
case CV_YUV2RGB_YV12:
case CV_YUV2BGR_YV12:
case CV_YUV2RGBA_YV12:
case CV_YUV2BGRA_YV12:
return new YV12Reader();
case CV_YUV2RGB_IYUV:
case CV_YUV2BGR_IYUV:
case CV_YUV2RGBA_IYUV:
case CV_YUV2BGRA_IYUV:
return new IYUVReader();
case CV_YUV2RGB_UYVY:
case CV_YUV2BGR_UYVY:
case CV_YUV2RGBA_UYVY:
case CV_YUV2BGRA_UYVY:
return new UYVYReader();
//case CV_YUV2RGB_VYUY = 109,
//case CV_YUV2BGR_VYUY = 110,
//case CV_YUV2RGBA_VYUY = 113,
//case CV_YUV2BGRA_VYUY = 114,
// return ??
case CV_YUV2RGB_YUY2:
case CV_YUV2BGR_YUY2:
case CV_YUV2RGBA_YUY2:
case CV_YUV2BGRA_YUY2:
return new YUY2Reader();
case CV_YUV2RGB_YVYU:
case CV_YUV2BGR_YVYU:
case CV_YUV2RGBA_YVYU:
case CV_YUV2BGRA_YVYU:
return new YVYUReader();
case CV_YUV2GRAY_420:
return new NV21Reader();
case CV_YUV2GRAY_UYVY:
return new UYVYReader();
case CV_YUV2GRAY_YUY2:
return new YUY2Reader();
case CV_YUV2BGR:
case CV_YUV2RGB:
return new YUV888Reader();
default:
return 0;
}
}
RGBwriter* RGBwriter::getWriter(int code)
{
switch(code)
{
case CV_YUV2RGB_NV12:
case CV_YUV2RGB_NV21:
case CV_YUV2RGB_YV12:
case CV_YUV2RGB_IYUV:
case CV_YUV2RGB_UYVY:
//case CV_YUV2RGB_VYUY:
case CV_YUV2RGB_YUY2:
case CV_YUV2RGB_YVYU:
case CV_YUV2RGB:
return new RGB888Writer();
case CV_YUV2BGR_NV12:
case CV_YUV2BGR_NV21:
case CV_YUV2BGR_YV12:
case CV_YUV2BGR_IYUV:
case CV_YUV2BGR_UYVY:
//case CV_YUV2BGR_VYUY:
case CV_YUV2BGR_YUY2:
case CV_YUV2BGR_YVYU:
case CV_YUV2BGR:
return new BGR888Writer();
case CV_YUV2RGBA_NV12:
case CV_YUV2RGBA_NV21:
case CV_YUV2RGBA_YV12:
case CV_YUV2RGBA_IYUV:
case CV_YUV2RGBA_UYVY:
//case CV_YUV2RGBA_VYUY:
case CV_YUV2RGBA_YUY2:
case CV_YUV2RGBA_YVYU:
return new RGBA8888Writer();
case CV_YUV2BGRA_NV12:
case CV_YUV2BGRA_NV21:
case CV_YUV2BGRA_YV12:
case CV_YUV2BGRA_IYUV:
case CV_YUV2BGRA_UYVY:
//case CV_YUV2BGRA_VYUY:
case CV_YUV2BGRA_YUY2:
case CV_YUV2BGRA_YVYU:
return new BGRA8888Writer();
default:
return 0;
};
}
GRAYwriter* GRAYwriter::getWriter(int code)
{
switch(code)
{
case CV_YUV2GRAY_420:
case CV_YUV2GRAY_UYVY:
case CV_YUV2GRAY_YUY2:
return new GRAYwriter();
default:
return 0;
}
}
template<class convertor>
void referenceYUV2RGB(const Mat& yuv, Mat& rgb, YUVreader* yuvReader, RGBwriter* rgbWriter)
{
convertor cvt;
for(int row = 0; row < rgb.rows; ++row)
for(int col = 0; col < rgb.cols; ++col)
rgbWriter->write(rgb, row, col, cvt.convert(yuvReader->read(yuv, row, col)));
}
template<class convertor>
void referenceYUV2GRAY(const Mat& yuv, Mat& rgb, YUVreader* yuvReader, GRAYwriter* grayWriter)
{
convertor cvt;
for(int row = 0; row < rgb.rows; ++row)
for(int col = 0; col < rgb.cols; ++col)
grayWriter->write(rgb, row, col, cvt.convert(yuvReader->read(yuv, row, col)));
}
CV_ENUM(YUVCVTS, CV_YUV2RGB_NV12, CV_YUV2BGR_NV12, CV_YUV2RGB_NV21, CV_YUV2BGR_NV21,
CV_YUV2RGBA_NV12, CV_YUV2BGRA_NV12, CV_YUV2RGBA_NV21, CV_YUV2BGRA_NV21,
CV_YUV2RGB_YV12, CV_YUV2BGR_YV12, CV_YUV2RGB_IYUV, CV_YUV2BGR_IYUV,
CV_YUV2RGBA_YV12, CV_YUV2BGRA_YV12, CV_YUV2RGBA_IYUV, CV_YUV2BGRA_IYUV,
CV_YUV2RGB_UYVY, CV_YUV2BGR_UYVY, CV_YUV2RGBA_UYVY, CV_YUV2BGRA_UYVY,
CV_YUV2RGB_YUY2, CV_YUV2BGR_YUY2, CV_YUV2RGB_YVYU, CV_YUV2BGR_YVYU,
CV_YUV2RGBA_YUY2, CV_YUV2BGRA_YUY2, CV_YUV2RGBA_YVYU, CV_YUV2BGRA_YVYU,
CV_YUV2GRAY_420, CV_YUV2GRAY_UYVY, CV_YUV2GRAY_YUY2,
CV_YUV2BGR, CV_YUV2RGB);
typedef ::testing::TestWithParam<YUVCVTS> Imgproc_ColorYUV;
TEST_P(Imgproc_ColorYUV, accuracy)
{
int code = GetParam();
RNG& random = theRNG();
YUVreader* yuvReader = YUVreader::getReader(code);
RGBwriter* rgbWriter = RGBwriter::getWriter(code);
GRAYwriter* grayWriter = GRAYwriter::getWriter(code);
int dcn = (rgbWriter == 0) ? grayWriter->channels() : rgbWriter->channels();
for(int iter = 0; iter < 30; ++iter)
{
Size sz(random.uniform(1, 641), random.uniform(1, 481));
if(yuvReader->requiresEvenWidth()) sz.width += sz.width % 2;
if(yuvReader->requiresEvenHeight()) sz.height += sz.height % 2;
Size ysz = yuvReader->size(sz);
Mat src = Mat(ysz.height, ysz.width * yuvReader->channels(), CV_8UC1).reshape(yuvReader->channels());
Mat dst = Mat(sz.height, sz.width * dcn, CV_8UC1).reshape(dcn);
Mat gold(sz, CV_8UC(dcn));
random.fill(src, RNG::UNIFORM, 0, 256);
if(rgbWriter)
referenceYUV2RGB<YUV2RGB_Converter>(src, gold, yuvReader, rgbWriter);
else
referenceYUV2GRAY<YUV2GRAY_Converter>(src, gold, yuvReader, grayWriter);
cv::cvtColor(src, dst, code, -1);
EXPECT_EQ(0, countOfDifferencies(gold, dst));
}
}
TEST_P(Imgproc_ColorYUV, roi_accuracy)
{
int code = GetParam();
RNG& random = theRNG();
YUVreader* yuvReader = YUVreader::getReader(code);
RGBwriter* rgbWriter = RGBwriter::getWriter(code);
GRAYwriter* grayWriter = GRAYwriter::getWriter(code);
int dcn = (rgbWriter == 0) ? grayWriter->channels() : rgbWriter->channels();
for(int iter = 0; iter < 30; ++iter)
{
Size sz(random.uniform(1, 641), random.uniform(1, 481));
if(yuvReader->requiresEvenWidth()) sz.width += sz.width % 2;
if(yuvReader->requiresEvenHeight()) sz.height += sz.height % 2;
int roi_offset_top = random.uniform(0, 6);
int roi_offset_bottom = random.uniform(0, 6);
int roi_offset_left = random.uniform(0, 6);
int roi_offset_right = random.uniform(0, 6);
Size ysz = yuvReader->size(sz);
Mat src_full(ysz.height + roi_offset_top + roi_offset_bottom, ysz.width + roi_offset_left + roi_offset_right, CV_8UC(yuvReader->channels()));
Mat dst_full(sz.height + roi_offset_left + roi_offset_right, sz.width + roi_offset_top + roi_offset_bottom, CV_8UC(dcn), Scalar::all(0));
Mat gold_full(dst_full.size(), CV_8UC(dcn), Scalar::all(0));
random.fill(src_full, RNG::UNIFORM, 0, 256);
Mat src = src_full(Range(roi_offset_top, roi_offset_top + ysz.height), Range(roi_offset_left, roi_offset_left + ysz.width));
Mat dst = dst_full(Range(roi_offset_left, roi_offset_left + sz.height), Range(roi_offset_top, roi_offset_top + sz.width));
Mat gold = gold_full(Range(roi_offset_left, roi_offset_left + sz.height), Range(roi_offset_top, roi_offset_top + sz.width));
if(rgbWriter)
referenceYUV2RGB<YUV2RGB_Converter>(src, gold, yuvReader, rgbWriter);
else
referenceYUV2GRAY<YUV2GRAY_Converter>(src, gold, yuvReader, grayWriter);
cv::cvtColor(src, dst, code, -1);
EXPECT_EQ(0, countOfDifferencies(gold_full, dst_full));
}
}
INSTANTIATE_TEST_CASE_P(cvt420, Imgproc_ColorYUV,
::testing::Values((int)CV_YUV2RGB_NV12, (int)CV_YUV2BGR_NV12, (int)CV_YUV2RGB_NV21, (int)CV_YUV2BGR_NV21,
(int)CV_YUV2RGBA_NV12, (int)CV_YUV2BGRA_NV12, (int)CV_YUV2RGBA_NV21, (int)CV_YUV2BGRA_NV21,
(int)CV_YUV2RGB_YV12, (int)CV_YUV2BGR_YV12, (int)CV_YUV2RGB_IYUV, (int)CV_YUV2BGR_IYUV,
(int)CV_YUV2RGBA_YV12, (int)CV_YUV2BGRA_YV12, (int)CV_YUV2RGBA_IYUV, (int)CV_YUV2BGRA_IYUV,
(int)CV_YUV2GRAY_420));
INSTANTIATE_TEST_CASE_P(cvt422, Imgproc_ColorYUV,
::testing::Values((int)CV_YUV2RGB_UYVY, (int)CV_YUV2BGR_UYVY, (int)CV_YUV2RGBA_UYVY, (int)CV_YUV2BGRA_UYVY,
(int)CV_YUV2RGB_YUY2, (int)CV_YUV2BGR_YUY2, (int)CV_YUV2RGB_YVYU, (int)CV_YUV2BGR_YVYU,
(int)CV_YUV2RGBA_YUY2, (int)CV_YUV2BGRA_YUY2, (int)CV_YUV2RGBA_YVYU, (int)CV_YUV2BGRA_YVYU,
(int)CV_YUV2GRAY_UYVY, (int)CV_YUV2GRAY_YUY2));