1 //
2 //  ImageProcessTest.cpp
3 //  MNNTests
4 //
5 //  Created by MNN on 2019/01/10.
6 //  Copyright © 2018, Alibaba Group Holding Limited
7 //
8 
9 #include <MNN/ImageProcess.hpp>
10 #include <cmath>
11 #include <memory>
12 #include <map>
13 #include "MNNTestSuite.h"
14 
15 using namespace MNN;
16 using namespace MNN::CV;
17 
genSourceData(int h,int w,int bpp)18 static std::vector<uint8_t> genSourceData(int h, int w, int bpp) {
19     std::vector<uint8_t> source(h * w * bpp);
20     for (int y = 0; y < h; ++y) {
21         auto pixelY = source.data() + w * y * bpp;
22         int magicY  = ((h - y) * (h - y)) % 79;
23         for (int x = 0; x < w; ++x) {
24             auto pixelX = pixelY + x * bpp;
25             int magicX  = (x * x) % 113;
26             for (int p = 0; p < bpp; ++p) {
27                 int magic = (magicX + magicY + p * p * p) % 255;
28                 pixelX[p] = magic;
29             }
30         }
31     }
32     return source;
33 }
34 
35 // format in {YUV_NV21, YUV_NV12, YUV_I420}
36 // dstFormat in {RGBA, BGRA, RGB, BGR, GRAY}
genYUVData(int h,int w,ImageFormat format,ImageFormat dstFormat,std::vector<uint8_t> & source,std::vector<uint8_t> & dest)37 static int genYUVData(int h, int w, ImageFormat format, ImageFormat dstFormat,
38                       std::vector<uint8_t>& source, std::vector<uint8_t>& dest) {
39     // https://www.jianshu.com/p/e67f79f10c65
40     if (format != YUV_NV21 && format != YUV_NV12 && /* YUV420sp(bi-planer): NV12, NV21 */
41         format != YUV_I420                          /* YUV420p(planer): I420 or YV12 */) {
42         return -1;
43     }
44     bool yuv420p = (format != YUV_NV12 && format != YUV_NV21);
45 
46     int bpp = 0;
47     if (dstFormat == RGBA || dstFormat == BGRA) {
48         bpp = 4;
49     } else if (dstFormat == RGB || dstFormat == BGR) {
50         bpp = 3;
51     } else if (dstFormat == GRAY) {
52         bpp = 1;
53     }
54     if (bpp == 0) {
55         return -2;
56     }
57 
58     // YUV420, Y: h*w, UV: (h/2)*(w/2)*2
59     int ySize = h * w, uvSize = (h/2)*(w/2)*2;
60     source.resize(ySize + uvSize);
61     dest.resize(h * w * bpp);
62 
63     auto dstData = dest.data();
64     for (int y = 0; y < h; ++y) {
65         auto pixelY  = source.data() + w * y;
66         auto pixelUV = source.data() + w * h + (y / 2) * (yuv420p ? w / 2 : w);
67         int magicY   = ((h - y) * (h - y)) % 79;
68         for (int x = 0; x < w; ++x) {
69             int magicX  = ((x % 113) * (x % 113)) % 113, xx = x / 2;
70             int yVal   = (magicX + magicY) % 255;
71 
72             int uVal, vVal;
73             int uIndex = (yuv420p ? xx : 2 * xx);
74             int vIndex = (yuv420p ? xx + (h/2)*(w/2) : 2 * xx + 1);
75             if (format != YUV_NV12 && format != YUV_I420) {
76                 std::swap(uIndex, vIndex);
77             }
78             if (y % 2 == 0 && x % 2 == 0) {
79                 magicX      = ((((xx % 283) * (xx % 283)) % 283) * (((xx % 283) * (xx % 283)) % 283)) % 283;
80                 uVal  = (magicX + magicY) % 255;
81                 vVal  = (magicX + magicY * 179) % 255;
82                 pixelUV[uIndex] = uVal;
83                 pixelUV[vIndex] = vVal;
84             } else {
85                 uVal = pixelUV[uIndex];
86                 vVal = pixelUV[vIndex];
87             }
88             pixelY[x]   = yVal;
89 
90             int Y = yVal, U = uVal - 128, V = vVal - 128;
91             auto dstData = dest.data() + (y * w + x) * bpp;
92             if (dstFormat == GRAY) {
93                 dstData[0] = Y;
94                 continue;
95             }
96             Y     = Y << 6;
97 #define CLAMP(x, minVal, maxVal) std::min(std::max((x), (minVal)), (maxVal))
98             int r = CLAMP((Y + 73 * V) >> 6, 0, 255);
99             int g = CLAMP((Y - 25 * U - 37 * V) >> 6, 0, 255);
100             int b = CLAMP((Y + 130 * U) >> 6, 0, 255);
101 
102             dstData[0] = r;
103             dstData[1] = g;
104             dstData[2] = b;
105             if (dstFormat == BGRA || dstFormat == BGR) {
106                 std::swap(dstData[0], dstData[2]);
107             }
108             if (bpp == 4) {
109                 dstData[3] = 255;
110             }
111         }
112     }
113     return 0;
114 }
115 
116 class ImageProcessGrayToGrayTest : public MNNTestCase {
117 public:
118     virtual ~ImageProcessGrayToGrayTest() = default;
run(int precision)119     virtual bool run(int precision) {
120         int w = 27, h = 1, size = w * h;
121         auto integers = genSourceData(h, w, 1);
122         std::vector<float> floats(size * 4);
123         std::shared_ptr<MNN::Tensor> tensor(
124             MNN::Tensor::create<float>(std::vector<int>{1, 1, h, w}, floats.data(), Tensor::CAFFE_C4));
125 
126         ImageProcess::Config config;
127         config.sourceFormat = GRAY;
128         config.destFormat   = GRAY;
129         std::shared_ptr<ImageProcess> process(ImageProcess::create(config));
130         process->convert(integers.data(), w, h, 0, tensor.get());
131         for (int i = 0; i < floats.size() / 4; ++i) {
132             int s = floats[4 * i + 0];
133             if (s != integers[i]) {
134                 MNN_ERROR("Error for turn gray to float:%d, %d -> %f\n", i, integers[i], floats[4 * i]);
135                 return false;
136             }
137         }
138         return true;
139     }
140 };
141 MNNTestSuiteRegister(ImageProcessGrayToGrayTest, "cv/image_process/gray_to_gray");
142 
143 class ImageProcessGrayToGrayBilinearTransformTest : public MNNTestCase {
144 public:
145     virtual ~ImageProcessGrayToGrayBilinearTransformTest() = default;
run(int precision)146     virtual bool run(int precision) {
147         ImageProcess::Config config;
148         config.sourceFormat = GRAY;
149         config.destFormat   = GRAY;
150         config.filterType   = BILINEAR;
151         config.wrap         = CLAMP_TO_EDGE;
152         std::shared_ptr<ImageProcess> process(ImageProcess::create(config));
153 
154         int sw = 1280;
155         int sh = 720;
156         int dw = 360;
157         int dh = 640;
158         Matrix tr;
159         tr.setScale(1.0 / sw, 1.0 / sh);
160         tr.postRotate(30, 0.5f, 0.5f);
161         tr.postScale(dw, dh);
162         tr.invert(&tr);
163         process->setMatrix(tr);
164 
165         auto integers = genSourceData(sh, sw, 1);
166         std::shared_ptr<Tensor> tensor(
167             Tensor::create<float>(std::vector<int>{1, 1, dw, dh}, nullptr, Tensor::CAFFE_C4));
168         for (int i = 0; i < 10; ++i) {
169             process->convert(integers.data(), sw, sh, 0, tensor.get());
170         }
171         auto floats   = tensor->host<float>();
172         int expects[] = {18, 36, 14, 36, 18, 44, 30, 60, 50, 24};
173         for (int v = 0; v < 10; ++v) {
174             if (fabsf(floats[4 * v] - (float)expects[v]) >= 2) {
175                 MNN_ERROR("Error for %d, %.f, correct=%d\n", v, floats[4 * v], expects[v]);
176                 return false;
177             }
178         }
179         return true;
180     }
181 };
182 MNNTestSuiteRegister(ImageProcessGrayToGrayBilinearTransformTest, "cv/image_process/gray_to_gray_bilinear_transorm");
183 
184 class ImageProcessGrayToGrayNearestTransformTest : public MNNTestCase {
185 public:
186     virtual ~ImageProcessGrayToGrayNearestTransformTest() = default;
run(int precision)187     virtual bool run(int precision) {
188         ImageProcess::Config config;
189         config.sourceFormat = GRAY;
190         config.destFormat   = GRAY;
191         config.filterType   = NEAREST;
192         config.wrap         = ZERO;
193         std::shared_ptr<ImageProcess> process(ImageProcess::create(config));
194 
195         int sw = 1280;
196         int sh = 720;
197         int dw = 360;
198         int dh = 640;
199         Matrix tr;
200         tr.setScale(1.0 / sw, 1.0 / sh);
201         tr.postRotate(90, 0.5f, 0.5f);
202         tr.postScale(dw, dh);
203         tr.invert(&tr);
204         process->setMatrix(tr);
205 
206         auto integers = genSourceData(sh, sw, 1);
207         std::shared_ptr<Tensor> tensor(
208             Tensor::create<float>(std::vector<int>{1, 1, dw, dh}, nullptr, Tensor::CAFFE_C4));
209         for (int i = 0; i < 10; ++i) {
210             process->convert(integers.data(), sw, sh, 0, tensor.get());
211         }
212         auto floats  = tensor->host<float>();
213         int expect[] = {0, 4, 16, 36, 64, 21, 65, 38, 19, 8};
214         for (int v = 0; v < 10; ++v) {
215             if ((int)(floats[4 * v]) != expect[v]) {
216                 MNN_ERROR("Error for %d, %.f, correct=%d\n", v, floats[4 * v], expect[v]);
217                 return false;
218             }
219         }
220         return true;
221     }
222 };
223 MNNTestSuiteRegister(ImageProcessGrayToGrayNearestTransformTest, "cv/image_process/gray_to_gray_nearest_transorm");
224 
225 class ImageProcessGrayToRGBATest : public MNNTestCase {
226 public:
227     virtual ~ImageProcessGrayToRGBATest() = default;
run(int precision)228     virtual bool run(int precision) {
229         int w = 15, h = 1, size = w * h;
230         auto gray = genSourceData(h, w, 1);
231         std::vector<uint8_t> rgba(size * 4);
232         std::shared_ptr<MNN::Tensor> tensor(MNN::Tensor::create<uint8_t>(std::vector<int>{1, h, w, 4}, rgba.data()));
233 
234         ImageProcess::Config config;
235         config.sourceFormat = GRAY;
236         config.destFormat   = RGBA;
237         std::shared_ptr<ImageProcess> process(ImageProcess::create(config));
238         process->convert(gray.data(), w, h, 0, tensor.get());
239         for (int i = 0; i < size; ++i) {
240             int s = gray[i];
241             int r = rgba[4 * i + 0];
242             int g = rgba[4 * i + 1];
243             int b = rgba[4 * i + 2];
244 
245             int y = s;
246             int a = rgba[4 * i + 3];
247 
248             if (y != r || y != g || y != b || a != 255) {
249                 MNN_ERROR("Turn gray to RGBA:%d, %d -> %d,%d,%d,%d\n", i, s, r, g, b, a);
250                 return false;
251             }
252         }
253         return true;
254     }
255 };
256 MNNTestSuiteRegister(ImageProcessGrayToRGBATest, "cv/image_process/gray_to_rgba");
257 
258 class ImageProcessBGRToGrayTest : public MNNTestCase {
259 public:
260     virtual ~ImageProcessBGRToGrayTest() = default;
run(int precision)261     virtual bool run(int precision) {
262         int w = 15, h = 1, size = w * h;
263         auto bgr = genSourceData(h, w, 3);
264         std::vector<uint8_t> gray(size);
265         std::shared_ptr<MNN::Tensor> tensor(MNN::Tensor::create<uint8_t>(std::vector<int>{1, h, w, 1}, gray.data()));
266 
267         ImageProcess::Config config;
268         config.sourceFormat = BGR;
269         config.destFormat   = GRAY;
270         std::shared_ptr<ImageProcess> process(ImageProcess::create(config));
271         process->convert(bgr.data(), w, h, 0, tensor.get());
272         for (int i = 0; i < size; ++i) {
273             int s = gray[i];
274             int r = bgr[3 * i + 2];
275             int g = bgr[3 * i + 1];
276             int b = bgr[3 * i + 0];
277             int y = (19 * r + 38 * g + 7 * b) >> 6;
278             if (abs(y - s) >= 2) {
279                 MNN_ERROR("Turn BGR to gray:%d, %d,%d,%d -> %d\n", i, r, g, b, s);
280                 return false;
281             }
282         }
283         return true;
284     }
285 };
286 MNNTestSuiteRegister(ImageProcessBGRToGrayTest, "cv/image_process/bgr_to_gray");
287 
288 class ImageProcessRGBToBGRTest : public MNNTestCase {
289 public:
run(int precision)290     virtual bool run(int precision) {
291         int w = 27, h = 1, size = w * h;
292         auto integers = genSourceData(h, w, 3);
293         std::vector<uint8_t> resultData(size * 3);
294         std::shared_ptr<MNN::Tensor> tensor(
295             MNN::Tensor::create<uint8_t>(std::vector<int>{1, h, w, 3}, resultData.data(), Tensor::TENSORFLOW));
296         ImageProcess::Config config;
297         config.sourceFormat = RGB;
298         config.destFormat   = BGR;
299 
300         std::shared_ptr<ImageProcess> process(ImageProcess::create(config));
301         process->convert(integers.data(), w, h, 0, tensor.get());
302         for (int i = 0; i < size; ++i) {
303             int r = resultData[3 * i + 2];
304             int g = resultData[3 * i + 1];
305             int b = resultData[3 * i + 0];
306             if (r != integers[3 * i + 0] || g != integers[3 * i + 1] || b != integers[3 * i + 2]) {
307                 MNN_ERROR("Error for turn rgb to bgr:\n %d,%d,%d->%d, %d, %d\n", integers[3 * i + 0],
308                           integers[3 * i + 1], integers[3 * i + 2], r, g, b);
309                 return false;
310             }
311         }
312         return true;
313     }
314 };
315 MNNTestSuiteRegister(ImageProcessRGBToBGRTest, "cv/image_process/rgb_to_bgr");
316 
317 class ImageProcessRGBAToBGRATest : public MNNTestCase {
318 public:
319     virtual ~ImageProcessRGBAToBGRATest() = default;
run(int precision)320     virtual bool run(int precision) {
321         int w = 27, h = 1, size = w * h;
322         auto integers = genSourceData(h, w, 4);
323         std::vector<uint8_t> floats(size * 4);
324         std::shared_ptr<MNN::Tensor> tensor(
325             MNN::Tensor::create<uint8_t>(std::vector<int>{1, h, w, 4}, floats.data(), Tensor::TENSORFLOW));
326         ImageProcess::Config config;
327         config.sourceFormat = RGBA;
328         config.destFormat   = BGRA;
329 
330         std::shared_ptr<ImageProcess> process(ImageProcess::create(config));
331         process->convert(integers.data(), w, h, 0, tensor.get());
332         for (int i = 0; i < floats.size() / 4; ++i) {
333             int r = floats[4 * i + 2];
334             int g = floats[4 * i + 1];
335             int b = floats[4 * i + 0];
336             if (r != integers[4 * i + 0] || g != integers[4 * i + 1] || b != integers[4 * i + 2]) {
337                 MNN_ERROR("Error for turn rgba to bgra:\n %d,%d,%d->%d, %d, %d, %d\n", integers[4 * i + 0],
338                           integers[4 * i + 1], integers[4 * i + 2], floats[4 * i + 0], floats[4 * i + 1],
339                           floats[4 * i + 2], floats[4 * i + 3]);
340                 return false;
341             }
342         }
343         return true;
344     }
345 };
346 MNNTestSuiteRegister(ImageProcessRGBAToBGRATest, "cv/image_process/rgba_to_bgra");
347 
348 class ImageProcessBGRToBGRTest : public MNNTestCase {
349 public:
350     virtual ~ImageProcessBGRToBGRTest() = default;
run(int precision)351     virtual bool run(int precision) {
352         int w = 27, h = 1, size = w * h;
353         auto integers = genSourceData(h, w, 3);
354         std::vector<float> floats(size * 4);
355         std::shared_ptr<MNN::Tensor> tensor(
356             MNN::Tensor::create<float>(std::vector<int>{1, 1, h, w}, floats.data(), Tensor::CAFFE_C4));
357         ImageProcess::Config config;
358         config.sourceFormat = BGR;
359         config.destFormat   = BGR;
360 
361         std::shared_ptr<ImageProcess> process(ImageProcess::create(config));
362         process->convert(integers.data(), w, h, 0, tensor.get());
363         for (int i = 0; i < floats.size() / 4; ++i) {
364             int r = floats[4 * i + 0];
365             int g = floats[4 * i + 1];
366             int b = floats[4 * i + 2];
367             if (r != integers[3 * i + 0] || g != integers[3 * i + 1] || b != integers[3 * i + 2]) {
368                 MNN_ERROR("Error for turn rgb to float:\n %d,%d,%d->%f, %f, %f, %f\n", integers[3 * i + 0],
369                           integers[3 * i + 1], integers[3 * i + 2], floats[4 * i + 0], floats[4 * i + 1],
370                           floats[4 * i + 2], floats[4 * i + 3]);
371                 return false;
372             }
373         }
374         return true;
375     }
376 };
377 MNNTestSuiteRegister(ImageProcessBGRToBGRTest, "cv/image_process/bgr_to_bgr");
378 
379 class ImageProcessRGBToGrayTest : public MNNTestCase {
380 public:
381     virtual ~ImageProcessRGBToGrayTest() = default;
run(int precision)382     virtual bool run(int precision) {
383         int w = 15, h = 1, size = w * h;
384         auto rgb = genSourceData(h, w, 3);
385         std::vector<uint8_t> gray(size);
386         std::shared_ptr<MNN::Tensor> tensor(MNN::Tensor::create<uint8_t>(std::vector<int>{1, h, w, 1}, gray.data()));
387         ImageProcess::Config config;
388         config.sourceFormat = RGB;
389         config.destFormat   = GRAY;
390 
391         std::shared_ptr<ImageProcess> process(ImageProcess::create(config));
392         process->convert(rgb.data(), w, h, 0, tensor.get());
393         for (int i = 0; i < size; ++i) {
394             int s = gray[i];
395             int r = rgb[3 * i + 0];
396             int g = rgb[3 * i + 1];
397             int b = rgb[3 * i + 2];
398             int y = (19 * r + 38 * g + 7 * b) >> 6;
399             if (abs(y - s) >= 2) {
400                 MNN_ERROR("Error: Turn RGB to gray:%d, %d,%d,%d -> %d\n", i, r, g, b, s);
401                 return false;
402             }
403         }
404         return true;
405     }
406 };
407 MNNTestSuiteRegister(ImageProcessRGBToGrayTest, "cv/image_process/rgb_to_gray");
408 
409 class ImageProcessRGBAToGrayTest : public MNNTestCase {
410 public:
411     virtual ~ImageProcessRGBAToGrayTest() = default;
run(int precision)412     virtual bool run(int precision) {
413         int w = 15, h = 1, size = w * h;
414         auto rgba = genSourceData(h, w, 4);
415         std::vector<uint8_t> gray(size);
416         std::shared_ptr<MNN::Tensor> tensor(MNN::Tensor::create<uint8_t>(std::vector<int>{1, h, w, 1}, gray.data()));
417 
418         ImageProcess::Config config;
419         config.sourceFormat = RGBA;
420         config.destFormat   = GRAY;
421         std::shared_ptr<ImageProcess> process(ImageProcess::create(config));
422         process->convert(rgba.data(), w, h, 0, tensor.get());
423         for (int i = 0; i < size; ++i) {
424             int s = gray[i];
425             int r = rgba[4 * i + 0];
426             int g = rgba[4 * i + 1];
427             int b = rgba[4 * i + 2];
428             int y = (19 * r + 38 * g + 7 * b) >> 6;
429             if (abs(y - s) >= 2) {
430                 MNN_ERROR("Turn RGBA to gray:%d, %d,%d,%d -> %d\n", i, r, g, b, s);
431                 return false;
432             }
433         }
434         return true;
435     }
436 };
437 MNNTestSuiteRegister(ImageProcessRGBAToGrayTest, "cv/image_process/rgba_to_gray");
438 
439 class ImageProcessRGBAToGrayBilinearTransformTest : public MNNTestCase {
440 public:
441     virtual ~ImageProcessRGBAToGrayBilinearTransformTest() = default;
run(int precision)442     virtual bool run(int precision) {
443         ImageProcess::Config config;
444         config.sourceFormat = RGBA;
445         config.destFormat   = GRAY;
446         config.filterType   = BILINEAR;
447         config.wrap         = CLAMP_TO_EDGE;
448         std::shared_ptr<ImageProcess> process(ImageProcess::create(config));
449 
450         int sw = 1280;
451         int sh = 720;
452         int dw = 360;
453         int dh = 640;
454         Matrix tr;
455         tr.setScale(1.0 / sw, 1.0 / sh);
456         tr.postRotate(30, 0.5f, 0.5f);
457         tr.postScale(dw, dh);
458         tr.invert(&tr);
459         process->setMatrix(tr);
460 
461         auto integers = genSourceData(sh, sw, 4);
462         std::shared_ptr<Tensor> tensor(
463             Tensor::create<float>(std::vector<int>{1, 1, dw, dh}, nullptr, Tensor::CAFFE_C4));
464         process->convert(integers.data(), sw, sh, 0, tensor.get());
465         auto floats  = tensor->host<float>();
466         int expect[] = {19, 37, 15, 37, 19, 45, 31, 61, 51, 25};
467         for (int v = 0; v < 10; ++v) {
468             if (fabsf(floats[4 * v] - (float)expect[v]) >= 2) {
469                 MNN_ERROR("Error for %d, %.f, correct=%d\n", v, floats[4 * v], expect[v]);
470                 return false;
471             }
472         }
473         return true;
474     }
475 };
476 MNNTestSuiteRegister(ImageProcessRGBAToGrayBilinearTransformTest, "cv/image_process/rgba_to_gray_bilinear_transorm");
477 
478 class ImageProcessRGBAToGrayNearestTransformTest : public MNNTestCase {
479 public:
480     virtual ~ImageProcessRGBAToGrayNearestTransformTest() = default;
run(int precision)481     virtual bool run(int precision) {
482         ImageProcess::Config config;
483         config.sourceFormat = RGBA;
484         config.destFormat   = GRAY;
485         config.filterType   = NEAREST;
486         config.wrap         = CLAMP_TO_EDGE;
487         std::shared_ptr<ImageProcess> process(ImageProcess::create(config));
488 
489         int sw = 1280;
490         int sh = 720;
491         int dw = 360;
492         int dh = 640;
493         Matrix tr;
494         tr.setScale(1.0 / sw, 1.0 / sh);
495         tr.postRotate(60, 0.5f, 0.5f);
496         tr.postScale(dw, dh);
497         tr.invert(&tr);
498         process->setMatrix(tr);
499 
500         auto integers = genSourceData(sh, sw, 4);
501         std::shared_ptr<Tensor> tensor(
502             Tensor::create<float>(std::vector<int>{1, 1, dw, dh}, nullptr, Tensor::CAFFE_C4));
503         for (int i = 0; i < 10; ++i) {
504             process->convert(integers.data(), sw, sh, 0, tensor.get());
505         }
506         auto floats  = tensor->host<float>();
507         int expect[] = {3, 50, 26, 17, 5, 1, 5, 10, 26, 50};
508         for (int v = 0; v < 10; ++v) {
509             if ((int)(floats[4 * v]) != expect[v]) {
510                 MNN_ERROR("Error for %d, %.f, correct=%d\n", v, floats[4 * v], expect[v]);
511                 return false;
512             }
513         }
514         return true;
515     }
516 };
517 MNNTestSuiteRegister(ImageProcessRGBAToGrayNearestTransformTest, "cv/image_process/rgba_to_gray_nearest_transorm");
518 
519 class ImageProcessRGBAToBGRTest : public MNNTestCase {
520 public:
521     virtual ~ImageProcessRGBAToBGRTest() = default;
run(int precision)522     virtual bool run(int precision) {
523         int w = 15, h = 1, size = w * h;
524         auto rgba = genSourceData(h, w, 4);
525         std::vector<uint8_t> bgr(size * 3);
526         std::shared_ptr<MNN::Tensor> tensor(MNN::Tensor::create<uint8_t>(std::vector<int>{1, h, w, 3}, bgr.data()));
527 
528         ImageProcess::Config config;
529         config.sourceFormat = RGBA;
530         config.destFormat   = BGR;
531         std::shared_ptr<ImageProcess> process(ImageProcess::create(config));
532         process->convert(rgba.data(), w, h, 0, tensor.get());
533         for (int i = 0; i < size; ++i) {
534             if (rgba[4 * i + 0] != bgr[3 * i + 2] || rgba[4 * i + 1] != bgr[3 * i + 1] ||
535                 rgba[4 * i + 2] != bgr[3 * i + 0]) {
536                 MNN_ERROR("Error: Turn RGBA to BGR:%d, %d,%d,%d,%d -> %d,%d,%d\n", i, rgba[4 * i + 0], rgba[4 * i + 1],
537                           rgba[4 * i + 2], rgba[4 * i + 3], bgr[3 * i + 0], bgr[3 * i + 1], bgr[3 * i + 2]);
538                 return false;
539             }
540         }
541         return true;
542     }
543 };
544 MNNTestSuiteRegister(ImageProcessRGBAToBGRTest, "cv/image_process/rgba_to_bgr");
545 
546 // Test for _blitC3ToFloatC3
547 class ImageProcessBGRToBGRFloatBlitterTest : public MNNTestCase {
548 public:
549     virtual ~ImageProcessBGRToBGRFloatBlitterTest() = default;
run(int precision)550     virtual bool run(int precision) {
551         int w = 27, h = 27, size = w * h;
552         auto integers = genSourceData(h, w, 3);
553         std::vector<float> floats(size * 3);
554         std::shared_ptr<MNN::Tensor> tensor(
555             MNN::Tensor::create<float>(std::vector<int>{1, h, w, 3}, floats.data(), Tensor::TENSORFLOW));
556         ImageProcess::Config config;
557         config.sourceFormat = BGR;
558         config.destFormat   = BGR;
559 
560         const float means[3]   = {127.5f, 127.5f, 127.5f};
561         const float normals[3] = {2.0f / 255.0f, 2.0f / 255.0f, 2.0f / 255.0f};
562         memcpy(config.mean, means, sizeof(means));
563         memcpy(config.normal, normals, sizeof(normals));
564 
565         std::shared_ptr<ImageProcess> process(ImageProcess::create(config));
566         process->convert(integers.data(), w, h, 0, tensor.get());
567         for (int i = 0; i < size; ++i) {
568             for (int j = 0; j < 3; ++j) {
569                 float result = floats[3 * i + j];
570                 float right  = (integers[3 * i + j] - means[j]) * normals[j];
571                 if (fabs(result - right) > 1e-6f) {
572                     MNN_ERROR("Error for blitter bgr to bgr\n%d -> %f, right: %f\n", integers[3 * i + j], result,
573                               right);
574                     return false;
575                 }
576             }
577         }
578         return true;
579     }
580 };
581 MNNTestSuiteRegister(ImageProcessBGRToBGRFloatBlitterTest, "cv/image_process/bgr_to_bgr_blitter");
582 
583 // Test for _blitC1ToFloatC1
584 class ImageProcessGrayToGrayFloatBlitterTest : public MNNTestCase {
585 public:
586     virtual ~ImageProcessGrayToGrayFloatBlitterTest() = default;
run(int precision)587     virtual bool run(int precision) {
588         int w = 27, h = 27, size = w * h;
589         auto integers = genSourceData(h, w, 1);
590         std::vector<float> floats(size);
591         std::shared_ptr<MNN::Tensor> tensor(
592             MNN::Tensor::create<float>(std::vector<int>{1, h, w, 1}, floats.data(), Tensor::TENSORFLOW));
593         ImageProcess::Config config;
594         config.sourceFormat = GRAY;
595         config.destFormat   = GRAY;
596 
597         const float means[1]   = {127.5f};
598         const float normals[1] = {2.0f / 255.0f};
599         memcpy(config.mean, means, sizeof(means));
600         memcpy(config.normal, normals, sizeof(normals));
601 
602         std::shared_ptr<ImageProcess> process(ImageProcess::create(config));
603         process->convert(integers.data(), w, h, 0, tensor.get());
604         for (int i = 0; i < size; ++i) {
605             float result = floats[i];
606             float right  = (integers[i] - means[0]) * normals[0];
607             if (fabs(result - right) > 1e-6f) {
608                 MNN_PRINT("raw: %d, result: %f, right: %f\n", integers[i], result, right);
609                 MNN_ERROR("Error for blitter gray to gray\n");
610                 return false;
611             }
612         }
613         return true;
614     }
615 };
616 MNNTestSuiteRegister(ImageProcessGrayToGrayFloatBlitterTest, "cv/image_process/gray_to_gray_blitter");
617 
618 class ImageProcessYUVTestCommmon : public MNNTestCase {
619 protected:
620     virtual ~ImageProcessYUVTestCommmon() = default;
test(ImageFormat sourceFormat,ImageFormat destFormat,int bpp,int sw,int sh)621     bool test(ImageFormat sourceFormat, ImageFormat destFormat, int bpp, int sw, int sh) {
622         std::map<ImageFormat, std::string> formatMap = {
623             {RGBA, "RGBA"}, {RGB, "RGB"}, {BGRA, "BGRA"}, {BGR, "BGR"}, {GRAY, "GRAY"},
624             {YUV_NV21, "NV21"}, {YUV_NV12, "NV12"}, {YUV_I420, "I420"}
625         };
626         auto sourceStr = formatMap[sourceFormat].c_str(), destStr = formatMap[destFormat].c_str();
627         //MNN_PRINT("%s_to_%s\n", sourceStr, destStr);
628 
629         ImageProcess::Config config;
630         config.sourceFormat = sourceFormat;
631         config.destFormat   = destFormat;
632         //config.filterType   = NEAREST;
633         //config.wrap         = CLAMP_TO_EDGE;
634         std::shared_ptr<ImageProcess> process(ImageProcess::create(config));
635 
636         //Matrix tr;
637         //process->setMatrix(tr);
638         std::vector<uint8_t> src, dst;
639         genYUVData(sh, sw, sourceFormat, destFormat, src, dst);
640 
641         std::shared_ptr<Tensor> tensor(
642             Tensor::create<uint8_t>(std::vector<int>{1, sh, sw, bpp}, nullptr, Tensor::TENSORFLOW));
643         process->convert(src.data(), sw, sh, 0, tensor.get());
644         for (int y = 0; y < sh; ++y) {
645             auto srcY_Y  = src.data() + y * sw;
646             auto srcY_UV = src.data() + (y / 2) * (sw / 2) * 2 + sw * sh;
647             for (int x = 0; x < sw; ++x) {
648                 auto rightData = dst.data() + (y * sw + x) * bpp;
649                 auto testData = tensor->host<uint8_t>() + (y * sw + x) * bpp;
650 
651                 bool wrong = false;
652                 for (int i = 0; i < bpp && !wrong; ++i) {
653                     if (abs(rightData[i] - testData[i]) > 5) {
654                         wrong = true;
655                     }
656                 }
657                 if (wrong) {
658                     int Y = srcY_Y[x], U = srcY_UV[(x / 2) * 2], V = srcY_UV[(x / 2) * 2 + 1];
659                     MNN_ERROR("Error for %s to %s (%d, %d):  %d, %d, %d -> ", sourceStr, destStr, y, x, Y, U, V);
660                     for (int i = 0; i < bpp; ++i) {
661                         MNN_ERROR("%d, ", rightData[i]);
662                     }
663                     MNN_ERROR("wrong:");
664                     for (int i = 0; i < bpp; ++i) {
665                         MNN_ERROR(" %d%s", testData[i], (i < bpp ? ",": ""));
666                     }
667                     MNN_ERROR("\n");
668                     return false;
669                 }
670             }
671         }
672         return true;
673     }
674 };
675 
676 class ImageProcessYUVBlitterTest : public ImageProcessYUVTestCommmon {
677 public:
678     virtual ~ImageProcessYUVBlitterTest() = default;
run(int precision)679     virtual bool run(int precision) {
680         std::vector<ImageFormat> srcFromats = {YUV_NV21, YUV_NV12, YUV_I420};
681         std::vector<ImageFormat> dstFormats = {RGBA, RGB, BGRA, BGR, GRAY};
682         std::vector<int> bpps = {4, 3, 4, 3, 1};
683         bool succ = true;
684         for (auto srcFormat : srcFromats) {
685             for (int i = 0; i < dstFormats.size(); ++i) {
686                 succ = succ && test(srcFormat, dstFormats[i], bpps[i], 1920, 1080);
687             }
688         }
689         return succ;
690     }
691 };
692 // {YUV_NV21, YUV_NV12, YUV_I420} -> {RGBA, RGB, BGRA, BGR, GRAY} unit test
693 MNNTestSuiteRegister(ImageProcessYUVBlitterTest, "cv/image_process/yuv_blitter");
694