一种改进的条形码定位方案,基于openCV实现,附完整源代码

这篇具有很好参考价值的文章主要介绍了一种改进的条形码定位方案,基于openCV实现,附完整源代码。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

        全世界常用和不常用的条码类型大概有一百多种,常用的条码类型一般是指在世界上的多个国家或者地区使用比如EAN-13码、UPC-A码、Code-128码、Code-39码、EAN/UCC-128码、ITF-14码等等,而不常用的条形码可能只是在某些国家地区,或者仅在某一个行业使用,比较少见。

opencv条形码识别,opencv,人工智能,计算机视觉

         根据条形码的长边的黑条长度是否一致,本文将条形码简单的区分为等长和不等长两种类型,例如Code 128每个黑条的长度都是一致的,为等长条形码,而EAN - 8、EAN - 13左右两边的黑条比中间的多出来一截,为不等长条形码。

        最近工作中的项目有识别条形码的需求,在条形码的图片较理想时,目前的开源库(openCV、ZXing、ZBar等)都可以满足需求,但是实际应用后发现当条码存在于图片中的某个位置时所有的开源库都定位不到了,当我手动截取条形码的ROI区域后又可以识别成功,并且openCV目前还没有提供对code 128的支持,所以在开发过程中,条形码图片预处理和定位比条形码的识别更重要。

        条形码的定位不同于QR码的定位,有三个回字的轮廓可以作为显著的特征被提取,而条码则缺少这种特征,在背景环境较复杂时,很难被精准的定位。在参考了网上很多开源的定位方案后,发现很多方案都存在一定的局限性,例如被采取最多的方案是对原图像灰度并高斯模糊处理,在x方向上对灰度图像求取sobel边缘并二值化,利用形态学运算(闭运算、膨胀等)时离散的条形轮廓合并为完整的四边形条形码轮廓,通过findContours函数实现对轮廓的提取,最后通过形态学特征(面积、惯量比、外接矩形等)筛选出条形码实现定位。

opencv条形码识别,opencv,人工智能,计算机视觉

opencv条形码识别,opencv,人工智能,计算机视觉

opencv条形码识别,opencv,人工智能,计算机视觉

         这种方法的局限性在于,由于只对水平方向求取边缘,当条形码倾斜放置时将无法定位,又因为形态学计算,当图像背景复杂时,很容易产生粘连现象从而引入干扰,并且当条形码的大小、拍摄角度发生变化时,相邻的黑条之间距离也会发生变化,因此需要开发者反复调整形态学计算的参数,增加了开发者的负担,此方法仅适用于场景单一的环境。基于原作者的思路,经过三天的摸索,以code 128为例,提供一种通用性更强的改进方法,并开放完整源代码。

        【图像预处理】

cv::Mat src = cv::imread("path");
cv::Mat gray,bilateral,laplacian,thresh;
cv::cvtColor(src,gray,cv::COLOR_BGR2GRAY);
cv::bilateralFilter(gray,bilateral,25,25,25);
cv::Laplacian(bilateral,laplacian,bilateral.depth(),3,3);
cv::threshold(laplacian,thresh,64,255,cv::THRESH_BINARY);

        对输入图像src灰度变换,进行双边滤波在减少图像颗粒噪声的同时保留图像边缘,求取拉普拉斯导数得到边缘图像,最后通过threshold对图像二值化,得到黑白轮廓图像,这边不建议使用Canny作为边缘提取的算子,因为Canny算子提取出的边缘太细,且当条形码黑条间距过小时存在粘连,不利于后续处理

opencv条形码识别,opencv,人工智能,计算机视觉

opencv条形码识别,opencv,人工智能,计算机视觉

opencv条形码识别,opencv,人工智能,计算机视觉

opencv条形码识别,opencv,人工智能,计算机视觉

 【轮廓提取】

for(size_t index = 0;index < contours.size();index++){
    std::vector<cv::Point> blob = contours[index];
    cv::RotatedRect minRect = cv::minAreaRect(blob);
    double area = cv::contourArea(blob);

    if(area < 20){
        continue;
    }

    float min = std::fmin(minRect.size.width,minRect.size.height);
    float max = std::fmax(minRect.size.width,minRect.size.height);
    float inertia_ratio = max/min;

    if(inertia_ratio < 5){
        continue;
    }

    if(area / double(min * max) < 0.1){
        continue;
    }

    ptSet.push_back(minRect.center);
    rectSet.push_back(minRect);
    contourSet.push_back(blob);

    cv::drawContours(contourImage,contours,int(index),cv::Scalar(0,0,255),-1);
}

        使用findContours函数进行轮廓提取,筛选出所有符合条件的轮廓,例如这里我们筛选掉面积不足20像素,惯量比(长轴与短轴尺寸的比值)不足5,并且轮廓实际面积不及最小外接矩形面积的十分之一的轮廓,一轮筛选后,我们得到了可能满足是条形码的所有轮廓,并记录下它们的中心坐标

opencv条形码识别,opencv,人工智能,计算机视觉

  【条形码拟合】

        上一步中,得到了所有可能的条形的中心坐标,因为本文以code 128为例,黑条的长度相等,那么所有黑条的中心点一定位于同一条直线上,那么如何拟合出这一条直线呢,因为其他干扰项的存在,最小二乘法的效果往往不好,所以采用RANSAC(随机抽样一致算法),通过随机抽样拟合,多次迭代后排除掉异常点即得到我们想要的直线,本文不对算法做详细的介绍,只在末尾提供源代码。经过拟合后根据中心点是否位于直线上即可得到目标条形码的轮廓。

opencv条形码识别,opencv,人工智能,计算机视觉

         如图所示,红色轮廓为干扰项,绿色轮廓为条形码,圆圈为每个轮廓的中心点,水平的黄色直线为拟合出的直线,对所有绿色的轮廓作为一个点集合,使用cv::minAreaRect函数得到最小外接矩形,如蓝色矩形所示,垂直的黄色直线为其垂线,以这两条直线为坐标系,根据中学就学到的点与直线的关系,即可得到条形码四个象限的各自角点坐标

opencv条形码识别,opencv,人工智能,计算机视觉

        这个过程还会存在一个问题:有些干扰项的中心点正好落在了直线上,因此也被拟合了进去,针对这个现象采用中位数过滤方法,具体思路是根据条形码每个黑条轮廓的长轴尺寸、以及最小外接矩形的旋转角度基本一致,可以作为一个很好的二维特征作为区分条码和干扰项的依据,对这个特征各自取中位数,想象把他们放在一个二维坐标系上,条码的特征点是非常密集的聚集在一起的,如黄色圈内的绿点所示,而干扰项就是离黄圈圆心(也就是中位数)非常远的红色点,根据每个特征点到中位数的距离,设定一个阈值,即可排除掉所有的干扰

 【透视变换实现条形码校正】

        经过了三轮筛选,我们终于得到了非常具体的条形码位置信息,但是对于不够成熟的开源读码库来说还是需要做最后一步处理,也就是用过透视变换将条形码校正,上一步中提到了我们要精准的获取条形码各个象限的角点坐标,也是为了透视变换而服务的。透视变换的前后四个角点必须一一对应,而一旦角点找错了,进行透视变换的时候就会出错,由于条形码的长度、旋转角度无法确定,故不能通过简单的方式判断哪个点是左上角、哪个点是右下角,必须根据点与直线的关系来确定。为了给变换后的条形码保留一片空白边框区域,变换前的四个角点应该往外点,也就是等比例适当放大其外接矩形,取放大后的角点。经过测试,在不同条码大小、拍摄角度、不同背景环境下,本方法均能够较好的实现定位,本文示例图片定位的结果如下图所示:

opencv条形码识别,opencv,人工智能,计算机视觉

        解决了单一等长条形码的定位问题,针对更加复杂的情况,例如不等长条码、或者多条形码同时提取的问题,暂无具体的源代码,有需要的读者可以尝试下面的思路:

        针对定位不等长的条形码,由于条形码的不等长,所有中心点的位置无法拟合成一条完美的直线,那么数量少的长边就会被过滤掉,针对这个现象,可以调整RANCAC算法的参数,也可以根据点到直线的距离公式,单独遍历每一个中心点到拟合直线的距离,再根据实际情况筛选。

opencv条形码识别,opencv,人工智能,计算机视觉

        对于多条形码的定位,我们可以按部就班使用上面的方法,仅需要实现每定位一个条形码,就把其相关的轮廓、中心点等信息从集合中剔除,循环以上的过程,直到图像中再也找不到其他条形码为止。

【完整代码】

        RANSAC直线拟合、中位数滤波代码

#ifndef RANSAC_H
#define RANSAC_H

#include <opencv2/opencv.hpp>

class Filter
{
public:
    static Filter * GetInstance();
public:
    void filterLineRANSAC(std::vector<cv::Point2d> ptSet,double & a,double & b,double & c,std::vector<bool> & inlierFlag);
    void filterOutLierPointMAD(std::vector<cv::Point2d> ptSet,double threshold,std::vector<bool> & inlierFlag);
private:
    Filter() = default;
    ~Filter() = default;
private:
    //直线样本中两随机点位置不能太近
    bool verifyComposition(const std::vector<cv::Point2d> pts);

    //得到直线拟合样本,即在直线采样点集上随机选2个点
    bool getSample(std::vector<int> set, std::vector<int> &sset);

    //根据点集拟合直线ax+by+c=0,res为残差
    void calcLinePara(std::vector<cv::Point2d> pts, double & a, double & b, double & c, double & res);

    //生成[0,1]之间符合高斯分布的数
    double gaussianRandom();

    //生成[0,1]之间符合均匀分布的数
    double uniformRandom();
};

#endif // RANSAC_H
#include "Filter.h"

Filter * Filter::GetInstance()
{
    static Filter obj;
    return &obj;
}

double Filter::gaussianRandom(void)
{
    static int next_gaussian = 0;
    static double saved_gaussian_value;

    double fac, rsq, v1, v2;

    if (next_gaussian == 0) {
        do {
            v1 = 2 * uniformRandom() - 1;
            v2 = 2 * uniformRandom() - 1;
            rsq = v1*v1 + v2*v2;
        } while (rsq >= 1.0 || rsq == 0.0);
        fac = sqrt(-2 * log(rsq) / rsq);
        saved_gaussian_value = v1*fac;
        next_gaussian = 1;
        return v2*fac;
    }
    else {
        next_gaussian = 0;
        return saved_gaussian_value;
    }
}

double Filter::uniformRandom(void)
{
    return double(rand()) / double(RAND_MAX);
}

void Filter::calcLinePara(std::vector<cv::Point2d> pts, double & a, double & b, double & c, double & res)
{
    res = 0;
    cv::Vec4f line;
    std::vector<cv::Point2f> ptsF;
    for (unsigned int i = 0; i < pts.size(); i++){
        ptsF.push_back(pts[i]);
    }

    fitLine(ptsF, line, cv::DIST_L2, 0, 1e-2, 1e-2);
    a = double(line[1]);
    b = double(-line[0]);
    c = double(line[0] * line[3] - line[1] * line[2]);

    for (unsigned int i = 0; i < pts.size(); i++)
    {
        double resid_ = fabs(pts[i].x * a + pts[i].y * b + c);
        res += resid_;
    }
    res /= pts.size();
}

bool Filter::getSample(std::vector<int> set, std::vector<int> &sset)
{
    int i[2];
    if (set.size() > 2)
    {
        do
        {
            for (int n = 0; n < 2; n++)
                i[n] = int(uniformRandom() * (set.size() - 1));
        } while (!(i[1] != i[0]));
        for (int n = 0; n < 2; n++)
        {
            sset.push_back(i[n]);
        }
    }
    else
    {
        return false;
    }
    return true;
}

bool Filter::verifyComposition(const std::vector<cv::Point2d> pts)
{
    cv::Point2d pt1 = pts[0];
    cv::Point2d pt2 = pts[1];
    if (abs(pt1.x - pt2.x) < 5 && abs(pt1.y - pt2.y) < 5)
        return false;

    return true;
}

void Filter::filterLineRANSAC(std::vector<cv::Point2d> ptSet, double & a, double & b, double & c, std::vector<bool> & inlierFlag)
{
    double residual_error = 2.99; //内点阈值

    bool stop_loop = false;
    int maximum = 0;  //最大内点数

    //最终内点标识及其残差
    inlierFlag = std::vector<bool>(ptSet.size(), false);
    std::vector<double> resids_(ptSet.size(), 3);
    int sample_count = 0;
    int N = 500;

    double res = 0;

    // Filter
    srand(uint(time(nullptr))); //设置随机数种子
    std::vector<int> ptsID;
    for (unsigned int i = 0; i < ptSet.size(); i++){
        ptsID.push_back(int(i));
    }
    while (N > sample_count && !stop_loop)
    {
        std::vector<bool> inlierstemp;
        std::vector<double> residualstemp;
        std::vector<int> ptss;
        int inlier_count = 0;
        if (!getSample(ptsID, ptss))
        {
            stop_loop = true;
            continue;
        }

        std::vector<cv::Point2d> pt_sam;
        pt_sam.push_back(ptSet[uint(ptss[0])]);
        pt_sam.push_back(ptSet[uint(ptss[1])]);

        if (!verifyComposition(pt_sam))
        {
            ++sample_count;
            continue;
        }

        // 计算直线方程
        calcLinePara(pt_sam, a, b, c, res);
        //内点检验
        for (unsigned int i = 0; i < ptSet.size(); i++)
        {
            cv::Point2d pt = ptSet[i];
            double resid_ = fabs(pt.x * a + pt.y * b + c);
            residualstemp.push_back(resid_);
            inlierstemp.push_back(false);
            if (resid_ < residual_error)
            {
                ++inlier_count;
                inlierstemp[i] = true;
            }
        }
        // 找到最佳拟合直线
        if (inlier_count >= maximum)
        {
            maximum = inlier_count;
            resids_ = residualstemp;
            inlierFlag = inlierstemp;
        }
        // 更新Filter迭代次数,以及内点概率
        if (inlier_count == 0)
        {
            N = 500;
        }
        else
        {
            double epsilon = 1.0 - double(inlier_count) / double(ptSet.size()); //野值点比例
            double p = 0.99; //所有样本中存在1个好样本的概率
            double s = 2.0;
            N = int(log(1.0 - p) / log(1.0 - pow((1.0 - epsilon), s)));
        }
        ++sample_count;
    }
}

void Filter::filterOutLierPointMAD(std::vector<cv::Point2d> ptSet,double threshold,std::vector<bool> & inlierFlag)
{
    double * data_x = new double[ptSet.size()];
    double * data_y = new double[ptSet.size()];

    for(size_t index = 0;index < ptSet.size();index++){
        data_x[index] = ptSet[index].x;
        data_y[index] = ptSet[index].y;
    }

    auto on_sort = [](double v1,double v2){
        return v1 > v2;
    };
    std::sort(data_x,&data_x[ptSet.size()],on_sort);
    std::sort(data_y,&data_y[ptSet.size()],on_sort);

    double median_x = (ptSet.size() % 2 == 1) ? data_x[ptSet.size() / 2] : (data_x[ptSet.size() / 2] + data_x[ptSet.size() / 2 - 1]) / 2;
    double median_y = (ptSet.size() % 2 == 1) ? data_y[ptSet.size() / 2] : (data_y[ptSet.size() / 2] + data_y[ptSet.size() / 2 - 1]) / 2;

    cv::Point2d median = cv::Point2d(median_x,median_y);
    auto distance = [](cv::Point2d p1,cv::Point2d p2) -> double{
        return std::pow(std::pow(p1.x - p2.x,2) + std::pow(p1.y - p2.y,2),0.5);
    };

    inlierFlag.clear();
    for(size_t index = 0;index < ptSet.size();index++){
        inlierFlag.push_back(distance(ptSet[index],median) < threshold);
    }

    delete[] data_x;
    delete[] data_y;
}

        条形码定位识别代码,本文采用QZxing库

QString decodeBarCode(cv::Mat & src)
{
    cv::Mat gray,bilateral,laplacian,thresh;
    cv::cvtColor(src,gray,cv::COLOR_BGR2GRAY);
    cv::bilateralFilter(gray,bilateral,25,25,25);
    cv::Laplacian(bilateral,laplacian,bilateral.depth(),3,3);
    cv::threshold(laplacian,thresh,64,255,cv::THRESH_BINARY);

    cv::imshow("gray",gray);
    cv::imshow("bilateral",bilateral);
    cv::imshow("laplacian",laplacian);
    cv::imshow("thresh",thresh);

    cv::Mat contourImage(src.rows,src.cols,CV_8UC3,cv::Scalar(0,0,0));
    std::vector<std::vector<cv::Point>> contours;
    std::vector<cv::Vec4i> hierarchy;
    std::vector<cv::Point2d> ptSet;
    std::vector<cv::RotatedRect> rectSet;
    std::vector<std::vector<cv::Point>> contourSet;
    cv::findContours(thresh,contours,hierarchy,cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);

    for(size_t index = 0;index < contours.size();index++){
        std::vector<cv::Point> blob = contours[index];
        cv::RotatedRect minRect = cv::minAreaRect(blob);
        double area = cv::contourArea(blob);

        if(area < 20){
            continue;
        }

        float min = std::fmin(minRect.size.width,minRect.size.height);
        float max = std::fmax(minRect.size.width,minRect.size.height);
        float inertia_ratio = max/min;

        if(inertia_ratio < 5){
            continue;
        }

        if(area / double(min * max) < 0.1){
            continue;
        }

        ptSet.push_back(minRect.center);
        rectSet.push_back(minRect);
        contourSet.push_back(blob);

        cv::drawContours(contourImage,contours,int(index),cv::Scalar(0,0,255),-1);
    }

    if(ptSet.size() < 10){
        return QString();
    }

    double A, B, C;
    std::vector<bool> inliers_LINE;
    Filter::GetInstance()->filterLineRANSAC(ptSet, A, B, C, inliers_LINE);

    std::vector<std::vector<cv::Point>> contour_ok;
    std::vector<cv::Point2d> keypoint_set;

    for(size_t index = 0;index < inliers_LINE.size();index++){
        if(inliers_LINE[index]){
            double angle = std::min(double(rectSet[index].angle),90 - double(rectSet[index].angle));
            double length = std::max(double(rectSet[index].size.width),double(rectSet[index].size.height));

            keypoint_set.push_back(cv::Point2d(angle,length));
            contour_ok.push_back(contourSet[index]);
            cv::circle(contourImage,ptSet[index],10,cv::Scalar(255,255,255),2);
        } else {
            cv::circle(contourImage,ptSet[index],10,cv::Scalar(128,128,128),2);
        }
    }
    cv::drawContours(contourImage,contour_ok,-1,cv::Scalar(0,255,0),-1);

    std::vector<bool> inliers_POINT;
    Filter::GetInstance()->filterOutLierPointMAD(keypoint_set,10,inliers_POINT);

    std::vector<cv::Point> contours_barCode;
    for(size_t index = 0;index < keypoint_set.size();index++){
        if(inliers_POINT[index]){
            contours_barCode.insert(contours_barCode.end(), contour_ok[index].begin(), contour_ok[index].end());
        }
    }

    if(contours_barCode.empty()){
        return QString();
    }

    cv::RotatedRect barCodeRect = cv::minAreaRect(contours_barCode);

    cv::Size2f size = barCodeRect.size;
    size.width = std::min(size.width * 1.2f,size.width + 30);
    size.height = std::min(size.height * 1.2f,size.height + 30);
    barCodeRect = cv::RotatedRect(barCodeRect.center,size,barCodeRect.angle);

    drawRotatedRect(contourImage,barCodeRect,cv::Scalar(255,255,0),2);
    cv::Point2f arr[4];
    barCodeRect.points(arr);

    cv::Mat dst_warp,dst_warpRotateScale,dst_warpTransformation,dst_warpFlip;
    cv::Point2f srcPoints[4];
    cv::Point2f dstPoints[4];

    cv::Point2f center = barCodeRect.center;
    if(fabs(B) > 0){
        double v_A = B;
        double v_B = -A;
        double v_C = A * double(center.y) - B * double(center.x);

        auto h_function = [A,B,C](double x)->double{
            return -A/B*x - C/B;
        };

        auto v_function = [v_A,v_B,v_C](double y)->double{
            return - y * v_B / v_A - v_C / v_A;
        };

        for(size_t index = 0;index < 4;index++){
            double x = double(arr[index].x);
            double y = double(arr[index].y);

            if(y < h_function(x) && x > v_function(y)){
                srcPoints[0] = arr[index];
                cv::circle(contourImage,arr[index],10,cv::Scalar(255,255,128),-1);
            }

            if(y < h_function(x) && x < v_function(y)){
                srcPoints[1] = arr[index];
                cv::circle(contourImage,arr[index],10,cv::Scalar(255,128,255),-1);
            }

            if(y > h_function(x) && x < v_function(y)){
                srcPoints[2] = arr[index];
                cv::circle(contourImage,arr[index],10,cv::Scalar(255,128,128),-1);
            }

            if(y > h_function(x) && x > v_function(y)){
                srcPoints[3] = arr[index];
                cv::circle(contourImage,arr[index],10,cv::Scalar(0,128,255),-1);
            }
        }

        cv::Point2d ptStart, ptEnd;
        ptStart.x = 0;
        ptStart.y = h_function(ptStart.x);
        ptEnd.x = contourImage.cols;
        ptEnd.y = h_function(ptEnd.x);
        cv::line(contourImage, ptStart, ptEnd, cv::Scalar(0, 255, 255), 2, 8);

        ptStart.x = v_function(0);
        ptStart.y = 0;
        ptEnd.x = v_function(contourImage.rows);
        ptEnd.y = contourImage.rows;
        cv::line(contourImage, ptStart, ptEnd, cv::Scalar(0, 255, 255), 2, 8);
    } else {
        for(size_t index = 0;index < 4;index++){
            double x = double(arr[index].x);
            double y = double(arr[index].y);

            if(x > -C/A && y < double(center.y)){
                srcPoints[0] = arr[index];
                cv::circle(contourImage,arr[index],10,cv::Scalar(255,255,128),-1);
            }

            if(x < -C/A && y < double(center.y)){
                srcPoints[1] = arr[index];
                cv::circle(contourImage,arr[index],10,cv::Scalar(255,128,255),-1);
            }

            if(x < -C/A && y > double(center.y)){
                srcPoints[2] = arr[index];
                cv::circle(contourImage,arr[index],10,cv::Scalar(255,128,128),-1);
            }

            if(x > -C/A && y > double(center.y)){
                srcPoints[3] = arr[index];
                cv::circle(contourImage,arr[index],10,cv::Scalar(0,128,255),-1);
            }
            cv::circle(contourImage,arr[index],10,cv::Scalar(255,255,255),2);
        }

        cv::Point2d ptStart, ptEnd;
        ptStart.x = -C/A;
        ptStart.y = 0;
        ptEnd.x = -C/A;
        ptEnd.y = contourImage.rows;
        cv::line(contourImage, ptStart, ptEnd, cv::Scalar(0, 255, 255), 2, 8);

        ptStart.x = 0;
        ptStart.y = double(center.y);
        ptEnd.x = contourImage.cols;
        ptEnd.y = double(center.y);
        cv::line(contourImage, ptStart, ptEnd, cv::Scalar(0, 255, 255), 2, 8);
    }

    auto distance = [](cv::Point2f p1,cv::Point2f p2)->double{
        return std::pow(std::pow(p1.x - p2.x,2) + std::pow(p1.y - p2.y,2),0.5);
    };

    int width = int(distance(srcPoints[0],srcPoints[1])) + int(distance(srcPoints[2],srcPoints[3]));
    int height = int(distance(srcPoints[1],srcPoints[2])) + int(distance(srcPoints[0],srcPoints[3]));

    dstPoints[0] = cv::Point2f(width,0);
    dstPoints[1] = cv::Point2f(0,0);
    dstPoints[2] = cv::Point2f(0,height);
    dstPoints[3] = cv::Point2f(width,height);

    cv::Mat M1 = cv::getPerspectiveTransform(srcPoints, dstPoints);
    warpPerspective(src, dst_warp, M1, cv::Size(width,height));

    cv::imshow("dst_warp",dst_warp);
    cv::waitKey(0);

    QZXing qzxing;
    qzxing.setDecoder(QZXing::DecoderFormat_CODE_128);
    return qzxing.decodeImage(Mat2Image(dst_warp));
}

参考博客:opencv练习--条形码定位识别_opencv识别条形码位置_ZZU-Hanqi_Duan的博客-CSDN博客

RANSAC估计——以直线拟合为例_随机采样一致性拟合直线_Gareth Wang的博客-CSDN博客

C++去除离群值 MAD算法 - 月下叉猹 - 博客园 (cnblogs.com)文章来源地址https://www.toymoban.com/news/detail-778008.html

到了这里,关于一种改进的条形码定位方案,基于openCV实现,附完整源代码的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Android OpenCV(七十五): 看看刚”转正“的条形码识别

    2021年,我们写过一篇《OpenCV 条码识别 Android 平台实践》,当时的条形码识别模块位于 opencv_contrib 仓库,但是 OpenCV 4.8.0 版本开始, 条形码识别模块已移动到 OpenCV 主仓库,至此我们无需自行编译即可轻松地调用条形码识别能力。 Bar code detector and decoder moved from Contrib to main re

    2024年02月12日
    浏览(38)
  • Python - OpenCV识别条形码、二维码(已封装,拿来即用)

    此代码可识别条形码和二维码,已封装好,拿来即用: 结果:

    2024年02月12日
    浏览(42)
  • H5实现扫码读取二维码条形码功能(二维码+条形码)

    本文主要介绍二维码实现的原理 1、使用插件 npm install @zxing/library 2、主要用到 BrowserMultiFormatReader 这个构造函数,用于打开摄像头 视图 核心代码(以vue3写法举例) 二维码的样式 ``

    2024年02月11日
    浏览(51)
  • Java生成条形码

    生成条形码工具类:   生成结果如下:  

    2024年02月15日
    浏览(47)
  • uniapp生成条形码?

    首先先在插件市场找到条形码 链接:https://ext.dcloud.net.cn/search?q= 下载到项目里面 三、在pages.json中写入以下: 四、在html页面 这样就已经差不多了 条形码可以出来了

    2024年02月04日
    浏览(44)
  • java代码实现生成条形码

    2024年02月20日
    浏览(40)
  • 【Java】批量生成条形码-itextpdf

    批量生成条形码 Controller Service

    2024年02月12日
    浏览(35)
  • 【MAUI】条形码,二维码扫描功能

    本系列文章面向移动开发小白,从零开始进行平台相关功能开发,演示如何参考平台的官方文档使用MAUI技术来开发相应功能。 移动端的扫描条形码、二维码的功能已经随处可见,已经很难找到一个不支持扫描的App了,但是微软的MAUI竟然没有提供,那么我们应该如何实现呢?

    2024年02月04日
    浏览(41)
  • 如何使用 Python 生成和读取条形码

    条形码在我们的日常生活中很常见。只需几个简单的步骤,您就可以使用 Python 轻松生成和扫描条形码。 当您从商店购买商品时,您所购买的物品上的平行黑条纹,具有不同宽度,被称为条形码。条形码是一种将数据以视觉、机器可读的方式表示的方法。条形码被用于存储有

    2024年02月04日
    浏览(30)
  • JS 生成条形码(一维码)jsBarcode

    script 引入 地址:https://cdn.jsdelivr.net/npm/jsbarcode@3.11.5/dist/JsBarcode.all.min.js 也可以进官网查看地址。 npm方式 安装: 页面引入: HTML部分加入svg容器 JS 代码部分 三、结果 参数设置(options) option 默认值 类型 说明 format “auto” (CODE128) String 条形码的类型 width 2 Number 每个条条的宽

    2024年01月20日
    浏览(38)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包