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