C++利用opencv實現(xiàn)單目測距的實現(xiàn)示例
閑來無事,用C++做了一個簡易的單目測距。算法用的cv自帶的,改改參數(shù)就行。實現(xiàn)了讀取照片測距,讀取筆記本攝像頭測距,讀取視頻測距三個功能。
為什么不用雙目測距?因為沒錢買攝像頭……
原理:相似三角形原理,已知焦距的情況下檢測被測物在圖片中所占的像素寬度來判斷被測物與攝像頭的距離,同時也可以得出被測物的大概長寬。注意:攝像頭要和被測物體平行,如果不平行在側(cè)面看來攝像頭和物體之間的連線就與水平方向有一個夾角a,被測物體在圖片中的大小會受到影響,從而影響測量效果。
誤差分析:導(dǎo)致測量效果不好的原因有很多,比如說攝像頭與被測物沒有在同一高度,攝像頭標(biāo)定的焦距不準(zhǔn)確,不同視頻拍攝的攝像頭焦距不同,測量出來也不一樣,所以測量前需要對攝像頭進行標(biāo)定處理得到焦距,這里使用的是傳統(tǒng)的標(biāo)定方法。
步驟:
①標(biāo)定得出焦距
②對圖片進行高斯濾波處理(平滑操作,過濾操作,去噪操作)
③邊緣檢測
④畫出檢測出的輪廓并返回最小的外接矩形(可以設(shè)置畫出全部輪廓或者是最大的輪廓)
⑤計算距離
檢測攝像頭只需要把攝像頭獲取到的畫面逐幀分析,就相當(dāng)于對照片分析,視頻也是一個道理。這里我設(shè)置了一個保存最小距離和最大距離的變量,當(dāng)然這只能作為一個參考值并不是準(zhǔn)確的,因為攝像頭和視頻測距都沒有和被測物平行。
效果演示:
效果不穩(wěn)定,對于單張圖片效果有時候誤差只有幾厘米,有時候誤差就幾十厘米甚至超過一百厘米。這個測量的距離是圖片中檢測到的最小矩形到攝像頭的距離,所以對于背景比較干凈的矩形圖片很好識別,識別的很規(guī)整,但是對于一些背景雜亂的圖片,識別效果就差強人意。這里以兩種不同的圖片做對比。

其對應(yīng)的圖片處理效果如下(實際誤差只有1厘米):

對于陽臺的一棵樹:

我也不知道我和這棵樹距離有好遠,測出來163cm,但是肯定超過了這個距離。

代碼(拿了代碼記得點贊評論一波哦):
沒有分文件編寫,配置好opencv就可以直接用,效果不保證,也就圖一樂。
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
//2903.85 2438.65 2904.1
#define KNOWN_DISTANCE 70.866 //已知距離
#define KNOWN_WIDTH 0.787 //已知寬度0.787 11.69
#define KNOWN_HEIGHT 0.787 //已知高度0.787 8.27
//需要提前獲取,通過標(biāo)定步驟獲得的
#define KNOWN_FOCAL_LENGTH 3000 //已知焦距
double MaxDistance= DBL_MIN;//最大距離
double MinDistance= DBL_MAX;//最小距離
void GetPicture(Mat& SrcImage, int choice);
void GetCamera(int choice);
void GetVideo(int choice);
double GetTheDistanceToCamera(double KnownWidth, double FocalLength, double PerWidth);
double CalculateFocalDistance(Mat& Image);
RotatedRect FindMarker(Mat& SrcImage);
A4紙尺寸:210mm×297mm(16開紙)
int main(int argv, char* argc[])
{
int choice = 0;
cout << "請輸入你想選擇的模式" << endl;
cout << "識別圖片請輸入:1" << endl;
cout << "實時攝像頭識別請輸入:2" << endl;
cout << "讀取視頻請輸入:3" << endl;
cin >> choice;
if (choice == 1 ){
Mat SrcImage = imread("輸入圖片絕對路徑在這里.jpg", IMREAD_COLOR);//獲取圖片
GetPicture(SrcImage,choice);
cout << "圖像中檢測過的輪廓中,最大距離為:" << MaxDistance << "cm" << endl;
cout << "圖像中檢測過的輪廓中,最小距離為:" << MinDistance << "cm" << endl;
}
else if (choice == 2) {
GetCamera(choice);
}
else if (choice == 3) {
GetVideo(choice);
}
}
void GetPicture(Mat& SrcImage,int choice) {
//以下程序用于標(biāo)定相機獲得“焦距”
namedWindow("輸入窗口", 0);
resizeWindow("輸入窗口", 600, 600);//限定窗口大小
imshow("輸入窗口", SrcImage);//輸出窗口
double FocalLength = 0.0;
FocalLength = CalculateFocalDistance(SrcImage); //獲得焦距
//以下程序用于實際計算距離
RotatedRect Marker;
Marker = FindMarker(SrcImage);
double DistanceInches = 0.0;
DistanceInches = GetTheDistanceToCamera(KNOWN_WIDTH, KNOWN_FOCAL_LENGTH, Marker.size.width); //計算相機與目標(biāo)之間的距離
DistanceInches = DistanceInches * 2.54; //轉(zhuǎn)換成cm 1英寸=2.54厘米
//獲取檢測到的最大最小距離的范圍
if (DistanceInches > MaxDistance)
MaxDistance = DistanceInches;
if (DistanceInches < MinDistance)
MinDistance = DistanceInches;
cout << "DistanceInches(cm):" << DistanceInches << endl; //顯示的單位為厘米
putText(SrcImage, format("distance:%f", DistanceInches), Point(100, 100), FONT_HERSHEY_SIMPLEX, 2, Scalar(0, 0, 255), 10, LINE_8);//在圖片上顯示文本
namedWindow("輸出窗口", 0);
resizeWindow("輸出窗口", 600, 600);
imshow("輸出窗口", SrcImage);
if (choice != 1) { //如果檢測視頻或者攝像頭,就延時1ms,如果檢測圖片,就停留在當(dāng)前幀
waitKey(1);
}
else if (choice == 1) {
waitKey(0);
}
}
void GetCamera(int choice) {
Mat frame;
VideoCapture capture(0);//讀取視攝像頭實時畫面數(shù)據(jù),0默認是筆記本的攝像頭;如果是外接攝像頭,這里改為1
while (true)
{
capture >> frame; //讀取當(dāng)前幀
GetPicture(frame,choice);
int key = waitKey(10);
if (key == 32) {
break;
}
}
cout << "圖像中檢測過的輪廓中,最大距離為:" << MaxDistance << "cm" << endl;
cout << "圖像中檢測過的輪廓中,最小距離為:" << MinDistance << "cm" << endl;
capture.release(); //釋放攝像頭資源
destroyAllWindows(); //釋放全部窗口
}
void GetVideo(int choice) {
VideoCapture capture("視頻的絕對路徑.mp4");
Mat frame;
if (capture.isOpened()) //判斷視頻是否成功打開
{
//capture.grab() 從視頻文件或捕獲設(shè)備中抓取下一個幀
while (capture.grab()) {
capture >> frame;
GetPicture(frame,choice);
waitKey(1);
/*int key = waitKey(10);
if (key == 32) {
waitKey(0);
}
if (key == 27) {
break;
}*/
}
}
cout << "圖像中最大距離為:" << MaxDistance << "cm" << endl;
cout << "圖像中最小距離為:" << MinDistance << "cm" << endl;
waitKey();
}
double GetTheDistanceToCamera(double KnownWidth, double FocalLength, double PerWidth)
{
return (KnownWidth * FocalLength) / PerWidth; //計算目標(biāo)到相機的距離 KnownWidth-為已知的目標(biāo)的寬度 FocalLength-焦距 PerWidth-圖像中寬度的像素數(shù)
}
RotatedRect FindMarker(Mat& SrcImage)//畫出圖形的邊界并返回最小外接矩形
{
Mat GrayImage;
cvtColor(SrcImage, GrayImage, COLOR_BGR2GRAY);//將SrcImage復(fù)制給GrayImage
Mat GaussImage;
//將GrayImage通過高斯濾波處理后存放到GaussImage中
GaussianBlur(GrayImage, GaussImage, Size(3, 7), 3); //高斯濾波,對圖像進行濾波操作(平滑操作、過濾操作,去噪操作)
Mat EdgeImage;
Canny(GaussImage, EdgeImage, 100, 200);//邊緣檢測
/*這段代碼可省略,只是用來看效果。
namedWindow("復(fù)制圖", 0);
resizeWindow("復(fù)制圖", 600, 600);
imshow("復(fù)制圖", GrayImage);
namedWindow("高斯濾波處理", 0);
resizeWindow("高斯濾波處理", 600, 600);
imshow("高斯濾波處理", GaussImage);
namedWindow("邊緣檢測處理", 0);
resizeWindow("邊緣檢測處理", 600, 600);
imshow("邊緣檢測處理", EdgeImage);
*/
vector<vector<Point>> Contours;
vector<Vec4i> Hierarchy;
findContours(EdgeImage.clone(), Contours, Hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE /*,Point(10,20)*/);//檢測物體輪廓
double Area = -1;
int Index = -1;
for (int i = 0; i < Contours.size(); i++)//得到最大的輪廓然后畫出來,其實是一個點集
{
if (contourArea(Contours[i]) > Area)
{
Area = contourArea(Contours[i]);
Index = i;
}
}
//第三個參數(shù)為-1的時候就畫出全部輪廓
drawContours(SrcImage, Contours, -1, Scalar(0, 0, 255), 10, LINE_8);//畫出物體的輪廓
RotatedRect Rorect;
Rorect = minAreaRect(Contours[Index]); //得到被測物的最小外接矩形
return Rorect;
}
double CalculateFocalDistance(Mat& Image)//計算拍照相機的焦距
{
RotatedRect Marker;
Marker = FindMarker(Image);
double FocalLength = 0.0;
double FocalWide = 0.0;
FocalLength = (Marker.size.height * KNOWN_DISTANCE) / KNOWN_WIDTH; //計算焦距單位為圖像像素 依據(jù)公式: F=(P*D)/W F-焦距 D-距離 P-像素寬度 W-目標(biāo)的真實寬度(單位英寸)
FocalWide = (Marker.size.width * KNOWN_DISTANCE) / KNOWN_HEIGHT;
cout << "標(biāo)定焦距:" << FocalLength << endl;
cout << "標(biāo)定焦距:" << FocalLength << endl;
return FocalLength;
//1857.71
}
有關(guān)圖像的算法都是已經(jīng)封裝好了改參數(shù)直接用就行了,沒有涉及到什么需要自己寫的高難度算法。
下面還有一些比較各個邊緣檢測效果的算法代碼:
玩玩就行,原理我也不清楚,只會用現(xiàn)成的。
#include"opencv2/opencv.hpp"
using namespace cv;
using namespace std;
void main()
{
//顯示原圖像
Mat image = imread("C:/Users/蔣林宏/Desktop/圖片/240A.jpg");
namedWindow("原圖",0);
resizeWindow("原圖", 600, 600);
imshow("原圖", image);
//canny邊緣檢測的簡單用法
Mat result;
Canny(image, result, 150, 70);
namedWindow("canny邊緣檢測后的圖像",0);
resizeWindow("canny邊緣檢測后的圖像", 600, 600);
imshow("canny邊緣檢測后的圖像", result);
//高階的canny用法,轉(zhuǎn)成灰度圖,降噪,用canny,最后將得到的邊緣作為掩碼,拷貝原圖到效果圖上,得到彩色邊緣圖
Mat grayimage, edge;
cvtColor(image, grayimage, COLOR_BGR2GRAY);
boxFilter(grayimage, edge, -1, Size(3, 3));
Canny(edge, edge, 150, 70);
Mat dst;
dst = Scalar::all(123);
image.copyTo(dst, edge);
namedWindow("canny高階邊緣檢測后的圖像",0);
resizeWindow("canny高階邊緣檢測后的圖像", 600, 600);
imshow("canny高階邊緣檢測后的圖像", dst);
//sobel算子邊緣檢測
Mat x_result, y_result;
Sobel(image, x_result, 0, 1, 0);
Sobel(image, y_result, 0, 0, 1);
addWeighted(x_result, 0.5, y_result, 0.5, 0, result);
/*imshow("sobel邊緣檢測后x軸的圖像", x_result);
imshow("sobel邊緣檢測后y軸的圖像", y_result);*/
namedWindow("sobel邊緣檢測后的圖像",0);
resizeWindow("sobel邊緣檢測后的圖像", 600, 600);
imshow("sobel邊緣檢測后的圖像", result);
//laplacian邊緣檢測
Laplacian(image, result, 0);
namedWindow("laplacian邊緣檢測后的圖像",0);
resizeWindow("laplacian邊緣檢測后的圖像", 600, 600);
imshow("laplacian邊緣檢測后的圖像", result);
//scharr濾波器
boxFilter(image, image, -1, Size(3, 3));
Scharr(image, x_result, 0, 1, 0);
Scharr(image, x_result, 0, 0, 1);
addWeighted(x_result, 0.5, y_result, 0.5, 0, result);
namedWindow("scharr邊緣檢測后的圖像",0);
resizeWindow("scharr邊緣檢測后的圖像", 600, 600);
imshow("scharr邊緣檢測后的圖像", result);
waitKey();
}
效果:
說實話有點陰森。

到此這篇關(guān)于C++利用opencv實現(xiàn)單目測距的實現(xiàn)示例的文章就介紹到這了,更多相關(guān)C++ opencv單目測距內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
一文帶你學(xué)習(xí)C/C++中的<Windows.h>庫
c語言 #include<windows.h>是寫window程序需要的重要頭文件,下面這篇文章主要給大家介紹了C/C++中<Windows.h>庫的相關(guān)資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下2023-01-01
Win11+?VS2022編譯?FFmpeg6.0?靜態(tài)庫的詳細過程
這篇文章主要介紹了Win11+VS2022編譯FFmpeg6.0靜態(tài)庫的方法,本文通過圖文實例代碼相結(jié)合給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-08-08
C++中HTTP?代理服務(wù)器的設(shè)計與實現(xiàn)詳解
代理服務(wù)器,即允許一個網(wǎng)絡(luò)終端(一般為客戶端)通過這個服務(wù)與另一?個網(wǎng)絡(luò)終端(一般為服務(wù)器)進行非直接的連接,下面我們就來看看如何使用C++設(shè)計與實現(xiàn)一個HTTP?代理服務(wù)器吧2024-01-01
詳解C/C++中const關(guān)鍵字的用法及其與宏常量的比較
簡單的說const關(guān)鍵字修飾的變量具有常屬性,也就是說它所修飾的變量不能被修改,下文給大家介紹C/C++中const關(guān)鍵字的用法及其與宏常量的比較,需要的朋友可以參考下2017-07-07

