From 8cdf7bb84b1ff0214f7c477dc56e1546c21224ef Mon Sep 17 00:00:00 2001 From: Vadim Pisarevsky Date: Fri, 9 Nov 2018 12:57:27 +0300 Subject: [PATCH] refined QRCodeDetector API for OpenCV 4.0 (#13086) * refined QRCodeDetector API for OpenCV 4.0 * expanded and tested QRCodeDetector::detectAndDecode() --- .../objdetect/include/opencv2/objdetect.hpp | 55 ++++++++------ .../objdetect/perf/perf_qrcode_pipeline.cpp | 19 +++-- modules/objdetect/src/qrcode.cpp | 72 ++++++++++++++----- modules/objdetect/test/test_qrcode.cpp | 13 ++-- samples/cpp/live_detect_qrcode.cpp | 26 +++---- 5 files changed, 127 insertions(+), 58 deletions(-) diff --git a/modules/objdetect/include/opencv2/objdetect.hpp b/modules/objdetect/include/opencv2/objdetect.hpp index 34f58cdf79..5c870971de 100644 --- a/modules/objdetect/include/opencv2/objdetect.hpp +++ b/modules/objdetect/include/opencv2/objdetect.hpp @@ -667,36 +667,51 @@ public: void groupRectangles(std::vector& rectList, std::vector& weights, int groupThreshold, double eps) const; }; -class CV_EXPORTS QRCodeDetector +class CV_EXPORTS_W QRCodeDetector { public: - QRCodeDetector(); + CV_WRAP QRCodeDetector(); ~QRCodeDetector(); - void setEpsX(double epsX); - void setEpsY(double epsY); + /** @brief sets the epsilon used during the horizontal scan of QR code stop marker detection. + @param epsX Epsilon neighborhood, which allows you to determine the horizontal pattern + of the scheme 1:1:3:1:1 according to QR code standard. + */ + CV_WRAP void setEpsX(double epsX); + /** @brief sets the epsilon used during the vertical scan of QR code stop marker detection. + @param epsY Epsilon neighborhood, which allows you to determine the vertical pattern + of the scheme 1:1:3:1:1 according to QR code standard. + */ + CV_WRAP void setEpsY(double epsY); + + /** @brief Detects QR code in image and returns the quadrangle containing the code. + @param img grayscale or color (BGR) image containing (or not) QR code. + @param points Output vector of vertices of the minimum-area quadrangle containing the code. + */ + CV_WRAP bool detect(InputArray img, OutputArray points) const; + + /** @brief Decodes QR code in image once it's found by the detect() method. + Returns UTF8-encoded output string or empty string if the code cannot be decoded. + + @param img grayscale or color (BGR) image containing QR code. + @param points Quadrangle vertices found by detect() method (or some other algorithm). + @param straight_qrcode The optional output image containing rectified and binarized QR code + */ + CV_WRAP std::string decode(InputArray img, InputArray points, OutputArray straight_qrcode = noArray()); + + /** @brief Both detects and decodes QR code - bool detect(InputArray in, OutputArray points) const; + @param img grayscale or color (BGR) image containing QR code. + @param points opiotnal output array of vertices of the found QR code quadrangle. Will be empty if not found. + @param straight_qrcode The optional output image containing rectified and binarized QR code + */ + CV_WRAP std::string detectAndDecode(InputArray img, OutputArray points=noArray(), + OutputArray straight_qrcode = noArray()); protected: struct Impl; Ptr p; }; -/** @brief Detect QR code in image and return minimum area of quadrangle that describes QR code. - @param in Matrix of the type CV_8U containing an image where QR code are detected. - @param points Output vector of vertices of a quadrangle of minimal area that describes QR code. - @param eps_x Epsilon neighborhood, which allows you to determine the horizontal pattern of the scheme 1:1:3:1:1 according to QR code standard. - @param eps_y Epsilon neighborhood, which allows you to determine the vertical pattern of the scheme 1:1:3:1:1 according to QR code standard. - */ -CV_EXPORTS bool detectQRCode(InputArray in, std::vector &points, double eps_x = 0.2, double eps_y = 0.1); - -/** @brief Decode QR code in image and return text that is encrypted in QR code. - @param in Matrix of the type CV_8UC1 containing an image where QR code are detected. - @param points Input vector of vertices of a quadrangle of minimal area that describes QR code. - @param decoded_info String information that is encrypted in QR code. - @param straight_qrcode Matrix of the type CV_8UC1 containing an binary straight QR code. - */ -CV_EXPORTS bool decodeQRCode(InputArray in, InputArray points, std::string &decoded_info, OutputArray straight_qrcode = noArray()); //! @} objdetect } diff --git a/modules/objdetect/perf/perf_qrcode_pipeline.cpp b/modules/objdetect/perf/perf_qrcode_pipeline.cpp index 09139a2df4..e3a0bc68c6 100644 --- a/modules/objdetect/perf/perf_qrcode_pipeline.cpp +++ b/modules/objdetect/perf/perf_qrcode_pipeline.cpp @@ -21,7 +21,8 @@ PERF_TEST_P_(Perf_Objdetect_QRCode, detect) ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path; std::vector< Point > corners; - TEST_CYCLE() ASSERT_TRUE(detectQRCode(src, corners)); + QRCodeDetector qrcode; + TEST_CYCLE() ASSERT_TRUE(qrcode.detect(src, corners)); SANITY_CHECK(corners); } @@ -37,8 +38,13 @@ PERF_TEST_P_(Perf_Objdetect_QRCode, decode) std::vector< Point > corners; std::string decoded_info; - ASSERT_TRUE(detectQRCode(src, corners)); - TEST_CYCLE() ASSERT_TRUE(decodeQRCode(src, corners, decoded_info, straight_barcode)); + QRCodeDetector qrcode; + ASSERT_TRUE(qrcode.detect(src, corners)); + TEST_CYCLE() + { + decoded_info = qrcode.decode(src, corners, straight_barcode); + ASSERT_FALSE(decoded_info.empty()); + } std::vector decoded_info_uint8_t(decoded_info.begin(), decoded_info.end()); SANITY_CHECK(decoded_info_uint8_t); @@ -69,7 +75,8 @@ PERF_TEST_P_(Perf_Objdetect_Not_QRCode, detect) rng.fill(not_qr_code, RNG::UNIFORM, Scalar(0), Scalar(1)); } - TEST_CYCLE() ASSERT_FALSE(detectQRCode(not_qr_code, corners)); + QRCodeDetector qrcode; + TEST_CYCLE() ASSERT_FALSE(qrcode.detect(not_qr_code, corners)); SANITY_CHECK_NOTHING(); } @@ -77,7 +84,6 @@ PERF_TEST_P_(Perf_Objdetect_Not_QRCode, detect) PERF_TEST_P_(Perf_Objdetect_Not_QRCode, decode) { Mat straight_barcode; - std::string decoded_info; std::vector< Point > corners; corners.push_back(Point( 0, 0)); corners.push_back(Point( 0, 5)); corners.push_back(Point(10, 0)); corners.push_back(Point(15, 15)); @@ -91,7 +97,8 @@ PERF_TEST_P_(Perf_Objdetect_Not_QRCode, decode) rng.fill(not_qr_code, RNG::UNIFORM, Scalar(0), Scalar(1)); } - TEST_CYCLE() ASSERT_FALSE(decodeQRCode(not_qr_code, corners, decoded_info, straight_barcode)); + QRCodeDetector qrcode; + TEST_CYCLE() ASSERT_TRUE(qrcode.decode(not_qr_code, corners, straight_barcode).empty()); SANITY_CHECK_NOTHING(); } #endif diff --git a/modules/objdetect/src/qrcode.cpp b/modules/objdetect/src/qrcode.cpp index e3c84c2d9b..63a507310c 100644 --- a/modules/objdetect/src/qrcode.cpp +++ b/modules/objdetect/src/qrcode.cpp @@ -778,7 +778,15 @@ bool QRCodeDetector::detect(InputArray in, OutputArray points) const { Mat inarr = in.getMat(); CV_Assert(!inarr.empty()); - CV_Assert(inarr.type() == CV_8UC1); + CV_Assert(inarr.depth() == CV_8U); + int incn = inarr.channels(); + if( incn == 3 || incn == 4 ) + { + Mat gray; + cvtColor(inarr, gray, COLOR_BGR2GRAY); + inarr = gray; + } + QRDetect qrdet; qrdet.init(inarr, p->epsX, p->epsY); if (!qrdet.localization()) { return false; } @@ -788,15 +796,6 @@ bool QRCodeDetector::detect(InputArray in, OutputArray points) const return true; } -CV_EXPORTS bool detectQRCode(InputArray in, vector &points, double eps_x, double eps_y) -{ - QRCodeDetector qrdetector; - qrdetector.setEpsX(eps_x); - qrdetector.setEpsY(eps_y); - - return qrdetector.detect(in, points); -} - class QRDecode { public: @@ -1060,11 +1059,20 @@ bool QRDecode::fullDecodingProcess() #endif } -CV_EXPORTS bool decodeQRCode(InputArray in, InputArray points, std::string &decoded_info, OutputArray straight_qrcode) +CV_EXPORTS std::string QRCodeDetector::decode(InputArray in, InputArray points, + OutputArray straight_qrcode) { Mat inarr = in.getMat(); CV_Assert(!inarr.empty()); - inarr.convertTo(inarr, CV_8UC1); + CV_Assert(inarr.depth() == CV_8U); + + int incn = inarr.channels(); + if( incn == 3 || incn == 4 ) + { + Mat gray; + cvtColor(inarr, gray, COLOR_BGR2GRAY); + inarr = gray; + } CV_Assert(points.isVector()); vector src_points; @@ -1074,18 +1082,50 @@ CV_EXPORTS bool decodeQRCode(InputArray in, InputArray points, std::string &deco QRDecode qrdec; qrdec.init(inarr, src_points); - bool exit_flag = qrdec.fullDecodingProcess(); + bool ok = qrdec.fullDecodingProcess(); - decoded_info = qrdec.getDecodeInformation(); + std::string decoded_info = qrdec.getDecodeInformation(); - if (exit_flag && straight_qrcode.needed()) + if (ok && straight_qrcode.needed()) { qrdec.getStraightBarcode().convertTo(straight_qrcode, straight_qrcode.fixedType() ? straight_qrcode.type() : CV_32FC2); } - return exit_flag; + return ok ? decoded_info : std::string(); +} + +CV_EXPORTS std::string QRCodeDetector::detectAndDecode(InputArray in, + OutputArray points_, + OutputArray straight_qrcode) +{ + Mat inarr = in.getMat(); + CV_Assert(!inarr.empty()); + CV_Assert(inarr.depth() == CV_8U); + + int incn = inarr.channels(); + if( incn == 3 || incn == 4 ) + { + Mat gray; + cvtColor(inarr, gray, COLOR_BGR2GRAY); + inarr = gray; + } + + vector points; + bool ok = detect(inarr, points); + if( points_.needed() ) + { + if( ok ) + Mat(points).copyTo(points_); + else + points_.release(); + } + std::string decoded_info; + if( ok ) + decoded_info = decode(inarr, points, straight_qrcode); + return decoded_info; } + } diff --git a/modules/objdetect/test/test_qrcode.cpp b/modules/objdetect/test/test_qrcode.cpp index 5019127b3f..d13fef9654 100644 --- a/modules/objdetect/test/test_qrcode.cpp +++ b/modules/objdetect/test/test_qrcode.cpp @@ -66,9 +66,13 @@ TEST_P(Objdetect_QRCode, regression) std::vector corners; std::string decoded_info; - ASSERT_TRUE(detectQRCode(src, corners)); + QRCodeDetector qrcode; #ifdef HAVE_QUIRC - ASSERT_TRUE(decodeQRCode(src, corners, decoded_info, straight_barcode)); + decoded_info = qrcode.detectAndDecode(src, corners, straight_barcode); + ASSERT_FALSE(corners.empty()); + ASSERT_FALSE(decoded_info.empty()); +#else + ASSERT_TRUE(qrcode.detect(src, corners)); #endif const std::string dataset_config = findDataFile(root + "dataset_config.json", false); @@ -119,10 +123,11 @@ TEST(Objdetect_QRCode_basic, not_found_qrcode) Mat straight_barcode; std::string decoded_info; Mat zero_image = Mat::zeros(256, 256, CV_8UC1); - EXPECT_FALSE(detectQRCode(zero_image, corners)); + QRCodeDetector qrcode; + EXPECT_FALSE(qrcode.detect(zero_image, corners)); #ifdef HAVE_QUIRC corners = std::vector(4); - EXPECT_ANY_THROW(decodeQRCode(zero_image, corners, decoded_info, straight_barcode)); + EXPECT_ANY_THROW(qrcode.decode(zero_image, corners, straight_barcode)); #endif } diff --git a/samples/cpp/live_detect_qrcode.cpp b/samples/cpp/live_detect_qrcode.cpp index 6851e724a0..0c938257b2 100644 --- a/samples/cpp/live_detect_qrcode.cpp +++ b/samples/cpp/live_detect_qrcode.cpp @@ -86,6 +86,7 @@ int liveQRCodeDetect() return -4; } + QRCodeDetector qrcode; TickMeter total; for(;;) { @@ -97,11 +98,11 @@ int liveQRCodeDetect() cvtColor(frame, src, COLOR_BGR2GRAY); total.start(); - bool result_detection = detectQRCode(src, transform); + bool result_detection = qrcode.detect(src, transform); if (result_detection) { - bool result_decode = decodeQRCode(src, transform, decode_info, straight_barcode); - if (result_decode) { cout << decode_info << '\n'; } + decode_info = qrcode.decode(src, transform, straight_barcode); + if (!decode_info.empty()) { cout << decode_info << '\n'; } } total.stop(); double fps = 1 / total.getTimeSec(); @@ -110,7 +111,7 @@ int liveQRCodeDetect() if (result_detection) { getMatWithQRCodeContour(frame, transform); } getMatWithFPS(frame, fps); - imshow("Live detect QR code", frame); + imshow("Live QR code detector", frame); if( waitKey(30) > 0 ) { break; } } return 0; @@ -119,33 +120,34 @@ int liveQRCodeDetect() int showImageQRCodeDetect(string in, string out) { Mat src = imread(in, IMREAD_GRAYSCALE), straight_barcode; - string decode_info; + string decoded_info; vector transform; const int count_experiments = 10; double transform_time = 0.0; - bool result_detection = false, result_decode = false; + bool result_detection = false; TickMeter total; + QRCodeDetector qrcode; for (size_t i = 0; i < count_experiments; i++) { total.start(); transform.clear(); - result_detection = detectQRCode(src, transform); + result_detection = qrcode.detect(src, transform); total.stop(); transform_time += total.getTimeSec(); total.reset(); if (!result_detection) { break; } total.start(); - result_decode = decodeQRCode(src, transform, decode_info, straight_barcode); + decoded_info = qrcode.decode(src, transform, straight_barcode); total.stop(); transform_time += total.getTimeSec(); total.reset(); - if (!result_decode) { break; } + if (decoded_info.empty()) { break; } } double fps = count_experiments / transform_time; - if (!result_detection) { cout << "Not find QR-code." << '\n'; return -2; } - if (!result_decode) { cout << "Not decode QR-code." << '\n'; return -3; } + if (!result_detection) { cout << "QR code not found\n"; return -2; } + if (decoded_info.empty()) { cout << "QR code cannot be decoded\n"; return -3; } Mat color_src = imread(in); getMatWithQRCodeContour(color_src, transform); @@ -166,7 +168,7 @@ int showImageQRCodeDetect(string in, string out) cout << "Output image file path: " << out << '\n'; cout << "Size: " << color_src.size() << '\n'; cout << "FPS: " << fps << '\n'; - cout << "Decode info: " << decode_info << '\n'; + cout << "Decoded info: " << decoded_info << '\n'; vector compression_params; compression_params.push_back(IMWRITE_PNG_COMPRESSION); -- GitLab