1. 程式人生 > >基於Qt、opencv的規則圖形尺寸識別

基於Qt、opencv的規則圖形尺寸識別

第一步:開啟攝像頭

void MainWindow::on_OpenCameraBtn_clicked()
{
    capture = cvCreateCameraCapture(0);//開啟攝像頭,從攝像頭中獲取視訊
    if(capture==NULL)
    {
        qDebug()<<"error!";
    }
    timer->start(50);//開始計時,超時則發出timeout()訊號,1000為1秒,50毫秒取一幀
}

其中cvCreateCameraCapture()函式中引數表示為攝像頭的ID,0表示使用任意一個攝像頭,如果電腦有多個攝像頭,還需指定ID,返回capture指標,這使得後面可以用從視訊流獲取幀的辦法來處理影象。

第二步:獲取影象

    frame = cvQueryFrame(capture);//從攝像頭中抓取並返回每一幀
    // 將抓取到的幀,從IplImage格式轉換為QImage格式,rgbSwapped是為了顯示效果色彩好一些
    //QImage::Format_RGB888不同的攝像頭用不同的格式。
   QImage image = QImage((const uchar*)frame->imageData,
                         frame->width, frame->height,
                         QImage::Format_RGB888).rgbSwapped();

此處獲取的影象為Mat型別,但是為了顯示效果更好,使用rebSwapped()函式,轉換為QImage型別,後邊黑白二值化後要轉換回Mat型別,因為opencv視覺庫是對Mat型別的影象操作,或者此處不用轉換,灰度處理和黑白二值化都用opencv中的函式。

第三步:灰度處理


   for (int i = 0; i < image.width(); i++)
      {
          for (int j= 0; j < image.height(); j++)
          {
              QRgb color = image.pixel(i, j);
              int gray = qGray(color);

              image.setPixel(i, j, qRgba(gray, gray, gray, qAlpha(color)));
          }
      }

              ui->label_3->setPixmap(QPixmap::fromImage(image));

灰度處理我並沒用用opencv庫,而是用的加權平均法,符合人眼對顏色的感知,對綠色感知強,對藍色感知弱,F(i,j) = 0.30R(i,j) + 0.59G(i,j) + 0.11B(i,j)),迴圈處理每個畫素點就可以了。若是使用opencv直接對Mat型影象操作,應該使用函式cvtColor(srcImage,dstImage,CV_BGR2GRAY);

第三步:黑白二值化

   for (int i = 0; i < image.width(); i++)
      {
          for (int j= 0; j < image.height(); j++)
          {
              QRgb color = image.pixel(i, j);
              int gray = qGray(color);

              if(gray<120)
              {
                  gray=0;
              }
              else
              {
                  gray=255;
              }

              image.setPixel(i, j, qRgba(gray, gray, gray, qAlpha(color)));
          }
      }

轉化為二值化影象程式碼和灰度處理差不多,只是在迴圈每個畫素點的時候加了個判斷,低於閾值歸零為純黑色,高於閾值歸255為純白色。

第四步:濾波

         cv::Mat result1;
         cv::Mat image1;


         image1 = cv::Mat(image.height(), image.width(), CV_8UC3, (void*)image.bits(), image.bytesPerLine());
         cv::cvtColor(image1, image1, CV_BGR2RGB);

         //中值濾波
         cv::Mat medianFilter;
         cv::Mat medianFilter1;
         cv::medianBlur (image1,medianFilter1,3);

        //均值濾波
         cv::blur(medianFilter1,medianFilter,cv::Size(5,5));

從黑白影象到輪廓需要濾波,否則干擾很大。濾波前,需要將QImage型別的影象轉化為Mat型別的影象,使用opencv中Mat()函式即刻完成轉換。

我使用了兩個濾波,均值濾波模糊影象,中值濾波去掉椒鹽噪聲,效果還算明顯。

第五步:輪廓提取


    cv::Canny(medianFilter,result1,150,220);
    cv::namedWindow("Canny medianFilter");
    cv::imshow("Canny medianFilter",result1);

使用Canny函式即可完成轉換。

函式說明:

第一個引數表示輸入影象,必須為單通道灰度圖。

第二個引數表示輸出的邊緣影象,為單通道黑白圖。

第三個引數和第四個引數表示閾值,這二個閾值中當中的小閾值用來控制邊緣連線,大的閾值用來控制強邊緣的初始分割即如果一個畫素的梯度大與上限值,則被認為是邊緣畫素,如果小於下限閾值,則被拋棄。如果該點的梯度在兩者之間則當這個點與高於上限值的畫素點連線時我們才保留,否則刪除。

第五個引數表示Sobel 運算元大小,預設為3即表示一個3*3的矩陣。Sobel 運算元與高斯拉普拉斯運算元都是常用的邊緣運算元,詳細的數學原理可以查閱專業書籍。

第六步:獲取周長與面積


    vector<vector<Point> > contours;
    vector<Vec4i> hierarchy;

    findContours( result1, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_SIMPLE, Point(0, 0) );

    /// 計算矩
    vector<Moments> mu(contours.size() );
    for( unsigned int i = 0; i < contours.size(); i++ )
       { mu[i] = moments( contours[i], false ); }

    ///  計算中心矩:
    vector<Point2f> mc( contours.size() );
    for( unsigned int i = 0; i < contours.size(); i++ )
       { mc[i] = Point2f( mu[i].m10/mu[i].m00 , mu[i].m01/mu[i].m00 ); }

    cv::Mat drawing = Mat::zeros(result1.size(), CV_8UC3);                                                         //繪製輪廓
    for (unsigned int i = 0; i< contours.size(); i++)
    {
        Scalar color = Scalar(255, 0, 0);
        drawContours(drawing, contours, i, color, 2, 8, hierarchy, 0, Point());                         //繪製外層和內層輪廓
        circle(drawing, mc[i], 4, color, -1, 8, 0);
    }
    for (unsigned int i = 0; i< contours.size(); i++)
    {
       // printf(" 通過m00計算出輪廓[%d]的面積: (M_00) = %.2f \n計算出的面積=%.2f , 長度: %.2f \n", i, mu[i].m00, contourArea(contours[i]), arcLength(contours[i], true));
              qDebug()<<"通過m00計算出輪廓[%d]的面積: (M_00)"<<mu[i].m00*0.00007194;
              qDebug()<<"計算出的面積="<<contourArea(contours[i])*0.00007194;
              qDebug()<<"長度="<<arcLength(contours[i],false)*0.002;
              S=contourArea(contours[i])*0.00013548;
              L=arcLength(contours[i],false)*0.01149425;
   //2cm圓
              if(L<6.6&&L>=6.8)
              {
                  L=L+0.4;
              }
              if(L<6.4&&L>=6.6)
              {
                  L=L-0.3;
              }
              if(L<6.4&&L>=6.2)
              {
                  L=L-0.1;
              }

              ui->lineEdit_2->setText(QString::number(S));
              ui->lineEdit->setText(QString::number(L));

        Scalar color2 = Scalar(255, 255, 0);
        drawContours(drawing, contours, i, color2, 2, 8, hierarchy, 0, Point());
        circle(drawing, mc[i], 4, color2, -1, 8, 0);
    }

opencv函式,直接上手使用,其中我為了更使結果更準確,加了一個2cm圓趨近。

第七步:關閉攝像頭

void MainWindow::on_CloseCameraBtn_clicked()
{
    timer->stop(); //停止取幀
    cvReleaseCapture(&capture); //釋放資源
}

總結:1.該軟體程式碼大部分使用opencv視覺庫,所有opencv庫一定要安裝正確,保證函式都能呼叫,opencv庫功能強大,學會使用並瞭解其中功能的原理會加深對演算法的認識,這次看opencv庫中的函式原理,基本都是根據數學知識得出來的,所有學好高數還是很有必要的,某寶有賣講解opencv的書。有時間(有錢)要買一本瞭解一下。

2.識別誤差主要來源與攝像頭與平面不平行,導致檢測到的影象與實物的二維影象不符,也有其他的方法進行尺寸識別,這次參加比賽有位老師跟我聊藥品尺寸測量,工業中藥品的尺寸測量精度已經可以達到了百分之99多。測量嘗試使用攝像機,能夠增加測量精度,還可以用熱成像技術測量尺寸。所以1.不能偏安一隅,要多去了解各類產品,開闊眼界。2.做東西要有針對性,把實物的各個效能協調好,要將實物最關鍵的地方做到行業的中上水平。