From d296d29a1c81e4533b137849ddfb7482e771ee13 Mon Sep 17 00:00:00 2001 From: Dmitry Kurtaev Date: Fri, 24 Nov 2023 11:35:36 +0300 Subject: [PATCH] Merge pull request #24299 from dkurt:qrcode_decode In-house QR codes decoding #24299 ### Pull Request Readiness Checklist QR codes decoding pipeline without 3rdparty dependency (Quirc library). Implemented according to standard https://github.com/yansikeim/QR-Code/blob/master/ISO%20IEC%2018004%202015%20Standard.pdf **Merge with extra**: https://github.com/opencv/opencv_extra/pull/1124 resolves https://github.com/opencv/opencv/issues/24225 resolves https://github.com/opencv/opencv/issues/17290 resolves https://github.com/opencv/opencv/issues/24318 https://github.com/opencv/opencv/issues/24346 Resources: * https://en.wikiversity.org/wiki/Reed%E2%80%93Solomon_codes_for_coders * https://en.wikipedia.org/wiki/Berlekamp%E2%80%93Massey_algorithm ``` Geometric mean (ms) Name of Test quirc new2 new2 vs quirc (x-factor) decode::Perf_Objdetect_Not_QRCode::("chessboard", 640x480) 9.151 9.157 1.00 decode::Perf_Objdetect_Not_QRCode::("chessboard", 1280x720) 21.609 21.609 1.00 decode::Perf_Objdetect_Not_QRCode::("chessboard", 1920x1080) 42.088 41.924 1.00 decode::Perf_Objdetect_Not_QRCode::("chessboard", 3840x2160) 169.737 169.050 1.00 decode::Perf_Objdetect_Not_QRCode::("random", 640x480) 8.552 8.611 0.99 decode::Perf_Objdetect_Not_QRCode::("random", 1280x720) 21.264 21.581 0.99 decode::Perf_Objdetect_Not_QRCode::("random", 1920x1080) 42.415 43.468 0.98 decode::Perf_Objdetect_Not_QRCode::("random", 3840x2160) 175.003 174.294 1.00 decode::Perf_Objdetect_Not_QRCode::("zero", 640x480) 8.528 8.421 1.01 decode::Perf_Objdetect_Not_QRCode::("zero", 1280x720) 21.548 21.209 1.02 decode::Perf_Objdetect_Not_QRCode::("zero", 1920x1080) 42.581 42.529 1.00 decode::Perf_Objdetect_Not_QRCode::("zero", 3840x2160) 176.231 174.410 1.01 decode::Perf_Objdetect_QRCode::"kanji.jpg" 6.105 6.072 1.01 decode::Perf_Objdetect_QRCode::"link_github_ocv.jpg" 6.069 6.076 1.00 decode::Perf_Objdetect_QRCode::"link_ocv.jpg" 6.143 6.240 0.98 decode::Perf_Objdetect_QRCode::"link_wiki_cv.jpg" 6.369 6.420 0.99 decode::Perf_Objdetect_QRCode::"russian.jpg" 6.558 6.549 1.00 decode::Perf_Objdetect_QRCode::"version_1_down.jpg" 5.634 5.621 1.00 decode::Perf_Objdetect_QRCode::"version_1_left.jpg" 5.560 5.609 0.99 decode::Perf_Objdetect_QRCode::"version_1_right.jpg" 5.539 5.631 0.98 decode::Perf_Objdetect_QRCode::"version_1_top.jpg" 5.622 5.566 1.01 decode::Perf_Objdetect_QRCode::"version_1_up.jpg" 5.569 5.534 1.01 decode::Perf_Objdetect_QRCode::"version_5_down.jpg" 6.514 6.436 1.01 decode::Perf_Objdetect_QRCode::"version_5_left.jpg" 6.668 6.479 1.03 decode::Perf_Objdetect_QRCode::"version_5_top.jpg" 6.481 6.484 1.00 decode::Perf_Objdetect_QRCode::"version_5_up.jpg" 7.011 6.513 1.08 decodeMulti::Perf_Objdetect_QRCode_Multi::("2_qrcodes.png", "aruco_based") 14.885 15.089 0.99 decodeMulti::Perf_Objdetect_QRCode_Multi::("2_qrcodes.png", "contours_based") 14.896 14.906 1.00 decodeMulti::Perf_Objdetect_QRCode_Multi::("3_close_qrcodes.png", "aruco_based") 6.661 6.663 1.00 decodeMulti::Perf_Objdetect_QRCode_Multi::("3_close_qrcodes.png", "contours_based") 6.614 6.592 1.00 decodeMulti::Perf_Objdetect_QRCode_Multi::("3_qrcodes.png", "aruco_based") 14.814 14.592 1.02 decodeMulti::Perf_Objdetect_QRCode_Multi::("3_qrcodes.png", "contours_based") 15.245 15.135 1.01 decodeMulti::Perf_Objdetect_QRCode_Multi::("4_qrcodes.png", "aruco_based") 10.923 10.881 1.00 decodeMulti::Perf_Objdetect_QRCode_Multi::("4_qrcodes.png", "contours_based") 10.680 10.128 1.05 decodeMulti::Perf_Objdetect_QRCode_Multi::("5_qrcodes.png", "contours_based") 11.788 11.576 1.02 decodeMulti::Perf_Objdetect_QRCode_Multi::("6_qrcodes.png", "aruco_based") 25.887 25.979 1.00 decodeMulti::Perf_Objdetect_QRCode_Multi::("6_qrcodes.png", "contours_based") 26.183 25.627 1.02 decodeMulti::Perf_Objdetect_QRCode_Multi::("7_qrcodes.png", "aruco_based") 32.786 32.253 1.02 decodeMulti::Perf_Objdetect_QRCode_Multi::("7_qrcodes.png", "contours_based") 24.290 24.435 0.99 decodeMulti::Perf_Objdetect_QRCode_Multi::("8_close_qrcodes.png", "aruco_based") 89.696 89.247 1.01 decodeMulti::Perf_Objdetect_QRCode_Multi::("8_close_qrcodes.png", "contours_based") 89.872 89.600 1.00 ``` --- CMakeLists.txt | 2 +- .../objdetect/perf/perf_qrcode_pipeline.cpp | 6 - .../src/graphical_code_detector_impl.hpp | 14 +- modules/objdetect/src/qrcode.cpp | 82 +-- modules/objdetect/src/qrcode_encoder.cpp | 571 ++++++++++++++++-- .../src/qrcode_encoder_table.inl.hpp | 9 + modules/objdetect/test/test_qr_utils.hpp | 7 +- modules/objdetect/test/test_qrcode.cpp | 64 +- modules/objdetect/test/test_qrcode_encode.cpp | 50 +- platforms/js/build_js.py | 2 +- 10 files changed, 656 insertions(+), 151 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 49c93d2406..60c9e8a473 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -456,7 +456,7 @@ OCV_OPTION(WITH_IMGCODEC_PXM "Include PNM (PBM,PGM,PPM) and PAM formats support" OCV_OPTION(WITH_IMGCODEC_PFM "Include PFM formats support" ON VISIBLE_IF TRUE VERIFY HAVE_IMGCODEC_PFM) -OCV_OPTION(WITH_QUIRC "Include library QR-code decoding" ON +OCV_OPTION(WITH_QUIRC "Include library QR-code decoding" OFF VISIBLE_IF TRUE VERIFY HAVE_QUIRC) OCV_OPTION(WITH_ANDROID_MEDIANDK "Use Android Media NDK for Video I/O (Android)" (ANDROID_NATIVE_API_LEVEL GREATER 20) diff --git a/modules/objdetect/perf/perf_qrcode_pipeline.cpp b/modules/objdetect/perf/perf_qrcode_pipeline.cpp index 150ed8cbbe..3d6360ca48 100644 --- a/modules/objdetect/perf/perf_qrcode_pipeline.cpp +++ b/modules/objdetect/perf/perf_qrcode_pipeline.cpp @@ -29,7 +29,6 @@ PERF_TEST_P_(Perf_Objdetect_QRCode, detect) SANITY_CHECK_NOTHING(); } -#ifdef HAVE_QUIRC PERF_TEST_P_(Perf_Objdetect_QRCode, decode) { const std::string name_current_image = GetParam(); @@ -52,7 +51,6 @@ PERF_TEST_P_(Perf_Objdetect_QRCode, decode) check_qr(root, name_current_image, "test_images", corners, {decoded_info}, pixels_error); SANITY_CHECK_NOTHING(); } -#endif typedef ::perf::TestBaseWithParam> Perf_Objdetect_QRCode_Multi; @@ -78,7 +76,6 @@ PERF_TEST_P_(Perf_Objdetect_QRCode_Multi, detectMulti) SANITY_CHECK_NOTHING(); } -#ifdef HAVE_QUIRC PERF_TEST_P_(Perf_Objdetect_QRCode_Multi, decodeMulti) { const std::string name_current_image = get<0>(GetParam()); @@ -116,7 +113,6 @@ PERF_TEST_P_(Perf_Objdetect_QRCode_Multi, decodeMulti) check_qr(root, name_current_image, "multiple_images", corners_result, decoded_info, pixels_error, true); SANITY_CHECK_NOTHING(); } -#endif INSTANTIATE_TEST_CASE_P(/*nothing*/, Perf_Objdetect_QRCode, ::testing::Values( @@ -163,7 +159,6 @@ PERF_TEST_P_(Perf_Objdetect_Not_QRCode, detect) SANITY_CHECK_NOTHING(); } -#ifdef HAVE_QUIRC PERF_TEST_P_(Perf_Objdetect_Not_QRCode, decode) { Mat straight_barcode; @@ -195,7 +190,6 @@ PERF_TEST_P_(Perf_Objdetect_Not_QRCode, decode) TEST_CYCLE() ASSERT_TRUE(qrcode.decode(not_qr_code, corners, straight_barcode).empty()); SANITY_CHECK_NOTHING(); } -#endif INSTANTIATE_TEST_CASE_P(/*nothing*/, Perf_Objdetect_Not_QRCode, ::testing::Combine( diff --git a/modules/objdetect/src/graphical_code_detector_impl.hpp b/modules/objdetect/src/graphical_code_detector_impl.hpp index 76429222ff..bb7ff5c177 100644 --- a/modules/objdetect/src/graphical_code_detector_impl.hpp +++ b/modules/objdetect/src/graphical_code_detector_impl.hpp @@ -20,6 +20,18 @@ struct GraphicalCodeDetector::Impl { OutputArray points, OutputArrayOfArrays straight_code) const = 0; }; +class QRCodeDecoder { +public: + virtual ~QRCodeDecoder(); + + static Ptr create(); + + virtual bool decode(const Mat& straight, String& decoded_info) = 0; + + QRCodeEncoder::EncodeMode mode; + QRCodeEncoder::ECIEncodings eci; +}; + } -#endif \ No newline at end of file +#endif diff --git a/modules/objdetect/src/qrcode.cpp b/modules/objdetect/src/qrcode.cpp index 811337b416..dd127b38b0 100644 --- a/modules/objdetect/src/qrcode.cpp +++ b/modules/objdetect/src/qrcode.cpp @@ -2727,7 +2727,6 @@ bool QRDecode::samplingForVersion() return true; } - static bool checkASCIIcompatible(const uint8_t* str, const size_t size) { for (size_t i = 0; i < size; ++i) { uint8_t byte = str[i]; @@ -2781,6 +2780,10 @@ static std::string encodeUTF8_bytesarray(const uint8_t* str, const size_t size) bool QRDecode::decodingProcess() { + QRCodeEncoder::EncodeMode mode; + QRCodeEncoder::ECIEncodings eci; + const uint8_t* payload; + size_t payload_len; #ifdef HAVE_QUIRC if (straight.empty()) { return false; } @@ -2810,65 +2813,79 @@ bool QRDecode::decodingProcess() CV_LOG_INFO(NULL, "QR: decoded with .version=" << qr_code_data.version << " .data_type=" << qr_code_data.data_type << " .eci=" << qr_code_data.eci << " .payload_len=" << qr_code_data.payload_len) - switch (qr_code_data.data_type) + mode = static_cast(qr_code_data.data_type); + eci = static_cast(qr_code_data.eci); + payload = qr_code_data.payload; + payload_len = qr_code_data.payload_len; +#else + auto decoder = QRCodeDecoder::create(); + if (!decoder->decode(straight, result_info)) + return false; + mode = decoder->mode; + eci = decoder->eci; + payload = reinterpret_cast(result_info.c_str()); + payload_len = result_info.size(); +#endif + + // Check output string format + switch (mode) { - case QUIRC_DATA_TYPE_NUMERIC: - if (!checkASCIIcompatible(qr_code_data.payload, qr_code_data.payload_len)) { + case QRCodeEncoder::EncodeMode::MODE_NUMERIC: + if (!checkASCIIcompatible(payload, payload_len)) { CV_LOG_INFO(NULL, "QR: DATA_TYPE_NUMERIC payload must be ACSII compatible string"); return false; } - result_info.assign((const char*)qr_code_data.payload, qr_code_data.payload_len); + result_info.assign((const char*)payload, payload_len); return true; - case QUIRC_DATA_TYPE_ALPHA: - if (!checkASCIIcompatible(qr_code_data.payload, qr_code_data.payload_len)) { + case QRCodeEncoder::EncodeMode::MODE_ALPHANUMERIC: + if (!checkASCIIcompatible(payload, payload_len)) { CV_LOG_INFO(NULL, "QR: DATA_TYPE_ALPHA payload must be ASCII compatible string"); return false; } - result_info.assign((const char*)qr_code_data.payload, qr_code_data.payload_len); + result_info.assign((const char*)payload, payload_len); return true; - case QUIRC_DATA_TYPE_BYTE: + case QRCodeEncoder::EncodeMode::MODE_BYTE: // https://en.wikipedia.org/wiki/Extended_Channel_Interpretation - if (qr_code_data.eci == QUIRC_ECI_UTF_8) { + if (eci == QRCodeEncoder::ECIEncodings::ECI_UTF8) { CV_LOG_INFO(NULL, "QR: payload ECI is UTF-8"); - if (!checkUTF8(qr_code_data.payload, qr_code_data.payload_len)) { + if (!checkUTF8(payload, payload_len)) { CV_LOG_INFO(NULL, "QUIRC_DATA_TYPE_BYTE with UTF-8 ECI must be UTF-8 compatible string"); return false; } - result_info.assign((const char*)qr_code_data.payload, qr_code_data.payload_len); - } else if (qr_code_data.eci == 25/*ECI_UTF_16BE*/) { + result_info.assign((const char*)payload, payload_len); + } else if (eci == 25/*ECI_UTF_16BE*/) { CV_LOG_INFO(NULL, "QR: UTF-16BE ECI is not supported"); return false; - } else if (checkASCIIcompatible(qr_code_data.payload, qr_code_data.payload_len)) { + } else if (checkASCIIcompatible(payload, payload_len)) { CV_LOG_INFO(NULL, "QR: payload is ASCII compatible (special handling for symbols encoding is not needed)"); - result_info.assign((const char*)qr_code_data.payload, qr_code_data.payload_len); + result_info.assign((const char*)payload, payload_len); } else { - if (checkUTF8(qr_code_data.payload, qr_code_data.payload_len)) { + if (checkUTF8(payload, payload_len)) { CV_LOG_INFO(NULL, "QR: payload QUIRC_DATA_TYPE_BYTE is UTF-8 compatible, return as-is"); - result_info.assign((const char*)qr_code_data.payload, qr_code_data.payload_len); + result_info.assign((const char*)payload, payload_len); } else { CV_LOG_INFO(NULL, "QR: assume 1-byte per symbol encoding"); - result_info = encodeUTF8_bytesarray(qr_code_data.payload, qr_code_data.payload_len); + result_info = encodeUTF8_bytesarray(payload, payload_len); } } return true; - case QUIRC_DATA_TYPE_KANJI: + case QRCodeEncoder::EncodeMode::MODE_KANJI: // FIXIT BUG: we must return UTF-8 compatible string CV_LOG_WARNING(NULL, "QR: Kanji is not supported properly"); - result_info.assign((const char*)qr_code_data.payload, qr_code_data.payload_len); + result_info.assign((const char*)payload, payload_len); + return true; + case QRCodeEncoder::EncodeMode::MODE_ECI: + CV_LOG_WARNING(NULL, "QR: ECI is not supported properly"); + result_info.assign((const char*)payload, payload_len); return true; + default: + CV_LOG_WARNING(NULL, "QR: unsupported QR data type"); + return false; } - - CV_LOG_WARNING(NULL, "QR: unsupported QR data type"); - return false; -#else - return false; -#endif - } bool QRDecode::straightDecodingProcess() { -#ifdef HAVE_QUIRC if (!updatePerspective(getHomography())) { return false; } if (!versionDefinition()) { return false; } if (useAlignmentMarkers) @@ -2876,24 +2893,15 @@ bool QRDecode::straightDecodingProcess() if (!samplingForVersion()) { return false; } if (!decodingProcess()) { return false; } return true; -#else - std::cout << "Library QUIRC is not linked. No decoding is performed. Take it to the OpenCV repository." << std::endl; - return false; -#endif } bool QRDecode::curvedDecodingProcess() { -#ifdef HAVE_QUIRC if (!preparingCurvedQRCodes()) { return false; } if (!versionDefinition()) { return false; } if (!samplingForVersion()) { return false; } if (!decodingProcess()) { return false; } return true; -#else - std::cout << "Library QUIRC is not linked. No decoding is performed. Take it to the OpenCV repository." << std::endl; - return false; -#endif } QRDecode::QRDecode(bool _useAlignmentMarkers): diff --git a/modules/objdetect/src/qrcode_encoder.cpp b/modules/objdetect/src/qrcode_encoder.cpp index 95e3e9bc35..fca74421a0 100644 --- a/modules/objdetect/src/qrcode_encoder.cpp +++ b/modules/objdetect/src/qrcode_encoder.cpp @@ -6,6 +6,8 @@ #include "precomp.hpp" #include "qrcode_encoder_table.inl.hpp" +#include "graphical_code_detector_impl.hpp" + namespace cv { using std::vector; @@ -19,6 +21,7 @@ const uint8_t INVALID_REGION_VALUE = 110; static void decToBin(const int dec_number, const int total_bits, std::vector &bin_number); static uint8_t gfPow(uint8_t x, int power); static uint8_t gfMul(const uint8_t x, const uint8_t y); +static uint8_t gfDiv(const uint8_t x, const uint8_t y); static void gfPolyMul(const vector &p, const vector &q, vector &product); static void gfPolyDiv(const vector ÷nd, const vector &divisor, const int ecc_num, vector "ient); static void polyGenerator(const int n, vector &result); @@ -51,6 +54,13 @@ static uint8_t gfMul(const uint8_t x, const uint8_t y) return gf_exp[(gf_log[x] + gf_log[y]) % 255]; } +static uint8_t gfDiv(const uint8_t x, const uint8_t y) +{ + if (x == 0 || y == 0) + return 0; + return gf_exp[(gf_log[x] + 255 - gf_log[y]) % 255]; +} + static void gfPolyMul(const vector &p, const vector &q, vector &product) { int len_p = (int)p.size(); @@ -141,6 +151,8 @@ static int mapSymbol(char c) return -1; } +static void maskData(const Mat& original, const int mask_type_num, Mat &masked); + QRCodeEncoder::QRCodeEncoder() { // nothing @@ -221,7 +233,6 @@ protected: void formatGenerate(const int mask_type_num, vector &format_array); void versionInfoGenerate(const int version_level_num, vector &version_array); void fillReserved(const vector &format_array, Mat &masked); - void maskData(const int mask_type_num, Mat &masked); void findAutoMaskType(); bool estimateVersion(const int input_length, EncodeMode mode, vector &possible_version); int versionAuto(const std::string &input_str); @@ -387,36 +398,10 @@ void QRCodeEncoderImpl::generateQR(const std::string &input) void QRCodeEncoderImpl::formatGenerate(const int mask_type_num, vector &format_array) { - const int mask_bits_num = 3; - const int level_bits_num = 2; - - std::vector mask_type_bin(mask_bits_num); - std::vector ec_level_bin(level_bits_num); - decToBin(mask_type_num, mask_bits_num, mask_type_bin); - decToBin(eccLevelToCode(ecc_level), level_bits_num, ec_level_bin); - - std::vector format_bits; - hconcat(ec_level_bin, mask_type_bin, format_bits); - std::reverse(format_bits.begin(), format_bits.end()); - - const int ecc_info_bits = 10; - - std::vector shift(ecc_info_bits, 0); - std::vector polynomial; - hconcat(shift, format_bits, polynomial); - - const int generator_len = 11; - const uint8_t generator_arr[generator_len] = {1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 1}; - std::vector format_generator (generator_arr, generator_arr + sizeof(generator_arr) / sizeof(generator_arr[0])); - vector ecc_code; - gfPolyDiv(polynomial, format_generator, ecc_info_bits, ecc_code); - hconcat(ecc_code, format_bits, format_array); - - const uint8_t mask_arr[MAX_FORMAT_LENGTH] = {0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1}; - std::vector system_mask (mask_arr, mask_arr + sizeof(mask_arr) / sizeof(mask_arr[0])); - for(int i = 0; i < MAX_FORMAT_LENGTH; i++) - { - format_array[i] ^= system_mask[i]; + int idx = (eccLevelToCode(ecc_level) << 3) | mask_type_num; + format_array.resize(MAX_FORMAT_LENGTH); + for (int i = 0; i < MAX_FORMAT_LENGTH; ++i) { + format_array[i] = (formatInfoLUT[idx] >> i) & 1; } } @@ -850,7 +835,7 @@ void QRCodeEncoderImpl::findAutoMaskType() { Mat test_result = masked_data.clone(); vector test_format = format; - maskData(cur_type, test_result); + maskData(original, cur_type, test_result); formatGenerate(cur_type, test_format); fillReserved(test_format, test_result); int continued_num = 0; @@ -962,8 +947,9 @@ void QRCodeEncoderImpl::findAutoMaskType() mask_type = best_index; } -void QRCodeEncoderImpl::maskData(const int mask_type_num, Mat& masked) +void maskData(const Mat& original, const int mask_type_num, Mat& masked) { + int version_size = original.rows; for (int i = 0; i < version_size; i++) { for (int j = 0; j < version_size; j++) @@ -1267,7 +1253,7 @@ void QRCodeEncoderImpl::structureFinalMessage() writeReservedArea(); writeData(); findAutoMaskType(); - maskData(mask_type, masked_data); + maskData(original, mask_type, masked_data); formatGenerate(mask_type, format); versionInfoGenerate(version_level, version_reserved); fillReserved(format, masked_data); @@ -1323,4 +1309,521 @@ Ptr QRCodeEncoder::create(const QRCodeEncoder::Params& parameters return makePtr(parameters); } +class QRCodeDecoderImpl : public QRCodeDecoder { +public: + bool decode(const Mat& straight, String& decoded_info) CV_OVERRIDE; + +private: + QRCodeEncoder::CorrectionLevel level; + int version; + + struct Bitstream { + int next(int bits) { + CV_Assert(idx < data.size()); + + int val = 0; + while (bits >= actualBits) { + val |= data[idx++] << (bits - actualBits); + bits -= actualBits; + actualBits = 8; + } + if (bits) { + val |= data[idx] >> (actualBits - bits); + actualBits -= bits; + data[idx] &= 255 >> (8 - actualBits); + } + return val; + } + + bool empty() { + return idx >= data.size(); + } + + std::vector data; + int actualBits = 8; + size_t idx = 0; + } bitstream; + + bool run(const Mat& straight, String& decoded_info); + bool decodeFormatInfo(const Mat& straight, int& mask); + bool correctFormatInfo(uint16_t& format_info); + void extractCodewords(Mat& source, std::vector& codewords); + bool errorCorrection(std::vector& codewords); + bool errorCorrectionBlock(std::vector& codewords); + void decodeSymbols(String& result); + void decodeNumeric(String& result); + void decodeAlpha(String& result); + void decodeByte(String& result); + void decodeECI(String& result); + void decodeKanji(String& result); +}; + +QRCodeDecoder::~QRCodeDecoder() +{ + // nothing +} + +Ptr QRCodeDecoder::create() { + return makePtr(); +} + +bool QRCodeDecoderImpl::decode(const Mat& _straight, String& decoded_info) { + Mat straight = ~_straight; // Invert modules + bool decoded = run(straight, decoded_info); + if (!decoded) { + cv::transpose(straight, straight); + decoded = run(straight, decoded_info); + } + return decoded; +} + +// Unmask format info bits and apply error correction +bool QRCodeDecoderImpl::correctFormatInfo(uint16_t& format_info) { + static const uint16_t mask_pattern = 0b101010000010010; + + cv::Hamming hd; + for (int i = 0; i < 32; ++i) { + // Compute Hamming distance + int distance = hd(reinterpret_cast(&formatInfoLUT[i]), + reinterpret_cast(&format_info), 2); + // Up to 3 bit errors might be corrected. + // So if distance is less or equal than 3 - we found a correct format info. + if (distance <= 3) { + format_info = formatInfoLUT[i] ^ mask_pattern; + return true; + } + } + return false; +} + +bool QRCodeDecoderImpl::decodeFormatInfo(const Mat& straight, int& mask) { + // Read left-top format info + uint16_t format_info = 0; + for (int i = 0; i < 6; ++i) + format_info |= (straight.at(i, 8) & 1) << i; + + format_info |= (straight.at(7, 8) & 1) << 6; + format_info |= (straight.at(8, 8) & 1) << 7; + format_info |= (straight.at(8, 7) & 1) << 8; + + for (int i = 9; i < 15; ++i) + format_info |= (straight.at(8, 14 - i) & 1) << i; + + bool correct = correctFormatInfo(format_info); + + // Format information 15bit sequence appears twice. + // Try extract format info from different position. + uint16_t format_info_dup = 0; + for (int i = 0; i < 8; ++i) + format_info_dup |= (straight.at(8, straight.cols - 1 - i) & 1) << i; + for (int i = 0; i < 7; ++i) + format_info_dup |= (straight.at(straight.rows - 7 + i, 8) & 1) << (i + 8); + + if (correctFormatInfo(format_info_dup)) { + // Both strings must be the same + if (correct && format_info != format_info_dup) + return false; + format_info = format_info_dup; + } else { + if (!correct) + return false; + } + + switch((format_info >> 13) & 0b11) { + case 0: level = QRCodeEncoder::CorrectionLevel::CORRECT_LEVEL_M; break; + case 1: level = QRCodeEncoder::CorrectionLevel::CORRECT_LEVEL_L; break; + case 2: level = QRCodeEncoder::CorrectionLevel::CORRECT_LEVEL_H; break; + case 3: level = QRCodeEncoder::CorrectionLevel::CORRECT_LEVEL_Q; break; + }; + mask = (format_info >> 10) & 0b111; + return true; +} + +bool QRCodeDecoderImpl::run(const Mat& straight, String& decoded_info) { + CV_Assert(straight.rows == straight.cols); + version = (straight.rows - 21) / 4 + 1; + + decoded_info = ""; + mode = static_cast(0); + eci = static_cast(0); + + // Decode format info + int maskPattern; + bool decoded = decodeFormatInfo(straight, maskPattern); + if (!decoded) { + return false; + } + + // Generate data mask + Mat masked = straight.clone(); + maskData(straight, maskPattern, masked); + + extractCodewords(masked, bitstream.data); + if (!errorCorrection(bitstream.data)) { + return false; + } + decodeSymbols(decoded_info); + return true; +} + +bool QRCodeDecoderImpl::errorCorrection(std::vector& codewords) { + CV_CheckEQ((int)codewords.size(), version_info_database[version].total_codewords, + "Number of codewords"); + + int numBlocks = version_info_database[version].ecc[level].num_blocks_in_G1 + + version_info_database[version].ecc[level].num_blocks_in_G2; + if (numBlocks == 1) { + return errorCorrectionBlock(codewords); + } + + size_t numData = 0; + std::vector blockSizes; + blockSizes.reserve(numBlocks); + for (int i = 0; i < version_info_database[version].ecc[level].num_blocks_in_G1; ++i) { + blockSizes.push_back(version_info_database[version].ecc[level].data_codewords_in_G1); + numData += blockSizes.back(); + } + for (int i = 0; i < version_info_database[version].ecc[level].num_blocks_in_G2; ++i) { + blockSizes.push_back(version_info_database[version].ecc[level].data_codewords_in_G2); + numData += blockSizes.back(); + } + + // TODO: parallel_for + std::vector> blocks(numBlocks); + int minBlockSize = *std::min_element(blockSizes.begin(), blockSizes.end()); + size_t offset = 0; + for (int i = 0; i < minBlockSize; ++i) { + for (int j = 0; j < numBlocks; ++j) { + blocks[j].push_back(codewords[offset++]); + } + } + // Put remaining data codewords + for (int j = 0; j < numBlocks; ++j) { + CV_Assert(blockSizes[j] == minBlockSize || blockSizes[j] == minBlockSize + 1); + if (blockSizes[j] > minBlockSize) + blocks[j].push_back(codewords[offset++]); + } + // Copy error correction codewords + int numEcc = version_info_database[version].ecc[level].ecc_codewords; + for (int i = 0; i < numEcc; ++i) { + for (int j = 0; j < numBlocks; ++j) { + blocks[j].push_back(codewords[offset++]); + } + } + + parallel_for_(Range(0, numBlocks), [&](const Range& r) { + for (int i = r.start; i < r.end; ++i) { + if (!errorCorrectionBlock(blocks[i])) { + blocks[i].clear(); + return; + } + } + }); + + // Collect blocks back after error correction. Trim error correction codewords. + codewords.resize(numData); + offset = 0; + for (size_t i = 0; i < blocks.size(); ++i) { + if (blocks[i].empty()) + return false; + std::copy(blocks[i].begin(), blocks[i].end(), codewords.begin() + offset); + offset += blocks[i].size(); + } + + return true; +} + +bool QRCodeDecoderImpl::errorCorrectionBlock(std::vector& codewords) { + size_t numEcc = version_info_database[version].ecc[level].ecc_codewords; + size_t numSyndromes = numEcc; + + // According to the ISO there is a formula for a number of the syndromes. + // However several tests don't pass the error correction step because of less number of syndromes: + // 1M: qrcodes/detection/lots/image001.jpg from BoofCV (8 syndromes by formula, 10 needed) + // 1L: Objdetect_QRCode_Multi.regression/13 (4 syndromes by formula, 6 needed) + // 2L: qrcodes/detection/brightness/image011.jpg from BoofCV (8 syndromes by formula, 10 needed) + if (numSyndromes % 2 == 1) + numSyndromes -= 1; + + // Compute syndromes + bool hasError = false; + std::vector syndromes(numSyndromes, codewords[0]); + for (size_t i = 0; i < syndromes.size(); ++i) { + for (size_t j = 1; j < codewords.size(); ++j) { + syndromes[i] = gfMul(syndromes[i], gfPow(2, static_cast(i))) ^ codewords[j]; + } + hasError |= syndromes[i] != 0; + } + if (!hasError) { + // Trim error correction codewords + codewords.resize(codewords.size() - numEcc); + return true; + } + + // Run Berlekamp–Massey algorithm to find error positions (coefficients of locator poly) + size_t L = 0; // number of assumed errors + size_t m = 1; // shift value (between C and B) + uint8_t b = 1; // discrepancy from last L update + + std::vector C(numSyndromes, 0); // Error locator polynomial + std::vector B(numSyndromes, 0); // A copy of error locator from previos L update + C[0] = B[0] = 1; + for (size_t i = 0; i < numSyndromes; ++i) { + CV_Assert(m + L - 1 < C.size()); // m >= 1 on any iteration + uint8_t discrepancy = syndromes[i]; + for (size_t j = 1; j <= L; ++j) { + discrepancy ^= gfMul(C[j], syndromes[i - j]); + } + + if (discrepancy == 0) { + m += 1; + } else { + std::vector C_copy = C; + uint8_t inv_b = gfDiv(1, b); + uint8_t tmp = gfMul(discrepancy, inv_b); + + for (size_t j = 0; j < L; ++j) { + C[m + j] ^= gfMul(tmp, B[j]); + } + + if (2 * L <= i) { + L = i + 1 - L; + B = C_copy; + b = discrepancy; + m = 1; + } else { + m += 1; + } + } + } + + // There is an error at i-th position if i is a root of locator poly + std::vector errLocs; + errLocs.reserve(L); + for (size_t i = 0; i < codewords.size(); ++i) { + uint8_t val = 1; + uint8_t pos = gfPow(2, static_cast(i)); + for (size_t j = 1; j <= L; ++j) { + val = gfMul(val, pos) ^ C[j]; + } + if (val == 0) { + errLocs.push_back(static_cast(codewords.size() - 1 - i)); + } + } + + // Number of assumed errors does not match number of error locations + if (errLocs.size() != L) + return false; + + // Forney algorithm for error correction using syndromes and known error locations + std::vector errEval; + gfPolyMul(C, syndromes, errEval); + + for (size_t i = 0; i < errLocs.size(); ++i) { + uint8_t numenator = 0, denominator = 0; + uint8_t X = gfPow(2, static_cast(codewords.size() - 1 - errLocs[i])); + uint8_t inv_X = gfDiv(1, X); + + for (size_t j = 0; j < L; ++j) { + numenator = gfMul(numenator, inv_X) ^ errEval[L - 1 - j]; + } + + // Compute demoninator as a product of (1-X_i * X_k) for i != k + // TODO: optimize, there is a dubplicated compute + denominator = 1; + for (size_t j = 0; j < errLocs.size(); ++j) { + if (i == j) + continue; + uint8_t Xj = gfPow(2, static_cast(codewords.size() - 1 - errLocs[j])); + denominator = gfMul(denominator, 1 ^ gfMul(inv_X, Xj)); + } + + uint8_t errValue = gfDiv(numenator, denominator); + codewords[errLocs[i]] ^= errValue; + } + + // Trim error correction codewords + codewords.resize(codewords.size() - numEcc); + return true; +} + +void QRCodeDecoderImpl::extractCodewords(Mat& source, std::vector& codewords) { + const VersionInfo& version_info = version_info_database[version]; + + // Mask alignment markers + std::vector alignCenters; + alignCenters.reserve(MAX_ALIGNMENT); + for (int i = 0; i < MAX_ALIGNMENT && version_info.alignment_pattern[i]; i++) + alignCenters.push_back(version_info.alignment_pattern[i]); + + for (size_t i = 0; i < alignCenters.size(); i++) + { + for (size_t j = 0; j < alignCenters.size(); j++) + { + if ((i == alignCenters.size() - 1 && j == 0) || (i == 0 && j == 0) || + (j == alignCenters.size() - 1 && i == 0)) + continue; + int x = alignCenters[i]; + int y = alignCenters[j]; + Mat area = source({x - 2, x + 3}, {y - 2, y + 3}); + area.setTo(INVALID_REGION_VALUE); + } + } + + // Mask detection markers + source.rowRange(0, 9).colRange(source.cols - 8, source.cols).setTo(INVALID_REGION_VALUE); + source.rowRange(0, 9).colRange(0, 9).setTo(INVALID_REGION_VALUE); + source.colRange(0, 9).rowRange(source.rows - 8, source.rows).setTo(INVALID_REGION_VALUE); + + // Mask Version Information blocks + if (version >= 7) { + source.rowRange(0, 6).colRange(source.cols - 12, source.cols - 9).setTo(INVALID_REGION_VALUE); + source.colRange(0, 6).rowRange(source.rows - 12, source.rows - 9).setTo(INVALID_REGION_VALUE); + } + + // Mask timing pattern + source.row(6) = INVALID_REGION_VALUE; + + std::vector bits; + bits.reserve(source.total() - source.cols); + bool moveUpwards = true; + for (auto& data : {source.colRange(7, source.cols), source.colRange(0, 6)}) { + for (int i = data.cols / 2 - 1; i >= 0; --i) { + Mat col0 = data.col(i * 2); + Mat col1 = data.col(i * 2 + 1); + for (int j = 0; j < data.rows; ++j) { + if (moveUpwards) { + bits.push_back(col1.at(data.rows - 1 - j)); + bits.push_back(col0.at(data.rows - 1 - j)); + } else { + bits.push_back(col1.at(j)); + bits.push_back(col0.at(j)); + } + } + moveUpwards = !moveUpwards; + } + } + + // Combine bits to codewords + size_t numCodewords = version_info.total_codewords; + codewords.resize(numCodewords); + + size_t offset = 0; + for (size_t i = 0; i < numCodewords; ++i) { + codewords[i] = 0; + for (size_t j = 0; j < 8; ++j) { + while (bits[offset] == INVALID_REGION_VALUE) { + offset += 1; + CV_Assert(offset < bits.size()); + } + codewords[i] |= (bits[offset] & 1) << (7 - j); + offset += 1; + } + } +} + +void QRCodeDecoderImpl::decodeSymbols(String& result) { + CV_Assert(!bitstream.empty()); + + // Decode depends on the mode + result = ""; + while (!bitstream.empty()) { + // Determine mode + auto currMode = static_cast(bitstream.next(4)); + if (this->mode == 0) { + mode = currMode; + } + + if (currMode == 0 || bitstream.empty()) + return; + if (currMode == QRCodeEncoder::EncodeMode::MODE_NUMERIC) + decodeNumeric(result); + else if (currMode == QRCodeEncoder::EncodeMode::MODE_ALPHANUMERIC) + decodeAlpha(result); + else if (currMode == QRCodeEncoder::EncodeMode::MODE_BYTE) + decodeByte(result); + else if (currMode == QRCodeEncoder::EncodeMode::MODE_ECI) + decodeECI(result); + else if (currMode == QRCodeEncoder::EncodeMode::MODE_KANJI) + decodeKanji(result); + else + CV_Error(Error::StsNotImplemented, format("mode %d", currMode)); + } +} + +void QRCodeDecoderImpl::decodeNumeric(String& result) { + int numDigits = bitstream.next(version <= 9 ? 10 : (version <= 26 ? 12 : 14)); + for (int i = 0; i < numDigits / 3; ++i) { + int triple = bitstream.next(10); + result += static_cast('0' + triple / 100); + result += static_cast('0' + (triple / 10) % 10); + result += static_cast('0' + triple % 10); + } + int remainingDigits = numDigits % 3; + if (remainingDigits) { + int triple = bitstream.next(remainingDigits == 1 ? 4 : 7); + if (remainingDigits == 2) + result += '0' + (triple / 10) % 10; + result += '0' + triple % 10; + } +} + +void QRCodeDecoderImpl::decodeAlpha(String& result) { + static const char map[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', + 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', + 'U', 'V', 'W', 'X', 'Y', 'Z', ' ', '$', '%', '*', + '+', '-', '.', '/', ':'}; + + int num = bitstream.next(version <= 9 ? 9 : (version <= 26 ? 11 : 13)); + for (int i = 0; i < num / 2; ++i) { + int tuple = bitstream.next(11); + result += map[tuple / 45]; + result += map[tuple % 45]; + } + if (num % 2) { + int value = bitstream.next(6); + result += map[value]; + } +} + +void QRCodeDecoderImpl::decodeByte(String& result) { + int num = bitstream.next(version <= 9 ? 8 : 16); + for (int i = 0; i < num; ++i) { + result += static_cast(bitstream.next(8)); + } +} + +void QRCodeDecoderImpl::decodeECI(String& result) { + int eciAssignValue = bitstream.next(8); + for (int i = 0; i < 8; ++i) { + if (eciAssignValue & 1 << (7 - i)) + eciAssignValue |= bitstream.next(8) << (i + 1) * 8; + else + break; + } + if (this->eci == 0) { + this->eci = static_cast(eciAssignValue); + } + decodeSymbols(result); + +} + +void QRCodeDecoderImpl::decodeKanji(String& result) { + int num = bitstream.next(version <= 9 ? 8 : (version <= 26 ? 10 : 12)); + for (int i = 0; i < num; ++i) { + int data = bitstream.next(13); + int high_byte = data / 0xC0; + int low_byte = data - high_byte * 0xC0; + int symbol = (high_byte << 8) + low_byte; + if (0 <= symbol && symbol <= 0x9FFC - 0x8140) { + symbol += 0x8140; + } else if (0xE040 - 0xC140 <= symbol && symbol <= 0xEBBF - 0xC140) { + symbol += 0xC140; + } + result += (symbol >> 8) & 0xff; + result += symbol & 0xff; + } +} + } diff --git a/modules/objdetect/src/qrcode_encoder_table.inl.hpp b/modules/objdetect/src/qrcode_encoder_table.inl.hpp index fc2ec37038..5a9c071aff 100644 --- a/modules/objdetect/src/qrcode_encoder_table.inl.hpp +++ b/modules/objdetect/src/qrcode_encoder_table.inl.hpp @@ -857,4 +857,13 @@ static const uint8_t gf_log[256] = { 0x4f, 0xae, 0xd5, 0xe9, 0xe6, 0xe7, 0xad, 0xe8, 0x74, 0xd6, 0xf4, 0xea, 0xa8, 0x50, 0x58, 0xaf }; + +// There are only 32 combinations of format info sequences. +static const uint16_t formatInfoLUT[32] = { + 0x5412, 0x5125, 0x5e7c, 0x5b4b, 0x45f9, 0x40ce, 0x4f97, 0x4aa0, + 0x77c4, 0x72f3, 0x7daa, 0x789d, 0x662f, 0x6318, 0x6c41, 0x6976, + 0x1689, 0x13be, 0x1ce7, 0x19d0, 0x0762, 0x0255, 0x0d0c, 0x083b, + 0x355f, 0x3068, 0x3f31, 0x3a06, 0x24b4, 0x2183, 0x2eda, 0x2bed +}; + } diff --git a/modules/objdetect/test/test_qr_utils.hpp b/modules/objdetect/test/test_qr_utils.hpp index 115c767f71..5186acc7ad 100644 --- a/modules/objdetect/test/test_qr_utils.hpp +++ b/modules/objdetect/test/test_qr_utils.hpp @@ -10,9 +10,6 @@ void check_qr(const string& root, const string& name_current_image, const string const std::vector& corners, const std::vector& decoded_info, const int max_pixel_error, bool isMulti = false) { -#ifndef HAVE_QUIRC - CV_UNUSED(decoded_info); -#endif const std::string dataset_config = findDataFile(root + "dataset_config.json"); FileStorage file_config(dataset_config, FileStorage::READ); ASSERT_TRUE(file_config.isOpened()) << "Can't read validation data: " << dataset_config; @@ -50,7 +47,7 @@ void check_qr(const string& root, const string& name_current_image, const string EXPECT_NEAR(y, corners[i].y, max_pixel_error); } } -#ifdef HAVE_QUIRC + if (decoded_info.size() == 0ull) return; if (isMulti) { @@ -70,7 +67,7 @@ void check_qr(const string& root, const string& name_current_image, const string std::string original_info = config["info"]; EXPECT_EQ(decoded_info[0], original_info); } -#endif + return; // done } } diff --git a/modules/objdetect/test/test_qrcode.cpp b/modules/objdetect/test/test_qrcode.cpp index 89954c993a..63b50a2a59 100644 --- a/modules/objdetect/test/test_qrcode.cpp +++ b/modules/objdetect/test/test_qrcode.cpp @@ -56,9 +56,8 @@ TEST(Objdetect_QRCode, generate_test_data) std::string decoded_info; ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path; EXPECT_TRUE(detectQRCode(src, corners)); -#ifdef HAVE_QUIRC EXPECT_TRUE(decodeQRCode(src, corners, decoded_info, straight_barcode)); -#endif + file_config << "x" << "[:"; for (size_t j = 0; j < corners.size(); j++) { file_config << corners[j].x; } file_config << "]"; @@ -95,9 +94,8 @@ TEST(Objdetect_QRCode_Close, generate_test_data) Size new_size(width, height); resize(src, barcode, new_size, 0, 0, INTER_LINEAR_EXACT); EXPECT_TRUE(detectQRCode(barcode, corners)); -#ifdef HAVE_QUIRC EXPECT_TRUE(decodeQRCode(barcode, corners, decoded_info, straight_barcode)); -#endif + file_config << "x" << "[:"; for (size_t j = 0; j < corners.size(); j++) { file_config << corners[j].x; } file_config << "]"; @@ -133,9 +131,8 @@ TEST(Objdetect_QRCode_Monitor, generate_test_data) Size new_size(width, height); resize(src, barcode, new_size, 0, 0, INTER_LINEAR_EXACT); EXPECT_TRUE(detectQRCode(barcode, corners)); -#ifdef HAVE_QUIRC EXPECT_TRUE(decodeQRCode(barcode, corners, decoded_info, straight_barcode)); -#endif + file_config << "x" << "[:"; for (size_t j = 0; j < corners.size(); j++) { file_config << corners[j].x; } file_config << "]"; @@ -165,9 +162,8 @@ TEST(Objdetect_QRCode_Curved, generate_test_data) std::string decoded_info; ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path; EXPECT_TRUE(detectQRCode(src, corners)); -#ifdef HAVE_QUIRC EXPECT_TRUE(decodeCurvedQRCode(src, corners, decoded_info, straight_barcode)); -#endif + file_config << "x" << "[:"; for (size_t j = 0; j < corners.size(); j++) { file_config << corners[j].x; } file_config << "]"; @@ -198,11 +194,10 @@ TEST(Objdetect_QRCode_Multi, generate_test_data) std::vector corners; QRCodeDetector qrcode; EXPECT_TRUE(qrcode.detectMulti(src, corners)); -#ifdef HAVE_QUIRC std::vector decoded_info; std::vector straight_barcode; EXPECT_TRUE(qrcode.decodeMulti(src, corners, decoded_info, straight_barcode)); -#endif + file_config << "x" << "[:"; for(size_t j = 0; j < corners.size(); j += 4) { @@ -256,15 +251,11 @@ TEST_P(Objdetect_QRCode, regression) std::vector corners; std::string decoded_info; QRCodeDetector qrcode; -#ifdef HAVE_QUIRC decoded_info = qrcode.detectAndDecode(src, corners, straight_barcode); ASSERT_FALSE(corners.empty()); ASSERT_FALSE(decoded_info.empty()); int expected_barcode_type = CV_8UC1; EXPECT_EQ(expected_barcode_type, straight_barcode.type()); -#else - ASSERT_TRUE(qrcode.detect(src, corners)); -#endif check_qr(root, name_current_image, "test_images", corners, {decoded_info}, pixels_error); } @@ -287,15 +278,11 @@ TEST_P(Objdetect_QRCode_Close, regression) std::vector corners; std::string decoded_info; QRCodeDetector qrcode; -#ifdef HAVE_QUIRC decoded_info = qrcode.detectAndDecode(barcode, corners, straight_barcode); ASSERT_FALSE(corners.empty()); ASSERT_FALSE(decoded_info.empty()); int expected_barcode_type = CV_8UC1; EXPECT_EQ(expected_barcode_type, straight_barcode.type()); -#else - ASSERT_TRUE(qrcode.detect(barcode, corners)); -#endif check_qr(root, name_current_image, "close_images", corners, {decoded_info}, pixels_error); } @@ -318,15 +305,11 @@ TEST_P(Objdetect_QRCode_Monitor, regression) std::vector corners; std::string decoded_info; QRCodeDetector qrcode; -#ifdef HAVE_QUIRC decoded_info = qrcode.detectAndDecode(barcode, corners, straight_barcode); ASSERT_FALSE(corners.empty()); ASSERT_FALSE(decoded_info.empty()); int expected_barcode_type = CV_8UC1; EXPECT_EQ(expected_barcode_type, straight_barcode.type()); -#else - ASSERT_TRUE(qrcode.detect(barcode, corners)); -#endif check_qr(root, name_current_image, "monitor_images", corners, {decoded_info}, pixels_error); } @@ -344,15 +327,11 @@ TEST_P(Objdetect_QRCode_Curved, regression) std::vector corners; std::string decoded_info; QRCodeDetector qrcode; -#ifdef HAVE_QUIRC decoded_info = qrcode.detectAndDecodeCurved(src, corners, straight_barcode); ASSERT_FALSE(corners.empty()); ASSERT_FALSE(decoded_info.empty()); int expected_barcode_type = CV_8UC1; EXPECT_EQ(expected_barcode_type, straight_barcode.type()); -#else - ASSERT_TRUE(qrcode.detect(src, corners)); -#endif check_qr(root, name_current_image, "test_images", corners, {decoded_info}, pixels_error); } @@ -375,7 +354,6 @@ TEST_P(Objdetect_QRCode_Multi, regression) } std::vector corners; std::vector decoded_info; -#ifdef HAVE_QUIRC std::vector straight_barcode; EXPECT_TRUE(qrcode.detectAndDecodeMulti(src, decoded_info, corners, straight_barcode)); ASSERT_FALSE(corners.empty()); @@ -383,9 +361,6 @@ TEST_P(Objdetect_QRCode_Multi, regression) int expected_barcode_type = CV_8UC1; for(size_t i = 0; i < straight_barcode.size(); i++) EXPECT_EQ(expected_barcode_type, straight_barcode[i].type()); -#else - ASSERT_TRUE(qrcode.detectMulti(src, corners)); -#endif check_qr(root, name_current_image, "multiple_images", corners, decoded_info, pixels_error, true); } @@ -398,7 +373,6 @@ INSTANTIATE_TEST_CASE_P(/**/, Objdetect_QRCode_Multi, testing::Combine(testing:: TEST(Objdetect_QRCode_decodeMulti, decode_regression_16491) { -#ifdef HAVE_QUIRC Mat zero_image = Mat::zeros(256, 256, CV_8UC1); Point corners_[] = {Point(16, 16), Point(128, 16), Point(128, 128), Point(16, 128), Point(16, 16), Point(128, 16), Point(128, 128), Point(16, 128)}; @@ -413,7 +387,6 @@ TEST(Objdetect_QRCode_decodeMulti, decode_regression_16491) Mat mat_corners(2, 4, CV_32SC2, (void*)&vec_corners[0]); QRCodeDetector mat_qrcode; EXPECT_NO_THROW(mat_qrcode.decodeMulti(zero_image, mat_corners, decoded_info, straight_barcode)); -#endif } typedef testing::TestWithParam Objdetect_QRCode_detectMulti; @@ -449,7 +422,6 @@ TEST_P(Objdetect_QRCode_detectAndDecodeMulti, check_output_parameters_type_19363 std::string image_path = findDataFile(root + name_current_image); Mat src = imread(image_path); ASSERT_FALSE(src.empty()) << "Can't read image: " << image_path; -#ifdef HAVE_QUIRC GraphicalCodeDetector qrcode = QRCodeDetector(); if (method == "aruco_based") { qrcode = QRCodeDetectorAruco(); @@ -467,7 +439,6 @@ TEST_P(Objdetect_QRCode_detectAndDecodeMulti, check_output_parameters_type_19363 ASSERT_FALSE(corners.empty()); for(size_t i = 0; i < straight_barcode.size(); i++) EXPECT_EQ(expected_barcode_type, straight_barcode[i].type()); -#endif } INSTANTIATE_TEST_CASE_P(/**/, Objdetect_QRCode_detectAndDecodeMulti, testing::Values("contours_based", "aruco_based")); @@ -487,9 +458,7 @@ TEST(Objdetect_QRCode_detect, detect_regression_20882) cv::String decoded_info; EXPECT_TRUE(qrcode.detect(src, corners)); EXPECT_TRUE(!corners.empty()); -#ifdef HAVE_QUIRC EXPECT_NO_THROW(qrcode.decode(src, corners, straight_barcode)); -#endif } TEST(Objdetect_QRCode_basic, not_found_qrcode) @@ -500,10 +469,8 @@ TEST(Objdetect_QRCode_basic, not_found_qrcode) Mat zero_image = Mat::zeros(256, 256, CV_8UC1); QRCodeDetector qrcode; EXPECT_FALSE(qrcode.detect(zero_image, corners)); -#ifdef HAVE_QUIRC corners = std::vector(4); EXPECT_ANY_THROW(qrcode.decode(zero_image, corners, straight_barcode)); -#endif } TEST(Objdetect_QRCode_detect, detect_regression_21287) @@ -521,9 +488,7 @@ TEST(Objdetect_QRCode_detect, detect_regression_21287) cv::String decoded_info; EXPECT_TRUE(qrcode.detect(src, corners)); EXPECT_TRUE(!corners.empty()); -#ifdef HAVE_QUIRC EXPECT_NO_THROW(qrcode.decode(src, corners, straight_barcode)); -#endif } TEST(Objdetect_QRCode_detect_flipped, regression_23249) @@ -549,12 +514,10 @@ TEST(Objdetect_QRCode_detect_flipped, regression_23249) EXPECT_TRUE(qrcode.detect(src, corners)); EXPECT_TRUE(!corners.empty()); std::string decoded_msg; - #ifdef HAVE_QUIRC - const std::string &expect_msg = flipped_image.second; - EXPECT_NO_THROW(decoded_msg = qrcode.decode(src, corners, straight_barcode)); - ASSERT_FALSE(straight_barcode.empty()) << "Can't decode qrimage."; - EXPECT_EQ(expect_msg, decoded_msg); - #endif + const std::string &expect_msg = flipped_image.second; + EXPECT_NO_THROW(decoded_msg = qrcode.decode(src, corners, straight_barcode)); + ASSERT_FALSE(straight_barcode.empty()) << "Can't decode qrimage."; + EXPECT_EQ(expect_msg, decoded_msg); } } @@ -577,12 +540,10 @@ TEST(Objdetect_QRCode_decode, decode_regression_21929) EXPECT_TRUE(qrcode.detect(src, corners)); EXPECT_TRUE(!corners.empty()); -#ifdef HAVE_QUIRC cv::String decoded_msg; EXPECT_NO_THROW(decoded_msg = qrcode.decode(src, corners, straight_barcode)); ASSERT_FALSE(straight_barcode.empty()) << "Can't decode qrimage."; EXPECT_EQ(expect_msg, decoded_msg); -#endif } TEST(Objdetect_QRCode_decode, decode_regression_version_25) @@ -603,12 +564,11 @@ TEST(Objdetect_QRCode_decode, decode_regression_version_25) EXPECT_TRUE(qrcode.detect(src, corners)); EXPECT_TRUE(!corners.empty()); -#ifdef HAVE_QUIRC + cv::String decoded_msg; EXPECT_NO_THROW(decoded_msg = qrcode.decode(src, corners, straight_barcode)); ASSERT_FALSE(straight_barcode.empty()) << "Can't decode qrimage."; EXPECT_EQ(expect_msg, decoded_msg); -#endif } TEST_P(Objdetect_QRCode_detectAndDecodeMulti, decode_9_qrcodes_version7) @@ -639,9 +599,6 @@ TEST_P(Objdetect_QRCode_detectAndDecodeMulti, decode_9_qrcodes_version7) TEST(Objdetect_QRCode_detectAndDecode, utf8_output) { -#ifndef HAVE_QUIRC - throw SkipTestException("Quirc is required for decoding"); -#else const std::string name_current_image = "umlaut.png"; const std::string root = "qrcode/"; @@ -655,7 +612,6 @@ TEST(Objdetect_QRCode_detectAndDecode, utf8_output) std::string decoded_info = qrcode.detectAndDecode(src, corners, straight); EXPECT_FALSE(decoded_info.empty()); EXPECT_NE(decoded_info.find("M\xc3\xbcllheimstrasse"), std::string::npos); -#endif // HAVE_QUIRC } }} // namespace diff --git a/modules/objdetect/test/test_qrcode_encode.cpp b/modules/objdetect/test/test_qrcode_encode.cpp index 5dc31a4c6e..7f5eb37f09 100644 --- a/modules/objdetect/test/test_qrcode_encode.cpp +++ b/modules/objdetect/test/test_qrcode_encode.cpp @@ -118,9 +118,7 @@ TEST(Objdetect_QRCode_Encode, generate_test_data) } std::string decoded_info = ""; -#ifdef HAVE_QUIRC EXPECT_TRUE(decodeQRCode(resized_src, corners, decoded_info, straight_barcode)) << "The QR code cannot be decoded: " << image_path; -#endif file_config << "info" << decoded_info; file_config << "}"; } @@ -306,7 +304,6 @@ TEST(Objdetect_QRCode_Encode_Decode, regression) corners[k].y = corners[k].y * height_ratio; } -#ifdef HAVE_QUIRC Mat straight_barcode; std::string output_info = QRCodeDetector().decode(resized_src, corners, straight_barcode); EXPECT_FALSE(output_info.empty()) @@ -314,7 +311,6 @@ TEST(Objdetect_QRCode_Encode_Decode, regression) << " version: " << version << " error correction level: " << (int)level; EXPECT_EQ(input_info, output_info) << "The generated QRcode is not same as test data." << " Mode: " << (int)mode << " version: " << version << " error correction level: " << (int)level; -#endif } } } @@ -356,12 +352,10 @@ TEST(Objdetect_QRCode_Encode_Kanji, regression) corners[j].y = corners[j].y * height_ratio; } -#ifdef HAVE_QUIRC Mat straight_barcode; std::string decoded_info = QRCodeDetector().decode(resized_src, corners, straight_barcode); EXPECT_FALSE(decoded_info.empty()) << "The generated QRcode cannot be decoded."; EXPECT_EQ(input_info, decoded_info); -#endif } } @@ -423,21 +417,15 @@ TEST(Objdetect_QRCode_Encode_Decode_Structured_Append, DISABLED_regression) corners[m].y = corners[m].y * height_ratio; } -#ifdef HAVE_QUIRC Mat straight_barcode; std::string decoded_info = QRCodeDetector().decode(resized_src, corners, straight_barcode); EXPECT_FALSE(decoded_info.empty()) << "The generated QRcode cannot be decoded." << " Mode: " << modes[i] << " structures number: " << k << "/" << j; output_info += decoded_info; -#endif } -#ifdef HAVE_QUIRC EXPECT_EQ(input_info, output_info) << "The generated QRcode is not same as test data." << " Mode: " << mode << " structures number: " << j; -#else - std::cout << "Mode=" << mode << ": Unable to verify generated QR codes - QUIRC is disabled" << std::endl; -#endif } } } @@ -566,4 +554,42 @@ TEST(Objdetect_QRCode_Encode_Decode, auto_version_pick) } } +// Test two QR codes which error correction procedure requires more number of +// syndroms that described in the ISO/IEC 18004 +typedef testing::TestWithParam> Objdetect_QRCode_decoding; +TEST_P(Objdetect_QRCode_decoding, error_correction) +{ + const std::string filename = get<0>(GetParam()); + const std::string expected = get<1>(GetParam()); + + QRCodeDetector qrcode; + cv::String decoded_msg; + Mat src = cv::imread(findDataFile("qrcode/" + filename), IMREAD_GRAYSCALE); + + std::vector corners(4); + corners[0] = Point2f(0, 0); + corners[1] = Point2f(src.cols * 1.0f, 0); + corners[2] = Point2f(src.cols * 1.0f, src.rows * 1.0f); + corners[3] = Point2f(0, src.rows * 1.0f); + + Mat resized_src; + resize(src, resized_src, fixed_size, 0, 0, INTER_AREA); + float width_ratio = resized_src.cols * 1.0f / src.cols; + float height_ratio = resized_src.rows * 1.0f / src.rows; + for(size_t m = 0; m < corners.size(); m++) + { + corners[m].x = corners[m].x * width_ratio; + corners[m].y = corners[m].y * height_ratio; + } + + Mat straight_barcode; + EXPECT_NO_THROW(decoded_msg = qrcode.decode(resized_src, corners, straight_barcode)); + ASSERT_FALSE(straight_barcode.empty()) << "Can't decode qrimage " << filename; + EXPECT_EQ(expected, decoded_msg); +} +INSTANTIATE_TEST_CASE_P(/**/, Objdetect_QRCode_decoding, testing::ValuesIn(std::vector>{ + {"err_correct_1M.png", "New"}, + {"err_correct_2L.png", "Version 2 QR Code Test Image"}, +})); + }} // namespace diff --git a/platforms/js/build_js.py b/platforms/js/build_js.py index 3e8edfe4ad..07fda10d35 100644 --- a/platforms/js/build_js.py +++ b/platforms/js/build_js.py @@ -118,7 +118,7 @@ class Builder: "-DWITH_GPHOTO2=OFF", "-DWITH_LAPACK=OFF", "-DWITH_ITT=OFF", - "-DWITH_QUIRC=ON", + "-DWITH_QUIRC=OFF", "-DBUILD_ZLIB=ON", "-DBUILD_opencv_apps=OFF", "-DBUILD_opencv_calib3d=ON",