基於OpenCV的攝像頭采集印刷體數字識別


去年剛學完OpenCV練手的東西,用攝像頭采集印刷體數字之后做模板匹配,識別出結果之后用串口發給下位機,能夠實現基本功能,魯棒性不是很好,角度不太正的時候會有比較嚴重的誤識別

原則上來說代碼應該做一下封裝的,但是整個代碼算上注釋也才100+行,就不費這個力氣了,反正本來也只是為了練手隨便寫的一個項目

最后放到Jetson nano上面跑的時候出了一些攝像頭驅動有關的問題,鼓搗半天依然無果,很氣

識別+串口發送的完整代碼貼上

#include "../Inc/Header.h"
#include "../Inc/Serial.hpp"

int main()
{   
    //打開攝像頭
    VideoCapture cam;
    cam.open(2);

    if(!cam.isOpened())
    {
        return -1;
    }

    //打開串口並設置
    Serialport port("/dev/ttyUSB0");
    port.set_opt(115200,8,'N',1);

    //定義數據類型
    Mat frame;
    Mat bin_frame;
    Mat con_frame;
    Rect temp_rect;
    Mat temp_ROI;
    Mat dst_ROI;
    Mat bin_ROI;
    Mat con_num;

    dst_ROI.create(Size(10,10),CV_16UC1);
    
    bool stop = false;
    while(!stop)
    {
        cam >> frame;
 //       GaussianBlur(frame,frame,Size(7,7),0,0);
        //變成灰度圖
        cvtColor(frame,bin_frame,CV_BGR2GRAY);
        //二值化
        threshold(bin_frame,bin_frame,200,255,THRESH_BINARY);

        con_frame = Mat::zeros(bin_frame.size(),bin_frame.type());
        con_num = Mat::zeros(Size(182,234),bin_frame.type());

        vector<vector<Point>> contours;
    	vector<Vec4i> hierarchy;
	    //指定CV_RETR_EXTERNAL尋找數字的外輪廓
	    findContours(bin_frame, contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);
	    //繪制輪廓
	    drawContours(con_frame, contours, -1, 255);

        for (size_t i = 0; i < contours.size(); i++)
        {
            temp_rect = boundingRect(contours[i]);
            //篩選矩形,根據大小和長寬比去篩其實有風險,拿遠了可能就識別不到了
            if(temp_rect.width >= 100 && temp_rect.height >= 100  && temp_rect.width <= 300 && temp_rect.height <= 400 && (float)temp_rect.width/(float)temp_rect.height > 0.75 && (float)temp_rect.width/(float)temp_rect.height < 0.8)
            {
                //  cout << temp_rect.width <<endl;
                //  cout << temp_rect.height << endl;
                //  cout << (float)temp_rect.width/(float)temp_rect.height << endl;
                // rectangle(frame,temp_rect,Scalar(100,0,0),5);
                temp_ROI = frame(temp_rect);
                dst_ROI = temp_ROI.clone();
                //resize(dst_ROI,dst_ROI,Size(182,234));

                //在篩選出的矩形里面畫出數字輪廓
                cvtColor(dst_ROI,bin_ROI,CV_BGR2GRAY);
                // Mat con_num;
                threshold(bin_ROI,bin_ROI,200,255,THRESH_BINARY_INV);
                con_num = Mat::zeros(bin_ROI.size(),bin_ROI.type());
	            findContours(bin_ROI, contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);
                drawContours(con_num, contours, -1, 255);
                resize(con_num,con_num,Size(182,234));
                // imshow("ROI區域",dst_ROI);
                // imshow("二值化ROI區域",bin_ROI);
               // imshow("數字輪廓",con_num);
            }
        }

        char filename[9];
        char window_name[9];
        vector<Mat> number;
        Mat model_num_image;

        //建立模板
        for (int i = 1; i < 10; i++)
        {
            Mat con_num;
            //讀取模板數字圖片
            sprintf(filename,"%d.png",i);
            model_num_image = imread(filename);
            cvtColor(model_num_image,model_num_image,CV_BGR2GRAY);
            //二值化
            threshold(model_num_image,model_num_image,0,255,THRESH_BINARY_INV);
            //畫輪廓
            //指定CV_RETR_EXTERNAL尋找數字的外輪廓
	        findContours(model_num_image, contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);
	        //繪制輪廓
            con_num = Mat::zeros(model_num_image.size(),model_num_image.type());
	        drawContours(con_num, contours, -1, 255);
            number.push_back(con_num);
        }
    
        int con_sum = 0;

         for(int i=0;i < con_num.cols;i++)
        {
            for (int j = 0; j < con_num.rows; j++)
            {
                con_sum += con_num.at<uchar>(j,i);
            }
        }

        int sum[9] = {0};
        int seq = 0;

        //確保有識別到
        if(con_sum != 0)
        {
            //求ROI輪廓圖和模板圖的差,然后求和,然后做差
            for (int i = 0; i < number.size(); i++)
            {
                Mat subImage;
                absdiff(number[i],con_num,subImage);
                //求和
                for(int k=0;k < subImage.cols;k++)
                {
                    for (int j = 0; j < subImage.rows; j++)
                    {
                        sum[i] += subImage.at<uchar>(j,k);
                    }
                }
                // cout << "NO." << i+1 <<endl;
                // cout << sum[i] << endl;
            }
        
            int min = 500000;
            for (int i = 0; i < 9; i++)
            {
                if(sum[i] < min)
                {
                    min = sum[i];
                    seq = i+1;
                }
            }
        }
        
        //串口發送
        char result[2];
        result[0] = '0'+seq;
        result[1] = '\0';
    
        imshow("原始圖像",frame);
        imshow("輪廓圖",con_frame);
        imshow("數字輪廓",con_num);

        if(seq != 0)
        {
            cout << "經過識別,該數字為" << seq <<endl;
            port.send((char*)"the result is:");
            port.send(result);
            port.send((char*)"\r\n");

        }   

        if(char(waitKey(30)) == 'q')
            stop = true;   
    }
    return 0;
}

一般我們使用的usb相機是uvc相機,想要查看uvc相機的參數需要通過以下命令

v4l2-ctl -d /dev/video0 --all

其中/dev/video0根據情況改成自己的設備

這里我自己操作之后獲得的相機參數為:

Driver Info:
        Driver name      : uvcvideo
        Card type        : Integrated Camera: Integrated C
        Bus info         : usb-0000:00:14.0-6
        Driver version   : 5.0.18
        Capabilities     : 0x84a00001
                Video Capture
                Metadata Capture
                Streaming
                Extended Pix Format
                Device Capabilities
        Device Caps      : 0x04200001
                Video Capture
                Streaming
                Extended Pix Format
Priority: 2
Video input : 0 (Camera 1: ok)
Format Video Capture:
        Width/Height      : 1280/720
        Pixel Format      : 'MJPG' (Motion-JPEG)
        Field             : None
        Bytes per Line    : 0
        Size Image        : 1843200
        Colorspace        : sRGB
        Transfer Function : Default (maps to sRGB)
        YCbCr/HSV Encoding: Default (maps to ITU-R 601)
        Quantization      : Default (maps to Full Range)
        Flags             : 
Crop Capability Video Capture:
        Bounds      : Left 0, Top 0, Width 1280, Height 720
        Default     : Left 0, Top 0, Width 1280, Height 720
        Pixel Aspect: 1/1
Selection: crop_default, Left 0, Top 0, Width 1280, Height 720, Flags: 
Selection: crop_bounds, Left 0, Top 0, Width 1280, Height 720, Flags: 
Streaming Parameters Video Capture:
        Capabilities     : timeperframe
        Frames per second: 30.000 (30/1)
        Read buffers     : 0
                     brightness 0x00980900 (int)    : min=0 max=255 step=1 default=128 value=128
                       contrast 0x00980901 (int)    : min=0 max=255 step=1 default=32 value=32
                     saturation 0x00980902 (int)    : min=0 max=100 step=1 default=64 value=64
                            hue 0x00980903 (int)    : min=-180 max=180 step=1 default=0 value=0
 white_balance_temperature_auto 0x0098090c (bool)   : default=1 value=1
                          gamma 0x00980910 (int)    : min=90 max=150 step=1 default=120 value=120
           power_line_frequency 0x00980918 (menu)   : min=0 max=2 default=1 value=1
      white_balance_temperature 0x0098091a (int)    : min=2800 max=6500 step=1 default=4000 value=4000 flags=inactive
                      sharpness 0x0098091b (int)    : min=0 max=7 step=1 default=2 value=2
         backlight_compensation 0x0098091c (int)    : min=0 max=2 step=1 default=1 value=1
                  exposure_auto 0x009a0901 (menu)   : min=0 max=3 default=3 value=3
              exposure_absolute 0x009a0902 (int)    : min=4 max=1250 step=1 default=156 value=156 flags=inactive
         exposure_auto_priority 0x009a0903 (bool)   : default=0 value=1

值得一提的是我把自己寫的數字識別代碼移植到jetson nano上時,不出所料的出了有趣的bug———打不開攝像頭,報錯信息大致如下:

另外之前直接apt-get的cmake版本太低,導致cmake-gui都裝不上,所以這里貼一下源碼安裝

https://blog.csdn.net/chenjiehua123456789/article/details/78683285

即使對於高版本也是同理,但是需要注意的是要把qt也一起安裝好,因為腳本會帶着cmake-gui一起安裝,沒有qt是裝不了的(我是吃了又弄錯源的虧,白白折騰一下午)

關於gstreamer的問題,理論上使用gstreamer是可以打開攝像頭,並且可以鏈接各種組件,設置參數等,是一個很方便的工具

在實際使用時可以先用gst-launch來測試能否打開攝像頭(見這篇博客)

https://blog.csdn.net/fanyue1997/article/details/84332087

我使用

gst-launch-1.0 v4l2src device=/dev/video0 ! autovideosink

可以打開主機的前置攝像頭,但是將設備地址改成usb攝像頭的之后就發現打不開攝像頭了,報錯如下——

設置暫停管道 ...
管道正在使用且不需要 PREROLL ...
錯誤:來自組件 /GstPipeline:pipeline0/GstV4l2Src:v4l2src0:Internal data stream error.
額外的調試信息:
gstbasesrc.c(3072): gst_base_src_loop (): /GstPipeline:pipeline0/GstV4l2Src:v4l2src0:
streaming stopped, reason not-negotiated (-4)
錯誤: 管道不需要 preroll.
設置暫停管道 ...
設置備用管道 ...
設置 NULL 管道 ...
釋放管道資源 ...

gstreamer學習

把主機上跑通的代碼移植到jetson nano上面去之后,發現攝像頭都打不開
研究了一下發現和gstreamer有關,這個東西是英偉達專門用來處理視頻流的,和他們的硬件加速貼合的比較好,所以還是有研究的價值的
https://blog.csdn.net/sakulafly/article/category/1819383/2?
https://blog.csdn.net/u013554213/article/details/79676129
https://blog.csdn.net/csdnhuaong/article/details/79825088

這里是gstreamer中的gst-launch-1.0的指令手冊

https://gstreamer.freedesktop.org/documentation/tools/gst-launch.html?gi-language=c

https://www.cnblogs.com/huty/p/8517019.html

逐漸開始熟悉gstreamer的指令,可以用來設置相機的參數,比如我的自帶攝像頭

gst-launch-1.0 -v v4l2src device=/dev/video0 ! video/x-raw,format=YUY2,width=640,height=360,framerate=30/1  ! xvimagesink

其中,

-v可以用來顯示攝像頭的信息細節

v4l2src是一個用來支持usb攝像頭的參數

device=$path

第一個!之后跟着的是自己設置的攝像頭參數,包括格式,寬度,高度,幀率

第二個!之后跟着的是sink,出了xvimagesink之外,autovideosink同樣是可以打開的

發現了非常好玩的問題,一些參數不對的話可能會導致攝像頭無法打開,比如如果我將video0的framerate修改到20/1會導致相機無法打開

破案了,gstreamer根本就不支持MJPG格式的輸出,只支持YUY2......我們親愛的英偉達就他媽的該死

video/x-raw:
         format: { I420, YV12, YUY2, UYVY, AYUV, VUYA, RGBx, BGRx, xRGB, xBGR, RGBA, BGRA, ARGB, ABGR, RGB, BGR, Y41B, Y42B, YVYU, Y444, v210, v216, Y210, Y410, NV12, NV21, GRAY8, GRAY16_BE, GRAY16_LE, v308, RGB16, BGR16, RGB15, BGR15, UYVP, A420, RGB8P, YUV9, YVU9, IYU1, ARGB64, AYUV64, r210, I420_10BE, I420_10LE, I422_10BE, I422_10LE, Y444_10BE, Y444_10LE, GBR, GBR_10BE, GBR_10LE, NV16, NV24, NV12_64Z32, A420_10BE, A420_10LE, A422_10BE, A422_10LE, A444_10BE, A444_10LE, NV61, P010_10BE, P010_10LE, IYU2, VYUY, GBRA, GBRA_10BE, GBRA_10LE, BGR10A2_LE, RGB10A2_LE, GBR_12BE, GBR_12LE, GBRA_12BE, GBRA_12LE, I420_12BE, I420_12LE, I422_12BE, I422_12LE, Y444_12BE, Y444_12LE, GRAY10_LE32, NV12_10LE32, NV16_10LE32, NV12_10LE40 }
          width: [ 1, 2147483647 ]
         height: [ 1, 2147483647 ]
      framerate: [ 0/1, 2147483647/1 ]

video/x-raw(ANY):
         format: { I420, YV12, YUY2, UYVY, AYUV, VUYA, RGBx, BGRx, xRGB, xBGR, RGBA, BGRA, ARGB, ABGR, RGB, BGR, Y41B, Y42B, YVYU, Y444, v210, v216, Y210, Y410, NV12, NV21, GRAY8, GRAY16_BE, GRAY16_LE, v308, RGB16, BGR16, RGB15, BGR15, UYVP, A420, RGB8P, YUV9, YVU9, IYU1, ARGB64, AYUV64, r210, I420_10BE, I420_10LE, I422_10BE, I422_10LE, Y444_10BE, Y444_10LE, GBR, GBR_10BE, GBR_10LE, NV16, NV24, NV12_64Z32, A420_10BE, A420_10LE, A422_10BE, A422_10LE, A444_10BE, A444_10LE, NV61, P010_10BE, P010_10LE, IYU2, VYUY, GBRA, GBRA_10BE, GBRA_10LE, BGR10A2_LE, RGB10A2_LE, GBR_12BE, GBR_12LE, GBRA_12BE, GBRA_12LE, I420_12BE, I420_12LE, I422_12BE, I422_12LE, Y444_12BE, Y444_12LE, GRAY10_LE32, NV12_10LE32, NV16_10LE32, NV12_10LE40 }
          width: [ 1, 2147483647 ]
         height: [ 1, 2147483647 ]
      framerate: [ 0/1, 2147483647/1 ]

而當我使用vlc的時候我發現輕輕松松就解決問題了

cvlc v4l2:///dev/video2

所以說到頭就是gstreamer本身的兼容性問題

更令人無力吐槽的是在jetson nano上調用opencv下的videocapture的話,會默認使用gstreamer,我覺得要么直接買YUY2的攝像頭,要么放棄使用jetson系列,不知為啥我現在越來越傾向於后者了,可能是因為我還沒開始弄加速,現在的jetson nano給我用的像是一個樹莓派.......

還是我太年輕了,調查之后發現像qv4l2這樣的上位機可以調相機參數來着,包括輸出數據格式......我在自己電腦的攝像頭上做了測試,成功了,不過在某個垃圾攝像機上就不是那么成功了......始終都是mjpg,切不到別的格式上面去,找廠家再了解了解情況吧......

v4l2-ctl -d /dev/video0 --list-formats

如果你能同時看到YUYV和MJPG,那么說明這個相機有調的空間,但是如果兩個都是MJPG,那就真的gg了

不過我可以試試錄段視頻下來,打不開攝像頭就打不開吧

理論上來說通過文件打開的方式可以用playbin的方式就可以很容易的打開

gst-launch-1.0 playbin uri=<file:///path/to/movie.avi>

在各路大佬的忽悠之下,開始弄qt,發現qt-creator確實是一個好用的ide,而且qmake 有自己獨立於cmake的一套編寫規則,也很簡單明了,但是也支持cmake

至少在jetson上面使用體驗遠比vscode要好,綜合考慮之下我可能會逐漸往qt上面遷移開發?但是vscode強大的插件功能還是很有吸引力的,唉......再說吧,ide是其次的,最重要的還是code能力

https://blog.csdn.net/tennysonsky/article/details/48004119

山重水復疑無路,柳暗花明又一村?

從csdn上的一個RMer博客上發現了東南大學的開源,他們是在tx2上做的開發,感覺很有借鑒性,現在正在讀他們的開源代碼,我覺得很有意思


免責聲明!

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



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