diff --git a/modules/imgproc/src/demosaicing.cpp b/modules/imgproc/src/demosaicing.cpp index 65faf057c9..b268df753c 100644 --- a/modules/imgproc/src/demosaicing.cpp +++ b/modules/imgproc/src/demosaicing.cpp @@ -1041,9 +1041,11 @@ static void Bayer2RGB_( const Mat& srcmat, Mat& dstmat, int code ) int dst_step = (int)(dstmat.step/sizeof(T)); Size size = srcmat.size(); int blue = (code == COLOR_BayerBG2BGR || code == COLOR_BayerGB2BGR || - code == COLOR_BayerBG2BGRA || code == COLOR_BayerGB2BGRA ) ? -1 : 1; + code == COLOR_BayerBG2BGRA || code == COLOR_BayerGB2BGRA || + code == COLOR_BayerBG2BGR_VNG || code == COLOR_BayerGB2BGR_VNG) ? -1 : 1; int start_with_green = (code == COLOR_BayerGB2BGR || code == COLOR_BayerGR2BGR || - code == COLOR_BayerGB2BGRA || code == COLOR_BayerGR2BGRA); + code == COLOR_BayerGB2BGRA || code == COLOR_BayerGR2BGRA || + code == COLOR_BayerGB2BGR_VNG || code == COLOR_BayerGR2BGR_VNG); int dcn = dstmat.channels(); size.height -= 2; @@ -1073,8 +1075,20 @@ static void Bayer2RGB_( const Mat& srcmat, Mat& dstmat, int code ) /////////////////// Demosaicing using Variable Number of Gradients /////////////////////// -static void Bayer2RGB_VNG_8u( const Mat& srcmat, Mat& dstmat, int code ) +static void Bayer2RGB_VNG_8u( const Mat& _srcmat, Mat& dstmat, int code ) { + // for too small images use the simple interpolation algorithm + if( MIN(_srcmat.size().width, _srcmat.size().height) < 8 ) + { + Bayer2RGB_( _srcmat, dstmat, code ); + return; + } + + // VNG uses a 5x5 filter to calculate the gradient around the target pixel. + // To make it simple for edge pixels, 2 pixel paddings are added using reflection. + cv::Mat srcmat; + copyMakeBorder(_srcmat, srcmat, 2, 2, 2, 2, BORDER_REFLECT_101); + const uchar* bayer = srcmat.ptr(); int bstep = (int)srcmat.step; uchar* dst = dstmat.ptr(); @@ -1084,24 +1098,15 @@ static void Bayer2RGB_VNG_8u( const Mat& srcmat, Mat& dstmat, int code ) int blueIdx = code == COLOR_BayerBG2BGR_VNG || code == COLOR_BayerGB2BGR_VNG ? 0 : 2; bool greenCell0 = code != COLOR_BayerBG2BGR_VNG && code != COLOR_BayerRG2BGR_VNG; - // for too small images use the simple interpolation algorithm - if( MIN(size.width, size.height) < 8 ) - { - Bayer2RGB_( srcmat, dstmat, code ); - return; - } - const int brows = 3, bcn = 7; int N = size.width, N2 = N*2, N3 = N*3, N4 = N*4, N5 = N*5, N6 = N*6, N7 = N*7; int i, bufstep = N7*bcn; cv::AutoBuffer _buf(bufstep*brows); ushort* buf = _buf.data(); - bayer += bstep*2; - - for( int y = 2; y < size.height - 4; y++ ) + for( int y = 2; y < size.height - 2; y++ ) { - uchar* dstrow = dst + dststep*y + 6; + uchar* dstrow = dst + dststep*(y - 2); // srcmat has 2 pixel paddings, but dstmat has no padding. const uchar* srow; for( int dy = (y == 2 ? -1 : 1); dy <= 1; dy++ ) @@ -1583,24 +1588,9 @@ static void Bayer2RGB_VNG_8u( const Mat& srcmat, Mat& dstmat, int code ) } while( i < N - 2 ); - for( i = 0; i < 6; i++ ) - { - dst[dststep*y + 5 - i] = dst[dststep*y + 8 - i]; - dst[dststep*y + (N - 2)*3 + i] = dst[dststep*y + (N - 3)*3 + i]; - } - greenCell0 = !greenCell0; blueIdx ^= 2; } - - for( i = 0; i < size.width*3; i++ ) - { - dst[i] = dst[i + dststep] = dst[i + dststep*2]; - dst[i + dststep*(size.height-4)] = - dst[i + dststep*(size.height-3)] = - dst[i + dststep*(size.height-2)] = - dst[i + dststep*(size.height-1)] = dst[i + dststep*(size.height-5)]; - } } //////////////////////////////// Edge-Aware Demosaicing ////////////////////////////////// diff --git a/modules/imgproc/test/test_color.cpp b/modules/imgproc/test/test_color.cpp index d97c0ab706..b843448035 100644 --- a/modules/imgproc/test/test_color.cpp +++ b/modules/imgproc/test/test_color.cpp @@ -1905,6 +1905,64 @@ TEST(Imgproc_ColorBayerVNG, regression) } } +// See https://github.com/opencv/opencv/issues/5089 +// See https://github.com/opencv/opencv/issues/27225 +typedef tuple VNGandINT; +typedef testing::TestWithParam Imgproc_ColorBayerVNG_Codes; + +TEST_P(Imgproc_ColorBayerVNG_Codes, regression27225) +{ + const cv::ColorConversionCodes codeVNG = get<0>(GetParam()); + const int margin = (codeVNG == cv::COLOR_BayerGB2BGR_VNG || codeVNG == cv::COLOR_BayerGR2BGR_VNG)? 5 : 4; + + cv::Mat in = cv::Mat::eye(16, 16, CV_8UC1) * 255; + cv::resize(in, in, {}, 2, 2, cv::INTER_NEAREST); + + cv::Mat out; + EXPECT_NO_THROW(cv::cvtColor(in, out, codeVNG)); + + for(int iy=0; iy < out.size().height; iy++) { + for(int ix=0; ix < out.size().width; ix++) { + // Avoid to test around main diagonal pixels. + if(cv::abs(ix - iy) < margin) { + continue; + } + // Others should be completely black. + const Vec3b pixel = out.at(iy, ix); + EXPECT_EQ(pixel[0], 0) << cv::format(" - iy = %d, ix = %d", iy, ix); + EXPECT_EQ(pixel[1], 0) << cv::format(" - iy = %d, ix = %d", iy, ix); + EXPECT_EQ(pixel[2], 0) << cv::format(" - iy = %d, ix = %d", iy, ix); + } + } +} + +TEST_P(Imgproc_ColorBayerVNG_Codes, regression27225_small) +{ + // for too small images use the simple interpolation algorithm + const cv::ColorConversionCodes codeVNG = get<0>(GetParam()); + const cv::ColorConversionCodes codeINT = get<1>(GetParam()); + cv::Mat in = cv::Mat::eye(7, 7, CV_8UC1) * 255; + + cv::Mat outVNG; + EXPECT_NO_THROW(cv::cvtColor(in, outVNG, codeVNG)); + cv::Mat outINT; + EXPECT_NO_THROW(cv::cvtColor(in, outINT, codeINT)); + + Mat diff; + absdiff(outVNG, outINT, diff); + + imwrite("outVNG.png", outVNG); + imwrite("outINT.png", outINT); + EXPECT_EQ(0, countNonZero(diff.reshape(1) > 1)); +} + +INSTANTIATE_TEST_CASE_P(/**/, Imgproc_ColorBayerVNG_Codes, + testing::Values( + make_tuple(cv::COLOR_BayerBG2BGR_VNG, cv::COLOR_BayerBG2BGR), + make_tuple(cv::COLOR_BayerGB2BGR_VNG, cv::COLOR_BayerGB2BGR), + make_tuple(cv::COLOR_BayerRG2BGR_VNG, cv::COLOR_BayerRG2BGR), + make_tuple(cv::COLOR_BayerGR2BGR_VNG, cv::COLOR_BayerGR2BGR))); + // creating Bayer pattern template static void calculateBayerPattern(const Mat& src, Mat& bayer, const char* pattern)