C++利用opencv實現單目測距的實現示例

閑來無事,用C++做瞭一個簡易的單目測距。算法用的cv自帶的,改改參數就行。實現瞭讀取照片測距,讀取筆記本攝像頭測距,讀取視頻測距三個功能。

為什麼不用雙目測距?因為沒錢買攝像頭……

原理:相似三角形原理,已知焦距的情況下檢測被測物在圖片中所占的像素寬度來判斷被測物與攝像頭的距離,同時也可以得出被測物的大概長寬。註意:攝像頭要和被測物體平行,如果不平行在側面看來攝像頭和物體之間的連線就與水平方向有一個夾角a,被測物體在圖片中的大小會受到影響,從而影響測量效果。

誤差分析:導致測量效果不好的原因有很多,比如說攝像頭與被測物沒有在同一高度,攝像頭標定的焦距不準確,不同視頻拍攝的攝像頭焦距不同,測量出來也不一樣,所以測量前需要對攝像頭進行標定處理得到焦距,這裡使用的是傳統的標定方法。

步驟:

①標定得出焦距

②對圖片進行高斯濾波處理(平滑操作,過濾操作,去噪操作)

③邊緣檢測

④畫出檢測出的輪廓並返回最小的外接矩形(可以設置畫出全部輪廓或者是最大的輪廓)

⑤計算距離

檢測攝像頭隻需要把攝像頭獲取到的畫面逐幀分析,就相當於對照片分析,視頻也是一個道理。這裡我設置瞭一個保存最小距離和最大距離的變量,當然這隻能作為一個參考值並不是準確的,因為攝像頭和視頻測距都沒有和被測物平行。

效果演示:

效果不穩定,對於單張圖片效果有時候誤差隻有幾厘米,有時候誤差就幾十厘米甚至超過一百厘米。這個測量的距離是圖片中檢測到的最小矩形到攝像頭的距離,所以對於背景比較幹凈的矩形圖片很好識別,識別的很規整,但是對於一些背景雜亂的圖片,識別效果就差強人意。這裡以兩種不同的圖片做對比。

其對應的圖片處理效果如下(實際誤差隻有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
//需要提前獲取,通過標定步驟獲得的
#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) {
	
	//以下程序用於標定相機獲得“焦距”
	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); //計算相機與目標之間的距離
	DistanceInches = DistanceInches * 2.54;  //轉換成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,如果檢測圖片,就停留在當前幀
		waitKey(1);
	}
	else if (choice == 1) {
		waitKey(0);
	}
}
 
void GetCamera(int choice) {
 
	Mat frame;
	VideoCapture capture(0);//讀取視攝像頭實時畫面數據,0默認是筆記本的攝像頭;如果是外接攝像頭,這裡改為1
 
	while (true)
	{
		capture >> frame;            //讀取當前幀
		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() 從視頻文件或捕獲設備中抓取下一個幀
		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; //計算目標到相機的距離   KnownWidth-為已知的目標的寬度  FocalLength-焦距   PerWidth-圖像中寬度的像素數
}
 
RotatedRect FindMarker(Mat& SrcImage)//畫出圖形的邊界並返回最小外接矩形
{
	Mat GrayImage;
	cvtColor(SrcImage, GrayImage, COLOR_BGR2GRAY);//將SrcImage復制給GrayImage
 
	Mat GaussImage;
	//將GrayImage通過高斯濾波處理後存放到GaussImage中
	GaussianBlur(GrayImage, GaussImage, Size(3, 7), 3); //高斯濾波,對圖像進行濾波操作(平滑操作、過濾操作,去噪操作)
 
	Mat EdgeImage;
	Canny(GaussImage, EdgeImage, 100, 200);//邊緣檢測
	
/*這段代碼可省略,隻是用來看效果。
	namedWindow("復制圖", 0);
	resizeWindow("復制圖", 600, 600);
	imshow("復制圖", 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;
		}
	}
	//第三個參數為-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;  //計算焦距單位為圖像像素  依據公式: F=(P*D)/W   F-焦距   D-距離   P-像素寬度   W-目標的真實寬度(單位英寸)
	FocalWide = (Marker.size.width * KNOWN_DISTANCE) / KNOWN_HEIGHT;
	cout << "標定焦距:" << FocalLength << endl;
	cout << "標定焦距:" << FocalLength << endl;
	return FocalLength;
	//1857.71
}

有關圖像的算法都是已經封裝好瞭改參數直接用就行瞭,沒有涉及到什麼需要自己寫的高難度算法。

下面還有一些比較各個邊緣檢測效果的算法代碼:

玩玩就行,原理我也不清楚,隻會用現成的。

#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用法,轉成灰度圖,降噪,用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();
}

效果:

說實話有點陰森。

到此這篇關於C++利用opencv實現單目測距的實現示例的文章就介紹到這瞭,更多相關C++ opencv單目測距內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: