[opencv]QRcodeScanner二维码相关技术集成

Posted lx17746071609

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[opencv]QRcodeScanner二维码相关技术集成相关的知识,希望对你有一定的参考价值。

 

#include <utility>

/**
 * @User: leoxae
 * @Date: 2019-08-22 
 * @Time: 10:56
 *  
 */

#include "QRCodeScanner.h"
#include "ImgProcession.h"
#include "../math/PlaneGeometry.h"


using namespace std;
using namespace cv;


/**
 * 获取二维码识别结果
 * @param mat 输入图片
 * @param model 1:zxing 2:zbar 0:先zbar再zxing
 * @param flag true进行预处理 false不进行预处理
 * @return
 */
vector<string> QRCodeScanner::get(Mat mat, int model, bool flag) {

    mat = imgtrans(mat);
    vector<string> qrlist;
    string result;
    vector<Mat> findmat = FindQRcode(mat);
    if (!findmat.empty()) {
        for (auto itx = findmat.begin(); itx != findmat.end(); itx++) {
            Mat qrmat = *itx;
            if (model == 1) {
                result = scanQRCodeByZxing(qrmat);
            } else if (model == 2) {
                result = scanQRCodeByZBar(qrmat);
            } else if (model == 0) {
                result = scanQRCodeByZBar(qrmat);
                if (result.empty()) {
                    result = scanQRCodeByZxing(qrmat);
                }
            }
            if (result.empty()) result = "";
            qrlist.emplace_back(result);
        }
    }
    return qrlist;
}

/**
 * 获取最终的轮廓
 * @param img
 * @param contours
 * @param hierarchy
 * @return
 */
vector<vector<Point>> QRCodeScanner::getfinalcontours(Mat img, vector<vector<Point>> contours, vector<Vec4i> hierarchy) {
    //变量及容器声明
    int hight = img.rows;
    int width = img.cols;

    vector<int> found;
    vector<vector<Point>> found_contours;
    for (int t = 0; t < contours.size(); t++) {
        double area = contourArea(contours[t]);
        if (area < 100) continue;

        RotatedRect rect = minAreaRect(contours[t]);
        // 根据矩形特征进行几何分析
        float w = rect.size.width;
        float h = rect.size.height;
        //根据轮廓层级结构查找二维码三个定位矩形轮廓
        if (w < width / 4 && h < hight / 4) {
            int k = t;
            int c = 0;
            while (hierarchy[k][2] != -1) {
                k = hierarchy[k][2];
                c = c + 1;
            }
            if (c >= 2) {
                found.push_back(t);
                found_contours.push_back(contours[t]);
            }
        }
    }

    return found_contours;
}




/**
 * 获取QRcode的mat集合
 * @param img
 * @return
 */
vector<Mat> QRCodeScanner::FindQRcode(Mat img) {

//    Mat hsv;
//    cvtColor(img, hsv, COLOR_BGR2HSV);
//    Scalar lower_white(0, 0, 0);
//    Scalar upper_white(180, 255, 180);
//    Mat mask_white;
//    inRange(hsv, lower_white, upper_white, mask_white);
//    Mat gray = mask_white;
//    threshold(gray, gray, 100, 255, THRESH_OTSU + THRESH_BINARY);
//    Scalar color(1, 1, 255);
//    Mat threshold_output;
//    threshold(gray, threshold_output, 112, 255, THRESH_BINARY);
//    imshow("threshold_output",threshold_output);
//    waitKey();
    Mat gray;
    Mat resimg;
    cvtColor(img, gray, COLOR_BGR2GRAY);
    adaptiveThreshold(gray, gray, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY, 25, 10);
//    GaussianBlur(gray,gray,Size(3,3),0,0);
    imshow("gray", gray);
    waitKey();

    vector<Mat> qrMatList;
    vector<Point> qrPointList;
    vector<vector<Point>> qrcontourList;
    vector<int> indexx;
    vector<vector<Point>> contours;
    vector<Vec4i> hierachy;
    findContours(gray, contours, hierachy, RETR_TREE, CHAIN_APPROX_SIMPLE);
    if (hierachy.empty()) {
        qrMatList.emplace_back(img);
        return qrMatList;
    }

    for (int i = 0; i < hierachy.size(); i++) {
        auto child = hierachy[i][2];
        auto child_child = hierachy[child][2];
        if (child != -1 && child_child != -1) {
            if (AreaRate(contours[i], contours[child]) >= 10 && AreaRate(contours[i], contours[child]) < 40 &&
                AreaRate(contours[child], contours[child_child]) >= 10 &&
                AreaRate(contours[child], contours[child_child]) < 40 && contourArea(contours[i]) > 100) {
                Point pt1 = ContourCenter(contours[i]);
                Point pt2 = ContourCenter(contours[child]);
                Point pt3 = ContourCenter(contours[child_child]);
                if (CheckRec(pt1, pt2, pt3) && hierachy[i][1] != -1) {
                    drawContours(img, contours, i, Scalar(255, 0, 0), 1, 1);
//                    cout << "indexss==>>" << i << endl;
                    imshow("drawContours", img);
                    waitKey();
                    qrPointList.emplace_back(pt1);
                    qrPointList.emplace_back(pt2);
                    qrPointList.emplace_back(pt3);
                    indexx.emplace_back(i);
                    qrcontourList.emplace_back(contours[i]);
                }
            }
        }
    }

    //去除重复元素
    qrPointList.erase(unique(qrPointList.begin(), qrPointList.end()), qrPointList.end());
    vector<vector<int>> levelList = JudgeTriangle(qrcontourList, indexx);
    if (levelList.empty()) {
        qrMatList.emplace_back(img);
        return qrMatList;
    }
    cout << "qrcontourList==>>" << qrcontourList.size() << endl;

//    for (int i = 0; i < qrPointList.size(); i++) {
//        cout << "qrpoint==>>" << qrPointList[i] << endl;
//    }
    for (int i = 0; i < levelList.size(); i++) {
        cout << "level==>" << levelList[i][0] << "," << levelList[i][1] << "," << levelList[i][2] << endl;
        vector<Point> contoursList;
        contoursList = mergeContours(contours[indexx[levelList[i][0]]], contours[indexx[levelList[i][1]]],contours[indexx[levelList[i][2]]]);
        RotatedRect rect = minAreaRect(contoursList);
        Point2f p[4];
        rect.points(p);

        vector<Point> real_points{p[0], p[1], p[2], p[3]};
        Rect ROI = boundingRect(real_points);
        if (ROI.width > 2 * ROI.height) {
            ROI.width = ROI.height;
        } else if (ROI.width > ROI.height) {
            ROI.height = ROI.width;
        } else if (ROI.width < ROI.height) {
            ROI.width = ROI.height;
        }
        Rect finalRoi (ROI.x - 30,ROI.y - 30,ROI.width + 30,ROI.height + 30);
        Mat roimat = img(finalRoi);
        imshow("roimat", roimat);
        waitKey();
        qrMatList.emplace_back(roimat);
    }
    return qrMatList;
}


bool cmpp_sort(int a, int b) {
    return a < b;//按照学号升序排列
}

/**
 * 判断是否有三个点可以围成等腰直角三角形
 * @param qrPointList
 * @return
 */
vector<vector<int>> QRCodeScanner::JudgeTriangle(vector<vector<Point>> qrcontourList, vector<int> indexx) {

    vector<vector<int>> levelList;
    vector<Point> pointList;
    if (qrcontourList.size() < 3) {
        return levelList;
    }
    for (int i = 0; i < qrcontourList.size(); i++) {
        Point pt = ContourCenter(qrcontourList[i]);
        pointList.emplace_back(pt);
    }
    for (int i = 0; i < indexx.size(); i++) {
        for (int j = i + 1; j < indexx.size(); j++) {
            for (int k = j + 1; k < indexx.size(); k++) {
                int distance1 = (int) PlaneGeometry::NodeDistance(pointList[i], pointList[j]);
                int distance2 = (int) PlaneGeometry::NodeDistance(pointList[i], pointList[k]);
                int distance3 = (int) PlaneGeometry::NodeDistance(pointList[j], pointList[k]);
                vector<int> distanceList{distance1, distance2, distance3};
                sort(distanceList.begin(), distanceList.end(), cmpp_sort);
                int idx = abs(distanceList[1] - distanceList[0]);
//                cout << "abs(distanceList[1] - distanceList[0])==>" << abs(distanceList[1] - distanceList[0]) << endl;
                if (idx < 10) {
                    if (abs(sqrtf(pow(distanceList[0], 2) + pow(distanceList[1], 2))) - distanceList[2] < 15) {
//                        cout << "i,j,k==" << i << "," << j << "," << k << endl;
                        vector<int> level{i, j, k};
                        levelList.emplace_back(level);
                    }
                }
            }

        }
    }

    return levelList;
}

/**
 * 合并拼接三个矩形的轮廓
 * @param pt1
 * @param pt2
 * @param pt3
 * @return
 */
vector<Point> QRCodeScanner::mergeContours(vector<Point> &pt1, vector<Point> &pt2, vector<Point> &pt3) {

    vector<Point> contoursList;
    contoursList.insert(contoursList.end(), pt1.begin(), pt1.end());
    contoursList.insert(contoursList.end(), pt2.begin(), pt2.end());
    contoursList.insert(contoursList.end(), pt3.begin(), pt3.end());
    return contoursList;
}


/**
 * 判断三层轮廓的中心点间距是否够小
 * @param pt1
 * @param pt2
 * @param pt3
 * @return
 */
bool QRCodeScanner::CheckRec(Point pt1, Point pt2, Point pt3) {
    int distance1 = (int) PlaneGeometry::NodeDistance(pt1, pt2);
    int distance2 = (int) PlaneGeometry::NodeDistance(pt1, pt3);
    int distance3 = (int) PlaneGeometry::NodeDistance(pt2, pt3);
    int sum = (distance1 + distance2 + distance3) / 5;
    return sum < 5;
}

/**
 * 计算轮廓中心点
 * @param contour
 * @return
 */
Point QRCodeScanner::ContourCenter(vector<Point> &contour) {
    Moments mu;
    Point mc;
    mu = moments(contour, false);
    mc = Point2d(mu.m10 / mu.m00, mu.m01 / mu.m00);
    return mc;
}

/**
 * 计算轮廓面积的比值
 * @param contour1
 * @param contour2
 * @return
 */
double QRCodeScanner::AreaRate(vector<Point> &contour1, vector<Point> &contour2) {

    double area1 = contourArea(contour1, false);
    double area2 = contourArea(contour2, false);
    if (area1 == 0 || area2 == 0) {
        return 0;
    }
    double ratio = area1 * 1.0 / area2;
    ratio = int(ratio * 10);
    return ratio;
}



/**
 * 中心点检测筛选轮廓
 * 思路:提取每一个轮廓的中心,然后分别求每两个中心间的距离,
 * 最后将距离最短的两个中心的索引和距离第二短的两个中心的索引值赋予索引向量index,
 * 基于这样的假设,如果图片中有类似于二维码三个顶点这样的图形,只要不是集中在一起出现,
 * 那么其中心间的距离很大可能高于二维码三个顶点间的距离,则会被check_center()函数排除掉。
 * @param c
 * @param index
 */
void QRCodeScanner::check_center(vector<vector<Point>> contours, vector<int> &index) {
    float dmin1 = 10000;
    float dmin2 = 10000;
    for (int i = 0; i < contours.size(); i++) {
        RotatedRect rect_i = minAreaRect(contours[i]);
        for (int j = i + 1; j < contours.size(); j++) {
            RotatedRect rect_j = minAreaRect(contours[j]);
            float d = PlaneGeometry::NodeDistance(rect_i.center, rect_j.center);
            if (d < dmin2 && d > 10) {
                if (d < dmin1 && d > 10) {
                    dmin2 = dmin1;
                    dmin1 = d;
                    index[2] = index[0];
                    index[3] = index[1];
                    index[0] = i;
                    index[1] = j;
                } else {
                    dmin2 = d;
                    index[2] = i;
                    index[3] = j;
                }
            }
        }
    }
}


/**
 * 地垫卡片透视变换
 * @param img
 * @return
 */
Mat QRCodeScanner::imgtrans(Mat img) {
    int width = img.cols;
    int height = img.rows;
    Mat dst = img.clone();

    //透视变换
    vector<Point2f> srcPoints = {
            Point2f(0, 0),
            Point2f(width, 0),
            Point2f(0, height),
            Point2f(width, height)
    };

    vector<Point2f> dstPoints = {
            Point2f(105, height),
            Point2f(width - 105, height),
            Point2f(0, 0),
            Point2f(width, 0)
    };

    Mat transform = getPerspectiveTransform(srcPoints, dstPoints);
    Mat transmat;
    warpPerspective(dst, transmat, transform, Size(width, height));

    return transmat;
}


/**
 * zbar识别二维码
 * @param mat
 * @param model
 * @return
 */
string QRCodeScanner::scanQRCodeByZBar(cv::Mat mat) {
    zbar::ImageScanner scanner;
    scanner.set_config(zbar::ZBAR_NONE, zbar::ZBAR_CFG_ENABLE, 1);
    string result;

    if (!mat.empty()) {
        Mat gray;
        cvtColor(mat, gray, COLOR_BGR2GRAY);
        int w = mat.cols, h = mat.rows;
        zbar::Image image(w, h, "Y800", (uchar *) gray.data, w * h);
        const int n = scanner.scan(image);
        if (n > 0) {
            for (zbar::Image::SymbolIterator symbol = image.symbol_begin(); symbol != image.symbol_end(); ++symbol) {
                //result = symbol->get_type_name() + symbol->get_data().c_str();
                result = symbol->get_data();
                break;
            }
        }
        image.set_data(NULL, 0);

        if (result.empty()) {
            Mat gray0;
            int w1 = mat.cols, h1 = mat.rows;
            resize(mat, mat, Size(w * 1.2, h * 1.2));
            w1 = mat.cols, h1 = mat.rows;
            cvtColor(mat, gray0, COLOR_BGR2GRAY);
            zbar::Image image0(w1, h1, "Y800", (uchar *) gray0.data, w1 * h1);
            const int n0 = scanner.scan(image0);
            if (n0 > 0) {
                for (zbar::Image::SymbolIterator symbol = image0.symbol_begin();
                     symbol != image0.symbol_end(); symbol) {
                    //result = symbol->get_type_name() + symbol->get_data().c_str();
                    result = symbol->get_data();
                    break;
                }
            }
            image0.set_data(NULL, 0);
        }
        return result;
    } else {
        result = "";
        return result;
    }
}


/**
 * Unicode转码
 * @param wstr
 * @return
 */
string QRCodeScanner::UnicodeToANSI(const std::wstring &wstr) {
    std::string ret;
    std::mbstate_t state = {};
    const wchar_t *src = wstr.data();
    size_t len = std::wcsrtombs(nullptr, &src, 0, &state);
    if (static_cast<size_t>(-1) != len) {
        std::unique_ptr<char[]> buff(new char[len + 1]);
        len = std::wcsrtombs(buff.get(), &src, len, &state);
        if (static_cast<size_t>(-1) != len) {
            ret.assign(buff.get(), len);
        }
    }
    return ret;
}


/**
 * zxing识别二维码
 * @param mat
 * @return
 */
string QRCodeScanner::scanQRCodeByZxing(Mat matSrc) {

    using namespace ZXing;
    cvtColor(matSrc, matSrc, COLOR_BGR2GRAY);

    int width = matSrc.cols;
    int height = matSrc.rows;


    auto *pixels = new unsigned char[height * width];

    int index = 0;
    for (int i = 0; i < height; ++i) {
        for (int j = 0; j < width; ++j) {
            pixels[index++] = matSrc.at<unsigned char>(i, j);
        }
    }

    std::shared_ptr<GenericLuminanceSource> luminance =
            std::make_shared<GenericLuminanceSource>(
                    0, 0, width, height,
                    pixels, width * sizeof(unsigned char));
    std::shared_ptr<BinaryBitmap> binImage = std::make_shared<HybridBinarizer>(luminance);

    DecodeHints hints;
    std::vector<ZXing::BarcodeFormat> formats = {
            ZXing::BarcodeFormat(11)
    };
    hints.setPossibleFormats(formats);
    auto reader = new MultiFormatReader(hints);

    Result result = reader->read(*binImage);

    std::string content = UnicodeToANSI(result.text());
    //回收
    delete[] pixels;
    delete reader;

    return content;
}

 

/**
 * @User: leoxae
 * @Date: 2019-08-22 
 * @Time: 10:56
 *  
 */

#ifndef KEEKOAIC_QRCODESCANNER_H
#define KEEKOAIC_QRCODESCANNER_H

#include "../../globals.h"
#include "zbar.h"
#include <src/MultiFormatReader.h>
#include <src/DecodeHints.h>
#include <src/BinaryBitmap.h>
#include <src/Result.h>
#include <src/GenericLuminanceSource.h>
#include <src/HybridBinarizer.h>


class QRCodeScanner {


public:


/**
     * 二维码识别
     * @param mat
     * @param model 1zxing,2zbar
     * @return
     */
    vector<string> get(Mat mat, int model, bool flag);


    /**
     * 中心点检测筛选轮廓
     * @param c
     * @param index
     */
    void check_center(vector<vector<Point>> contours, vector<int> &index);

    vector<vector<Point>> getfinalcontours(Mat img, vector<vector<Point>> contours, vector<Vec4i> hierarchy);
private:


    /**
     * zxing识别二维码
     * @param mat
     * @return
     */
    string scanQRCodeByZxing(Mat mat);

    /**
     * zxing辅助函数
     * unicode转码
     * @param wstr
     * @return
     */
    string UnicodeToANSI(const wstring &wstr);

    /**
     * zbar识别二维码
     * @param mat
     * @return
     */
    std::string scanQRCodeByZBar(cv::Mat mat);
    /**
     * 透视变换地垫卡片
     * @param img
     * @return
     */
    Mat imgtrans(Mat img);


    /**
     * 判断是否有三个点可以围成等腰直角三角形
     * @param qrPointList
     * @return
     */
    vector<vector<int>> JudgeTriangle(vector<vector<Point>> qrcontourList, vector<int> indexx);


    /**
     * 获取QRcode的mat集合
     * @param img
     * @return
     */
    vector<Mat> FindQRcode(Mat img);


    /**
     * 合并拼接三个矩形的轮廓
     * @param pt1
     * @param pt2
     * @param pt3
     * @return
     */
    vector<Point> mergeContours(vector<Point> &pt1, vector<Point> &pt2, vector<Point> &pt3);


    /**
     * 判断三层轮廓的中心点间距是否够小
     * @param pt1
     * @param pt2
     * @param pt3
     * @return
     */
    bool CheckRec(Point pt1, Point pt2, Point pt3);


    /**
     * 计算轮廓中心点
     * @param contour
     * @return
     */
    Point ContourCenter(vector<Point> &contour);


    /**
     * 计算轮廓面积的比值
     * @param contour1
     * @param contour2
     * @return
     */
    double AreaRate(vector<Point> &contour1, vector<Point> &contour2);



};


#endif //KEEKOAIC_QRCODESCANNER_H

 

以上是关于[opencv]QRcodeScanner二维码相关技术集成的主要内容,如果未能解决你的问题,请参考以下文章

Progressive Web App (PWA) 二维码扫描器

OpenCV中Mat与二维数组之间的转换

打开/关闭手电筒在QRcodeScanner中反应原生

在 QRcodeScanner React native 中打开/关闭手电筒

opencv 图像变换原理详解 图像平移 图像旋转 图像缩放

在 QRcodeScanner React native 中打开/关闭手电筒 - 替代解决方案