有手就行的智能車(chē)視覺(jué)尋路算法
前言
被同學(xué)拉進(jìn)了智能車(chē)完全模型組做智能車(chē)的上層視覺(jué)軟件算法,在交流群里吹水很多人起哄說(shuō)讓寫(xiě)一篇博客來(lái)教他們?cè)趺磳?xiě)尋路。眾望所托,今天就開(kāi)始了這一系列的更新,保證有手就行,從最基礎(chǔ)的開(kāi)始。
其它的話(huà):距離上次更新數(shù)據(jù)結(jié)構(gòu)博客也很久了,那個(gè)博客并沒(méi)有鴿,在復(fù)習(xí)二叉樹(shù)的時(shí)候感覺(jué)這東西沒(méi)啥好寫(xiě)的,和鏈表一樣,而后面一直在看算法方面的東西,所以沒(méi)有更新,后面的樹(shù)狀數(shù)組等等都會(huì)有更新的。
一、提出一份完整的需求
一份需求一定要層層細(xì)分:
最籠統(tǒng)的需求
- 能夠識(shí)別道路線(xiàn)
- 能夠識(shí)別道路標(biāo)識(shí)
- 能夠給出運(yùn)動(dòng)方向
- 特殊任務(wù)狀態(tài)下的尋路
- 使用json控制一些變量的輸入,方便后期調(diào)車(chē)
- 不重要:圖形化界面,修改json文件并查看小車(chē)實(shí)時(shí)狀態(tài);使用socket等遠(yuǎn)程實(shí)時(shí)調(diào)試小車(chē)
這些就是最籠統(tǒng)的概念,接下來(lái)要細(xì)分到每一個(gè)部分上去:
識(shí)別道路線(xiàn)
識(shí)別道路線(xiàn)會(huì)遇到多種情況,包括直路,左右轉(zhuǎn)彎,十字路口,環(huán)島。
- 精確的識(shí)別出直線(xiàn)
- 判斷出左右轉(zhuǎn)彎狀態(tài)
- 十字路口優(yōu)化,單獨(dú)處理十字路口
- 環(huán)島入環(huán),出環(huán)等狀態(tài)的判斷
識(shí)別道路標(biāo)識(shí)
道路標(biāo)識(shí)識(shí)別方法比較單一,只能是檢測(cè)到目標(biāo)然后進(jìn)行圖像分類(lèi)確定標(biāo)識(shí)種類(lèi)。這就是需求了。
方案直接在這里給出:
- 直接使用目標(biāo)檢測(cè)算法,如yolo。但是效率會(huì)低,選擇哪種算法請(qǐng)自行考慮。
- 傳統(tǒng)圖像算法優(yōu)化目標(biāo)檢測(cè),通過(guò)圖像處理去到標(biāo)識(shí)物的最小正接矩形范圍,提取出圖片,通過(guò)圖像分類(lèi)算法直接得到標(biāo)識(shí)類(lèi)別。
方便起見(jiàn),直接選擇方案一絕對(duì)是最好的,如果想要提升幀率,可以考慮借鑒方案二,或者自己找到其它更優(yōu)解也是可以的。
給出運(yùn)動(dòng)方向
這里需要通過(guò)數(shù)學(xué)方法計(jì)算出小車(chē)后面需要行進(jìn)的方向,具體如下
- 獲得小車(chē)行進(jìn)路線(xiàn)狀態(tài)
- 數(shù)學(xué)方法處理路線(xiàn),如擬合成直線(xiàn)等
- 通過(guò)斜率等判斷轉(zhuǎn)向度數(shù)
- pwm
- 串口傳輸pwm舵機(jī)打角信息
特殊任務(wù)
根據(jù)比賽規(guī)則自行調(diào)試,這里暫時(shí)不寫(xiě)需求
json文件
- 編寫(xiě)json文件讀取協(xié)議
- 通過(guò)讀取json文件初始化程序配置
本教程只針對(duì)基礎(chǔ)尋路部分進(jìn)行教學(xué)
二、板子的算力限制
這塊板子真的是算力有限,只能說(shuō)比樹(shù)莓派強(qiáng)。在這樣算力有限的情況下要是想跑yolov5或者一些圖像分割算法的話(huà)那肯定是很難的,幀數(shù)很低,做人臉識(shí)別還可以,但是做這種實(shí)時(shí)高速識(shí)別的東西確實(shí)不太好。
因此,對(duì)于道路的識(shí)別我們要采用傳統(tǒng)視覺(jué)算法。
三、從零開(kāi)始
從零開(kāi)始我們要先知道需要的一些基礎(chǔ)技術(shù),好進(jìn)行學(xué)習(xí)。
1. 語(yǔ)言基礎(chǔ)
推薦學(xué)習(xí)C/C++,python可針對(duì)深度學(xué)習(xí)單獨(dú)學(xué)習(xí)。
推薦教程:
黑馬程序員匠心之作|C++教程從0到1入門(mén)編程,學(xué)習(xí)編程不再難
從0基礎(chǔ)系統(tǒng)化學(xué)習(xí)C++,不可能學(xué)不會(huì)
MOOC浙大翁愷C語(yǔ)言
菜鳥(niǎo)教程python
2. OpenCV部分
用啥建議直接百度,最好谷歌,有一些問(wèn)題上stackoverflow也有解,注意搜索的姿勢(shì)就行(
如果是想了解一下,這里推薦看賈志剛老師的教程(網(wǎng)上很多,就不放鏈接了),但是很容易困,稍微了解下,剩下的慢慢學(xué),用到的算法我都會(huì)寫(xiě)是怎么回事。
3. 開(kāi)發(fā)環(huán)境搭建
對(duì)于大型項(xiàng)目,尤其是需要放到linux上的,我更傾向于使用Clion來(lái)編寫(xiě),當(dāng)然vs也是不錯(cuò)的,vscode可以用,就是有點(diǎn)費(fèi)勁。
因此我選取Clion作為我開(kāi)發(fā)這個(gè)尋路程序的IDE。
有了IDE,接下來(lái)就該了解一下需要配置什么環(huán)境:
首先是C++編譯環(huán)境,Clion上建議使用CMake,它也會(huì)幫你生成CMakeLists.txt這個(gè)文件,同時(shí)我的CMake用的是clang++編譯器,還可以選擇g++等,可以自己選擇編譯器。
有了C++的編譯環(huán)境,需要配置OpenCV庫(kù),EB板子自帶一個(gè)低版本OpenCV,如果直接在上面開(kāi)發(fā)可以直接看一下怎么用就行了。如果想在自己電腦上調(diào)試的話(huà),可以百度,有很多教程,每個(gè)人遇到的問(wèn)題都不一樣,但是網(wǎng)上基本都有解了。
串口通訊庫(kù),可能會(huì)需要,也可能自己寫(xiě)。
額外的,為了后面讀取json文件,需要選擇一個(gè)json解釋器的庫(kù),這個(gè)暫時(shí)不談。
有可能自己需要一個(gè)日志系統(tǒng),選擇自己喜歡的日志庫(kù)就行。
圖形庫(kù)可以暫時(shí)不做,因此不裝了先。
總結(jié)下來(lái)就是下面這些:
- IDE
- C++編譯器
- CMake幫助編譯大型項(xiàng)目
- OpenCV環(huán)境
- 串口庫(kù)(可能需要)
- json解釋器(暫時(shí)不用管)
- 日志庫(kù)(暫時(shí)不用管)
- 圖形庫(kù)(暫時(shí)不用管)
4. 創(chuàng)建項(xiàng)目
選擇好自己想要的路行,c++標(biāo)準(zhǔn)建議選擇c++14、c++17或者c++20,接下來(lái)點(diǎn)擊創(chuàng)建。
這是創(chuàng)建后自動(dòng)生成的主函數(shù)文件,我們來(lái)看一下文件樹(shù)。
紅色框出的部分是cmake編譯生成的東西,不用管,我們來(lái)看另外兩個(gè)文件。
這個(gè)是main.cpp,生成了一個(gè)hello world測(cè)試程序,我們可以點(diǎn)一下右上角的運(yùn)行按鈕測(cè)試一下能否使用:
這里可以看到程序運(yùn)行正常,證明Clion成功找到CMake程序,CMake成功找到了c++編譯器并進(jìn)行編譯,同時(shí)運(yùn)行。
這兩個(gè)按鈕用的比較多,第一個(gè)是上面用到的那個(gè)功能,后面這個(gè)是調(diào)試按鈕,也就是debug。
我們來(lái)看看剩下的CMakeLists.txt這個(gè)文件:
這就是用來(lái)編譯c++項(xiàng)目的cmake的配置文件,具體使用方法百度找個(gè)文檔就可以很快入門(mén)了。
接下來(lái)就該將OpenCV引入項(xiàng)目,直接在這個(gè)文件中加入下面語(yǔ)句:
#find OpenCV
find_package(OpenCV REQUIRED)
include_directories(${OpenCV_INCLUDE_DIRS})
和:
target_link_libraries(zhiNengChe ${OpenCV_LIBS})
然后將main.cpp改成下面這段代碼,運(yùn)行測(cè)試能否編譯成功,如果不可以,請(qǐng)檢查自己的opencv是否安裝正確。(請(qǐng)將圖片路徑,紅色圈出部分,修改為自己的圖片路徑,隨便找一張圖片測(cè)試就行)
#include <iostream>
#include <opencv2/opencv.hpp>
int main() {
cv::Mat frame = cv::imread("../114.png");
cv::imshow("test", frame);
cv::waitKey(0);
return 0;
}
好了,到這里,所有暫時(shí)需要的環(huán)境已經(jīng)配置完成,可以開(kāi)始項(xiàng)目的內(nèi)容了。
附測(cè)試用圖:
四、圖像預(yù)處理
1. 創(chuàng)建文件
右鍵文件夾,新建圖像預(yù)處理類(lèi)。
會(huì)在側(cè)面顯示出新生成的類(lèi)文件。我們把他們重構(gòu)到兩個(gè)文件夾中,一個(gè)取名為include,另一個(gè)取名為src,方便前期管理文件,后期要更加細(xì)分。
直接將文件拖動(dòng)到文件夾中,點(diǎn)擊重構(gòu),即可自動(dòng)重構(gòu)CMakeLists.txt。
2. 編寫(xiě)類(lèi)文件
文件會(huì)自動(dòng)生成一些信息,學(xué)過(guò)c++應(yīng)該能看懂了。
我們直接處理圖像:
在這個(gè)類(lèi)中我需要一份原圖像數(shù)組,一份處理后的圖像數(shù)組。一個(gè)處理圖像函數(shù),一個(gè)獲取處理后圖像的接口函數(shù):
Pretreatment.hpp
//
// Created by JYSimilar on 2023/2/2.
//
#ifndef ZHI_NENG_CHE_PRETREATMENT_HPP
#define ZHI_NENG_CHE_PRETREATMENT_HPP
#include <iostream>
#include <opencv2/opencv.hpp>
/*
* @brief: 圖像預(yù)處理類(lèi)
* @brief: 實(shí)例化后通過(guò)run函數(shù)傳入原圖,通過(guò)getBinaryImage獲取二值化結(jié)果圖
*
*/
class Pretreatment {
private:
cv::Mat m_frame; // 原圖
cv::Mat m_binary; // 二值化圖像
private: // 臨時(shí)參數(shù)
cv::Mat m_element1 = getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3));
cv::Mat m_element2 = getStructuringElement(cv::MORPH_RECT, cv::Size(7, 7));
std::vector<cv::Point2f> m_srcPoint;
std::vector<cv::Point2f> m_dstPoint;
cv::Mat m_perspectiveMat;
int m_cols, m_rows;
private:
/*
* @brief: 處理圖像函數(shù)
*/
bool processImage();
public:
// 初始化圖像大小
Pretreatment(const int cols, const int rows):
m_cols(cols),
m_rows(rows)
{
m_srcPoint.resize(4);
m_dstPoint.resize(4);
}
/*
* @brief: 處理圖像函數(shù)接口
*/
void run(const cv::Mat frame);
/*
* @brief: 獲取二值化圖像接口
*/
cv::Mat getBinaryImage();
};
#endif //ZHI_NENG_CHE_PRETREATMENT_HPP
Pretreatment.cpp
//
// Created by JYSimilar on 2023/2/2.
//
#include "../include/Pretreatment.hpp"
/**************************** 類(lèi)內(nèi)程序 ****************************/
bool Pretreatment :: processImage() {
// 可以直接做灰度二值化,很方便,也好用好調(diào)
// 這里處理比較復(fù)雜了
if (m_frame.empty()) return false;
cv::Mat gray;
std::vector<cv::Mat> bgr;
cvtColor(m_frame, gray, cv::COLOR_BGR2GRAY);
split(m_frame, bgr);
cv::Mat gray_bin, color_bin;
// 二值化等
cv::threshold(gray, gray_bin, 140, 255, cv::THRESH_BINARY);
cv::subtract(bgr[0], bgr[2], color_bin);
cv::threshold(color_bin, color_bin, 60, 255, cv::THRESH_BINARY);
m_binary = gray_bin - color_bin;
cv::morphologyEx(m_binary, m_binary, cv::MORPH_CLOSE, m_element1);
// 腐蝕膨脹
cv::erode(m_binary, m_binary, m_element2);
cv::erode(m_binary, m_binary, m_element2);
cv::dilate(m_binary, m_binary, m_element2);
// 后面這一部分整體提到初始化中(到透視變換矩陣位置),減少運(yùn)算時(shí)間
m_rows = m_binary.rows, m_cols = m_binary.cols;
m_srcPoint[0] = cv::Point2f(0, m_rows / 4);
m_srcPoint[1] = cv::Point2f(m_cols, m_rows / 4);
m_srcPoint[2] = cv::Point2f(m_cols, m_rows);
m_srcPoint[3] = cv::Point2f(0, m_rows);
m_dstPoint[0] = cv::Point2f(-230, 0);
m_dstPoint[1] = cv::Point2f(m_cols + 230, 0);
m_dstPoint[2] = cv::Point2f(m_cols - 230, m_rows);
m_dstPoint[3] = cv::Point2f(230, m_rows);
// 應(yīng)用透視變換,矯正成規(guī)則矩形
cv::Mat transform = getPerspectiveTransform(m_srcPoint, m_dstPoint);
warpPerspective(m_binary, m_perspectiveMat, transform, m_binary.size());
return true;
}
/**************************** 接口程序 ****************************/
void Pretreatment :: run(const cv::Mat frame){
// 復(fù)制圖片,使用=會(huì)修改原圖
frame.copyTo(m_frame);
if (!processImage()) {
std:: cout << "Image is empty...\n";
}
}
cv::Mat Pretreatment :: getBinaryImage(){
return m_perspectiveMat;
}
具體操作在代碼中有寫(xiě)注釋?zhuān)@里解釋一下灰度二值化。
我的方法不適合新上手的新人,放在這里做參考,僅供學(xué)習(xí)。
灰度二值化
灰度二值化就是將圖片先改成灰度圖,然后通過(guò)設(shè)定閾值,閾值內(nèi)設(shè)為某種亮度,閾值外設(shè)為某種亮度,來(lái)實(shí)現(xiàn)二值化,如亮度為100以?xún)?nèi)的都修改為0,高于100修改為255,這就是一種二值化操作了,在opencv中以API threshold
封裝,具體使用方法百度。
3. 處理結(jié)果
測(cè)試主函數(shù):
#include "../include/Pretreatment.hpp"
int main() {
Pretreatment pretreatment(640, 480);
cv::Mat frame = cv::imread("../114.png");
pretreatment.run(frame);
cv::Mat binary = pretreatment.getBinaryImage();
cv::imshow("test", binary);
cv::waitKey(0);
return 0;
}
文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-757132.html
五、結(jié)語(yǔ)
目前先寫(xiě)這么多了,如果反響不錯(cuò),會(huì)繼續(xù)寫(xiě)下一集的掃線(xiàn)算法。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-757132.html
到了這里,關(guān)于【智能車(chē)】從零寫(xiě)一份自己的完全模型智能車(chē)尋路算法(有手就行) --- 01的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!