抽空做了个简单的激光测距的小东西,这个方案简单易实现,而且可以用现有的opencv库很容易作进一步性能改进。 之前在一个老外的网上看到介绍,现在找不到了。 基本原理: ![]() 这个图很清晰的解释了测距的基本原理,假设激光投射到目标物体上,同时位于同一平面的摄像头画面内能够捕捉到激光照射点,(所以要求摄像头和激光头之间的距离h不能太远),激光光线和摄像头成像轴完全平行。这样,照射到目标物体的激光点和摄像头成像平面连线和摄像头轴线之间会有一个夹角theta,通过在摄像帧图像里面对这个激光点相对于轴心的像素点的判断,可以确定出theta的大小,从而就能得到距离D。D越远,画面中激光点离中心越近,D越小,激光点离中心越远。画面的激光点判断可以简单的认为最亮的点就是激光点,逐像素扫描。当然可以使用opencv的API实现更复杂更精确的激光点确定方法。 整个算法比较简单但是很实用。 D很容易由下式得出: ![]() 所以只有一个未知量theta,theta由几个因素决定,一个是画面中激光点离轴心的距离pfc,这个可以通过帧画面的处理得到, 另外两个是固定量,和具体摄像头有关,每个摄像头都不一样,rpc表示每个像素的弧度(其实就是与pfc想乘的因子),还有一个是弧度偏移补偿量,用于矫正。 ![]() 这样我们可以得到下式: ![]() 经过上面的分析,只有pfc一个变量,其他的都算已知量。那怎么得到rpc和ro呢? 可以通过实验得到: 在实际试验中先得到两组数据,分别是激光点离中心的距离和真实的距离D 矫正数据 光点离中心距离 D (cm) 61 137 137 64 代入 137 = 7.350/tan(61x+y) 64 = 7.350/tan(137x+y) 这样得到 Offset (ro) = 0.0053867 radians Gain (rpc) = 0.000795302 radians/pixel 采用的摄像头,很普通的那种,640*480像素 ![]() 做好了的实验板: ![]() 代码: #include <iostream> using namespace std; #include "opencv/cv.h" #p#分页标题#e##include "opencv2/highgui/highgui.hpp" int main( int argc, char** argv ) { cvNamedWindow( "Example", CV_WINDOW_AUTOSIZE ); CvCapture* capture; if (argc==1) { capture = cvCreateCameraCapture( 0 ); } else { capture = cvCreateFileCapture( argv[1] ); } assert( capture != NULL ); IplImage* frame; while(1) { frame = cvQueryFrame( capture ); if( !frame ) break; // 对取到的图像做处理 unsigned int W, H; unsigned int row, col; unsigned long i; unsigned int max_row; unsigned int max_col; unsigned char max_val = 0; const double gain = 0.000795302; const double offset = 0.0053867; const double h_cm = 7.350; // 我的摄像头和激光头距离7.35cm double range; unsigned int pixels_from_center; W = frame->width; H = frame->height; unsigned char *img_d = (unsigned char *)frame->imageData; // 假设激光点是最亮点,最简单的实现,一般来说确实是这样,但是在背景干扰大的情况下会误判,还有很大的提高空间 for (row = 0; row < H; row++) { for (col = 0; col < W; col++) { i = (unsigned long)(row*3*W + 3*col); if (*(img_d + i) >= max_val) { max_val = *(img_d + i); max_row = row; max_col = col; } } } max_val = 0;#p#分页标题#e# // 找到了画十字线标注 for (row = 0; row < H; row++) { for (col = 0; col < W; col++) { i = (unsigned long)(row*3*W + 3*col); if ((row == max_row) || (col == max_col)) *(img_d + i) = *(img_d + i + 1) = *(img_d + i + 2) = 255; } } pixels_from_center = max_row - H/2; // 算出距离并打印出来 range = h_cm / tan(pixels_from_center * gain + offset); cout << "W= " << W << ", H= " << H << ", Max Value at x=" << max_col << ", y= " << max_row << ", range= " << range << endl; cvShowImage( "Example", frame ); char c = cvWaitKey(10); if( c == 27 ) break; } cvReleaseCapture( &capture ); cvDestroyWindow( "Example" ); } 实际测试中误差还算可以,在5%以内,最主要是激光点的判断还有很大提高空间,opencv提供了不少API,网上也有一些文章 距离64cm: ![]() 距离152cm: ![]() |