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] 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