mirror of
https://github.com/opencv/opencv.git
synced 2024-11-24 03:00:14 +08:00
Merge pull request #24548 from dkurt:qrcode_struct_append_decode
QR codes Structured Append decoding mode #24548 ### Pull Request Readiness Checklist resolves https://github.com/opencv/opencv/issues/23245 Merge after https://github.com/opencv/opencv/pull/24299 Current proposal is to use `detectAndDecodeMulti` or `decodeMulti` for structured append mode decoding. 0-th QR code in a sequence gets a full message while the rest of codes will correspond to empty strings. See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [x] There is a reference to the original bug report and related work - [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [x] The feature is well documented and sample code can be built with the project CMake
This commit is contained in:
parent
ea94f7eba9
commit
fc32903b28
@ -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<std::string>& decoded_info, OutputArray points = noArray(),
|
||||
OutputArrayOfArrays straight_code = noArray()) const;
|
||||
@ -78,4 +82,4 @@ protected:
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -1016,6 +1016,17 @@ public:
|
||||
float coeff_expansion = 1.f;
|
||||
vector<Point2f> 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<size_t, double> &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<const uint8_t*>(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++) {
|
||||
|
@ -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<int>(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<uint8_t>(bitstream.next(4));
|
||||
total_num = static_cast<uint8_t>(1 + bitstream.next(4));
|
||||
parity = static_cast<uint8_t>(bitstream.next(8));
|
||||
}
|
||||
else
|
||||
CV_Error(Error::StsNotImplemented, format("mode %d", currMode));
|
||||
}
|
||||
|
@ -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<Mat> 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<Point2f> 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<Point2f> 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<cv::String> 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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user