opencv4.0.0中qr碼定位原始碼分析
概述
近期由於開發需要,需研究qr碼的定位和檢測,恰好opencv前端時間已經發布了4.0.0版本,就著這個機會學習一下opencv中的實現。需說明的是,opencv二維碼檢測模組是基於Quirc(git://github.com/dlbeer/quirc.git)這個開源庫整合開發的。
qr碼檢測到類為QRCodeDetector,其主要成員函式有
- 建構函式
- 解構函式
- void setEpsX(double epsX)、void setEpsY(double epsY):這兩個設定X、Y兩個方向上檢測出定位碼的係數,預設epsX = 0.2,epsY = 0.1;
- bool detect(cv::InputArray img, cv::OutputArray points) const:定位qr碼;
- string decode(cv::InputArray img, cv::InputArray points, cv::OutputArray straight_qrcode = cv::noArray()):解碼;
- std::string detectAndDecode(cv::InputArray img, cv::OutputArray points=cv::noArray(), cv::OutputArray straight_qrcode = cv::noArray()):定位和解碼。
定位
我們把重點放到定位detect()上,這基本上也是qr碼檢測成敗的關鍵。
bool QRCodeDetector::detect(InputArray in, OutputArray points) const { Mat inarr = in.getMat(); CV_Assert(!inarr.empty()); CV_Assert(inarr.depth() == CV_8U); int incn = inarr.channels(); if( incn == 3 || incn == 4 ) { Mat gray; cvtColor(inarr, gray, COLOR_BGR2GRAY); inarr = gray; } QRDetect qrdet; qrdet.init(inarr, p->epsX, p->epsY); if (!qrdet.localization()) { return false; } if (!qrdet.computeTransformationPoints()) { return false; } vector<Point2f> pnts2f = qrdet.getTransformationPoints(); Mat(pnts2f).convertTo(points, points.fixedType() ? points.type() : CV_32FC2); return true; }
可以看到detect函式先將圖片轉為灰度圖,然後執行呼叫QRDetect這個類來進行檢測,我們來分析QRDetect這個類。
class QRDetect
{
public:
void init(const Mat& src, double eps_vertical_ = 0.2, double eps_horizontal_ = 0.1);
bool localization();
bool computeTransformationPoints();
Mat getBinBarcode() { return bin_barcode; }
Mat getStraightBarcode() { return straight_barcode; }
vector<Point2f> getTransformationPoints() { return transformation_points; }
static Point2f intersectionLines(Point2f a1, Point2f a2, Point2f b1, Point2f b2);
protected:
vector<Vec3d> searchHorizontalLines();
vector<Point2f> separateVerticalLines(const vector<Vec3d> &list_lines);
void fixationPoints(vector<Point2f> &local_point);
vector<Point2f> getQuadrilateral(vector<Point2f> angle_list);
bool testBypassRoute(vector<Point2f> hull, int start, int finish);
inline double getCosVectors(Point2f a, Point2f b, Point2f c);
Mat barcode, bin_barcode, straight_barcode;
vector<Point2f> localization_points, transformation_points;
double eps_vertical, eps_horizontal, coeff_expansion;
};
QRCodeDetector detect函式中的QRDetect qrdet這個物件
- 先執行了一次init,在init中先將寬高小於512的圖片resize到512,然後執行了一個adaptiveThreshold操作,其中blocksize設為83?
- 然後進行localization,在其中分別使用searchHorizontalLines和separateVerticalLines來掃描行和列上的點,然後使用kmeans進行聚類,將列上的點聚類成3個,其中迭代次數為3次?
對聚類完後3個點使用fixationPoints來重新修正,在其中先用餘弦定理濾除掉任意角餘弦值小於0.85的情況,後面進行了一 系列的修正(待補充);
最後濾除相鄰點距離小於10pixel的情況。
3.在computeTransformationPoints()函式中主要有兩個操作,
一個是查詢對齊點,這裡直接搬運別人部落格上的內容(http://www.p-chao.com/2018-11-23/opencv4-0-0%E4%BA%8C%E7%BB%B4%E7%A0%81%E8%AF%86%E5%88%AB%E4%BB%A3%E7%A0%81%E7%AE%80%E6%9E%90/):
最開始會找到下圖中的三個紅色的特徵點,這個比較容易理解,因為左上角的點到其它兩個點的距離是差不多的。
然後在左下角和右上角的回字上,找離左上角紅點最遠的點,那麼可以得到兩個藍顏色的特徵點
根據紅藍點連線的交叉點,就可以得到第四各頂點,這樣對齊需要的點我們就都找到了
第二個是進行透視變換,opencv中選取了5組點對,在上述找到的4個頂點外,還通過計算對角線交點算出了中心點,使用這5額點來計算Homograph,然後透視變換回去,就可以得到一個理想的正對我們的二維碼了。
解碼
opencv直接呼叫了quirc解碼,該部分不是關注重點,本文不做贅述。