基于opencv的視覺巡線實現(xiàn)
共 9213字,需瀏覽 19分鐘
·
2024-07-29 10:05
點擊上方“小白學(xué)視覺”,選擇加"星標(biāo)"或“置頂”
重磅干貨,第一時間送達(dá)
前言
這段時間在和學(xué)弟打軟件杯的比賽,有項任務(wù)就是機器人的視覺巡線,這雖然不是什么稀奇的事情,但是對于一開始不了解視覺的我來說可以說是很懵了,所以現(xiàn)在就想著和大家分享一下,來看看是如何基于opencv來實現(xiàn)巡線的。我這里以ubuntu20.04為例了
正文
1.查看相機設(shè)備
首先要完成視覺巡線那必不可少的就是相機了,使用
ll /dev/video*
來查看相機。
這里可以看到我有兩個相機設(shè)備,一個是我電腦自帶的相機video0,另一個是我的usb相機video1。
2.顯示實時圖像
新建一個工作空間,然后新建一個cpp文件,然后進(jìn)行相機的初始化,以及調(diào)用窗口實時顯示圖像
using namespace std;int camera_width = 640;int camera_height = 480;int main(int argc, char const *argv[]){// 初始化變量和對象cv::VideoCapture cap(1);cap.set(CAP_PROP_FRAME_WIDTH, camera_width);cap.set(CAP_PROP_FRAME_HEIGHT, camera_height);// 循環(huán)處理每一幀圖像while (true) {cv::Mat color_image;cap.read(color_image);if (color_image.empty()) {cerr << "Failed to capture image" << endl;break;}imshow("Color Image", color_image);char key = waitKey(1);if (key == 'q') {break;}}// 釋放資源cap.release();destroyAllWindows();return 0;}
這里初始化cv::VideoCapture cap(1)傳入的參數(shù)就是上面查看到的設(shè)備,如果想要調(diào)用系統(tǒng)自帶相機,那就改為cap(0)。
3.巡線函數(shù)
我這里函數(shù)聲明如下:
tuple<cv::Mat, float, bool, bool, bool> followBlindPath(cv::Mat color_image)
由于我想要多個返回值所以就采用了tuple模版,后面采用tie函數(shù)進(jìn)行解包,其中輸入?yún)?shù)為要識別的圖片,輸出參數(shù)分別為經(jīng)識別后標(biāo)記的圖片,以及水平方向上偏差(后面會具體解釋是什么偏差),后面三個布爾值表示三個狀態(tài),分別為巡線,轉(zhuǎn)彎和停止。
在識別開始之前,由于圖片在opencv保存的格式默認(rèn)為BGR格式圖片,我們要轉(zhuǎn)為HSV格式,因為后面的操作都是基于HSV圖片進(jìn)行的。
cv::cvtColor(color_image, hsvFrame, COLOR_BGR2HSV);
效果如下:
然后指定HSV的色域,scalar函數(shù)三個參數(shù)分別為色調(diào)(Hue)、飽和度(Saturation)和亮度(Value),我這里設(shè)置的值為黃色的色域。
cv::Scalar color_lower = cv::Scalar(10, 40, 120);cv::Scalar color_upper = cv::Scalar(40, 255, 255);cv::inRange(hsvFrame, color_lower, color_upper,color_mask);
inRange函數(shù)用于判斷一個像素或像素矩陣是否在指定的范圍內(nèi),hsvFrame是輸入圖像,返回圖像color_mask是一個二值圖像,即在色域內(nèi)的為白色,色域外為黑色。
處理效果如下:
然后進(jìn)行濾波,過濾掉一些噪聲然后進(jìn)行膨脹和腐蝕的操作使得圖片識別效果更好
//矩形結(jié)構(gòu)cv::Mat dilate_kernel = cv::getStructuringElement(MORPH_RECT, Size(10, 10));cv::Mat erode_kernel = cv::getStructuringElement(MORPH_RECT, Size(5, 5));cv::medianBlur(color_mask, color_mask, 9); // 中值濾波cv::dilate(color_mask, color_mask, dilate_kernel); // 膨脹cv::erode(color_mask, color_mask, erode_kernel); // 腐蝕
上面定義了用于膨脹和腐蝕的矩形結(jié)構(gòu),然后進(jìn)行了濾波、膨脹和腐蝕。
效果如下:
接著就要劃定ROI區(qū)域了,也就是要識別的區(qū)域:
cv::Mat mask_roi = cv::color_mask(Rect(0, 0, camera_width, 20)); // 劃定ROI區(qū)域
我這里ROI區(qū)域為上面高20的區(qū)域,因為我實際的相機在下方,具體的ROI區(qū)域,還是要和相機的位置進(jìn)行相應(yīng)的設(shè)置
區(qū)域如下:
然后進(jìn)行識別操作:
vector<vector<cv::Point>> contours_roi;//cv::Vec4i是二維向量,vector<Vec4i> hierarchy;cv::findContours(mask_roi, contours_roi, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);if (!contours_roi.empty()) {//內(nèi)聯(lián)函數(shù),找到面積最大值vector<Point> c = *max_element(contours_roi.begin(), contours_roi.end(), [](vector<Point> a, vector<Point> b) {return contourArea(a) < contourArea(b);});//cv::boundingRect用于計算輪廓的最小邊界矩形。//它返回值是一個輪廓所包圍的最小矩形,該矩形的邊界與輪廓的邊界平行,并且完全包含輪廓。//cv::Rect 表示矩形cv::Rect bound_rect = boundingRect(c);//用于在圖像上繪制矩形。color_image輸入輸出圖像,bound_rect是標(biāo)記的矩形cv::rectangle(color_image, bound_rect, Scalar(255, 255, 255), 2);int center_x = bound_rect.x + bound_rect.width / 2;int center_y = bound_rect.y + bound_rect.height / 2;cv::circle(color_image, Point(center_x, center_y), 5, Scalar(0, 0, 255), -1);cv::imshow("Path3", color_image);last_center_x = center_x;return make_tuple(color_image, ((center_x / (float)camera_width) * 2 - 1), true, false, false);}
findContours用于在二值圖像中查找輪廓,其中:
contours:輸出的輪廓向量,每個輪廓表示為一個 std::vector對象
hierarchy:可選的輸出向量,包含了輪廓的層次結(jié)構(gòu)信息。默認(rèn)情況下,不輸出層次結(jié)構(gòu)信息,可以傳入一個空的 cv::OutputArray 對象。
輪廓數(shù)據(jù)存儲在 contours 和 hierarchy 兩個輸出參數(shù)中。
執(zhí)行findContours過后,判斷contours_roi.empty()是否為空,如果為空說明所劃的區(qū)域未識別到黃線,轉(zhuǎn)為了下一個狀態(tài),如果識別到了就找到面積最大的區(qū)域劃矩形框。
其中采用rectangle()函數(shù)繪制矩形框,然后找到中心點(center_x,center_y),使用circle繪制點,同時返回值第一個bool值置為true,其他為false。
最終結(jié)果如下:
如果上面contours_roi.empty()為空,則說明所劃的區(qū)域未識別到黃線,那么就需要重新劃定剩下ROI區(qū)域
else {// TurnStatemask_roi = cv::color_mask(Rect(0, 20, camera_width, 80)); // 劃定ROI區(qū)域vector<vector<Point>> contours_roi;vector<Vec4i> hierarchy;cv::findContours(mask_roi, contours_roi, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_SIMPLE);if (!contours_roi.empty()) {vector<Point> c = *max_element(contours_roi.begin(), contours_roi.end(), [](vector<Point> a, vector<Point> b) {return contourArea(a) < contourArea(b);});cv::Rect bound_rect = boundingRect(c);cv::rectangle(color_image, bound_rect, Scalar(255, 255, 255), 2);return make_tuple(color_image, ((last_center_x / (float)camera_width) * 2 - 1), false, true, false);}else{// StopStatereturn make_tuple(color_image, 0.0, false, false, true);}}
如果這部分contours_roi.empty()不為空說明識別到了黃線,該執(zhí)行轉(zhuǎn)彎動作,返回值第二個bool置為true,其他為false,如果這部分contours_roi.empty()仍為空,說明完成了全部線的跟隨,進(jìn)入了停止?fàn)顟B(tài),返回值最后一個置為true,其他為false。
4.偏差計算
那返回的偏差是如何計算的呢,這里是用2倍的中心點x坐標(biāo)減去圖片寬度最后比上圖片寬度實現(xiàn)的即:
last_center_x / (float)camera_width) * 2 - 1
這樣的意義可以使得中心點在左邊時,值為負(fù),在右邊時值為正,更為方便判斷轉(zhuǎn)向,然后將偏差以相應(yīng)的比例賦給相應(yīng)的機器人便可以啦。
這個偏差也是可以進(jìn)行PID的,這樣控制效果會更好,后續(xù)我看看要不要也分享一下,這次就到此結(jié)束啦!
下載1:OpenCV-Contrib擴展模塊中文版教程
在「小白學(xué)視覺」公眾號后臺回復(fù):擴展模塊中文教程,即可下載全網(wǎng)第一份OpenCV擴展模塊教程中文版,涵蓋擴展模塊安裝、SFM算法、立體視覺、目標(biāo)跟蹤、生物視覺、超分辨率處理等二十多章內(nèi)容。
下載2:Python視覺實戰(zhàn)項目52講
在「小白學(xué)視覺」公眾號后臺回復(fù):Python視覺實戰(zhàn)項目,即可下載包括圖像分割、口罩檢測、車道線檢測、車輛計數(shù)、添加眼線、車牌識別、字符識別、情緒檢測、文本內(nèi)容提取、面部識別等31個視覺實戰(zhàn)項目,助力快速學(xué)校計算機視覺。
下載3:OpenCV實戰(zhàn)項目20講
在「小白學(xué)視覺」公眾號后臺回復(fù):OpenCV實戰(zhàn)項目20講,即可下載含有20個基于OpenCV實現(xiàn)20個實戰(zhàn)項目,實現(xiàn)OpenCV學(xué)習(xí)進(jìn)階。
交流群
歡迎加入公眾號讀者群一起和同行交流,目前有SLAM、三維視覺、傳感器、自動駕駛、計算攝影、檢測、分割、識別、醫(yī)學(xué)影像、GAN、算法競賽等微信群(以后會逐漸細(xì)分),請掃描下面微信號加群,備注:”昵稱+學(xué)校/公司+研究方向“,例如:”張三 + 上海交大 + 視覺SLAM“。請按照格式備注,否則不予通過。添加成功后會根據(jù)研究方向邀請進(jìn)入相關(guān)微信群。請勿在群內(nèi)發(fā)送廣告,否則會請出群,謝謝理解~
