diff --git a/modules/objdetect/include/opencv2/objdetect/graphical_code_detector.hpp b/modules/objdetect/include/opencv2/objdetect/graphical_code_detector.hpp index 3535a8da1c..ed697c50c0 100644 --- a/modules/objdetect/include/opencv2/objdetect/graphical_code_detector.hpp +++ b/modules/objdetect/include/opencv2/objdetect/graphical_code_detector.hpp @@ -66,6 +66,10 @@ public: @param decoded_info UTF8-encoded output vector of string or empty vector of string if the codes cannot be decoded. @param points optional output vector of vertices of the found graphical code quadrangles. Will be empty if not found. @param straight_code The optional vector of images containing binarized codes + + - If there are QR codes encoded with a Structured Append mode on the image and all of them detected and decoded correctly, + method writes a full message to position corresponds to 0-th code in a sequence. The rest of QR codes from the same sequence + have empty string. */ CV_WRAP bool detectAndDecodeMulti(InputArray img, CV_OUT std::vector& decoded_info, OutputArray points = noArray(), OutputArrayOfArrays straight_code = noArray()) const; @@ -78,4 +82,4 @@ protected: } -#endif \ No newline at end of file +#endif diff --git a/modules/objdetect/src/graphical_code_detector_impl.hpp b/modules/objdetect/src/graphical_code_detector_impl.hpp index bb7ff5c177..987ccaab54 100644 --- a/modules/objdetect/src/graphical_code_detector_impl.hpp +++ b/modules/objdetect/src/graphical_code_detector_impl.hpp @@ -30,6 +30,9 @@ public: QRCodeEncoder::EncodeMode mode; QRCodeEncoder::ECIEncodings eci; + uint8_t parity = 0; + uint8_t sequence_num = 0; + uint8_t total_num = 1; }; } diff --git a/modules/objdetect/src/qrcode.cpp b/modules/objdetect/src/qrcode.cpp index 3590136e3b..71dd9d8257 100644 --- a/modules/objdetect/src/qrcode.cpp +++ b/modules/objdetect/src/qrcode.cpp @@ -1016,6 +1016,17 @@ public: float coeff_expansion = 1.f; vector getOriginalPoints() {return original_points;} bool useAlignmentMarkers; + + // Structured Append mode generates a sequence of QR codes. + // Final message is restored according to the index of the code in sequence. + // Different QR codes are grouped by a parity value. + bool isStructured() { return mode == QRCodeEncoder::EncodeMode::MODE_STRUCTURED_APPEND; } + struct { + uint8_t parity = 0; + uint8_t sequence_num = 0; + uint8_t total_num = 1; + } structure_info; + protected: double getNumModules(); Mat getHomography() { @@ -1068,6 +1079,8 @@ protected: std::string result_info; uint8_t version, version_size; float test_perspective_size; + QRCodeEncoder::EncodeMode mode; + struct sortPairAsc { bool operator()(const std::pair &a, @@ -2781,7 +2794,6 @@ 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; @@ -2826,6 +2838,9 @@ bool QRDecode::decodingProcess() eci = decoder->eci; payload = reinterpret_cast(result_info.c_str()); payload_len = result_info.size(); + structure_info.parity = decoder->parity; + structure_info.sequence_num = decoder->sequence_num; + structure_info.total_num = decoder->total_num; #endif // Check output string format @@ -2879,6 +2894,9 @@ bool QRDecode::decodingProcess() CV_LOG_WARNING(NULL, "QR: ECI is not supported properly"); result_info.assign((const char*)payload, payload_len); return true; + case QRCodeEncoder::EncodeMode::MODE_STRUCTURED_APPEND: + result_info.assign((const char*)payload, payload_len); + return true; default: CV_LOG_WARNING(NULL, "QR: unsupported QR data type"); return false; @@ -4076,11 +4094,44 @@ bool ImplContour::decodeMulti( } straight_qrcode.assign(tmp_straight_qrcodes); } + decoded_info.clear(); for (size_t i = 0; i < info.size(); i++) { - decoded_info.push_back(info[i]); + auto& decoder = qrdec[i]; + if (!decoder.isStructured()) + { + decoded_info.push_back(info[i]); + continue; + } + + // Store final message corresponding to 0-th code in a sequence. + if (decoder.structure_info.sequence_num != 0) + { + decoded_info.push_back(""); + continue; + } + + cv::String decoded = info[i]; + for (size_t idx = 1; idx < decoder.structure_info.total_num; ++idx) + { + auto it = std::find_if(qrdec.begin(), qrdec.end(), [&](QRDecode& dec) { + return dec.structure_info.parity == decoder.structure_info.parity && + dec.structure_info.sequence_num == idx; + }); + if (it != qrdec.end()) + { + decoded += info[it - qrdec.begin()]; + } + else + { + decoded = ""; + break; + } + } + decoded_info.push_back(decoded); } + alignmentMarkers.resize(src_points.size()); updateQrCorners.resize(src_points.size()*4ull); for (size_t i = 0ull; i < src_points.size(); i++) { diff --git a/modules/objdetect/src/qrcode_encoder.cpp b/modules/objdetect/src/qrcode_encoder.cpp index fca74421a0..41b69ebe46 100644 --- a/modules/objdetect/src/qrcode_encoder.cpp +++ b/modules/objdetect/src/qrcode_encoder.cpp @@ -342,7 +342,13 @@ int QRCodeEncoderImpl::versionAuto(const std::string& input_str) return -1; } - const auto tmp_version = findVersionCapacity((int)payload_tmp.size(), ecc_level, possible_version); + int nbits = static_cast(payload_tmp.size()); + + // Extra info for structure's position, total and parity + mode of final message + if (mode_type == MODE_STRUCTURED_APPEND) + nbits += 4 + 4 + 8 + 4; + + const auto tmp_version = findVersionCapacity(nbits, ecc_level, possible_version); return tmp_version; } @@ -365,7 +371,7 @@ void QRCodeEncoderImpl::generateQR(const std::string &input) auto string_itr = input.begin(); for (int i = struct_num; i > 0; --i) { - sequence_num = (uint8_t) i; + sequence_num = (uint8_t) (struct_num - i); size_t segment_begin = string_itr - input.begin(); size_t segment_end = (input.end() - string_itr) / i; @@ -1356,6 +1362,7 @@ private: void decodeByte(String& result); void decodeECI(String& result); void decodeKanji(String& result); + void decodeStructuredAppend(String& result); }; QRCodeDecoder::~QRCodeDecoder() @@ -1746,6 +1753,11 @@ void QRCodeDecoderImpl::decodeSymbols(String& result) { decodeECI(result); else if (currMode == QRCodeEncoder::EncodeMode::MODE_KANJI) decodeKanji(result); + else if (currMode == QRCodeEncoder::EncodeMode::MODE_STRUCTURED_APPEND) { + sequence_num = static_cast(bitstream.next(4)); + total_num = static_cast(1 + bitstream.next(4)); + parity = static_cast(bitstream.next(8)); + } else CV_Error(Error::StsNotImplemented, format("mode %d", currMode)); } diff --git a/modules/objdetect/test/test_qrcode_encode.cpp b/modules/objdetect/test/test_qrcode_encode.cpp index 45567b5d9b..87142e4690 100644 --- a/modules/objdetect/test/test_qrcode_encode.cpp +++ b/modules/objdetect/test/test_qrcode_encode.cpp @@ -349,7 +349,7 @@ TEST(Objdetect_QRCode_Encode_Kanji, regression) } } -TEST(Objdetect_QRCode_Encode_Decode_Structured_Append, DISABLED_regression) +TEST(Objdetect_QRCode_Encode_Decode_Structured_Append, regression) { // disabled since QR decoder probably doesn't support structured append mode qr codes const std::string root = "qrcode/decode_encode"; @@ -385,35 +385,43 @@ TEST(Objdetect_QRCode_Encode_Decode_Structured_Append, DISABLED_regression) vector qrcodes; encoder->encodeStructuredAppend(input_info, qrcodes); EXPECT_TRUE(!qrcodes.empty()) << "Can't generate this QR images"; + CV_CheckEQ(qrcodes.size(), (size_t)j, "Number of QR codes"); - std::string output_info = ""; + std::vector corners(4 * qrcodes.size()); for (size_t k = 0; k < qrcodes.size(); k++) { Mat qrcode = qrcodes[k]; + corners[4 * k] = Point2f(border_width, border_width); + corners[4 * k + 1] = Point2f(qrcode.cols * 1.0f - border_width, border_width); + corners[4 * k + 2] = Point2f(qrcode.cols * 1.0f - border_width, qrcode.rows * 1.0f - border_width); + corners[4 * k + 3] = Point2f(border_width, qrcode.rows * 1.0f - border_width); - std::vector corners(4); - corners[0] = Point2f(border_width, border_width); - corners[1] = Point2f(qrcode.cols * 1.0f - border_width, border_width); - corners[2] = Point2f(qrcode.cols * 1.0f - border_width, qrcode.rows * 1.0f - border_width); - corners[3] = Point2f(border_width, qrcode.rows * 1.0f - border_width); + float width_ratio = fixed_size.width * 1.0f / qrcode.cols; + float height_ratio = fixed_size.height * 1.0f / qrcode.rows; + resize(qrcode, qrcodes[k], fixed_size, 0, 0, INTER_AREA); - Mat resized_src; - resize(qrcode, resized_src, fixed_size, 0, 0, INTER_AREA); - float width_ratio = resized_src.cols * 1.0f / qrcode.cols; - float height_ratio = resized_src.rows * 1.0f / qrcode.rows; - for(size_t m = 0; m < corners.size(); m++) + for (size_t ki = 0; ki < 4; ki++) { - corners[m].x = corners[m].x * width_ratio; - corners[m].y = corners[m].y * height_ratio; + corners[4 * k + ki].x = corners[4 * k + ki].x * width_ratio + fixed_size.width * k; + corners[4 * k + ki].y = corners[4 * k + ki].y * height_ratio; } + } - 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; + Mat resized_src; + hconcat(qrcodes, resized_src); + + std::vector decoded_info; + cv::String output_info; + EXPECT_TRUE(QRCodeDetector().decodeMulti(resized_src, corners, decoded_info)); + for (size_t k = 0; k < decoded_info.size(); ++k) + { + if (!decoded_info[k].empty()) + output_info = decoded_info[k]; } + EXPECT_FALSE(output_info.empty()) + << "The generated QRcode cannot be decoded." << " Mode: " << modes[i] + << " structures number: " << j; + EXPECT_EQ(input_info, output_info) << "The generated QRcode is not same as test data." << " Mode: " << mode << " structures number: " << j; }