去年剛學完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上做的開發,感覺很有借鑒性,現在正在讀他們的開源代碼,我覺得很有意思