Opencv識別激光線檢測人的位置

剛接觸openCV,還是智能跟隨小車的項目。使用攝像頭加線狀激光發射器做人的位置檢測。

工具:850nm光波段,500mw線狀激光發射器,100度窄帶850或者廣譜加850攝像頭。
項目完成情況及功能:我做的部分是在框出人的前提下,提取人身上的激光線,現在的進度是大體一完成,精度也還可以,但是不適用在室外強光下使用。
項目算法思路:
基於論文ROI區域的查找方法,首先我們的激光是線狀的,水平發射出去,如果在太陽光不太強的情況下,那麼我們的激光在每一列應該是最亮的點,也就是灰度值最大,這樣我們就可以遍歷每一列找出灰度值最大的點。
但是由於激光打出去照射在人的身上會形成光斑,所以我們可以把每一列相同的最大的點只取最後一個,這樣就可以解決光斑的問題,不需要再用灰度重心法,或者極值法。
在實際應用中可能光斑比較分散,導致光斑的上邊沿和下邊沿都有分佈每列最大灰度值的點,所以還有設置一個寬度是框內大小,高度是20像素點的區域,要用這個區域來遍歷整個框,最後統計哪一行裏面包含的最大灰度值的點最多,就說明激光線在這個區域的可能性最大,最後取平均值確定激光線的位置。 雖然方法比較簡單,但是卻比較精準實用。
我也嘗試過其他的幾種方法,有下面三種:
一:閾值加sobel卷積和霍夫變換 此方法步驟是:
1、灰度處理。 2、sobel算子卷積。 3、閾值處理 4、霍夫變換找直線。 方法用自定義的sobel算子效果還算好一點。 但是sobel算子和激光線的光斑終究會把激光線變得彎曲,彎曲之後會和霍夫變換相矛盾,還是無法檢測的準確。
二:基於hessian矩陣的steger算法 此算法利用的是hessian矩陣的各向異性,找出在X方向和Y方向的特徵向量值,最後比較哪個大就用哪個特徵向量作爲法線的方向,最後用steger求出中心點,也可以達到亞像素級別。 這種方法在室內用,檢測光斑用着還可以,但是對於漫反射或者室外效果不好。
三:使用大閾值加形狀檢測來識別光斑,激光線,這種方法適用的場景更加單一,必須用閾值把除激光線以外的東西全通過大閾值(最少200),然後二值化處理掉,只留下激光,然後用findcounts檢測出輪廓,最後圖像擬合,畫出形狀,求出周長,算中心點。

    總結:後三種方法都不好用,當然我的最前面的方法在強光下也無法使用,而且還必須檢測到人的座標的前提下
    還有待完善,還請大家指點。

下面是源碼和測試效果。
我的gethub源碼是:https://github.com/BeiGuoDeXue/Laser-line-extraction.git

#include <opencv2\opencv.hpp>
#include <iostream>
#include <time.h>
using namespace std;
using namespace cv;
void gamma_correct(Mat& img, Mat& dst, double gamma) {
	Mat temp;
	CvMat tmp;

	img.convertTo(temp, CV_32FC1, 1.0 / 255.0, 0.0);
	tmp = temp;
	cvPow(&tmp, &tmp, gamma);
	temp.convertTo(dst, CV_8UC1, 255.0, 0.0);
}
int main(void)
{
	int max[2000];
	VideoCapture cap;
	cap.open(0); //打開攝像頭
	namedWindow("input image", CV_WINDOW_AUTOSIZE);
	//namedWindow(output_title, 0);
	while (1)
	{
		Mat src1;
		Mat src;
		vector<int> max1;
		int max_arry[2][1920] = {0};
		//src1 = imread("./photo/34.bmp", 1);
		//src1 = imread("./室外/2019-08-22-58.bmp", 1);
		//src = src1;
		cap >> src1; //讀取當前幀
		src = src1;
		//double t = (double)getTickCount();
		imshow("input image", src);
		if (!src.data) {
			printf("could not load image...\n");
			return -1;
		}
		double t = 0;
		t = (double)getTickCount();
		int src_width  = src.cols;
		int src_height = src.rows;
		cvtColor(src, src, CV_BGR2GRAY);
		//equalizeHist(src, src);
		//imshow("直方圖均衡化", gray);
		//gamma_correct(src, src, 2.0);                                  //gamma用來對亮的進行加強,對暗的地方進行抑制
		//imshow("伽馬校正",src);
		//erode(src_gray, src_gray, structureElement);
		//Mat structureElement = getStructuringElement(MORPH_RECT, Size(3, 3), Point(-1, -1));
		//dilate(src, src, structureElement); //膨脹
		//erode(src, src, structureElement);  //腐蝕  腐蝕的效果在視頻中還不錯
		//erode(src, src, structureElement);  //腐蝕
		//erode(src, src, structureElement);  //腐蝕
		//erode(src, src, structureElement);  //腐蝕
		//GaussianBlur(src, src, Size(0, 0), 6, 6);  //視頻中,高斯濾波效果還可以
		//medianBlur(src, src, 3);//中值濾波,沒必要
		//imwrite("./photo/3_1.jpg", src);

		int height = 400, width = 200;
		int  initial_X = (src_width - width) / 2, initial_Y = 50;
		Rect rect(initial_X, initial_Y, width, height);          //左上座標(x,y)和矩形的長(x)寬(y)
		rectangle(src1, rect, Scalar(255, 0, 0), 1, 1, 0);

		//int th;
		//Mat src2;
		//th = threshold(src, src2, 0, 255, CV_THRESH_OTSU);    //大律法也可以考慮不再使用
		////printf("th is:%d\n",th);
		//imshow("大律法", src2);
		//printf("cow and row is %d,%d\n", src.cols, src.rows);
		//printf("灰度和畫框:%f ms\n", (getTickCount() - t)*(1000) / getTickFrequency());
		//t= (double)getTickCount();
		uchar *pValue;
		int value_store = 0;
		for (int i = 0; i < src.cols; i++)                 //cols是列數,640
		{
			max[i] = 0;
			value_store = 0;
			pValue = src.ptr<uchar>(0);
			for (int j = 0; j < src.rows; j++)            //rows是行,480  Mat訪問座標是x y反着寫的。
			{
				//pValue = src.ptr<uchar>(j);
				if ((pValue[i] >= value_store) && (pValue[i] > 150))     //150爲最低的閾值
				{
					value_store = pValue[i];
					max[i] = j;
				}
				pValue += src_width;
			}
			max_arry[0][i]=i;
			max_arry[1][i]= max[i];
		}
		for (int k = 0; k <src_width; k++)
		{
			Point rpt;
			rpt.x = max_arry[0][k];             // max1[2 * k + 0];
			rpt.y = max_arry[1][k];             // max1[2 * k + 1];
			circle(src1, rpt, 0, Scalar(0, 0, 255));
		}
		int Max_sopt[2][1080] = { 0 };      //分成(src_height-20)段,
		int Max_order = 0;
		int Max_store = 0;
		int lengh = 20;                     //每段寬度是20個像素點,可以更改
		for (int j = 0; j < (src_height- lengh); j++)
		{
			for (int i = initial_X; i <= width+ initial_X; i++)
			{
				if ((max_arry[1][i] > (j + lengh)) && (max_arry[1][i] <= (j + lengh*2)))
				{
					Max_sopt[0][j]++;                    //每個段的個數
					Max_sopt[1][j] += max_arry[1][i];    //每個段的總和
				}
			}
			if (Max_sopt[0][j]>Max_store )
			{
				Max_store = Max_sopt[0][j];
				Max_order = j;                    //記錄下哪一個段的點數最多
			}
		}
		if ((Max_sopt[1][Max_order] > 0) && (Max_sopt[0][Max_order] > 0))
			Max_sopt[1][Max_order] = Max_sopt[1][Max_order] / Max_sopt[0][Max_order];//得到平均值,可以畫點
		Point rpt2;
		rpt2.x = width / 2 + initial_X;
		rpt2.y = Max_sopt[1][Max_order];
		circle(src1, rpt2, 10, Scalar(255, 0, 255));

		imshow("result", src1);
		printf("%f ms, X:%d,Y:%d\n", (getTickCount() - t)*(1000) / getTickFrequency(), rpt2.x, rpt2.y);
		waitKey(60);
	}
	//system("pause");
	return 0;
}

測試效果,方框是框出來的,中間圓點是激光線座標點。

在這裏插入圖片描述

在這裏插入圖片描述
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章