zoukankan      html  css  js  c++  java
  • 基于opencv.js实现二维码定位

        通过分析OpenCV.JS(官方下载地址 https://docs.opencv.org/_VERSION_/opencv.js)的白名单,我们可以了解目前官方PreBuild版本并没有实现QR识别。
    # Classes and methods whitelist
    core = {'': ['absdiff''add''addWeighted''bitwise_and''bitwise_not''bitwise_or''bitwise_xor''cartToPolar',
                 'compare''convertScaleAbs''copyMakeBorder''countNonZero''determinant''dft''divide''eigen',
                 'exp''flip''getOptimalDFTSize','gemm''hconcat''inRange''invert''kmeans''log''magnitude',
                 'max''mean''meanStdDev''merge''min''minMaxLoc''mixChannels''multiply''norm''normalize',
                 'perspectiveTransform''polarToCart''pow''randn''randu''reduce''repeat''rotate''setIdentity''setRNGSeed',
                 'solve''solvePoly''split''sqrt''subtract''trace''transform''transpose''vconcat'],
            'Algorithm': []}

    imgproc = {'': ['Canny''GaussianBlur''Laplacian''HoughLines''HoughLinesP''HoughCircles''Scharr','Sobel',
                    'adaptiveThreshold','approxPolyDP','arcLength','bilateralFilter','blur','boundingRect','boxFilter',
                    'calcBackProject','calcHist','circle','compareHist','connectedComponents','connectedComponentsWithStats',
                    'contourArea''convexHull''convexityDefects''cornerHarris','cornerMinEigenVal','createCLAHE',
                    'createLineSegmentDetector','cvtColor','demosaicing','dilate''distanceTransform','distanceTransformWithLabels',
                    'drawContours','ellipse','ellipse2Poly','equalizeHist','erode''filter2D''findContours','fitEllipse',
                    'fitLine''floodFill','getAffineTransform''getPerspectiveTransform''getRotationMatrix2D''getStructuringElement',
                    'goodFeaturesToTrack','grabCut','initUndistortRectifyMap''integral','integral2''isContourConvex''line',
                    'matchShapes''matchTemplate','medianBlur''minAreaRect''minEnclosingCircle''moments''morphologyEx',
                    'pointPolygonTest''putText','pyrDown','pyrUp','rectangle','remap''resize','sepFilter2D','threshold',
                    'undistort','warpAffine','warpPerspective','warpPolar','watershed',
                    'fillPoly''fillConvexPoly'],
               'CLAHE': ['apply''collectGarbage''getClipLimit''getTilesGridSize''setClipLimit''setTilesGridSize']}

    objdetect = {'': ['groupRectangles'],
                 'HOGDescriptor': ['load''HOGDescriptor''getDefaultPeopleDetector''getDaimlerPeopleDetector''setSVMDetector''detectMultiScale'],
                 'CascadeClassifier': ['load''detectMultiScale2''CascadeClassifier''detectMultiScale3''empty''detectMultiScale']}

    video = {'': ['CamShift''calcOpticalFlowFarneback''calcOpticalFlowPyrLK''createBackgroundSubtractorMOG2',
                 'findTransformECC''meanShift'],
             'BackgroundSubtractorMOG2': ['BackgroundSubtractorMOG2''apply'],
             'BackgroundSubtractor': ['apply''getBackgroundImage']}

    dnn = {'dnn_Net': ['setInput''forward'],
           '': ['readNetFromCaffe''readNetFromTensorflow''readNetFromTorch''readNetFromDarknet',
                'readNetFromONNX''readNet''blobFromImage']}

    features2d = {'Feature2D': ['detect''compute''detectAndCompute''descriptorSize''descriptorType''defaultNorm''empty''getDefaultName'],
                  'BRISK': ['create''getDefaultName'],
                  'ORB': ['create''setMaxFeatures''setScaleFactor''setNLevels''setEdgeThreshold''setFirstLevel''setWTA_K''setScoreType''setPatchSize''getFastThreshold''getDefaultName'],
                  'MSER': ['create''detectRegions''setDelta''getDelta''setMinArea''getMinArea''setMaxArea''getMaxArea''setPass2Only''getPass2Only''getDefaultName'],
                  'FastFeatureDetector': ['create''setThreshold''getThreshold''setNonmaxSuppression''getNonmaxSuppression''setType''getType''getDefaultName'],
                  'AgastFeatureDetector': ['create''setThreshold''getThreshold''setNonmaxSuppression''getNonmaxSuppression''setType''getType''getDefaultName'],
                  'GFTTDetector': ['create''setMaxFeatures''getMaxFeatures''setQualityLevel''getQualityLevel''setMinDistance''getMinDistance''setBlockSize''getBlockSize''setHarrisDetector''getHarrisDetector''setK''getK''getDefaultName'],
                  # 'SimpleBlobDetector': ['create'],
                  'KAZE': ['create''setExtended''getExtended''setUpright''getUpright''setThreshold''getThreshold''setNOctaves''getNOctaves''setNOctaveLayers''getNOctaveLayers''setDiffusivity''getDiffusivity''getDefaultName'],
                  'AKAZE': ['create''setDescriptorType''getDescriptorType''setDescriptorSize''getDescriptorSize''setDescriptorChannels''getDescriptorChannels''setThreshold''getThreshold''setNOctaves''getNOctaves''setNOctaveLayers''getNOctaveLayers''setDiffusivity''getDiffusivity''getDefaultName'],
                  'DescriptorMatcher': ['add''clear''empty''isMaskSupported''train''match''knnMatch''radiusMatch''clone''create'],
                  'BFMatcher': ['isMaskSupported''create'],
                  '': ['drawKeypoints''drawMatches''drawMatchesKnn']}

    photo = {'': ['createAlignMTB''createCalibrateDebevec''createCalibrateRobertson',
                  'createMergeDebevec''createMergeMertens''createMergeRobertson',
                  'createTonemapDrago''createTonemapMantiuk''createTonemapReinhard''inpaint'],
            'CalibrateCRF': ['process'],
            'AlignMTB' : ['calculateShift''shiftMat''computeBitmaps''getMaxBits''setMaxBits',
                          'getExcludeRange''setExcludeRange''getCut''setCut'],
            'CalibrateDebevec' : ['getLambda''setLambda''getSamples''setSamples''getRandom''setRandom'],
            'CalibrateRobertson' : ['getMaxIter''setMaxIter''getThreshold''setThreshold''getRadiance'],
            'MergeExposures' : ['process'],
            'MergeDebevec' : ['process'],
            'MergeMertens' : ['process''getContrastWeight''setContrastWeight''getSaturationWeight',
                              'setSaturationWeight''getExposureWeight''setExposureWeight'],
            'MergeRobertson' : ['process'],
            'Tonemap' : ['process' , 'getGamma''setGamma'],
            'TonemapDrago' : ['getSaturation''setSaturation''getBias''setBias',
                              'getSigmaColor''setSigmaColor''getSigmaSpace','setSigmaSpace'],
            'TonemapMantiuk' : ['getScale''setScale''getSaturation''setSaturation'],
            'TonemapReinhard' : ['getIntensity''setIntensity''getLightAdaptation''setLightAdaptation',
                                 'getColorAdaptation''setColorAdaptation']
            }

    aruco = {'': ['detectMarkers''drawDetectedMarkers''drawAxis''estimatePoseSingleMarkers''estimatePoseBoard''estimatePoseCharucoBoard''interpolateCornersCharuco''drawDetectedCornersCharuco'],
            'aruco_Dictionary': ['get''drawMarker'],
            'aruco_Board': ['create'],
            'aruco_GridBoard': ['create''draw'],
            'aruco_CharucoBoard': ['create''draw'],
            }

    calib3d = {'': ['findHomography''calibrateCameraExtended''drawFrameAxes''estimateAffine2D''getDefaultNewCameraMatrix''initUndistortRectifyMap''Rodrigues']}


    white_list = makeWhiteList([core, imgproc, objdetect, video, dnn, features2d, photo, aruco, calib3d])

        但是我们仍然可以通过轮廓分析的相关方法,去实现“基于opencv.js实现二维码定位”,这就是本篇BLOG的主要内容。
    一、基本原理
            主要内容请参考《OpenCV使用FindContours进行二维码定位》,这里重要的回顾一下。
            使用过FindContours直接寻找联通区域的函数。典型的运用在二维码上面:
        对于它的3个定位点,这种重复包含的特性,在图上只有不容易重复的三处,这是具有排它性的。
        那么轮廓识别的结果是如何展示的了?比如在这幅图中(白色区域为有数据的区域,黑色为无数据),0,1,2是第一层,然后里面是3,3的里面是4和5。(2a表示是2的内部),他们的关系应该是这样的:
    hierarchy.png
    所以我们只需要寻找某一个轮廓“有无爷爷轮廓”,就可以判断出来它是否“重复包含”
    值得参考的C++代码应该是这样的,其中注释部分已经说明的比较清楚。
    #include "opencv2/highgui/highgui.hpp"
    #include "opencv2/imgproc/imgproc.hpp"
    #include <iostream>
    #include <stdio.h>
    #include <stdlib.h>
    #include <math.h>
    using namespace cv;
    using namespace std;
    //找到所提取轮廓的中心点
    //在提取的中心小正方形的边界上每隔周长个像素提取一个点的坐标,求所提取四个点的平均坐标(即为小正方形的大致中心)
    Point Center_cal(vector<vector<Point> > contours,int i)
    {
        int centerx=0,centery=0,n=contours[i].size();
        centerx = (contours[i][n/4].x + contours[i][n*2/4].x + contours[i][3*n/4].x + contours[i][n-1].x)/4;
        centery = (contours[i][n/4].y + contours[i][n*2/4].y + contours[i][3*n/4].y + contours[i][n-1].y)/4;
        Point point1=Point(centerx,centery);
        return point1;
    }
    int main( int argc, char** argv[] )
    {
        Mat src = imread( "e:/sandbox/qrcode.jpg"1 );
        resize(src,src,Size(800,600));//标准大小
        Mat src_gray;
        Mat src_all=src.clone();
        Mat threshold_output;
        vector<vector<Point> > contours,contours2;
        vector<Vec4i> hierarchy;
        //预处理
        cvtColor( src, src_gray, CV_BGR2GRAY );
        blur( src_gray, src_gray, Size(3,3) ); //模糊,去除毛刺
        threshold( src_gray, threshold_output, 100255, THRESH_OTSU );
        //寻找轮廓 
        //第一个参数是输入图像 2值化的
        //第二个参数是内存存储器,FindContours找到的轮廓放到内存里面。
        //第三个参数是层级,**[Next, Previous, First_Child, Parent]** 的vector
        //第四个参数是类型,采用树结构
        //第五个参数是节点拟合模式,这里是全部寻找
        findContours( threshold_output, contours, hierarchy,  CV_RETR_TREE, CHAIN_APPROX_NONE, Point(00) );
        //轮廓筛选
        int c=0,ic=0,area=0;
        int parentIdx=-1;
        forint i = 0; i< contours.size(); i++ )
        {
            //hierarchy[i][2] != -1 表示不是最外面的轮廓
            if (hierarchy[i][2!= -1 && ic==0)
            {
                parentIdx = i; 
                ic++;
            }
            else if (hierarchy[i][2!= -1)
            {
                ic++;
            }
            //最外面的清0
            else if(hierarchy[i][2== -1)
            {
                ic = 0;
                parentIdx = -1;
            }
            //找到定位点信息
            if ( ic >= 2)
            {
                contours2.push_back(contours[parentIdx]);
                ic = 0;
                parentIdx = -1;
            }
        }
        //填充定位点
        for(int i=0; i<contours2.size(); i++)
            drawContours( src_all, contours2, i,  CV_RGB(0,255,0) , -1 );
        //连接定位点
        Point point[3];
        for(int i=0; i<contours2.size(); i++)
        {
            point[i] = Center_cal( contours2, i );
        }
        
        line(src_all,point[0],point[1],Scalar(0,0,255),2);
        line(src_all,point[1],point[2],Scalar(0,0,255),2);
        line(src_all,point[0],point[2],Scalar(0,0,255),2);
         
        imshow( "结果", src_all );
        waitKey(0);
        return(0);
    }

    二、算法重点
            由于hierarchy这块是比较缺乏文档的,在转换为JS的过程中存在一定困难,最终得到了以下的正确结果:
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>Hello OpenCV.js</title>
    <script async src="opencv.js" onload="onOpenCvReady();" type="text/javascript"></script>
    </head>
    <body>
    <h2>Hello OpenCV.js</h2>
    <p id="status">OpenCV.js is loading...</p>
    <div>
      <div class="inputoutput">
        <img id="imageSrc" alt="No Image" />
        <div class="caption">imageSrc <input type="file" id="fileInput" name="file" /></div>
      </div>
      <div class="inputoutput">
        <canvas id="canvasOutput" ></canvas>
        <div class="caption">canvasOutput</div>
      </div>
      <div class="inputoutput2">
        <canvas id="canvasOutput2" ></canvas>
        <div class="caption">canvasOutput2</div>
      </div>
    </div>
    <script type="text/javascript">
    let imgElement = document.getElementById('imageSrc');
    let inputElement = document.getElementById('fileInput');
    inputElement.addEventListener('change', (e) => {
      imgElement.src = URL.createObjectURL(e.target.files[0]);
    }, false);
    imgElement.onload = function() {
    let src = cv.imread(imgElement);
    let src_clone = cv.imread(imgElement);
    let dsize = new cv.Size(800600);
    // You can try more different parameters
    cv.resize(src, src, dsize);cv.resize(src_clone, src_clone, dsize);
    let dst = cv.Mat.zeros(src.rows,src.cols, cv.CV_8UC3);
    cv.cvtColor(src, src, cv.COLOR_RGBA2GRAY, 0);
    let ksize = new cv.Size(33);
    // You can try more different parameters 
    cv.blur(src, src, ksize); 

    cv.threshold(src, src, 100255, cv.THRESH_OTSU);
    let contours = new cv.MatVector();
    let contours2 = new cv.MatVector();
    let hierarchy = new cv.Mat();
    // You can try more different parameters
    cv.findContours(src, contours, hierarchy, cv.RETR_TREE, cv.CHAIN_APPROX_NONE);
    //轮廓筛选
    let c=0,ic=0,area=0;
    let parentIdx = -1;
    debugger
    forlet i = 0; i< contours.size(); i++ )
    {
        //let hier = hierarchy.intPtr(0, i)
        if (hierarchy.intPtr(0,i)[2] != -1 && ic==0)
        {
            parentIdx = i; 
            ic++;
        }
        else if (hierarchy.intPtr(0,i)[2] != -1)
        {
            ic++;
        }
        else if(hierarchy.intPtr(0,i)[2] == -1)
        {
            ic = 0;
            parentIdx = -1;
        }
        //找到定位点信息
        if ( ic >= 2)
        {
          //let cnt = matVec.get(0);
          contours2.push_back(contours.get(parentIdx));
          ic = 0;
          parentIdx = -1;
        }
        
    }
    console.log(contours2.size());

    //填充定位点
    for(let i=0; i<contours.size(); i++)
    {
      let color = new cv.Scalar(25500255); 
      cv.drawContours(src_clone, contours, i,color,1);
    }
    cv.imshow('canvasOutput', src_clone);

    for(let i=0; i<contours2.size(); i++)
    {
      let color = new cv.Scalar(Math.round(Math.random() * 255), Math.round(Math.random() * 255),
                                  Math.round(Math.random() * 255));
      cv.drawContours(dst, contours2, i, color, 1);
    }
    cv.imshow('canvasOutput2', dst);
    src.delete(); src_clone.delete();
    dst.delete(); contours.delete(); hierarchy.delete();

    };
    function onOpenCvReady() {
      document.getElementById('status').innerHTML = 'OpenCV.js is ready.';
    }
    </script>

    </body>
    </html>

    其中绝大多数部分都和C++相似,不同的地方已经标红。它能够成功运行,并且得到正确的定位。(这里OpenCVJS的相关运行情况请参考官方教程)
    三、研究收获
            这次研究的关键节点, 是建立了Debug机制。在JS代码中加入debugger语句,并且开启F12,则在调试的过程中,可以查看各个变量的信息。
    此外,非常重要的参考资料,就是OpenCV的官方教程。如果希望进一步进行研究的话,首先需要先收集掌握所有现有资料。
    感谢阅读至此,希望有所帮助。




  • 相关阅读:
    Tiddlywiki 维基程序使用手册
    Codeigniter 3.0 相关文档 part two
    css hack
    sql入门基础
    nodejs如何储存一个GBK编码的文件
    PHP 代码片段记录
    javascript 数字进制转换
    子网掩码计算题
    Trace文件过量生成问题解决
    PHP Header下载文件在IE文件名中文乱码问题
  • 原文地址:https://www.cnblogs.com/jsxyhelu/p/13040487.html
Copyright © 2011-2022 走看看