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