1 // This file is part of OpenCV project.
2 // It is subject to the license terms in the LICENSE file found in the top-level directory
3 // of this distribution and at http://opencv.org/license.html.
4 //
5 // Tencent is pleased to support the open source community by making WeChat QRCode available.
6 // Copyright (C) 2020 THL A29 Limited, a Tencent company. All rights reserved.
7 #include "precomp.hpp"
8 #include "opencv2/wechat_qrcode.hpp"
9 #include "decodermgr.hpp"
10 #include "detector/align.hpp"
11 #include "detector/ssd_detector.hpp"
12 #include "opencv2/core.hpp"
13 #include "opencv2/core/utils/filesystem.hpp"
14 #include "scale/super_scale.hpp"
15 #include "zxing/result.hpp"
16 namespace cv {
17 namespace wechat_qrcode {
18 class WeChatQRCode::Impl {
19 public:
Impl()20 Impl() {}
~Impl()21 ~Impl() {}
22 /**
23 * @brief detect QR codes from the given image
24 *
25 * @param img supports grayscale or color (BGR) image.
26 * @return vector<Mat> detected QR code bounding boxes.
27 */
28 std::vector<Mat> detect(const Mat& img);
29 /**
30 * @brief decode QR codes from detected points
31 *
32 * @param img supports grayscale or color (BGR) image.
33 * @param candidate_points detected points. we name it "candidate points" which means no
34 * all the qrcode can be decoded.
35 * @param points succussfully decoded qrcode with bounding box points.
36 * @return vector<string>
37 */
38 std::vector<std::string> decode(const Mat& img, std::vector<Mat>& candidate_points,
39 std::vector<Mat>& points);
40 int applyDetector(const Mat& img, std::vector<Mat>& points);
41 Mat cropObj(const Mat& img, const Mat& point, Align& aligner);
42 std::vector<float> getScaleList(const int width, const int height);
43 std::shared_ptr<SSDDetector> detector_;
44 std::shared_ptr<SuperScale> super_resolution_model_;
45 bool use_nn_detector_, use_nn_sr_;
46 };
47
WeChatQRCode(const String & detector_prototxt_path,const String & detector_caffe_model_path,const String & super_resolution_prototxt_path,const String & super_resolution_caffe_model_path)48 WeChatQRCode::WeChatQRCode(const String& detector_prototxt_path,
49 const String& detector_caffe_model_path,
50 const String& super_resolution_prototxt_path,
51 const String& super_resolution_caffe_model_path) {
52 p = makePtr<WeChatQRCode::Impl>();
53 if (!detector_caffe_model_path.empty() && !detector_prototxt_path.empty()) {
54 // initialize detector model (caffe)
55 p->use_nn_detector_ = true;
56 CV_Assert(utils::fs::exists(detector_prototxt_path));
57 CV_Assert(utils::fs::exists(detector_caffe_model_path));
58 p->detector_ = make_shared<SSDDetector>();
59 auto ret = p->detector_->init(detector_prototxt_path, detector_caffe_model_path);
60 CV_Assert(ret == 0);
61 } else {
62 p->use_nn_detector_ = false;
63 p->detector_ = NULL;
64 }
65 // initialize super_resolution_model
66 // it could also support non model weights by cubic resizing
67 // so, we initialize it first.
68 p->super_resolution_model_ = make_shared<SuperScale>();
69 if (!super_resolution_prototxt_path.empty() && !super_resolution_caffe_model_path.empty()) {
70 p->use_nn_sr_ = true;
71 // initialize dnn model (caffe format)
72 CV_Assert(utils::fs::exists(super_resolution_prototxt_path));
73 CV_Assert(utils::fs::exists(super_resolution_caffe_model_path));
74 auto ret = p->super_resolution_model_->init(super_resolution_prototxt_path,
75 super_resolution_caffe_model_path);
76 CV_Assert(ret == 0);
77 } else {
78 p->use_nn_sr_ = false;
79 }
80 }
81
detectAndDecode(InputArray img,OutputArrayOfArrays points)82 vector<string> WeChatQRCode::detectAndDecode(InputArray img, OutputArrayOfArrays points) {
83 CV_Assert(!img.empty());
84 CV_CheckDepthEQ(img.depth(), CV_8U, "");
85
86 if (img.cols() <= 20 || img.rows() <= 20) {
87 return vector<string>(); // image data is not enough for providing reliable results
88 }
89 Mat input_img;
90 int incn = img.channels();
91 CV_Check(incn, incn == 1 || incn == 3 || incn == 4, "");
92 if (incn == 3 || incn == 4) {
93 cvtColor(img, input_img, COLOR_BGR2GRAY);
94 } else {
95 input_img = img.getMat();
96 }
97 auto candidate_points = p->detect(input_img);
98 auto res_points = vector<Mat>();
99 auto ret = p->decode(input_img, candidate_points, res_points);
100 // opencv type convert
101 vector<Mat> tmp_points;
102 if (points.needed()) {
103 for (size_t i = 0; i < res_points.size(); i++) {
104 Mat tmp_point;
105 tmp_points.push_back(tmp_point);
106 res_points[i].convertTo(((OutputArray)tmp_points[i]), CV_32FC2);
107 }
108 points.createSameSize(tmp_points, CV_32FC2);
109 points.assign(tmp_points);
110 }
111 return ret;
112 };
113
decode(const Mat & img,vector<Mat> & candidate_points,vector<Mat> & points)114 vector<string> WeChatQRCode::Impl::decode(const Mat& img, vector<Mat>& candidate_points,
115 vector<Mat>& points) {
116 if (candidate_points.size() == 0) {
117 return vector<string>();
118 }
119 vector<string> decode_results;
120 for (auto& point : candidate_points) {
121 Mat cropped_img;
122 if (use_nn_detector_) {
123 Align aligner;
124 cropped_img = cropObj(img, point, aligner);
125 } else {
126 cropped_img = img;
127 }
128 // scale_list contains different scale ratios
129 auto scale_list = getScaleList(cropped_img.cols, cropped_img.rows);
130 for (auto cur_scale : scale_list) {
131 Mat scaled_img =
132 super_resolution_model_->processImageScale(cropped_img, cur_scale, use_nn_sr_);
133 string result;
134 DecoderMgr decodemgr;
135 auto ret = decodemgr.decodeImage(scaled_img, use_nn_detector_, result);
136
137 if (ret == 0) {
138 decode_results.push_back(result);
139 points.push_back(point);
140 break;
141 }
142 }
143 }
144
145 return decode_results;
146 }
147
detect(const Mat & img)148 vector<Mat> WeChatQRCode::Impl::detect(const Mat& img) {
149 auto points = vector<Mat>();
150
151 if (use_nn_detector_) {
152 // use cnn detector
153 auto ret = applyDetector(img, points);
154 CV_Assert(ret == 0);
155 } else {
156 auto width = img.cols, height = img.rows;
157 // if there is no detector, use the full image as input
158 auto point = Mat(4, 2, CV_32FC1);
159 point.at<float>(0, 0) = 0;
160 point.at<float>(0, 1) = 0;
161 point.at<float>(1, 0) = width - 1;
162 point.at<float>(1, 1) = 0;
163 point.at<float>(2, 0) = width - 1;
164 point.at<float>(2, 1) = height - 1;
165 point.at<float>(3, 0) = 0;
166 point.at<float>(3, 1) = height - 1;
167 points.push_back(point);
168 }
169 return points;
170 }
171
applyDetector(const Mat & img,vector<Mat> & points)172 int WeChatQRCode::Impl::applyDetector(const Mat& img, vector<Mat>& points) {
173 int img_w = img.cols;
174 int img_h = img.rows;
175
176 // hard code input size
177 int minInputSize = 400;
178 float resizeRatio = sqrt(img_w * img_h * 1.0 / (minInputSize * minInputSize));
179 int detect_width = img_w / resizeRatio;
180 int detect_height = img_h / resizeRatio;
181
182 points = detector_->forward(img, detect_width, detect_height);
183
184 return 0;
185 }
186
cropObj(const Mat & img,const Mat & point,Align & aligner)187 Mat WeChatQRCode::Impl::cropObj(const Mat& img, const Mat& point, Align& aligner) {
188 // make some padding to boost the qrcode details recall.
189 float padding_w = 0.1f, padding_h = 0.1f;
190 auto min_padding = 15;
191 auto cropped = aligner.crop(img, point, padding_w, padding_h, min_padding);
192 return cropped;
193 }
194
195 // empirical rules
getScaleList(const int width,const int height)196 vector<float> WeChatQRCode::Impl::getScaleList(const int width, const int height) {
197 if (width < 320 || height < 320) return {1.0, 2.0, 0.5};
198 if (width < 640 && height < 640) return {1.0, 0.5};
199 return {0.5, 1.0};
200 }
201 } // namespace wechat_qrcode
202 } // namespace cv