基于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] = '';
    
        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*)"
");

        }   

        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 }
           [ 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 }
           [ 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上做的开发,感觉很有借鉴性,现在正在读他们的开源代码,我觉得很有意思

原文地址:https://www.cnblogs.com/sasasatori/p/12256787.html