zoukankan      html  css  js  c++  java
  • 基于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上做的开发,感觉很有借鉴性,现在正在读他们的开源代码,我觉得很有意思

  • 相关阅读:
    angular.element函数
    mknod创建命名管道(I/O缓存)
    谈谈sipXecs及其它【转】
    Linux下判断cpu物理个数、几核
    shell 中判断文件/文件夹是否存在
    一个人可以用Open IMS Core做什么呢
    linux 下更改磁盘名
    PPTP 服务器配置
    IP多媒体子系统[转维基百科]
    针对用编译的方式安装时的卸载
  • 原文地址:https://www.cnblogs.com/sasasatori/p/12256787.html
Copyright © 2011-2022 走看看