基於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的官方教程。如果希望進一步進行研究的話,首先需要先收集掌握所有現有資料。
感謝閱讀至此,希望有所幫助。





免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM