opencv/modules/objdetect/test/test_qrcode_encode.cpp
Dmitry Kurtaev d296d29a1c
Merge pull request #24299 from dkurt:qrcode_decode
In-house QR codes decoding #24299

### Pull Request Readiness Checklist

QR codes decoding pipeline without 3rdparty dependency (Quirc library). Implemented according to standard https://github.com/yansikeim/QR-Code/blob/master/ISO%20IEC%2018004%202015%20Standard.pdf

**Merge with extra**: https://github.com/opencv/opencv_extra/pull/1124

resolves https://github.com/opencv/opencv/issues/24225
resolves https://github.com/opencv/opencv/issues/17290
resolves https://github.com/opencv/opencv/issues/24318 https://github.com/opencv/opencv/issues/24346

Resources:
* https://en.wikiversity.org/wiki/Reed%E2%80%93Solomon_codes_for_coders
* https://en.wikipedia.org/wiki/Berlekamp%E2%80%93Massey_algorithm

```
Geometric mean (ms)

                                   Name of Test                                      quirc   new2      new2   
                                                                                                        vs    
                                                                                                      quirc   
                                                                                                    (x-factor)
decode::Perf_Objdetect_Not_QRCode::("chessboard", 640x480)                           9.151   9.157     1.00   
decode::Perf_Objdetect_Not_QRCode::("chessboard", 1280x720)                         21.609  21.609     1.00   
decode::Perf_Objdetect_Not_QRCode::("chessboard", 1920x1080)                        42.088  41.924     1.00   
decode::Perf_Objdetect_Not_QRCode::("chessboard", 3840x2160)                        169.737 169.050    1.00   
decode::Perf_Objdetect_Not_QRCode::("random", 640x480)                               8.552   8.611     0.99   
decode::Perf_Objdetect_Not_QRCode::("random", 1280x720)                             21.264  21.581     0.99   
decode::Perf_Objdetect_Not_QRCode::("random", 1920x1080)                            42.415  43.468     0.98   
decode::Perf_Objdetect_Not_QRCode::("random", 3840x2160)                            175.003 174.294    1.00   
decode::Perf_Objdetect_Not_QRCode::("zero", 640x480)                                 8.528   8.421     1.01   
decode::Perf_Objdetect_Not_QRCode::("zero", 1280x720)                               21.548  21.209     1.02   
decode::Perf_Objdetect_Not_QRCode::("zero", 1920x1080)                              42.581  42.529     1.00   
decode::Perf_Objdetect_Not_QRCode::("zero", 3840x2160)                              176.231 174.410    1.01   
decode::Perf_Objdetect_QRCode::"kanji.jpg"                                           6.105   6.072     1.01   
decode::Perf_Objdetect_QRCode::"link_github_ocv.jpg"                                 6.069   6.076     1.00   
decode::Perf_Objdetect_QRCode::"link_ocv.jpg"                                        6.143   6.240     0.98   
decode::Perf_Objdetect_QRCode::"link_wiki_cv.jpg"                                    6.369   6.420     0.99   
decode::Perf_Objdetect_QRCode::"russian.jpg"                                         6.558   6.549     1.00   
decode::Perf_Objdetect_QRCode::"version_1_down.jpg"                                  5.634   5.621     1.00   
decode::Perf_Objdetect_QRCode::"version_1_left.jpg"                                  5.560   5.609     0.99   
decode::Perf_Objdetect_QRCode::"version_1_right.jpg"                                 5.539   5.631     0.98   
decode::Perf_Objdetect_QRCode::"version_1_top.jpg"                                   5.622   5.566     1.01   
decode::Perf_Objdetect_QRCode::"version_1_up.jpg"                                    5.569   5.534     1.01   
decode::Perf_Objdetect_QRCode::"version_5_down.jpg"                                  6.514   6.436     1.01   
decode::Perf_Objdetect_QRCode::"version_5_left.jpg"                                  6.668   6.479     1.03   
decode::Perf_Objdetect_QRCode::"version_5_top.jpg"                                   6.481   6.484     1.00   
decode::Perf_Objdetect_QRCode::"version_5_up.jpg"                                    7.011   6.513     1.08   
decodeMulti::Perf_Objdetect_QRCode_Multi::("2_qrcodes.png", "aruco_based")          14.885  15.089     0.99   
decodeMulti::Perf_Objdetect_QRCode_Multi::("2_qrcodes.png", "contours_based")       14.896  14.906     1.00   
decodeMulti::Perf_Objdetect_QRCode_Multi::("3_close_qrcodes.png", "aruco_based")     6.661   6.663     1.00   
decodeMulti::Perf_Objdetect_QRCode_Multi::("3_close_qrcodes.png", "contours_based")  6.614   6.592     1.00   
decodeMulti::Perf_Objdetect_QRCode_Multi::("3_qrcodes.png", "aruco_based")          14.814  14.592     1.02   
decodeMulti::Perf_Objdetect_QRCode_Multi::("3_qrcodes.png", "contours_based")       15.245  15.135     1.01   
decodeMulti::Perf_Objdetect_QRCode_Multi::("4_qrcodes.png", "aruco_based")          10.923  10.881     1.00   
decodeMulti::Perf_Objdetect_QRCode_Multi::("4_qrcodes.png", "contours_based")       10.680  10.128     1.05   
decodeMulti::Perf_Objdetect_QRCode_Multi::("5_qrcodes.png", "contours_based")       11.788  11.576     1.02   
decodeMulti::Perf_Objdetect_QRCode_Multi::("6_qrcodes.png", "aruco_based")          25.887  25.979     1.00   
decodeMulti::Perf_Objdetect_QRCode_Multi::("6_qrcodes.png", "contours_based")       26.183  25.627     1.02   
decodeMulti::Perf_Objdetect_QRCode_Multi::("7_qrcodes.png", "aruco_based")          32.786  32.253     1.02   
decodeMulti::Perf_Objdetect_QRCode_Multi::("7_qrcodes.png", "contours_based")       24.290  24.435     0.99   
decodeMulti::Perf_Objdetect_QRCode_Multi::("8_close_qrcodes.png", "aruco_based")    89.696  89.247     1.01   
decodeMulti::Perf_Objdetect_QRCode_Multi::("8_close_qrcodes.png", "contours_based") 89.872  89.600     1.00
```
2023-11-24 11:35:36 +03:00

596 lines
24 KiB
C++

// 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 {
#if !defined CV_CXX11
// Wrapper for generating seeded random number via std::rand.
template<unsigned Seed>
class SeededRandFunctor {
public:
SeededRandFunctor() { std::srand(Seed); }
int operator()(int i) { return std::rand() % (i + 1); }
};
#endif
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<Point2f> corners(4);
corners[0] = Point2f(border_width, border_width);
corners[1] = Point2f(qrcode.cols * 1.0f - border_width, border_width);
corners[2] = Point2f(qrcode.cols * 1.0f - border_width, qrcode.rows * 1.0f - border_width);
corners[3] = Point2f(border_width, qrcode.rows * 1.0f - border_width);
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 = "";
EXPECT_TRUE(decodeQRCode(resized_src, corners, decoded_info, straight_barcode)) << "The QR code cannot be decoded: " << image_path;
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<size_t>(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<QRCodeEncoder> 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<size_t>(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<QRCodeEncoder> 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<size_t>(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::mt19937 rand_gen {1};
std::shuffle(input_info.begin(), input_info.end(), rand_gen);
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<QRCodeEncoder::CorrectionLevel>(level);
params.mode = mode;
Ptr<QRCodeEncoder> 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<Point2f> corners(4);
corners[0] = Point2f(border_width, border_width);
corners[1] = Point2f(qrcode.cols * 1.0f - border_width, border_width);
corners[2] = Point2f(qrcode.cols * 1.0f - border_width, qrcode.rows * 1.0f - border_width);
corners[3] = Point2f(border_width, qrcode.rows * 1.0f - border_width);
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;
}
Mat straight_barcode;
std::string output_info = QRCodeDetector().decode(resized_src, corners, straight_barcode);
EXPECT_FALSE(output_info.empty())
<< "The generated QRcode cannot be decoded." << " Mode: " << (int)mode
<< " version: " << version << " error correction level: " << (int)level;
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", // "Hello World" in Japanese
"\x82\xa8\x95\xa0\x82\xaa\x8b\xf3\x82\xa2\x82\xc4\x82\xa2\x82\xdc\x82\xb7", // "I am hungry" in Japanese
"\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" // "Hello, I speak a little Japanese" in Japanese
};
for (int i = 0; i < testing_versions; i++)
{
std::string input_info = input_infos[i];
Ptr<QRCodeEncoder> encoder = QRCodeEncoder::create(params);
encoder->encode(input_info, qrcode);
std::vector<Point2f> corners(4);
corners[0] = Point2f(border_width, border_width);
corners[1] = Point2f(qrcode.cols * 1.0f - border_width, border_width);
corners[2] = Point2f(qrcode.cols * 1.0f - border_width, qrcode.rows * 1.0f - border_width);
corners[3] = Point2f(border_width, qrcode.rows * 1.0f - border_width);
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 = QRCodeDetector().decode(resized_src, corners, straight_barcode);
EXPECT_FALSE(decoded_info.empty()) << "The generated QRcode cannot be decoded.";
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<size_t>(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::mt19937 rand_gen {1};
std::shuffle(input_info.begin(), input_info.end(), rand_gen);
for (int j = min_stuctures_num; j < max_stuctures_num; j++)
{
QRCodeEncoder::Params params;
params.structure_number = j;
Ptr<QRCodeEncoder> encoder = QRCodeEncoder::create(params);
vector<Mat> 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<Point2f> corners(4);
corners[0] = Point2f(border_width, border_width);
corners[1] = Point2f(qrcode.cols * 1.0f - border_width, border_width);
corners[2] = Point2f(qrcode.cols * 1.0f - border_width, qrcode.rows * 1.0f - border_width);
corners[3] = Point2f(border_width, qrcode.rows * 1.0f - border_width);
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;
}
Mat straight_barcode;
std::string decoded_info = QRCodeDetector().decode(resized_src, corners, straight_barcode);
EXPECT_FALSE(decoded_info.empty())
<< "The generated QRcode cannot be decoded." << " Mode: " << modes[i]
<< " structures number: " << k << "/" << j;
output_info += decoded_info;
}
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
CV_ENUM(EncodeModes, QRCodeEncoder::EncodeMode::MODE_NUMERIC,
QRCodeEncoder::EncodeMode::MODE_ALPHANUMERIC,
QRCodeEncoder::EncodeMode::MODE_BYTE)
typedef ::testing::TestWithParam<EncodeModes> Objdetect_QRCode_Encode_Decode_Structured_Append_Parameterized;
TEST_P(Objdetect_QRCode_Encode_Decode_Structured_Append_Parameterized, regression_22205)
{
const std::string input_data = "the quick brown fox jumps over the lazy dog";
std::vector<cv::Mat> result_qrcodes;
cv::QRCodeEncoder::Params params;
int encode_mode = GetParam();
params.mode = static_cast<cv::QRCodeEncoder::EncodeMode>(encode_mode);
for(size_t struct_num = 2; struct_num < 5; ++struct_num)
{
params.structure_number = static_cast<int>(struct_num);
cv::Ptr<cv::QRCodeEncoder> encoder = cv::QRCodeEncoder::create(params);
encoder->encodeStructuredAppend(input_data, result_qrcodes);
EXPECT_EQ(result_qrcodes.size(), struct_num) << "The number of QR Codes requested is not equal"<<
"to the one returned";
}
}
INSTANTIATE_TEST_CASE_P(/**/, Objdetect_QRCode_Encode_Decode_Structured_Append_Parameterized, EncodeModes::all());
TEST(Objdetect_QRCode_Encode_Decode, regression_issue22029)
{
const cv::String msg = "OpenCV";
const int min_version = 1;
const int max_version = 40;
for ( int v = min_version ; v <= max_version ; v++ )
{
SCOPED_TRACE(cv::format("version=%d",v));
Mat qrimg;
QRCodeEncoder::Params params;
params.version = v;
Ptr<QRCodeEncoder> qrcode_enc = cv::QRCodeEncoder::create(params);
qrcode_enc->encode(msg, qrimg);
const int white_margin = 2;
const int finder_width = 7;
const int timing_pos = white_margin + 6;
int i;
// Horizontal Check
// (1) White margin(Left)
for(i = 0; i < white_margin ; i++ )
{
ASSERT_EQ((uint8_t)255, qrimg.at<uint8_t>(i, timing_pos)) << "i=" << i;
}
// (2) Finder pattern(Left)
for( ; i < white_margin + finder_width ; i++ )
{
ASSERT_EQ((uint8_t)0, qrimg.at<uint8_t>(i, timing_pos)) << "i=" << i;
}
// (3) Timing pattern
for( ; i < qrimg.rows - finder_width - white_margin; i++ )
{
ASSERT_EQ((uint8_t)(i % 2 == 0)?0:255, qrimg.at<uint8_t>(i, timing_pos)) << "i=" << i;
}
// (4) Finder pattern(Right)
for( ; i < qrimg.rows - white_margin; i++ )
{
ASSERT_EQ((uint8_t)0, qrimg.at<uint8_t>(i, timing_pos)) << "i=" << i;
}
// (5) White margin(Right)
for( ; i < qrimg.rows ; i++ )
{
ASSERT_EQ((uint8_t)255, qrimg.at<uint8_t>(i, timing_pos)) << "i=" << i;
}
// Vertical Check
// (1) White margin(Top)
for(i = 0; i < white_margin ; i++ )
{
ASSERT_EQ((uint8_t)255, qrimg.at<uint8_t>(timing_pos, i)) << "i=" << i;
}
// (2) Finder pattern(Top)
for( ; i < white_margin + finder_width ; i++ )
{
ASSERT_EQ((uint8_t)0, qrimg.at<uint8_t>(timing_pos, i)) << "i=" << i;
}
// (3) Timing pattern
for( ; i < qrimg.rows - finder_width - white_margin; i++ )
{
ASSERT_EQ((uint8_t)(i % 2 == 0)?0:255, qrimg.at<uint8_t>(timing_pos, i)) << "i=" << i;
}
// (4) Finder pattern(Bottom)
for( ; i < qrimg.rows - white_margin; i++ )
{
ASSERT_EQ((uint8_t)0, qrimg.at<uint8_t>(timing_pos, i)) << "i=" << i;
}
// (5) White margin(Bottom)
for( ; i < qrimg.rows ; i++ )
{
ASSERT_EQ((uint8_t)255, qrimg.at<uint8_t>(timing_pos, i)) << "i=" << i;
}
}
}
// This test reproduces issue https://github.com/opencv/opencv/issues/24366 only in a loop
TEST(Objdetect_QRCode_Encode_Decode, auto_version_pick)
{
cv::QRCodeEncoder::Params params;
params.correction_level = cv::QRCodeEncoder::CORRECT_LEVEL_L;
params.mode = cv::QRCodeEncoder::EncodeMode::MODE_AUTO;
cv::Ptr<cv::QRCodeEncoder> encoder = cv::QRCodeEncoder::create(params);
for (int len = 1; len < 19; len++) {
std::string input;
input.resize(len);
cv::randu(Mat(1, len, CV_8U, &input[0]), 'a', 'z' + 1);
cv::Mat qrcode;
encoder->encode(input, qrcode);
}
}
// Test two QR codes which error correction procedure requires more number of
// syndroms that described in the ISO/IEC 18004
typedef testing::TestWithParam<std::pair<std::string, std::string>> Objdetect_QRCode_decoding;
TEST_P(Objdetect_QRCode_decoding, error_correction)
{
const std::string filename = get<0>(GetParam());
const std::string expected = get<1>(GetParam());
QRCodeDetector qrcode;
cv::String decoded_msg;
Mat src = cv::imread(findDataFile("qrcode/" + filename), IMREAD_GRAYSCALE);
std::vector<Point2f> corners(4);
corners[0] = Point2f(0, 0);
corners[1] = Point2f(src.cols * 1.0f, 0);
corners[2] = Point2f(src.cols * 1.0f, src.rows * 1.0f);
corners[3] = Point2f(0, src.rows * 1.0f);
Mat resized_src;
resize(src, resized_src, fixed_size, 0, 0, INTER_AREA);
float width_ratio = resized_src.cols * 1.0f / src.cols;
float height_ratio = resized_src.rows * 1.0f / src.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;
}
Mat straight_barcode;
EXPECT_NO_THROW(decoded_msg = qrcode.decode(resized_src, corners, straight_barcode));
ASSERT_FALSE(straight_barcode.empty()) << "Can't decode qrimage " << filename;
EXPECT_EQ(expected, decoded_msg);
}
INSTANTIATE_TEST_CASE_P(/**/, Objdetect_QRCode_decoding, testing::ValuesIn(std::vector<std::pair<std::string, std::string>>{
{"err_correct_1M.png", "New"},
{"err_correct_2L.png", "Version 2 QR Code Test Image"},
}));
}} // namespace