[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

原文地址:https://www.cnblogs.com/lx17746071609/p/11704696.html

时间: 2024-10-10 19:31:06

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

C++二维码相关库编译

一.瞎想 坐在地铁上闲来无聊,突然想到了二维码,顺手就百度了下相关的资料,目前C++二维码相关的库不多,也就zbar(开源中国上下了半天也没下载下来).zxing,不过这两个库据说都是解析二维码的,不能生成二维码,这个是对于C++而言,如果你是搞C#或者java的,那么恭喜你,你可以直接使用zxing库来完成生成和解析二维码,具体参看 C++生成二维码总结,这篇文章主要是总结了下当前二维码相关的第三方库,个人觉着不错.QZXing是基于qt将zxing进行了封装 zxing实现二维码生成和解析,

二维码报修技术实现

1.痛点发现二维码给我们的生活,工作带来了便利,涉及多个领域,如扫码付款,扫码点餐,扫码乘车,扫码发快递等等,今天我们来说说二维码扫码报修,以前看到部门网管整天手忙脚乱,周报缺无以体现,只看到每天有不同的人通过电话,微信,QQ,邮件等多种方式进行故障报修.曾经也想着解决他们的问题,在网上找了不少系统,发现大多数需要员工注册,绑定,登录很是麻烦,我们都知道很少有人能耐心去完成这些过程,电脑上不了网或故障,根本没有办法进行故障报修.记得是在2017年某一天我突发奇想,能不能让报修采用扫码报修,不需要

二维码相关知识

1.关于前景色和背景色: 二维码的背景色的设置一定要比前景色和定位点的颜色要浅 能够方便地根据输入信息自动生成二维码图片.可以根据需要自由设置二维码的前景色.背景色:可以随意调节二维码分布的密集程度:可以设置二维码外围四周静区的大小. 二维码中间的标签可以是文字,其颜色.字体均可设置:也可以是图片,图片大小可以设置.

二维码相关内容收集

1.zbar 读取 http://zbar.sourceforge.net/download.html ZBar is an open source software suite for reading bar codes      from various sources, such as video streams, image files and raw      intensity sensors.  It supports many popular symbologies      (

基于opencv 识别、定位二维码 (c++版)

前言 因工作需要,需要定位图片中的二维码:我遂查阅了相关资料,也学习了opencv开源库.通过一番努力,终于很好的实现了二维码定位.本文将讲解如何使用opencv定位二维码. 定位二维码不仅仅是为了识别二维码:还可以通过二维码对图像进行水平纠正以及相邻区域定位.定位二维码,不仅需要图像处理相关知识,还需要分析二维码的特性,本文先从二维码的特性讲起. 1 二维码特性 二维码在设计之初就考虑到了识别问题,所以二维码有一些特征是非常明显的. 二维码有三个“回“”字形图案,这一点非常明显.中间的一个点位

(转)ZXing生成二维码和带logo的二维码,模仿微信生成二维码效果

场景:移动支付需要对二维码的生成与部署有所了解,掌握目前主流的二维码生成技术. 1 ZXing 生成二维码 首先说下,QRCode是日本人开发的,ZXing是google开发,barcode4j也是老美开发的,barcode4j对一维条形码处理的很好,而且支持的格式很多,当然也可以对二维码进行处理,效果个人感觉没有前两种好;ZXing对j2me,j2se,还有Android等支持也比较好,如果你是搞Android的或以后准备走Android,建议还是用zxing的比较好,毕竟都一个母亲(gool

LV4500二维码扫描器安装到机场闸机,实现扫码自助登机

通道闸机行业引进二维码识别技术+票务检验指的是通过将手机二维码扫描器(嵌入式.支持二次开发)集成内嵌于智能闸机来实现扫码过闸应用,像景区.写字楼.地铁行业等目前就广泛应用此项技术.如今二维扫描模组也可以用于机场闸机改造,即二维码手机登机服务,只要调出手机二维码在"二维码扫描口"晃一下即可实现自助登机.二维码登机服务功能的拓展全部依靠于条码扫描器,融合其二维码解码.采集和数据传输性能,跟地铁扫码过闸应用类似,通过此二维码电子票务检验方式不仅简化了复杂的登机流程,而且为旅客节省了宝贵的时间

你不知道的二维码扫描模组、二维码读头行业应用?

随着二维码识别技术的发展,近些年以二维码扫描模组为核心扫码硬件无论是生活还是工作,都给我们带来了前所未有的改变.设备扫描读取乘车码乘坐公交地铁.在自助机上刷支付宝微信付款码实现二维码支付等一系列O2O智能设备都离不开它.二维码扫描模组也称为二维码读头(或嵌入式二维码扫描器),可实现对纸质条码/屏幕二维码信息进行扫描和解码,接下来我们就来了解一下它有哪些热门行业应用呢?以便于帮助正在挑选二维码扫描器(嵌入式系列)的集成商客户的您有所帮助. (1)应用于公共交通扫码收费:嵌入集成到公交扫码支付.闸机

微信二维码支付native原生支付开发模式二

模式一相比,流程更为简单,不依赖设置的回调支付URL.商户后台系统先调用微信支付的统一下单接口,微信后台系统返回链接参数code_url,商户后台系统将code_url值生成二维码图片,用户使用微信客户端扫码后发起支付.注意:code_url有效期为2小时,过期后扫码不能再发起支付. 1.业务流程时序图 图6.9原生支付模式二时序图 业务流程说明: 1)商户后台系统根据用户选购的商品生成订单. 2)用户确认支付后调用微信支付[统一下单API]生成预支付交易: 3)微信支付系统收到请求后生成预支付