Merge pull request #24364 from bagelbytes61:bugfix/qrcode-version-estimator

Bugfix/qrcode version estimator #24364

Fixes https://github.com/opencv/opencv/issues/24366

### Pull Request Readiness Checklist

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:
Jeremy Lyda 2023-11-16 08:28:28 -05:00 committed by GitHub
parent 8c10545d3c
commit fad0dbb9ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 137 additions and 56 deletions

View File

@ -196,17 +196,18 @@ protected:
uint8_t total_num; uint8_t total_num;
vector<Mat> final_qrcodes; vector<Mat> final_qrcodes;
Ptr<VersionInfo> version_info; const VersionInfo* version_info;
Ptr<BlockParams> cur_ecc_params; const BlockParams* cur_ecc_params;
bool isNumeric(const std::string& input); bool isNumeric(const std::string& input) const;
bool isAlphaNumeric(const std::string& input); bool isAlphaNumeric(const std::string& input) const;
EncodeMode autoEncodeMode(const std::string &input) const ;
bool encodeByte(const std::string& input, vector<uint8_t> &output); bool encodeByte(const std::string& input, vector<uint8_t> &output);
bool encodeAlpha(const std::string& input, vector<uint8_t> &output); bool encodeAlpha(const std::string& input, vector<uint8_t> &output);
bool encodeNumeric(const std::string& input, vector<uint8_t> &output); bool encodeNumeric(const std::string& input, vector<uint8_t> &output);
bool encodeECI(const std::string& input, vector<uint8_t> &output); bool encodeECI(const std::string& input, vector<uint8_t> &output);
bool encodeKanji(const std::string& input, vector<uint8_t> &output); bool encodeKanji(const std::string& input, vector<uint8_t> &output);
bool encodeAuto(const std::string& input, vector<uint8_t> &output); bool encodeAuto(const std::string& input, vector<uint8_t> &output, EncodeMode *mode = nullptr);
bool encodeStructure(const std::string& input, vector<uint8_t> &output); bool encodeStructure(const std::string& input, vector<uint8_t> &output);
int eccLevelToCode(CorrectionLevel level); int eccLevelToCode(CorrectionLevel level);
void padBitStream(); void padBitStream();
@ -222,9 +223,9 @@ protected:
void fillReserved(const vector<uint8_t> &format_array, Mat &masked); void fillReserved(const vector<uint8_t> &format_array, Mat &masked);
void maskData(const int mask_type_num, Mat &masked); void maskData(const int mask_type_num, Mat &masked);
void findAutoMaskType(); void findAutoMaskType();
bool estimateVersion(const int input_length, vector<int> &possible_version); bool estimateVersion(const int input_length, EncodeMode mode, vector<int> &possible_version);
int versionAuto(const std::string &input_str); int versionAuto(const std::string &input_str);
int findVersionCapacity(const int input_length, const int ecc, const int version_begin, const int version_end); int findVersionCapacity(const int input_length, const int ecc, const std::vector<int>& possible_versions);
void generatingProcess(const std::string& input, Mat &qrcode); void generatingProcess(const std::string& input, Mat &qrcode);
void generateQR(const std::string& input); void generateQR(const std::string& input);
}; };
@ -247,17 +248,17 @@ int QRCodeEncoderImpl::eccLevelToCode(CorrectionLevel level)
"CORRECT_LEVEL_L, CORRECT_LEVEL_M, CORRECT_LEVEL_Q, CORRECT_LEVEL_H." ); "CORRECT_LEVEL_L, CORRECT_LEVEL_M, CORRECT_LEVEL_Q, CORRECT_LEVEL_H." );
} }
int QRCodeEncoderImpl::findVersionCapacity(const int input_length, const int ecc, const int version_begin, const int version_end) int QRCodeEncoderImpl::findVersionCapacity(const int input_length, const int ecc, const std::vector<int>& possible_versions)
{ {
int data_codewords, version_index = -1; int data_codewords, version_index = -1;
const int byte_len = 8; const int byte_len = 8;
version_index = -1; version_index = -1;
for (int i = version_begin; i < version_end; i++) for (int i : possible_versions)
{ {
Ptr<BlockParams> tmp_ecc_params = makePtr<BlockParams>(version_info_database[i].ecc[ecc]); auto& tmp_ecc_params = version_info_database[i].ecc[ecc];
data_codewords = tmp_ecc_params->data_codewords_in_G1 * tmp_ecc_params->num_blocks_in_G1 + data_codewords = tmp_ecc_params.data_codewords_in_G1 * tmp_ecc_params.num_blocks_in_G1 +
tmp_ecc_params->data_codewords_in_G2 * tmp_ecc_params->num_blocks_in_G2; tmp_ecc_params.data_codewords_in_G2 * tmp_ecc_params.num_blocks_in_G2;
if (data_codewords * byte_len >= input_length) if (data_codewords * byte_len >= input_length)
{ {
@ -268,53 +269,70 @@ int QRCodeEncoderImpl::findVersionCapacity(const int input_length, const int ecc
return version_index; return version_index;
} }
bool QRCodeEncoderImpl::estimateVersion(const int input_length, vector<int>& possible_version) static inline int getCapacity(int version, QRCodeEncoder::CorrectionLevel ecc_level, QRCodeEncoder::EncodeMode mode) {
const int* capacity = version_capacity_database[version].ec_level[ecc_level].encoding_modes;
switch (mode) {
case QRCodeEncoder::EncodeMode::MODE_NUMERIC:
return capacity[0];
case QRCodeEncoder::EncodeMode::MODE_ALPHANUMERIC:
return capacity[1];
case QRCodeEncoder::EncodeMode::MODE_BYTE:
return capacity[2];
case QRCodeEncoder::EncodeMode::MODE_KANJI:
return capacity[3];
default:
CV_Error(Error::StsNotImplemented, format("Unexpected mode %d", mode));
}
}
bool QRCodeEncoderImpl::estimateVersion(const int input_length, EncodeMode mode, vector<int>& possible_version)
{ {
possible_version.clear(); possible_version.clear();
if (input_length > version_capacity_database[40].ec_level[ecc_level].encoding_modes[1])
CV_Assert(mode != EncodeMode::MODE_AUTO);
if (input_length > getCapacity(MAX_VERSION, ecc_level, mode))
{
return false; return false;
if (input_length <= version_capacity_database[9].ec_level[ecc_level].encoding_modes[3])
{
possible_version.push_back(1);
} }
else if (input_length <= version_capacity_database[9].ec_level[ecc_level].encoding_modes[1])
int version = MAX_VERSION;
for (; version > 0; --version)
{ {
possible_version.push_back(1); if (input_length > getCapacity(version, ecc_level, mode)) {
possible_version.push_back(2); break;
}
} }
else if (input_length <= version_capacity_database[26].ec_level[ecc_level].encoding_modes[3])
if (version < MAX_VERSION)
{ {
possible_version.push_back(2); version += 1;
} }
else if (input_length <= version_capacity_database[26].ec_level[ecc_level].encoding_modes[1])
possible_version.push_back(version);
if (version < MAX_VERSION)
{ {
possible_version.push_back(2); possible_version.push_back(version + 1);
possible_version.push_back(3);
}
else
{
possible_version.push_back(3);
} }
return true; return true;
} }
int QRCodeEncoderImpl::versionAuto(const std::string& input_str) int QRCodeEncoderImpl::versionAuto(const std::string& input_str)
{ {
vector<int> possible_version;
estimateVersion((int)input_str.length(), possible_version);
int tmp_version = 0;
vector<uint8_t> payload_tmp; vector<uint8_t> payload_tmp;
int version_range[5] = {0, 1, 10, 27, 41}; EncodeMode mode;
for(size_t i = 0; i < possible_version.size(); i++) encodeAuto(input_str, payload_tmp, &mode);
{
int version_range_index = possible_version[i];
encodeAuto(input_str, payload_tmp); vector<int> possible_version;
tmp_version = findVersionCapacity((int)payload_tmp.size(), ecc_level, if (!estimateVersion((int)input_str.length(), mode, possible_version)) {
version_range[version_range_index], version_range[version_range_index + 1]); return -1;
if(tmp_version != -1)
break;
} }
const auto tmp_version = findVersionCapacity((int)payload_tmp.size(), ecc_level, possible_version);
return tmp_version; return tmp_version;
} }
@ -342,20 +360,23 @@ void QRCodeEncoderImpl::generateQR(const std::string &input)
std::string input_info = input.substr(segment_begin, segment_end); std::string input_info = input.substr(segment_begin, segment_end);
string_itr += segment_end; string_itr += segment_end;
int detected_version = versionAuto(input_info); int detected_version = versionAuto(input_info);
CV_Assert(detected_version != -1); int tmp_version_level = version_level;
if (version_level == 0) if (detected_version == -1)
version_level = detected_version; CV_Error(Error::StsBadArg, "The given input exceeds the maximum capacity of a QR code with the selected encoding mode and error correction level " );
else if (version_level < detected_version) else if (tmp_version_level == 0)
tmp_version_level = detected_version;
else if (tmp_version_level < detected_version)
CV_Error(Error::StsBadArg, "The given version is not suitable for the given input string length "); CV_Error(Error::StsBadArg, "The given version is not suitable for the given input string length ");
payload.clear(); payload.clear();
payload.reserve(MAX_PAYLOAD_LEN); payload.reserve(MAX_PAYLOAD_LEN);
format = vector<uint8_t> (15, 255); format = vector<uint8_t> (15, 255);
version_reserved = vector<uint8_t> (18, 255); version_reserved = vector<uint8_t> (18, 255);
version_size = (21 + (version_level - 1) * 4); version_size = (21 + (tmp_version_level - 1) * 4);
version_info = makePtr<VersionInfo>(version_info_database[version_level]); version_info = &version_info_database[tmp_version_level];
cur_ecc_params = makePtr<BlockParams>(version_info->ecc[ecc_level]); cur_ecc_params = &version_info->ecc[ecc_level];
original = Mat(Size(version_size, version_size), CV_8UC1, Scalar(255)); original = Mat(Size(version_size, version_size), CV_8UC1, Scalar(255));
masked_data = original.clone(); masked_data = original.clone();
Mat qrcode = masked_data.clone(); Mat qrcode = masked_data.clone();
@ -613,7 +634,7 @@ bool QRCodeEncoderImpl::encodeStructure(const std::string& input, vector<uint8_t
return encodeAuto(input, output); return encodeAuto(input, output);
} }
bool QRCodeEncoderImpl::isNumeric(const std::string& input) bool QRCodeEncoderImpl::isNumeric(const std::string& input) const
{ {
for (size_t i = 0; i < input.length(); i++) for (size_t i = 0; i < input.length(); i++)
{ {
@ -623,7 +644,7 @@ bool QRCodeEncoderImpl::isNumeric(const std::string& input)
return true; return true;
} }
bool QRCodeEncoderImpl::isAlphaNumeric(const std::string& input) bool QRCodeEncoderImpl::isAlphaNumeric(const std::string& input) const
{ {
for (size_t i = 0; i < input.length(); i++) for (size_t i = 0; i < input.length(); i++)
{ {
@ -633,14 +654,56 @@ bool QRCodeEncoderImpl::isAlphaNumeric(const std::string& input)
return true; return true;
} }
bool QRCodeEncoderImpl::encodeAuto(const std::string& input, vector<uint8_t>& output) QRCodeEncoder::EncodeMode QRCodeEncoderImpl::autoEncodeMode(const std::string &input) const
{ {
if (isNumeric(input)) if (isNumeric(input))
encodeNumeric(input, output); {
else if (isAlphaNumeric(input)) return EncodeMode::MODE_NUMERIC;
encodeAlpha(input, output); }
else
encodeByte(input, output); if (isAlphaNumeric(input))
{
return EncodeMode::MODE_ALPHANUMERIC;
}
return EncodeMode::MODE_BYTE;
}
bool QRCodeEncoderImpl::encodeAuto(const std::string& input, vector<uint8_t>& output, EncodeMode *mode)
{
const auto selected_mode = autoEncodeMode(input);
CV_Assert(selected_mode != EncodeMode::MODE_AUTO);
switch (selected_mode)
{
case EncodeMode::MODE_NUMERIC:
encodeNumeric(input, output);
break;
case EncodeMode::MODE_ALPHANUMERIC:
encodeAlpha(input, output);
break;
case EncodeMode::MODE_STRUCTURED_APPEND:
encodeByte(input, output);
break;
case EncodeMode::MODE_BYTE:
encodeByte(input, output);
break;
case EncodeMode::MODE_KANJI:
encodeKanji(input, output);
break;
case EncodeMode::MODE_ECI:
encodeECI(input, output);
break;
default:
break;
}
if (mode != nullptr)
{
*mode = selected_mode;
}
return true; return true;
} }

View File

@ -548,4 +548,22 @@ TEST(Objdetect_QRCode_Encode_Decode, regression_issue22029)
} }
} }
// This test reproduces issue https://github.com/opencv/opencv/issues/24366 only in a loop
TEST(Objdetect_QRCode_Encode_Decode, auto_version_pick)
{
cv::QRCodeEncoder::Params params;
params.correction_level = cv::QRCodeEncoder::CORRECT_LEVEL_L;
params.mode = cv::QRCodeEncoder::EncodeMode::MODE_AUTO;
cv::Ptr<cv::QRCodeEncoder> encoder = cv::QRCodeEncoder::create(params);
for (int len = 1; len < 19; len++) {
std::string input;
input.resize(len);
cv::randu(Mat(1, len, CV_8U, &input[0]), 'a', 'z' + 1);
cv::Mat qrcode;
encoder->encode(input, qrcode);
}
}
}} // namespace }} // namespace