From 3e51448ef0597f19c7f323d9237b8ad07402d1bf Mon Sep 17 00:00:00 2001 From: Qiushi Zheng <55949027+ZhengQiushi@users.noreply.github.com> Date: Tue, 16 Nov 2021 01:15:39 +0800 Subject: [PATCH 1/6] Merge pull request #17889 from ZhengQiushi:my_3.4 QR code (encoding process) * add qrcode encoder * qr encoder fixes * qr encoder: fix api and realization * fixed qr encoder, added eci and kanji modes * trigger CI * qr encoder constructor fixes Co-authored-by: APrigarina --- .../objdetect/include/opencv2/objdetect.hpp | 63 + .../misc/python/pyopencv_objdetect.hpp | 3 + modules/objdetect/src/qrcode_encoder.cpp | 1265 +++++++++++++++++ .../src/qrcode_encoder_table.inl.hpp | 860 +++++++++++ modules/objdetect/test/test_qrcode_encode.cpp | 433 ++++++ 5 files changed, 2624 insertions(+) create mode 100644 modules/objdetect/misc/python/pyopencv_objdetect.hpp create mode 100644 modules/objdetect/src/qrcode_encoder.cpp create mode 100644 modules/objdetect/src/qrcode_encoder_table.inl.hpp create mode 100644 modules/objdetect/test/test_qrcode_encode.cpp diff --git a/modules/objdetect/include/opencv2/objdetect.hpp b/modules/objdetect/include/opencv2/objdetect.hpp index 0387b10239..7c7f7d90d3 100644 --- a/modules/objdetect/include/opencv2/objdetect.hpp +++ b/modules/objdetect/include/opencv2/objdetect.hpp @@ -670,6 +670,69 @@ public: void groupRectangles(std::vector& rectList, std::vector& weights, int groupThreshold, double eps) const; }; +class CV_EXPORTS_W QRCodeEncoder { +protected: + QRCodeEncoder(); // use ::create() +public: + virtual ~QRCodeEncoder(); + + enum EncodeMode { + MODE_AUTO = -1, + MODE_NUMERIC = 1, // 0b0001 + MODE_ALPHANUMERIC = 2, // 0b0010 + MODE_BYTE = 4, // 0b0100 + MODE_ECI = 7, // 0b0111 + MODE_KANJI = 8, // 0b1000 + MODE_STRUCTURED_APPEND = 3 // 0b0011 + }; + + enum CorrectionLevel { + CORRECT_LEVEL_L = 0, + CORRECT_LEVEL_M = 1, + CORRECT_LEVEL_Q = 2, + CORRECT_LEVEL_H = 3 + }; + + enum ECIEncodings { + ECI_UTF8 = 26 + }; + + /** @brief QR code encoder parameters. + @param version The optional version of QR code (by default - maximum possible depending on + the length of the string). + @param correction_level The optional level of error correction (by default - the lowest). + @param mode The optional encoding mode - Numeric, Alphanumeric, Byte, Kanji, ECI or Structured Append. + @param structure_number The optional number of QR codes to generate in Structured Append mode. + */ + struct CV_EXPORTS_W_SIMPLE Params + { + CV_WRAP Params(); + CV_PROP_RW int version; + CV_PROP_RW CorrectionLevel correction_level; + CV_PROP_RW EncodeMode mode; + CV_PROP_RW int structure_number; + }; + + /** @brief Constructor + @param parameters QR code encoder parameters QRCodeEncoder::Params + */ + static CV_WRAP + Ptr create(const QRCodeEncoder::Params& parameters = QRCodeEncoder::Params()); + + /** @brief Generates QR code from input string. + @param encoded_info Input string to encode. + @param qrcode Generated QR code. + */ + CV_WRAP virtual void encode(const String& encoded_info, OutputArray qrcode) = 0; + + /** @brief Generates QR code from input string in Structured Append mode. The encoded message is splitting over a number of QR codes. + @param encoded_info Input string to encode. + @param qrcodes Vector of generated QR codes. + */ + CV_WRAP virtual void encodeStructuredAppend(const String& encoded_info, OutputArrayOfArrays qrcodes) = 0; + +}; + class CV_EXPORTS_W QRCodeDetector { public: diff --git a/modules/objdetect/misc/python/pyopencv_objdetect.hpp b/modules/objdetect/misc/python/pyopencv_objdetect.hpp new file mode 100644 index 0000000000..9c51a00292 --- /dev/null +++ b/modules/objdetect/misc/python/pyopencv_objdetect.hpp @@ -0,0 +1,3 @@ +#ifdef HAVE_OPENCV_OBJDETECT +typedef QRCodeEncoder::Params QRCodeEncoder_Params; +#endif diff --git a/modules/objdetect/src/qrcode_encoder.cpp b/modules/objdetect/src/qrcode_encoder.cpp new file mode 100644 index 0000000000..8329c1c890 --- /dev/null +++ b/modules/objdetect/src/qrcode_encoder.cpp @@ -0,0 +1,1265 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Copyright (C) 2021, Intel Corporation, all rights reserved. + +#include "precomp.hpp" +#include "qrcode_encoder_table.inl.hpp" +namespace cv +{ +using std::vector; + +const int MAX_PAYLOAD_LEN = 8896; +const int MAX_FORMAT_LENGTH = 15; +const int MAX_VERSION_LENGTH = 18; +const int MODE_BITS_NUM = 4; +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 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); +static int getBits(const int bits, const vector &payload, int &pay_index); + +static void decToBin(const int dec_number, const int total_bits, std::vector &bin_number) +{ + for (int i = 0; i < total_bits; i++) + { + bin_number[total_bits - i - 1] = (dec_number >> i) % 2; + } +} + +static void writeDecNumber(const int dec_number, const int total_bits, vector &output_bits) +{ + std::vector bin_number(total_bits); + decToBin(dec_number, total_bits, bin_number); + output_bits.insert(output_bits.end(), bin_number.begin(), bin_number.end()); +} + +static uint8_t gfPow(uint8_t x, int power) +{ + return gf_exp[(gf_log[x] * power) % 255]; +} + +static uint8_t gfMul(const uint8_t x, const uint8_t y) +{ + if (x == 0 || y == 0) + return 0; + return gf_exp[(gf_log[x] + gf_log[y]) % 255]; +} + +static void gfPolyMul(const vector &p, const vector &q, vector &product) +{ + int len_p = (int)p.size(); + int len_q = (int)q.size(); + vector temp_result(len_p + len_q - 1, 0); + + for (int j = 0; j < len_q; j++) + { + uint8_t q_val = q[j]; + if (!q_val) + continue; + for (int i = 0; i < len_p; i++) + { + uint8_t p_val = p[i]; + if (!p_val) + continue; + temp_result[i + j] ^= gfMul(p_val, q_val); + } + } + product = temp_result; +} + +static void gfPolyDiv(const vector ÷nd, const vector &divisor, const int bits_needed, vector "ient) +{ + int dividend_len = (int)dividend.size() - 1; + int divisor_len = (int)divisor.size() - 1; + vector temp = dividend; + + int times = dividend_len - divisor_len + 1; + for (int i = 0; i < times; i++) + { + uint8_t dividend_val = temp[dividend_len - i]; + if(dividend_val != 0) + { + for (int j = 0; j < divisor_len + 1; j++) + { + uint8_t divisor_val = divisor[divisor_len - j]; + if (divisor_val != 0) + { + temp[dividend_len - i - j] ^= gfMul(divisor_val, dividend_val); + } + } + } + } + quotient = vector(temp.begin(), temp.begin() + bits_needed); +} + +static void polyGenerator(const int n, vector &result) +{ + vector temp(2, 1); + result = vector(1, 1); + for (int i = 1; i <= n; i++) + { + temp[0] = gfPow(2, i - 1); + gfPolyMul(result, temp, result); + } +} + +static int getBits(const int bits, const vector &payload, int &pay_index) +{ + int result = 0; + for (int i = 0; i < bits; i++) + { + result = result << 1; + result += payload[pay_index++]; + } + return result; +} + +static int mapSymbol(char c) +{ + if (c >= '0' && c <= '9') + return (int)(c - '0'); + if (c >= 'A' && c <= 'Z') + return (int)(c - 'A') + 10; + switch (c) + { + case ' ': return 36 + 0; + case '$': return 36 + 1; + case '%': return 36 + 2; + case '*': return 36 + 3; + case '+': return 36 + 4; + case '-': return 36 + 5; + case '.': return 36 + 6; + case '/': return 36 + 7; + case ':': return 36 + 8; + } + return -1; +} + +QRCodeEncoder::QRCodeEncoder() +{ + // nothing +} + +QRCodeEncoder::~QRCodeEncoder() +{ + // nothing +} + +QRCodeEncoder::Params::Params() +{ + version = 0; + correction_level = CORRECT_LEVEL_L; + mode = MODE_AUTO; + structure_number = 1; +} + +class QRCodeEncoderImpl : public QRCodeEncoder +{ +public: + QRCodeEncoderImpl(const QRCodeEncoder::Params& parameters) : params(parameters) + { + version_level = parameters.version; + ecc_level = parameters.correction_level; + mode_type = parameters.mode; + struct_num = parameters.structure_number; + version_size = 21; + mask_type = 0; + parity = 0; + sequence_num = 0; + total_num = 0; + } + + void encode(const String& encoded_info, OutputArray qrcode) CV_OVERRIDE; + void encodeStructuredAppend(const String& input, OutputArrayOfArrays output) CV_OVERRIDE; + QRCodeEncoder::Params params; +protected: + int version_level; + CorrectionLevel ecc_level; + EncodeMode mode_type; + int struct_num; + int version_size; + int mask_type; + vector format; + vector version_reserved; + vector payload; + vector rearranged_data; + Mat original; + Mat masked_data; + uint8_t parity; + uint8_t sequence_num; + uint8_t total_num; + vector final_qrcodes; + + Ptr version_info; + Ptr cur_ecc_params; + + bool isNumeric(const std::string& input); + bool isAlphaNumeric(const std::string& input); + bool encodeByte(const std::string& input, vector &output); + bool encodeAlpha(const std::string& input, vector &output); + bool encodeNumeric(const std::string& input, vector &output); + bool encodeECI(const std::string& input, vector &output); + bool encodeKanji(const std::string& input, vector &output); + bool encodeAuto(const std::string& input, vector &output); + bool encodeStructure(const std::string& input, vector &output); + int eccLevelToCode(CorrectionLevel level); + void padBitStream(); + bool stringToBits(const std::string& input_info); + void eccGenerate(vector > &data_blocks, vector > &ecc_blocks); + void rearrangeBlocks(const vector > &data_blocks, const vector > &ecc_blocks); + void writeReservedArea(); + bool writeBit(int x, int y, bool value); + void writeData(); + void structureFinalMessage(); + 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, vector &possible_version); + int versionAuto(const std::string &input_str); + int findVersionCapacity(const int input_length, const int ecc, const int version_begin, const int version_end); + void generatingProcess(const std::string& input, Mat &qrcode); + void generateQR(const std::string& input); +}; + +int QRCodeEncoderImpl::eccLevelToCode(CorrectionLevel level) +{ + switch (level) + { + case CORRECT_LEVEL_L: + return 0b01; + case CORRECT_LEVEL_M: + return 0b00; + case CORRECT_LEVEL_Q: + return 0b11; + case CORRECT_LEVEL_H: + return 0b10; + } + CV_Error( Error::StsBadArg, + "Error correction level is incorrect. Available levels are" + "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 data_codewords, version_index = -1; + const int byte_len = 8; + version_index = -1; + + for (int i = version_begin; i < version_end; i++) + { + Ptr tmp_ecc_params = makePtr(version_info_database[i].ecc[ecc]); + 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; + + if (data_codewords * byte_len >= input_length) + { + version_index = i; + break; + } + } + return version_index; +} + +bool QRCodeEncoderImpl::estimateVersion(const int input_length, vector& possible_version) +{ + possible_version.clear(); + if (input_length > version_capacity_database[40].ec_level[ecc_level].encoding_modes[1]) + 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]) + { + possible_version.push_back(1); + possible_version.push_back(2); + } + else if (input_length <= version_capacity_database[26].ec_level[ecc_level].encoding_modes[3]) + { + possible_version.push_back(2); + } + else if (input_length <= version_capacity_database[26].ec_level[ecc_level].encoding_modes[1]) + { + possible_version.push_back(2); + possible_version.push_back(3); + } + else + { + possible_version.push_back(3); + } + return true; +} + +int QRCodeEncoderImpl::versionAuto(const std::string& input_str) +{ + vector possible_version; + estimateVersion((int)input_str.length(), possible_version); + int tmp_version = 0; + vector payload_tmp; + int version_range[5] = {0, 1, 10, 27, 41}; + for(size_t i = 0; i < possible_version.size(); i++) + { + int version_range_index = possible_version[i]; + if (version_range_index == 1) + { + tmp_version = 1; + } + else if (version_range_index == 2) + { + tmp_version = 10; + } + else + { + tmp_version = 27; + } + encodeAuto(input_str, payload_tmp); + tmp_version = findVersionCapacity((int)payload_tmp.size(), ecc_level, + version_range[version_range_index], version_range[version_range_index + 1]); + if(tmp_version != -1) + break; + } + return tmp_version; +} + +void QRCodeEncoderImpl::generateQR(const std::string &input) +{ + if (struct_num > 1) + { + for (size_t i = 0; i < input.length(); i++) + { + parity ^= input[i]; + } + if (struct_num > 16) + { + struct_num = 16; + } + total_num = (uint8_t) struct_num - 1; + } + int segment_len = (int) ceil((int) input.length() / struct_num); + + for (int i = 0; i < struct_num; i++) + { + sequence_num = (uint8_t) i; + int segment_begin = i * segment_len; + int segemnt_end = min((i + 1) * segment_len, (int) input.length()) - 1; + std::string input_info = input.substr(segment_begin, segemnt_end - segment_begin + 1); + int v = versionAuto(input_info); + if (version_level == 0) + version_level = v; + else if (version_level < v) + CV_Error(Error::StsBadArg, "The given version is not suitable for the given input string length "); + + payload.clear(); + payload.reserve(MAX_PAYLOAD_LEN); + final_qrcodes.clear(); + format = vector (15, 255); + version_reserved = vector (18, 255); + version_size = (21 + (version_level - 1) * 4); + version_info = makePtr(version_info_database[version_level]); + cur_ecc_params = makePtr(version_info->ecc[ecc_level]); + original = Mat(Size(version_size, version_size), CV_8UC1, Scalar(255)); + masked_data = original.clone(); + Mat qrcode = masked_data.clone(); + generatingProcess(input_info, qrcode); + final_qrcodes.push_back(qrcode); + } +} + +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]; + } +} + +void QRCodeEncoderImpl::versionInfoGenerate(const int version_level_num, vector &version_array) +{ + const int version_bits_num = 6; + std::vector version_bits(version_bits_num); + decToBin(version_level_num, version_bits_num, version_bits); + + std::reverse(version_bits.begin(), version_bits.end()); + vector shift(12, 0); + vector polynomial; + hconcat(shift, version_bits, polynomial); + + const int generator_len = 13; + const uint8_t generator_arr[generator_len] = {1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1}; + std::vector format_mask (generator_arr, generator_arr + sizeof(generator_arr) / sizeof(generator_arr[0])); + + vector ecc_code; + gfPolyDiv(polynomial, format_mask, 12, ecc_code); + hconcat(ecc_code, version_bits, version_array); +} + +bool QRCodeEncoderImpl::encodeAlpha(const std::string& input, vector& output) +{ + writeDecNumber(MODE_ALPHANUMERIC, MODE_BITS_NUM, output); + + int length_bits_num = 13; + if (version_level < 10) + length_bits_num = 9; + else if (version_level < 27) + length_bits_num = 11; + + int str_len = int(input.length()); + writeDecNumber(str_len, length_bits_num, output); + + const int alpha_symbol_bits = 11; + const int residual_bits = 6; + for (int i = 0; i < str_len - 1; i += 2) + { + int index_1 = mapSymbol(input[i]); + int index_2 = mapSymbol(input[i + 1]); + + if(index_1 == -1 || (index_2 == -1 && i + 1 < str_len)) + return false; + int alpha = index_1 * 45 + index_2; + + writeDecNumber(alpha, alpha_symbol_bits, output); + } + if (str_len % 2 != 0) + { + int index_residual_elem = mapSymbol(*input.rbegin()); + if(index_residual_elem == -1) + return false; + + writeDecNumber(index_residual_elem, residual_bits, output); + } + return true; +} + +bool QRCodeEncoderImpl::encodeByte(const std::string& input, vector& output) +{ + writeDecNumber(MODE_BYTE, MODE_BITS_NUM, output); + + int length_bits_num = 8; + if (version_level > 9) + length_bits_num = 16; + + int str_len = int(input.length()); + writeDecNumber(str_len, length_bits_num, output); + + const int byte_symbol_bits = 8; + for (int i = 0; i < str_len; i++) + { + writeDecNumber(uint8_t(input[i]), byte_symbol_bits, output); + } + return true; +} + +bool QRCodeEncoderImpl::encodeNumeric(const std::string& input,vector& output) +{ + writeDecNumber(MODE_NUMERIC, MODE_BITS_NUM, output); + + int length_bits_num = 10; + if (version_level >= 27) + length_bits_num = 14; + else if (version_level >= 10) + length_bits_num = 12; + + int str_len = int(input.length()); + writeDecNumber(str_len, length_bits_num, output); + + int count = 0; + const int num_symbol_bits = 10; + const int residual_bits[2] = {7, 4}; + while (count + 3 <= str_len) + { + if (input[count] > '9' || input[count] < '0' || + input[count + 1] > '9' || input[count + 1] < '0' || + input[count + 2] > '9' || input[count + 2] < '0') + return false; + int num = 100 * (int)(input[count] - '0') + + 10 * (int)(input[count + 1] - '0') + + (int)(input[count + 2] - '0'); + + writeDecNumber(num, num_symbol_bits, output); + count += 3; + } + if (count + 2 == str_len) + { + if (input[count] > '9' || input[count] < '0'|| + input[count + 1] >'9'|| input[count + 1] < '0') + return false; + int num = 10 * (int)(input[count] - '0') + + (int)(input[count + 1] - '0'); + + writeDecNumber(num, residual_bits[0], output); + } + else if (count + 1 == str_len) + { + if (input[count] > '9' || input[count] < '0') + return false; + int num = (int)(input[count] - '0'); + + writeDecNumber(num, residual_bits[1], output); + } + return true; +} + +bool QRCodeEncoderImpl::encodeECI(const std::string& input, vector& output) +{ + writeDecNumber(MODE_ECI, MODE_BITS_NUM, output); + const uint32_t assign_value_range[3] = {127, 16383, 999999}; + + // by adding other ECI modes `eci_assignment_number` can be moved to algorithm parameters + uint32_t eci_assignment_number = ECI_UTF8; // utf-8 + + int codewords = 1; + if(eci_assignment_number > assign_value_range[2]) + return false; + if (eci_assignment_number > assign_value_range[1]) + codewords = 3; + else if (eci_assignment_number > assign_value_range[0]) + codewords = 2; + + const int bits = 8; + switch (codewords) + { + case 1: + writeDecNumber(0, codewords, output); + writeDecNumber(eci_assignment_number, codewords * bits - 1, output); + break; + case 2: + writeDecNumber(2, codewords, output); + writeDecNumber(eci_assignment_number, codewords * bits - 2, output); + break; + case 3: + writeDecNumber(6, codewords, output); + writeDecNumber(eci_assignment_number, codewords * bits - 3, output); + break; + } + + encodeByte(input, output); + return true; +} + +bool QRCodeEncoderImpl::encodeKanji(const std::string& input, vector& output) +{ + writeDecNumber(MODE_KANJI, MODE_BITS_NUM, output); + + int length_bits_num = 8; + if (version_level >= 10) + length_bits_num = 10; + else if (version_level >= 27) + length_bits_num = 12; + + int str_len = int(input.length()) / 2; + writeDecNumber(str_len, length_bits_num, output); + + const int kanji_symbol_bits = 13; + int i = 0; + while(i < str_len * 2) + { + uint16_t high_byte = (uint16_t)(input[i] & 0xff); + uint16_t low_byte = (uint16_t)(input[i+1] & 0xff); + uint16_t per_char = (high_byte << 8) + (low_byte); + + if(0x8140 <= per_char && per_char <= 0x9FFC) + { + per_char -= 0x8140; + } + else if(0xE040 <= per_char && per_char <= 0xEBBF) + { + per_char -= 0xC140; + } + uint16_t new_high = per_char >> 8; + uint16_t result = new_high * 0xC0; + result += (per_char & 0xFF); + + writeDecNumber(result, kanji_symbol_bits, output); + i += 2; + } + return true; +} + +bool QRCodeEncoderImpl::encodeStructure(const std::string& input, vector& output) +{ + const int num_field = 4; + const int checksum_field = 8; + writeDecNumber(MODE_STRUCTURED_APPEND, MODE_BITS_NUM, output); + writeDecNumber(sequence_num, num_field, output); + writeDecNumber(total_num, num_field, output); + writeDecNumber(parity, checksum_field, output); + + return encodeAuto(input, output); +} + +bool QRCodeEncoderImpl::isNumeric(const std::string& input) +{ + for (size_t i = 0; i < input.length(); i++) + { + if (input[i] < '0' || input[i] > '9') + return false; + } + return true; +} + +bool QRCodeEncoderImpl::isAlphaNumeric(const std::string& input) +{ + for (size_t i = 0; i < input.length(); i++) + { + if (mapSymbol(input[i]) == -1) + return false; + } + return true; +} + +bool QRCodeEncoderImpl::encodeAuto(const std::string& input, vector& output) +{ + if (isNumeric(input)) + encodeNumeric(input, output); + else if (isAlphaNumeric(input)) + encodeAlpha(input, output); + else + encodeByte(input, output); + return true; +} + +void QRCodeEncoderImpl::padBitStream() +{ + int total_data = version_info->total_codewords - + cur_ecc_params->ecc_codewords * (cur_ecc_params->num_blocks_in_G1 + cur_ecc_params->num_blocks_in_G2); + const int bits = 8; + total_data *= bits; + int pad_num = total_data - (int)payload.size(); + + if (pad_num <= 0) + return; + else if (pad_num <= 4) + { + int payload_size = (int)payload.size(); + writeDecNumber(0, payload_size, payload); + } + else + { + writeDecNumber(0, 4, payload); + + int i = payload.size() % bits; + + if (i != 0) + { + writeDecNumber(0, bits - i, payload); + } + pad_num = total_data - (int)payload.size(); + + if (pad_num > 0) + { + const int pad_patterns[2] = {236, 17}; + int num = pad_num / bits; + const int pattern_size = 8; + for (int j = 0; j < num; j++) + { + writeDecNumber(pad_patterns[j % 2], pattern_size, payload); + } + } + } +} + +bool QRCodeEncoderImpl::stringToBits(const std::string& input_info) +{ + switch (mode_type) + { + case MODE_NUMERIC: + return encodeNumeric(input_info, payload); + case MODE_ALPHANUMERIC: + return encodeAlpha(input_info, payload); + case MODE_STRUCTURED_APPEND: + return encodeStructure(input_info, payload); + case MODE_BYTE: + return encodeByte(input_info, payload); + case MODE_ECI: + return encodeECI(input_info, payload); + case MODE_KANJI: + return encodeKanji(input_info, payload); + default: + return encodeAuto(input_info, payload); + } +}; + +void QRCodeEncoderImpl::eccGenerate(vector > &data_blocks, vector > &ecc_blocks) +{ + const int ec_codewords = cur_ecc_params->ecc_codewords; + int pay_index = 0; + vector g_x; + polyGenerator(ec_codewords, g_x); + int blocks = cur_ecc_params->num_blocks_in_G2 + cur_ecc_params->num_blocks_in_G1; + for (int i = 0; i < blocks; i++) + { + int block_len = 0; + + if (i < cur_ecc_params->num_blocks_in_G1) + { + block_len = cur_ecc_params->data_codewords_in_G1; + } + else + { + block_len = cur_ecc_params->data_codewords_in_G2; + } + vector block_i (block_len, 0); + + for (int j = 0; j < block_len; j++) + { + block_i[block_len - 1 - j] = (uchar)getBits(8, payload, pay_index); + } + vector dividend; + vector shift (ec_codewords, 0); + hconcat(shift, block_i, dividend); + vector ecc_i; + gfPolyDiv(dividend, g_x, ec_codewords, ecc_i); + data_blocks.push_back(block_i); + ecc_blocks.push_back(ecc_i); + } +} + +void QRCodeEncoderImpl::rearrangeBlocks(const vector > &data_blocks, const vector > &ecc_blocks) +{ + rearranged_data.clear(); + rearranged_data.reserve(MAX_PAYLOAD_LEN); + int blocks = cur_ecc_params->num_blocks_in_G2 + cur_ecc_params->num_blocks_in_G1; + int col_border = max(cur_ecc_params->data_codewords_in_G2, cur_ecc_params->data_codewords_in_G1); + int total_codeword_num = version_info->total_codewords; + int is_not_equal = cur_ecc_params->data_codewords_in_G2 - cur_ecc_params->data_codewords_in_G1; + for (int i = 0; i < total_codeword_num; i++) + { + int cur_col = i / blocks; + int cur_row = i % blocks; + int data_col = (int)data_blocks[cur_row].size() - 1; + int ecc_col = (int)ecc_blocks[cur_row].size() - 1; + uint8_t tmp = 0; + + if (cur_col < col_border) + { + if (is_not_equal && cur_col == cur_ecc_params->data_codewords_in_G2 - 1 && cur_row < cur_ecc_params->num_blocks_in_G1) + { + continue; + } + else + { + tmp = data_blocks[cur_row][data_col - cur_col]; + } + } + else + { + int index = ecc_col - (cur_col - col_border); + tmp = ecc_blocks[cur_row][index]; + } + rearranged_data.push_back(tmp); + } + const int remainder_len []= {0, + 0, 7, 7, 7, 7, 7, 0, 0, 0, 0, + 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, + 3, 3, 3, 3, 0, 0, 0, 0, 0, 0}; + int cur_remainder_len = remainder_len[version_level]; + if (cur_remainder_len != 0) + { + rearranged_data.push_back(0); + } +} + +void QRCodeEncoderImpl::findAutoMaskType() +{ + int best_index = 0; + int lowest_penalty = INT_MAX; + int penalty_two_value = 3, penalty_three_value = 40; + for (int cur_type = 0; cur_type < 8; cur_type++) + { + Mat test_result = masked_data.clone(); + vector test_format = format; + maskData(cur_type, test_result); + formatGenerate(cur_type, test_format); + fillReserved(test_format, test_result); + int continued_num = 0; + int penalty_one = 0, penalty_two = 0, penalty_three = 0, penalty_four = 0, penalty_total = 0; + int current_color = -1; + + for (int direction = 0; direction < 2; direction++) + { + if (direction != 0) + { + test_result = test_result.t(); + } + for (int i = 0; i < version_size; i++) + { + int per_row = 0; + for (int j = 0; j < version_size; j++) + { + if (j == 0) + { + current_color = test_result.at(i, j); + continued_num = 1; + continue; + } + if (current_color == test_result.at(i, j)) + { + continued_num += 1; + } + if (current_color != test_result.at(i, j) || j + 1 == version_size) + { + current_color = test_result.at(i, j); + if (continued_num >= 5) + { + per_row += 3 + continued_num - 5; + } + continued_num = 1; + } + } + penalty_one += per_row; + } + } + for (int i = 0; i < version_size - 1; i++) + { + for (int j = 0; j < version_size - 1; j++) + { + uint8_t color = test_result.at(i, j); + if (color == test_result.at(i, j + 1) && + color == test_result.at(i + 1, j + 1) && + color == test_result.at(i + 1, j)) + { + penalty_two += penalty_two_value; + } + } + } + Mat penalty_pattern[2]; + penalty_pattern[0] = (Mat_(1, 11) << 255, 255, 255, 255, 0, 255, 0, 0, 0, 255, 0); + penalty_pattern[1] = (Mat_(1, 11) << 0, 255, 0, 0, 0, 255, 0, 255, 255, 255, 255); + for (int direction = 0; direction < 2; direction++) + { + if (direction != 0) + { + test_result = test_result.t(); + } + for (int i = 0; i < version_size; i++) + { + int per_row = 0; + for (int j = 0; j < version_size - 10; j++) + { + Mat cur_test = test_result(Range(i, i + 1), Range(j, j + 11)); + for (int pattern_index = 0; pattern_index < 2; pattern_index++) + { + Mat diff = (penalty_pattern[pattern_index] != cur_test); + bool equal = (countNonZero(diff) == 0); + if (equal) + { + per_row += penalty_three_value; + } + } + } + penalty_three += per_row; + } + } + int dark_modules = 0; + int total_modules = 0; + for (int i = 0; i < version_size; i++) + { + for (int j = 0; j < version_size; j++) + { + if (test_result.at(i, j) == 0) + { + dark_modules += 1; + } + total_modules += 1; + } + } + int modules_percent = dark_modules * 100 / total_modules; + int lower_bound = 45; + int upper_bound = 55; + int diff = min(abs(modules_percent - lower_bound), abs(modules_percent - upper_bound)); + penalty_four = (diff / 5) * 10; + penalty_total = penalty_one + penalty_two + penalty_three + penalty_four; + if (penalty_total < lowest_penalty) + { + best_index = cur_type; + lowest_penalty = penalty_total; + } + } + mask_type = best_index; +} + +void QRCodeEncoderImpl::maskData(const int mask_type_num, Mat& masked) +{ + for (int i = 0; i < version_size; i++) + { + for (int j = 0; j < version_size; j++) + { + if (original.at(i, j) == INVALID_REGION_VALUE) + { + continue; + } + else if((mask_type_num == 0 && !((i + j) % 2)) || + (mask_type_num == 1 && !(i % 2)) || + (mask_type_num == 2 && !(j % 3)) || + (mask_type_num == 3 && !((i + j) % 3)) || + (mask_type_num == 4 && !(((i / 2) + (j / 3)) % 2)) || + (mask_type_num == 5 && !((i * j) % 2 + (i * j) % 3))|| + (mask_type_num == 6 && !(((i * j) % 2 + (i * j) % 3) % 2))|| + ((mask_type_num == 7 && !(((i * j) % 3 + (i + j) % 2) % 2)))) + { + masked.at(i, j) = original.at(i, j) ^ 255; + } + } + } +} + +void QRCodeEncoderImpl::writeReservedArea() +{ + vector finder_pattern(3); + finder_pattern[0] = Rect(Point(0, 0), Point(9, 9)); + finder_pattern[1] = Rect(Point(0, (unsigned)version_size - 8), Point(9, version_size)); + finder_pattern[2] = Rect(Point((unsigned)version_size - 8, 0), Point(version_size, 9)); + const int coordinates_num = 2; + int locator_position[coordinates_num] = {3, version_size - 1 - 3}; + + for (int first_coordinate = 0; first_coordinate < coordinates_num; first_coordinate++) + { + for (int second_coordinate = 0; second_coordinate < coordinates_num; second_coordinate++) + { + if (first_coordinate == 1 && second_coordinate == 1) + { + continue; + } + int x = locator_position[first_coordinate]; + int y = locator_position[second_coordinate]; + for (int i = -5; i <= 5; i++) + { + for (int j = -5; j <= 5; j++) + { + if (x + i < 0 || x + i >= version_size || y + j < 0 || y + j >= version_size) + { + continue; + } + if (!(abs(j) == 2 && abs(i) <= 2) && + !(abs(j) <= 2 && abs(i) == 2) && + !(abs(i) == 4) && !(abs(j) == 4)) + { + masked_data.at(x + i, y + j) = 0; + } + if ((y == locator_position[1] && j == -5) || (x == locator_position[1] && i == -5)) + { + continue; + } + else + { + original.at(x + i, y + j) = INVALID_REGION_VALUE; + } + } + } + + } + } + int x = locator_position[1] - 4; + int y = locator_position[0] + 5; + masked_data.at(x, y) = 0; + original.at(x, y) = INVALID_REGION_VALUE; + if (version_level >= 7) + { + for (int i = 0; i <= 6; i++) + { + for (int j = version_size - 11; j <= version_size - 8; j++) + { + original.at(i, j) = INVALID_REGION_VALUE; + original.at(j, i) = INVALID_REGION_VALUE; + } + } + } + for (int i = 0; i < version_size; i++) + { + for (int j = 0; j < version_size; j++) + { + if (original.at(i, j) == INVALID_REGION_VALUE) + { + continue; + } + if ((i == 6 || j == 6)) + { + original.at(i, j) = INVALID_REGION_VALUE; + if (!((i == 6) && (j - 7) % 2 == 0) && + !((j == 6) && ((i - 7) % 2 == 0))) + { + masked_data.at(i, j) = 0; + } + } + } + } + for (int first_coord = 0; first_coord < MAX_ALIGNMENT && version_info->alignment_pattern[first_coord]; first_coord++) + { + for (int second_coord = 0; second_coord < MAX_ALIGNMENT && version_info->alignment_pattern[second_coord]; second_coord++) + { + x = version_info->alignment_pattern[first_coord]; + y = version_info->alignment_pattern[second_coord]; + bool is_in_finder = false; + for (size_t i = 0; i < finder_pattern.size(); i++) + { + Rect rect = finder_pattern[i]; + if (x >= rect.tl().x && x <= rect.br().x + && + y >= rect.tl().y && y <= rect.br().y) + { + is_in_finder = true; + break; + } + } + if (!is_in_finder) + { + for (int i = -2; i <= 2; i++) + { + for (int j = -2; j <= 2; j++) + { + original.at(x + i, y + j) = INVALID_REGION_VALUE; + if ((j == 0 && i == 0) || (abs(j) == 2) || abs(i) == 2) + { + masked_data.at(x + i, y + j) = 0; + } + } + } + } + } + } +} + +bool QRCodeEncoderImpl::writeBit(int x, int y, bool value) +{ + if (original.at(y, x) == INVALID_REGION_VALUE) + { + return false; + } + if (!value) + { + original.at(y, x) = 0; + masked_data.at(y, x) = 0; + } + original.at(y, x) = static_cast(255 * value); + masked_data.at(y, x) = static_cast(255 * value); + return true; +} + +void QRCodeEncoderImpl::writeData() +{ + int y = version_size - 1; + int x = version_size - 1; + int dir = -1; + int count = 0; + int codeword_value = rearranged_data[0]; + while (x > 0) + { + if (x == 6) + { + x --; + } + for(int i = 0; i <= 1; i++) + { + bool bit_value = (codeword_value & (0x80 >> count % 8)) == 0; + bool success = writeBit(x - i, y, bit_value); + if (!success) + { + continue; + } + count++; + if (count % 8 == 0) + { + codeword_value = rearranged_data[count / 8]; + } + } + y += dir; + if (y < 0 || y >= version_size) + { + dir = -dir; + x -= 2; + y += dir; + } + } +} + +void QRCodeEncoderImpl::fillReserved(const vector &format_array, Mat &masked) +{ + for (int i = 0; i < 7; i++) + { + if (format_array[MAX_FORMAT_LENGTH - 1 - i] == 0) + { + masked.at(version_size - 1 - i, 8) = 255; + } + else + { + masked.at(version_size - 1 - i, 8) = 0; + } + } + for (int i = 0; i < 8; i++) + { + if (format_array[MAX_FORMAT_LENGTH - 1 - (7 + i)] == 0) + { + masked.at(8, version_size - 8 + i) = 255; + } + else + { + masked.at(8, version_size - 8 + i) = 0; + } + } + static const int xs_format[MAX_FORMAT_LENGTH] = { + 8, 8, 8, 8, 8, 8, 8, 8, 7, 5, 4, 3, 2, 1, 0 + }; + static const int ys_format[MAX_FORMAT_LENGTH] = { + 0, 1, 2, 3, 4, 5, 7, 8, 8, 8, 8, 8, 8, 8, 8 + }; + for (int i = MAX_FORMAT_LENGTH - 1; i >= 0; i--) + { + if (format_array[i] == 0) + { + masked.at(ys_format[i], xs_format[i]) = 255; + } + else + { + masked.at(ys_format[i], xs_format[i]) = 0; + } + } + + if (version_level >= 7) + { + const int max_size = version_size; + const int version_block_width = 2; + const int xs_version[version_block_width][MAX_VERSION_LENGTH] = { + { 5, 5, 5, + 4, 4, 4, + 3, 3, 3, + 2, 2, 2, + 1, 1, 1, + 0, 0, 0 + }, + { max_size - 9, max_size - 10, max_size - 11, + max_size - 9, max_size - 10, max_size - 11, + max_size - 9, max_size - 10, max_size - 11, + max_size - 9, max_size - 10, max_size - 11, + max_size - 9, max_size - 10, max_size - 11, + max_size - 9, max_size - 10, max_size - 11 + } + }; + const int ys_version[version_block_width][MAX_VERSION_LENGTH] = { + { max_size - 9, max_size - 10, max_size - 11, + max_size - 9, max_size - 10, max_size - 11, + max_size - 9, max_size - 10, max_size - 11, + max_size - 9, max_size - 10, max_size - 11, + max_size - 9, max_size - 10, max_size - 11, + max_size - 9, max_size - 10, max_size - 11 + }, + { 5, 5, 5, + 4, 4, 4, + 3, 3, 3, + 2, 2, 2, + 1, 1, 1, + 0, 0, 0, + } + }; + for (int i = 0; i < version_block_width; i++) + { + for (int j = 0; j < MAX_VERSION_LENGTH; j++) + { + if (version_reserved[MAX_VERSION_LENGTH - j - 1] == 0) + { + masked.at(ys_version[i][j], xs_version[i][j]) = 255; + } + else + { + masked.at(ys_version[i][j], xs_version[i][j]) = 0; + } + } + } + } +} + +void QRCodeEncoderImpl::structureFinalMessage() +{ + writeReservedArea(); + writeData(); + findAutoMaskType(); + maskData(mask_type, masked_data); + formatGenerate(mask_type, format); + versionInfoGenerate(version_level, version_reserved); + fillReserved(format, masked_data); +} + +void QRCodeEncoderImpl::generatingProcess(const std::string& input, Mat& final_result) +{ + vector > data_blocks, ecc_blocks; + if (!stringToBits(input)) + { + return; + } + padBitStream(); + eccGenerate(data_blocks, ecc_blocks); + rearrangeBlocks(data_blocks, ecc_blocks); + structureFinalMessage(); + final_result = masked_data.clone(); + const int border = 2; + copyMakeBorder(final_result, final_result, border, border, border, border, BORDER_CONSTANT, Scalar(255)); +} + +void QRCodeEncoderImpl::encode(const String& input, OutputArray output) +{ + if (output.kind() != _InputArray::MAT) + CV_Error(Error::StsBadArg, "Output should be cv::Mat"); + CV_Check((int)mode_type, mode_type != MODE_STRUCTURED_APPEND, "For structured append mode please call encodeStructuredAppend() method"); + CV_Check(struct_num, struct_num == 1, "For structured append mode please call encodeStructuredAppend() method"); + generateQR(input); + CV_Assert(!final_qrcodes.empty()); + output.assign(final_qrcodes[0]); +} + +void QRCodeEncoderImpl::encodeStructuredAppend(const String& input, OutputArrayOfArrays output) +{ + if (output.kind() != _InputArray::STD_VECTOR_MAT) + CV_Error(Error::StsBadArg, "Output should be vector of cv::Mat"); + mode_type = MODE_STRUCTURED_APPEND; + generateQR(input); + CV_Assert(!final_qrcodes.empty()); + output.create((int)final_qrcodes.size(), 1, final_qrcodes[0].type()); + vector dst; + output.getMatVector(dst); + for (int i = 0; i < (int)final_qrcodes.size(); i++) + { + output.getMatRef(i) = final_qrcodes[i]; + } +} + +Ptr QRCodeEncoder::create(const QRCodeEncoder::Params& parameters) +{ + return makePtr(parameters); +} + +} diff --git a/modules/objdetect/src/qrcode_encoder_table.inl.hpp b/modules/objdetect/src/qrcode_encoder_table.inl.hpp new file mode 100644 index 0000000000..fc2ec37038 --- /dev/null +++ b/modules/objdetect/src/qrcode_encoder_table.inl.hpp @@ -0,0 +1,860 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. +// +// Copyright (C) 2021, Intel Corporation, all rights reserved. +// +// This is .hpp file included from qrcode_encoder.cpp + +namespace cv { + +const int MAX_ALIGNMENT = 7; +const int MODES = 4; +const int ERROR_CORR_LEVELS = 4; +const int MAX_VERSION = 40; + +struct BlockParams +{ + int ecc_codewords; + int num_blocks_in_G1; + int data_codewords_in_G1; + int num_blocks_in_G2; + int data_codewords_in_G2; +}; + +struct VersionInfo +{ + int total_codewords; + int alignment_pattern[MAX_ALIGNMENT]; + BlockParams ecc[ERROR_CORR_LEVELS]; +}; + +/** numeric_mode; + alpha_mode; + byte_mode; + kanji mode; +*/ +struct ECLevelCapacity +{ + int encoding_modes[MODES]; +}; +struct CharacterCapacity +{ + ECLevelCapacity ec_level[ERROR_CORR_LEVELS]; +}; +const CharacterCapacity version_capacity_database[MAX_VERSION + 1] = +{ + { + { + {0, 1, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0}, + {0, 0, 0, 0} + } + }, + { + { + {41, 25, 17, 10}, + {34, 20, 14, 8}, + {27, 16, 11, 7}, + {17, 10, 7 , 4} + } + }, + { + { + {77, 47, 32, 20}, + {63, 38, 26, 16}, + {48, 29, 20, 12}, + {34, 20, 14, 8} + } + }, + { + { + {127, 77, 53, 32}, + {101, 61, 42, 26}, + {77, 47, 32, 20}, + {58, 35, 24, 15} + } + }, + { + { + {187, 114, 78, 48}, + {149, 90, 62, 38}, + {111, 67, 46, 28}, + {82, 50, 34, 21} + } + }, + { + { + {255, 154, 106, 65}, + {202, 122, 84, 52}, + {144, 87, 60, 37}, + {106, 64, 44, 27} + } + }, + { + { + {322, 195, 134, 82}, + {255, 154, 106, 65}, + {178, 108, 74, 45}, + {139, 84 , 58, 36} + } + }, + { + { + {370, 224, 154, 95}, + {293, 178, 122, 75}, + {207, 125, 86, 53}, + {154, 93 , 64, 39} + } + }, + { + { + {461, 279, 192, 118}, + {365, 221, 152, 93}, + {259, 157, 108, 66}, + {202, 122, 84, 52} + } + }, + { + { + {552, 335, 230, 141}, + {432, 262, 180, 111}, + {312, 189, 130, 80}, + {235, 143, 98, 60} + } + }, + { + { + {652, 395, 271, 167}, + {513, 311, 213, 131}, + {364, 221, 151, 93}, + {288, 174, 119, 74} + } + }, + { + { + {772, 468, 321, 198}, + {604, 366, 251, 155}, + {427, 259, 177, 109}, + {331, 200, 137, 85} + } + }, + { + { + {883, 535, 367, 226}, + {691, 419, 287, 177}, + {489, 296, 203, 125}, + {374, 227, 155, 96} + } + }, + { + { + {1022, 619, 425, 262}, + {796, 483, 331, 204}, + {580, 352, 241, 149}, + {427, 259, 177, 109} + } + }, + { + { + {1101, 667, 458, 282}, + {871, 528, 362, 223}, + {621, 376, 258, 159}, + {468, 283, 194, 120} + } + }, + { + { + {1250, 758, 520, 320}, + {991, 600, 412, 254}, + {703, 426, 292, 180}, + {530, 321, 220, 136} + } + }, + { + { + {1408, 854, 586, 361}, + {1082, 656, 450, 277}, + {775, 470, 322, 198}, + {602, 365, 250, 154} + } + }, + { + { + {1548, 938, 644, 397}, + {1212, 734, 504, 310}, + {876, 531, 364, 224}, + {674, 408, 280, 173} + } + }, + { + { + {1725, 1046, 718, 442}, + {1346, 816, 560, 345}, + {948, 574, 394, 243}, + {746, 452, 310, 191} + } + }, + { + { + {1903, 1153, 792, 488}, + {1500, 909, 624, 384}, + {1063, 644, 442, 272}, + {813, 493, 338, 208} + } + }, + { + { + {2061, 1249, 858, 528}, + {1600, 970, 666, 410}, + {1159, 702, 482, 297}, + {919, 557, 382, 235} + } + }, + { + { + {2232, 1352, 929, 572}, + {1708, 1035, 711, 438}, + {1224, 742, 509, 314}, + {969, 587, 403, 248} + } + }, + { + { + {2409, 1460, 1003, 618}, + {1872, 1134, 779, 480}, + {1358, 823, 565, 348}, + {1056, 640, 439, 270} + } + }, + { + { + {2620, 1588, 1091, 672}, + {2059, 1248, 857, 528}, + {1468, 890, 611, 376}, + {1108, 672, 461, 284} + } + }, + { + { + {2812, 1704, 1171, 721}, + {2188, 1326, 911, 561}, + {1588, 963, 661, 407}, + {1228, 744, 511, 315} + } + }, + { + { + {3057, 1853, 1273, 784}, + {2395, 1451, 997, 614}, + {1718, 1041, 715, 440}, + {1286, 779, 535, 330} + } + }, + { + { + {3283, 1990, 1367, 842}, + {2544, 1542, 1059, 652}, + {1804, 1094, 751, 462}, + {1425, 864, 593, 365} + } + }, + { + { + {3517, 2132, 1465, 902}, + {2701, 1637, 1125, 692}, + {1933, 1172, 805, 496}, + {1501, 910, 625, 385} + } + }, + { + { + {3669, 2223, 1528, 940}, + {2857, 1732, 1190, 732}, + {2085, 1263, 868, 534}, + {1581, 958, 658, 405} + } + }, + { + { + {3909, 2369, 1628, 1002}, + {3035, 1839, 1264, 778}, + {2181, 1322, 908, 559}, + {1677, 1016, 698, 430} + } + }, + { + { + {4158, 2520, 1732, 1066}, + {3289, 1994, 1370, 843}, + {2358, 1429, 982, 604}, + {1782, 1080, 742, 457} + } + }, + { + { + {4417, 2677, 1840, 1132}, + {3486, 2113, 1452, 894}, + {2473, 1499, 1030, 634}, + {1897, 1150, 790, 486} + } + + }, + { + { + {4686, 2840, 1952, 1201}, + {3693, 2238, 1538, 947}, + {2670, 1618, 1112, 684}, + {2022, 1226, 842, 518} + } + }, + { + { + {4965, 3009, 2068, 1273}, + {3909, 2369, 1628, 1002}, + {2805, 1700, 1168, 719}, + {2157, 1307, 898, 553} + } + }, + { + { + {5253, 3183, 2188, 1347}, + {4134, 2506, 1722, 1060}, + {2949, 1787, 1228, 756}, + {2301, 1394, 958 , 590} + } + }, + { + { + {5529, 3351, 2303, 1417}, + {4343, 2632, 1809, 1113}, + {3081, 1867, 1283, 790}, + {2361, 1431, 983, 605} + } + }, + { + { + {5836, 3537, 2431, 1496}, + {4588, 2780, 1911, 1176}, + {3244, 1966, 1351, 832}, + {2524, 1530, 1051, 647} + } + }, + { + { + {6153, 3729, 2563, 1577}, + {4775, 2894, 1989, 1224}, + {3417, 2071, 1423, 876}, + {2625, 1591, 1093, 673} + } + }, + { + { + {6479, 3927, 2699, 1661}, + {5039, 3054, 2099, 1292}, + {3599, 2181, 1499, 923}, + {2735, 1658, 1139, 701} + } + }, + { + { + {6743, 4087, 2809, 1729}, + {5313, 3220, 2213, 1362}, + {3791, 2298, 1579, 972}, + {2927, 1774, 1219, 750} + } + }, + { + { + {7089, 4296, 2953, 1817}, + {5596, 3391, 2331, 1435}, + {3993, 2420, 1663, 1024}, + {3057, 1852, 1273, 784} + } + } +}; + +const VersionInfo version_info_database[MAX_VERSION + 1] = +{ + { /* Version 0 */ + 0, + {0, 0, 0, 0, 0, 0, 0}, + { + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0}, + {0, 0, 0, 0, 0} + } + }, + { /* Version 1 */ + 26, + {0, 0, 0, 0, 0, 0, 0}, + { + {7, 1, 19, 0, 0}, + {10, 1, 16, 0, 0}, + {13, 1, 13, 0, 0}, + {17, 1, 9, 0, 0} + } + }, + { /* Version 2 */ + 44, + {6, 18, 0, 0, 0, 0, 0}, + { + {10, 1, 34, 0, 0}, + {16, 1, 28, 0, 0}, + {22, 1, 22, 0, 0}, + {28, 1, 16, 0, 0} + } + }, + { /* Version 3 */ + 70, + {6, 22, 0, 0, 0, 0, 0}, + { + {15, 1, 55, 0, 0}, + {26, 1, 44, 0, 0}, + {18, 2, 17, 0, 0}, + {22, 2, 13, 0, 0} + } + }, + { /* Version 4 */ + 100, + {6, 26, 0,0,0,0,0}, + { + {20, 1, 80, 0, 0}, + {18, 2, 32, 0, 0}, + {26, 2, 24, 0, 0}, + {16, 4, 9 , 0, 0} + } + }, + { /* Version 5 */ + 134, + {6, 30, 0, 0, 0, 0, 0}, + { + {26, 1, 108, 0, 0}, + {24, 2, 43, 0, 0}, + {18, 2, 15, 2, 16}, + {22, 2, 11, 2, 12} + } + }, + { /* Version 6 */ + 172, + {6, 34, 0, 0, 0, 0, 0}, + { + {18, 2, 68, 0, 0}, + {16, 4, 27, 0, 0}, + {24, 4, 19, 0, 0}, + {28, 4, 15, 0, 0} + } + }, + { /* Version 7 */ + 196, + {6, 22, 38, 0, 0, 0, 0}, + { + {20, 2, 78, 0, 0}, + {18, 4, 31, 0, 0}, + {18, 2, 14, 4, 15}, + {26, 4, 13, 1, 14} + } + }, + { /* Version 8 */ + 242, + {6, 24, 42, 0, 0, 0, 0}, + { + {24, 2, 97, 0, 0}, + {22, 2, 38, 2, 39}, + {22, 4, 18, 2, 19}, + {26, 4, 14, 2, 15} + } + }, + { /* Version 9 */ + 292, + {6, 26, 46, 0, 0, 0, 0}, + { + {30, 2, 116, 0, 0}, + {22, 3, 36, 2, 37}, + {20, 4, 16, 4, 17}, + {24, 4, 12, 4, 13} + } + }, + { /* Version 10 */ + 346, + {6, 28, 50, 0, 0, 0, 0}, + { + {18, 2, 68, 2, 69}, + {26, 4, 43, 1, 44}, + {24, 6, 19, 2, 20}, + {28, 6, 15, 2, 16} + } + }, + { /* Version 11 */ + 404, + {6, 30, 54, 0, 0, 0, 0}, + { + {20, 4, 81, 0, 0}, + {30, 1, 50, 4, 51}, + {28, 4, 22, 4, 23}, + {24, 3, 12, 8, 13} + } + }, + { /* Version 12 */ + 466, + {6, 32, 58, 0, 0, 0, 0}, + { + {24, 2, 92, 2, 93}, + {22, 6, 36, 2, 37}, + {26, 4, 20, 6, 21}, + {28, 7, 14, 4, 15} + } + }, + { /* Version 13 */ + 532, + {6, 34, 62, 0, 0, 0, 0}, + { + {26, 4, 107, 0, 0}, + {22, 8, 37, 1, 38}, + {24, 8, 20, 4, 21}, + {22, 12, 11, 4, 12} + } + }, + { /* Version 14 */ + 581, + {6, 26, 46, 66, 0, 0, 0}, + { + {30, 3, 115, 1, 116}, + {24, 4, 40, 5, 41}, + {20, 11, 16, 5, 17}, + {24, 11, 12, 5, 13} + } + }, + { /* Version 15 */ + 655, + {6, 26, 48, 70, 0, 0, 0}, + { + {22, 5, 87, 1, 88}, + {24, 5, 41, 5, 42}, + {30, 5, 24, 7, 25}, + {24, 11, 12, 7, 13} + } + }, + { /* Version 16 */ + 733, + {6, 26, 50, 74, 0, 0, 0}, + { + {24, 5, 98, 1, 99}, + {28, 7, 45, 3, 46}, + {24, 15, 19, 2, 20}, + {30, 3, 15, 13, 16} + } + }, + { /* Version 17 */ + 815, + {6, 30, 54, 78, 0, 0, 0}, + { + {28, 1, 107, 5, 108}, + {28, 10, 46, 1, 47}, + {28, 1, 22, 15, 23}, + {28, 2, 14, 17, 15} + } + }, + { /* Version 18 */ + 901, + {6, 30, 56, 82, 0, 0, 0}, + { + {30, 5, 120, 1, 121}, + {26, 9, 43, 4, 44}, + {28, 17, 22, 1, 23}, + {28, 2, 14, 19, 15} + } + }, + { /* Version 19 */ + 991, + {6, 30, 58, 86, 0, 0, 0}, + { + {28, 3, 113, 4, 114}, + {26, 3, 44, 11, 45}, + {26, 17, 21, 4, 22}, + {26, 9, 13, 16, 14} + } + }, + { /* Version 20 */ + 1085, + {6, 34, 62, 90, 0, 0, 0}, + { + {28, 3, 107, 5, 108}, + {26, 3, 41, 13, 42}, + {30, 15, 24, 5, 25}, + {28, 15, 15, 10, 16} + } + }, + { /* Version 21 */ + 1156, + {6, 28, 50, 72, 92, 0, 0}, + { + {28, 4, 116, 4, 117}, + {26, 17, 42, 0, 0}, + {28, 17, 22, 6, 23}, + {30, 19, 16, 6, 17} + } + }, + { /* Version 22 */ + 1258, + {6, 26, 50, 74, 98, 0, 0}, + { + {28, 2, 111, 7, 112}, + {28, 17, 46, 0, 0}, + {30, 7, 24, 16, 25}, + {24, 34, 13, 0, 0} + } + }, + { /* Version 23 */ + 1364, + {6, 30, 54, 78, 102, 0, 0}, + { + {30, 4, 121,5, 122}, + {28, 4, 47, 14, 48}, + {30, 11, 24, 14, 25}, + {30, 16, 15, 14, 16} + } + }, + { /* Version 24 */ + 1474, + {6, 28, 54, 80, 106, 0, 0}, + { + {30, 6, 117,4, 118}, + {28, 6, 45, 14, 46}, + {30, 11, 24, 16, 25}, + {30, 30, 16, 2, 17} + } + }, + { /* Version 25 */ + 1588, + {6, 32, 58, 84, 110, 0, 0}, + { + {26, 8, 106, 4, 107}, + {28, 8, 47, 13, 48}, + {30, 7, 24, 22, 25}, + {30, 22, 15, 13, 16} + } + }, + { /* Version 26 */ + 1706, + {6, 30, 58, 86, 114, 0, 0}, + { + {28, 10, 114, 2, 115}, + {28, 19, 46, 4, 47}, + {28, 28, 22, 6, 23}, + {30, 33, 16, 4, 17} + } + }, + { /* Version 27 */ + 1828, + {6, 34, 62, 90, 118, 0, 0}, + { + {30, 8, 122, 4, 123}, + {28, 22, 45, 3, 46}, + {30, 8, 23, 26, 24}, + {30, 12, 15, 28, 16} + } + }, + { /* Version 28 */ + 1921, + {6, 26, 50, 74, 98, 122, 0}, + { + {30, 3, 117, 10, 118}, + {28, 3, 45, 23, 46}, + {30, 4, 24, 31, 25}, + {30, 11, 15, 31, 16} + } + }, + { /* Version 29 */ + 2051, + {6, 30, 54, 78, 102, 126, 0}, + { + {30, 7, 116, 7, 117}, + {28, 21, 45, 7, 46}, + {30, 1, 23, 37, 24}, + {30, 19, 15, 26, 16} + } + }, + { /* Version 30 */ + 2185, + {6, 26, 52, 78, 104, 130, 0}, + { + {30, 5, 115, 10, 116}, + {28, 19, 47, 10, 48}, + {30, 15, 24, 25, 25}, + {30, 23, 15, 25, 16} + } + }, + { /* Version 31 */ + 2323, + {6, 30, 56, 82, 108, 134, 0}, + { + {30, 13, 115, 3, 116}, + {28, 2, 46, 29, 47}, + {30, 42, 24, 1, 25}, + {30, 23, 15, 28, 16} + } + }, + { /* Version 32 */ + 2465, + {6, 34, 60, 86, 112, 138, 0}, + { + {30, 17, 115, 0, 0}, + {28, 10, 46, 23, 47}, + {30, 10, 24, 35, 25}, + {30, 19, 15, 35, 16} + } + }, + { /* Version 33 */ + 2611, + {6, 30, 58, 86, 114, 142, 0}, + { + {30, 17, 115, 1, 116}, + {28, 14, 46, 21, 47}, + {30, 29, 24, 19, 25}, + {30, 11, 15, 46, 16} + } + }, + { /* Version 34 */ + 2761, + {6, 34, 62, 90, 118, 146, 0}, + { + {30, 13, 115, 6, 116}, + {28, 14, 46, 23, 47}, + {30, 44, 24, 7, 25}, + {30, 59, 16, 1, 17} + } + }, + { /* Version 35 */ + 2876, + {6, 30, 54, 78, 102, 126, 150}, + { + {30, 12, 121, 7, 122}, + {28, 12, 47, 26, 48}, + {30, 39, 24, 14, 25}, + {30, 22, 15, 41, 16} + } + }, + { /* Version 36 */ + 3034, + {6, 24, 50, 76, 102, 128, 154}, + { + {30, 6, 121, 14, 122}, + {28, 6, 47, 34, 48}, + {30, 46, 24, 10, 25}, + {30, 2, 15, 64, 16} + } + }, + { /* Version 37 */ + 3196, + {6, 28, 54, 80, 106, 132, 158}, + { + {30, 17, 122, 4, 123}, + {28, 29, 46, 14, 47}, + {30, 49, 24, 10, 25}, + {30, 24, 15, 46, 16} + } + }, + { /* Version 38 */ + 3362, + {6, 32, 58, 84, 110, 136, 162}, + { + {30, 4, 122, 18, 123}, + {28, 13, 46, 32, 47}, + {30, 48, 24, 14, 25}, + {30, 42, 15, 32, 16} + } + }, + { /* Version 39 */ + 3532, + {6, 26, 54, 82, 110, 138, 166}, + { + {30, 20, 117,4, 118}, + {28, 40, 47, 7, 48}, + {30, 43, 24, 22, 25}, + {30, 10, 15, 67, 16} + } + }, + { /* Version 40 */ + 3706, + {6, 30, 58, 86, 114, 142, 170}, + { + {30, 19, 118, 6, 119}, + {28, 18, 47, 31, 48}, + {30, 34, 24, 34, 25}, + {30, 20, 15, 61, 16} + } + } +}; + +static const uint8_t gf_exp[256] = { + 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, + 0x1d, 0x3a, 0x74, 0xe8, 0xcd, 0x87, 0x13, 0x26, + 0x4c, 0x98, 0x2d, 0x5a, 0xb4, 0x75, 0xea, 0xc9, + 0x8f, 0x03, 0x06, 0x0c, 0x18, 0x30, 0x60, 0xc0, + 0x9d, 0x27, 0x4e, 0x9c, 0x25, 0x4a, 0x94, 0x35, + 0x6a, 0xd4, 0xb5, 0x77, 0xee, 0xc1, 0x9f, 0x23, + 0x46, 0x8c, 0x05, 0x0a, 0x14, 0x28, 0x50, 0xa0, + 0x5d, 0xba, 0x69, 0xd2, 0xb9, 0x6f, 0xde, 0xa1, + 0x5f, 0xbe, 0x61, 0xc2, 0x99, 0x2f, 0x5e, 0xbc, + 0x65, 0xca, 0x89, 0x0f, 0x1e, 0x3c, 0x78, 0xf0, + 0xfd, 0xe7, 0xd3, 0xbb, 0x6b, 0xd6, 0xb1, 0x7f, + 0xfe, 0xe1, 0xdf, 0xa3, 0x5b, 0xb6, 0x71, 0xe2, + 0xd9, 0xaf, 0x43, 0x86, 0x11, 0x22, 0x44, 0x88, + 0x0d, 0x1a, 0x34, 0x68, 0xd0, 0xbd, 0x67, 0xce, + 0x81, 0x1f, 0x3e, 0x7c, 0xf8, 0xed, 0xc7, 0x93, + 0x3b, 0x76, 0xec, 0xc5, 0x97, 0x33, 0x66, 0xcc, + 0x85, 0x17, 0x2e, 0x5c, 0xb8, 0x6d, 0xda, 0xa9, + 0x4f, 0x9e, 0x21, 0x42, 0x84, 0x15, 0x2a, 0x54, + 0xa8, 0x4d, 0x9a, 0x29, 0x52, 0xa4, 0x55, 0xaa, + 0x49, 0x92, 0x39, 0x72, 0xe4, 0xd5, 0xb7, 0x73, + 0xe6, 0xd1, 0xbf, 0x63, 0xc6, 0x91, 0x3f, 0x7e, + 0xfc, 0xe5, 0xd7, 0xb3, 0x7b, 0xf6, 0xf1, 0xff, + 0xe3, 0xdb, 0xab, 0x4b, 0x96, 0x31, 0x62, 0xc4, + 0x95, 0x37, 0x6e, 0xdc, 0xa5, 0x57, 0xae, 0x41, + 0x82, 0x19, 0x32, 0x64, 0xc8, 0x8d, 0x07, 0x0e, + 0x1c, 0x38, 0x70, 0xe0, 0xdd, 0xa7, 0x53, 0xa6, + 0x51, 0xa2, 0x59, 0xb2, 0x79, 0xf2, 0xf9, 0xef, + 0xc3, 0x9b, 0x2b, 0x56, 0xac, 0x45, 0x8a, 0x09, + 0x12, 0x24, 0x48, 0x90, 0x3d, 0x7a, 0xf4, 0xf5, + 0xf7, 0xf3, 0xfb, 0xeb, 0xcb, 0x8b, 0x0b, 0x16, + 0x2c, 0x58, 0xb0, 0x7d, 0xfa, 0xe9, 0xcf, 0x83, + 0x1b, 0x36, 0x6c, 0xd8, 0xad, 0x47, 0x8e, 0x01 +}; +static const uint8_t gf_log[256] = { + 0x00, 0xff, 0x01, 0x19, 0x02, 0x32, 0x1a, 0xc6, + 0x03, 0xdf, 0x33, 0xee, 0x1b, 0x68, 0xc7, 0x4b, + 0x04, 0x64, 0xe0, 0x0e, 0x34, 0x8d, 0xef, 0x81, + 0x1c, 0xc1, 0x69, 0xf8, 0xc8, 0x08, 0x4c, 0x71, + 0x05, 0x8a, 0x65, 0x2f, 0xe1, 0x24, 0x0f, 0x21, + 0x35, 0x93, 0x8e, 0xda, 0xf0, 0x12, 0x82, 0x45, + 0x1d, 0xb5, 0xc2, 0x7d, 0x6a, 0x27, 0xf9, 0xb9, + 0xc9, 0x9a, 0x09, 0x78, 0x4d, 0xe4, 0x72, 0xa6, + 0x06, 0xbf, 0x8b, 0x62, 0x66, 0xdd, 0x30, 0xfd, + 0xe2, 0x98, 0x25, 0xb3, 0x10, 0x91, 0x22, 0x88, + 0x36, 0xd0, 0x94, 0xce, 0x8f, 0x96, 0xdb, 0xbd, + 0xf1, 0xd2, 0x13, 0x5c, 0x83, 0x38, 0x46, 0x40, + 0x1e, 0x42, 0xb6, 0xa3, 0xc3, 0x48, 0x7e, 0x6e, + 0x6b, 0x3a, 0x28, 0x54, 0xfa, 0x85, 0xba, 0x3d, + 0xca, 0x5e, 0x9b, 0x9f, 0x0a, 0x15, 0x79, 0x2b, + 0x4e, 0xd4, 0xe5, 0xac, 0x73, 0xf3, 0xa7, 0x57, + 0x07, 0x70, 0xc0, 0xf7, 0x8c, 0x80, 0x63, 0x0d, + 0x67, 0x4a, 0xde, 0xed, 0x31, 0xc5, 0xfe, 0x18, + 0xe3, 0xa5, 0x99, 0x77, 0x26, 0xb8, 0xb4, 0x7c, + 0x11, 0x44, 0x92, 0xd9, 0x23, 0x20, 0x89, 0x2e, + 0x37, 0x3f, 0xd1, 0x5b, 0x95, 0xbc, 0xcf, 0xcd, + 0x90, 0x87, 0x97, 0xb2, 0xdc, 0xfc, 0xbe, 0x61, + 0xf2, 0x56, 0xd3, 0xab, 0x14, 0x2a, 0x5d, 0x9e, + 0x84, 0x3c, 0x39, 0x53, 0x47, 0x6d, 0x41, 0xa2, + 0x1f, 0x2d, 0x43, 0xd8, 0xb7, 0x7b, 0xa4, 0x76, + 0xc4, 0x17, 0x49, 0xec, 0x7f, 0x0c, 0x6f, 0xf6, + 0x6c, 0xa1, 0x3b, 0x52, 0x29, 0x9d, 0x55, 0xaa, + 0xfb, 0x60, 0x86, 0xb1, 0xbb, 0xcc, 0x3e, 0x5a, + 0xcb, 0x59, 0x5f, 0xb0, 0x9c, 0xa9, 0xa0, 0x51, + 0x0b, 0xf5, 0x16, 0xeb, 0x7a, 0x75, 0x2c, 0xd7, + 0x4f, 0xae, 0xd5, 0xe9, 0xe6, 0xe7, 0xad, 0xe8, + 0x74, 0xd6, 0xf4, 0xea, 0xa8, 0x50, 0x58, 0xaf +}; +} diff --git a/modules/objdetect/test/test_qrcode_encode.cpp b/modules/objdetect/test/test_qrcode_encode.cpp new file mode 100644 index 0000000000..92ab73cff1 --- /dev/null +++ b/modules/objdetect/test/test_qrcode_encode.cpp @@ -0,0 +1,433 @@ +// This file is part of OpenCV project. +// It is subject to the license terms in the LICENSE file found in the top-level directory +// of this distribution and at http://opencv.org/license.html. + +#include "test_precomp.hpp" +namespace opencv_test { namespace { + +std::string encode_qrcode_images_name[] = { + "version1_mode1.png", "version1_mode2.png", "version1_mode4.png", + "version2_mode1.png", "version2_mode2.png", "version2_mode4.png", + "version3_mode2.png", "version3_mode4.png", + "version4_mode4.png" +}; + +std::string encode_qrcode_eci_images_name[] = { + "version1_mode7.png", + "version2_mode7.png", + "version3_mode7.png", + "version4_mode7.png", + "version5_mode7.png" +}; + +const Size fixed_size = Size(200, 200); +const float border_width = 2.0; + +int establishCapacity(QRCodeEncoder::EncodeMode mode, int version, int capacity) +{ + int result = 0; + capacity *= 8; + capacity -= 4; + switch (mode) + { + case QRCodeEncoder::MODE_NUMERIC: + { + if (version >= 10) + capacity -= 12; + else + capacity -= 10; + int tmp = capacity / 10; + result = tmp * 3; + if (tmp * 10 + 7 <= capacity) + result += 2; + else if (tmp * 10 + 4 <= capacity) + result += 1; + break; + } + case QRCodeEncoder::MODE_ALPHANUMERIC: + { + if (version < 10) + capacity -= 9; + else + capacity -= 13; + int tmp = capacity / 11; + result = tmp * 2; + if (tmp * 11 + 6 <= capacity) + result++; + break; + } + case QRCodeEncoder::MODE_BYTE: + { + if (version > 9) + capacity -= 16; + else + capacity -= 8; + result = capacity / 8; + break; + } + default: + break; + } + return result; +} + +// #define UPDATE_TEST_DATA +#ifdef UPDATE_TEST_DATA + +TEST(Objdetect_QRCode_Encode, generate_test_data) +{ + const std::string root = "qrcode/encode"; + const std::string dataset_config = findDataFile(root + "/" + "dataset_config.json"); + FileStorage file_config(dataset_config, FileStorage::WRITE); + + file_config << "test_images" << "["; + size_t images_count = sizeof(encode_qrcode_images_name) / sizeof(encode_qrcode_images_name[0]); + for (size_t i = 0; i < images_count; i++) + { + file_config << "{:" << "image_name" << encode_qrcode_images_name[i]; + std::string image_path = findDataFile(root + "/" + encode_qrcode_images_name[i]); + + Mat src = imread(image_path, IMREAD_GRAYSCALE); + Mat straight_barcode; + EXPECT_TRUE(!src.empty()) << "Can't read image: " << image_path; + + 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); + + 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 j = 0; j < corners.size(); j++) + { + corners[j].x = corners[j].x * width_ratio; + corners[j].y = corners[j].y * height_ratio; + } + + 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 << "}"; + } + file_config << "]"; + file_config.release(); +} +#else + +typedef testing::TestWithParam< std::string > Objdetect_QRCode_Encode; +TEST_P(Objdetect_QRCode_Encode, regression) { + const int pixels_error = 3; + const std::string name_current_image = GetParam(); + const std::string root = "qrcode/encode"; + + std::string image_path = findDataFile(root + "/" + name_current_image); + 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; + { + FileNode images_list = file_config["test_images"]; + size_t images_count = static_cast(images_list.size()); + ASSERT_GT(images_count, 0u) << "Can't find validation data entries in 'test_images': " << dataset_config; + + for (size_t index = 0; index < images_count; index++) + { + FileNode config = images_list[(int)index]; + std::string name_test_image = config["image_name"]; + if (name_test_image == name_current_image) + { + std::string original_info = config["info"]; + Ptr encoder = QRCodeEncoder::create(); + Mat result; + encoder->encode(original_info, result); + EXPECT_FALSE(result.empty()) << "Can't generate QR code image"; + + Mat src = imread(image_path, IMREAD_GRAYSCALE); + Mat straight_barcode; + EXPECT_TRUE(!src.empty()) << "Can't read image: " << image_path; + + double diff_norm = cvtest::norm(result - src, NORM_L1); + EXPECT_NEAR(diff_norm, 0.0, pixels_error) << "The generated QRcode is not same as test data. The difference: " << diff_norm; + + return; // done + } + } + FAIL() << "Not found results in config file:" << dataset_config + << "\nRe-run tests with enabled UPDATE_ENCODE_TEST_DATA macro to update test data."; + } +} + +typedef testing::TestWithParam< std::string > Objdetect_QRCode_Encode_ECI; +TEST_P(Objdetect_QRCode_Encode_ECI, regression) { + const int pixels_error = 3; + const std::string name_current_image = GetParam(); + const std::string root = "qrcode/encode"; + + std::string image_path = findDataFile(root + "/" + name_current_image); + 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; + { + FileNode images_list = file_config["test_images"]; + size_t images_count = static_cast(images_list.size()); + ASSERT_GT(images_count, 0u) << "Can't find validation data entries in 'test_images': " << dataset_config; + QRCodeEncoder::Params params; + params.mode = QRCodeEncoder::MODE_ECI; + + for (size_t index = 0; index < images_count; index++) + { + FileNode config = images_list[(int)index]; + std::string name_test_image = config["image_name"]; + if (name_test_image == name_current_image) + { + std::string original_info = config["info"]; + Mat result; + Ptr encoder = QRCodeEncoder::create(params); + encoder->encode(original_info, result); + EXPECT_FALSE(result.empty()) << "Can't generate QR code image"; + + Mat src = imread(image_path, IMREAD_GRAYSCALE); + Mat straight_barcode; + EXPECT_TRUE(!src.empty()) << "Can't read image: " << image_path; + + double diff_norm = cvtest::norm(result - src, NORM_L1); + EXPECT_NEAR(diff_norm, 0.0, pixels_error) << "The generated QRcode is not same as test data. The difference: " << diff_norm; + + return; // done + } + } + FAIL() << "Not found results in config file:" << dataset_config + << "\nRe-run tests with enabled UPDATE_ENCODE_TEST_DATA macro to update test data."; + } +} + +INSTANTIATE_TEST_CASE_P(/**/, Objdetect_QRCode_Encode, testing::ValuesIn(encode_qrcode_images_name)); +INSTANTIATE_TEST_CASE_P(/**/, Objdetect_QRCode_Encode_ECI, testing::ValuesIn(encode_qrcode_eci_images_name)); + +TEST(Objdetect_QRCode_Encode_Decode, regression) +{ + const std::string root = "qrcode/decode_encode"; + const int min_version = 1; + const int test_max_version = 5; + const int max_ec_level = 3; + const std::string dataset_config = findDataFile(root + "/" + "symbol_sets.json"); + const std::string version_config = findDataFile(root + "/" + "capacity.json"); + + FileStorage file_config(dataset_config, FileStorage::READ); + FileStorage capacity_config(version_config, FileStorage::READ); + ASSERT_TRUE(file_config.isOpened()) << "Can't read validation data: " << dataset_config; + ASSERT_TRUE(capacity_config.isOpened()) << "Can't read validation data: " << version_config; + + FileNode mode_list = file_config["symbols_sets"]; + FileNode capacity_list = capacity_config["version_ecc_capacity"]; + + size_t mode_count = static_cast(mode_list.size()); + ASSERT_GT(mode_count, 0u) << "Can't find validation data entries in 'test_images': " << dataset_config; + + const int testing_modes = 3; + QRCodeEncoder::EncodeMode modes[testing_modes] = { + QRCodeEncoder::MODE_NUMERIC, + QRCodeEncoder::MODE_ALPHANUMERIC, + QRCodeEncoder::MODE_BYTE + }; + + for (int i = 0; i < testing_modes; i++) + { + QRCodeEncoder::EncodeMode mode = modes[i]; + FileNode config = mode_list[i]; + + std::string symbol_set = config["symbols_set"]; + + for(int version = min_version; version <= test_max_version; version++) + { + FileNode capa_config = capacity_list[version - 1]; + for(int level = 0; level <= max_ec_level; level++) + { + const int cur_capacity = capa_config["ecc_level"][level]; + + int true_capacity = establishCapacity(mode, version, cur_capacity); + + std::string input_info = symbol_set; + std::random_shuffle(input_info.begin(),input_info.end()); + int count = 0; + if((int)input_info.length() > true_capacity) + { + input_info = input_info.substr(0, true_capacity); + } + else + { + while ((int)input_info.length() != true_capacity) + { + input_info += input_info.substr(count%(int)input_info.length(), 1); + count++; + } + } + + QRCodeEncoder::Params params; + params.version = version; + params.correction_level = static_cast(level); + params.mode = mode; + Ptr encoder = QRCodeEncoder::create(params); + Mat qrcode; + encoder->encode(input_info, qrcode); + EXPECT_TRUE(!qrcode.empty()) << "Can't generate this QR image (" << "mode: " << (int)mode << + " version: "<< version <<" error correction level: "<< (int)level <<")"; + + 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); + + 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 k = 0; k < corners.size(); k++) + { + corners[k].x = corners[k].x * width_ratio; + corners[k].y = corners[k].y * height_ratio; + } + + std::string output_info = ""; + Mat straight_barcode; +#ifdef HAVE_QUIRC + bool success = decodeQRCode(resized_src, corners, output_info, straight_barcode); + EXPECT_TRUE(success) << "The generated QRcode cannot be decoded." << " Mode: " << (int)mode<< + " version: " << version << " error correction level: " << (int)level; +#endif + 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; + } + } + } + +} + +TEST(Objdetect_QRCode_Encode_Kanji, regression) +{ + QRCodeEncoder::Params params; + params.mode = QRCodeEncoder::MODE_KANJI; + + Mat qrcode; + + const int testing_versions = 3; + std::string input_infos[testing_versions] = {"\x82\xb1\x82\xf1\x82\xc9\x82\xbf\x82\xcd\x90\xa2\x8a\x45", // こんにちは世界 + "\x82\xa8\x95\xa0\x82\xaa\x8b\xf3\x82\xa2\x82\xc4\x82\xa2\x82\xdc\x82\xb7", // お腹が空いています + "\x82\xb1\x82\xf1\x82\xc9\x82\xbf\x82\xcd\x81\x41\x8e\x84\x82\xcd\x8f\xad\x82\xb5\x93\xfa\x96\x7b\x8c\xea\x82\xf0\x98\x62\x82\xb5\x82\xdc\x82\xb7" // こんにちは、私は少し日本語を話します + }; + + for (int i = 0; i < testing_versions; i++) + { + std::string input_info = input_infos[i]; + Ptr encoder = QRCodeEncoder::create(params); + encoder->encode(input_info, qrcode); + + 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); + + 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 j = 0; j < corners.size(); j++) + { + corners[j].x = corners[j].x * width_ratio; + corners[j].y = corners[j].y * height_ratio; + } + + Mat straight_barcode; + std::string decoded_info = ""; + +#ifdef HAVE_QUIRC + EXPECT_TRUE(decodeQRCode(resized_src, corners, decoded_info, straight_barcode)) << "The generated QRcode cannot be decoded."; +#endif + EXPECT_EQ(input_info, decoded_info); + } +} + +TEST(Objdetect_QRCode_Encode_Decode_Structured_Append, DISABLED_regression) +{ + // disabled since QR decoder probably doesn't support structured append mode qr codes + const std::string root = "qrcode/decode_encode"; + const std::string dataset_config = findDataFile(root + "/" + "symbol_sets.json"); + const std::string version_config = findDataFile(root + "/" + "capacity.json"); + + FileStorage file_config(dataset_config, FileStorage::READ); + ASSERT_TRUE(file_config.isOpened()) << "Can't read validation data: " << dataset_config; + + FileNode mode_list = file_config["symbols_sets"]; + + size_t mode_count = static_cast(mode_list.size()); + ASSERT_GT(mode_count, 0u) << "Can't find validation data entries in 'test_images': " << dataset_config; + + int modes[] = {1, 2, 4}; + const int min_stuctures_num = 2; + const int max_stuctures_num = 5; + for (int i = 0; i < 3; i++) + { + int mode = modes[i]; + FileNode config = mode_list[i]; + + std::string symbol_set = config["symbols_set"]; + + std::string input_info = symbol_set; + std::random_shuffle(input_info.begin(), input_info.end()); + + for (int j = min_stuctures_num; j < max_stuctures_num; j++) + { + QRCodeEncoder::Params params; + params.structure_number = j; + Ptr encoder = QRCodeEncoder::create(params); + vector qrcodes; + encoder->encodeStructuredAppend(input_info, qrcodes); + EXPECT_TRUE(!qrcodes.empty()) << "Can't generate this QR images"; + + std::string output_info = ""; + for (size_t k = 0; k < qrcodes.size(); k++) + { + Mat qrcode = qrcodes[k]; + + 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); + + 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++) + { + corners[m].x = corners[m].x * width_ratio; + corners[m].y = corners[m].y * height_ratio; + } + + std::string decoded_info = ""; + Mat straight_barcode; +#ifdef HAVE_QUIRC + bool success = decodeQRCode(resized_src, corners, decoded_info, straight_barcode); + EXPECT_TRUE(success) << "The generated QRcode cannot be decoded." << " Mode: " << mode << + " structures number: " << k << "/" << j; +#endif + output_info += decoded_info; + } + EXPECT_EQ(input_info, output_info) << "The generated QRcode is not same as test data." << " Mode: " << mode << + " structures number: " << j; + } + } +} + +#endif // UPDATE_QRCODE_TEST_DATA + +}} // namespace From 473f10877c218c4ac307a179773e0a003454aec2 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Tue, 16 Nov 2021 17:07:27 +0000 Subject: [PATCH 2/6] doc(videoio): fix apiPreference note, replace DSHOW(deprecated)->MSMF --- modules/videoio/doc/videoio_overview.markdown | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/modules/videoio/doc/videoio_overview.markdown b/modules/videoio/doc/videoio_overview.markdown index 26930ce87b..57d3d2a931 100644 --- a/modules/videoio/doc/videoio_overview.markdown +++ b/modules/videoio/doc/videoio_overview.markdown @@ -32,27 +32,26 @@ Select the backend at runtime OpenCV automatically selects and uses first available backend (`apiPreference=cv::CAP_ANY`). -As advanced usage you can select the backend to use at runtime. Currently this option is -available only with %VideoCapture. +As advanced usage you can select the backend to use at runtime. -For example to grab from default camera using Direct Show as backend +For example to grab from default camera using Microsoft Media Foundation (MSMF) as backend ```cpp //declare a capture object -cv::VideoCapture cap(0, cv::CAP_DSHOW); +cv::VideoCapture cap(0, cv::CAP_MSMF); //or specify the apiPreference with open -cap.open(0, cv::CAP_DSHOW); +cap.open(0, cv::CAP_MSMF); ``` -If you want to grab from a file using the Direct Show as backend: +If you want to grab from a file using the Microsoft Media Foundation (MSMF) as backend: ```cpp //declare a capture object -cv::VideoCapture cap(filename, cv::CAP_DSHOW); +cv::VideoCapture cap(filename, cv::CAP_MSMF); //or specify the apiPreference with open -cap.open(filename, cv::CAP_DSHOW); +cap.open(filename, cv::CAP_MSMF); ``` @sa cv::VideoCapture::open() , cv::VideoCapture::VideoCapture() From de7f8eec047769dcca20177f371f9b5b59dfd5cd Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Thu, 18 Nov 2021 05:40:16 +0000 Subject: [PATCH 3/6] js(test): pin cli-table dependency --- modules/js/test/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/js/test/package.json b/modules/js/test/package.json index 87a878ca46..304d4eb3ce 100644 --- a/modules/js/test/package.json +++ b/modules/js/test/package.json @@ -4,6 +4,7 @@ "version": "1.0.1", "dependencies": { "ansi-colors": "^4.1.1", + "cli-table": "0.3.6", "minimist": "^1.2.0", "node-qunit": "latest" }, From 79d4e865fe8032fb68a3b17d1383567eff9f6e16 Mon Sep 17 00:00:00 2001 From: nickjackolson Date: Sun, 14 Nov 2021 20:43:50 +0100 Subject: [PATCH 4/6] Add warning message to imread() Add a warning message using CV_LOG__WARNING(). This way api behaviour is preserved. Outputs are the same but user gets an extra warning in case fopen() fails to access image file for some reason. This would help new users and also debugging complex apps which use imread() Signed-off-by: nickjackolson --- modules/imgcodecs/src/loadsave.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/imgcodecs/src/loadsave.cpp b/modules/imgcodecs/src/loadsave.cpp index c8fcbea7ee..bd87c379ab 100644 --- a/modules/imgcodecs/src/loadsave.cpp +++ b/modules/imgcodecs/src/loadsave.cpp @@ -226,8 +226,10 @@ static ImageDecoder findDecoder( const String& filename ) { FILE* f= fopen( filename.c_str(), "rb" ); /// in the event of a failure, return an empty image decoder - if( !f ) + if( !f ) { + CV_LOG_WARNING(NULL, "imread_('" << filename << "'): can't open/read file: check file path/integrity"); return ImageDecoder(); + } // read the file signature String signature(maxlen, ' '); From b696928a5b73f27d3b3c4678cea8bc2e5fd845d7 Mon Sep 17 00:00:00 2001 From: nickjackolson Date: Wed, 17 Nov 2021 21:56:55 +0100 Subject: [PATCH 5/6] add !empty assertion in seamlessClone() issue #20617 addresses lack of warnings on seamlessClone() function when src is None. This commit adds source check using CV_Assert therefore debugging would be easier. Signed-off-by: nickjackolson --- modules/photo/src/seamless_cloning.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/photo/src/seamless_cloning.cpp b/modules/photo/src/seamless_cloning.cpp index d09203577b..d21a3f21fd 100644 --- a/modules/photo/src/seamless_cloning.cpp +++ b/modules/photo/src/seamless_cloning.cpp @@ -67,6 +67,7 @@ static Mat checkMask(InputArray _mask, Size size) void cv::seamlessClone(InputArray _src, InputArray _dst, InputArray _mask, Point p, OutputArray _blend, int flags) { CV_INSTRUMENT_REGION(); + CV_Assert(!_src.empty()); const Mat src = _src.getMat(); const Mat dest = _dst.getMat(); From d4741eece17480b4744ee423003a25b5c20f38a0 Mon Sep 17 00:00:00 2001 From: Vincent Rabaud Date: Mon, 15 Nov 2021 13:09:02 +0100 Subject: [PATCH 6/6] Fix H clamping for very small negative values. In case of very small negative h (e.g. -1e-40), with the current implementation, you will go through the first condition and end up with h = 6.f, and will miss the second condition. --- modules/imgproc/src/color_hsv.simd.hpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/modules/imgproc/src/color_hsv.simd.hpp b/modules/imgproc/src/color_hsv.simd.hpp index 7501f4b113..b1eb50d7a4 100644 --- a/modules/imgproc/src/color_hsv.simd.hpp +++ b/modules/imgproc/src/color_hsv.simd.hpp @@ -955,12 +955,11 @@ struct HLS2RGB_f float p1 = 2*l - p2; h *= hscale; - if( h < 0 ) - do h += 6; while( h < 0 ); - else if( h >= 6 ) - do h -= 6; while( h >= 6 ); + // We need both loops to clamp (e.g. for h == -1e-40). + while( h < 0 ) h += 6; + while( h >= 6 ) h -= 6; - assert( 0 <= h && h < 6 ); + CV_DbgAssert( 0 <= h && h < 6 ); sector = cvFloor(h); h -= sector;