1. 程式人生 > >opencv4.0.0中qr碼定位原始碼分析

opencv4.0.0中qr碼定位原始碼分析

概述

近期由於開發需要,需研究qr碼的定位和檢測,恰好opencv前端時間已經發布了4.0.0版本,就著這個機會學習一下opencv中的實現。需說明的是,opencv二維碼檢測模組是基於Quirc(git://github.com/dlbeer/quirc.git)這個開源庫整合開發的。

qr碼檢測到類為QRCodeDetector,其主要成員函式有

  1. 建構函式
  2. 解構函式
  3. void setEpsX(double epsX)、void setEpsY(double epsY):這兩個設定X、Y兩個方向上檢測出定位碼的係數,預設epsX = 0.2,epsY = 0.1;
  4. bool detect(cv::InputArray img, cv::OutputArray points) const:定位qr碼;
  5. string decode(cv::InputArray img, cv::InputArray points, cv::OutputArray straight_qrcode = cv::noArray()):解碼;
  6. 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這個物件

  1. 先執行了一次init,在init中先將寬高小於512的圖片resize到512,然後執行了一個adaptiveThreshold操作,其中blocksize設為83?
  2. 然後進行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解碼,該部分不是關注重點,本文不做贅述。