本帖最后由 xukejing 于 2017-4-23 21:46 编辑
这块RK3066板子附带了一个USB摄像头,我们今天来玩玩这个摄像头吧。我最近脑洞比较大,想出了一个超级有趣的视觉测距算法。
照相机成像的过程如下图所示。
如果有一束与摄像头光轴平行但相距一定距离的激光打在物体上并被摄像头采集到,我们就能在摄像头输出图像上捕捉到一个亮点。
那么,下一步能做些什么呢?
我举个松下激光位移传感器的例子,小伙伴们感受一下。
上面这个传感器的激光光轴离传感器太近了,所以有效量程很近。我们来山寨个0.5米到2米量程的。
当摄像头拍摄一个距离1米的物体时,它的光路示意图是这样的。
在上图中,半径R为1米。如果我们用尺测量的是AB这个长度,由解析几何可以计算得到圆弧CD的长度和角COD的大小。在角COD里面,每个像素点都一一对应了圆弧CD上的点。
讨论:虚线OG是视角COD内部的一条光路,当角GOF变化时,弧线GF的长度与角度是线性的;但线段EF的长度与角度的线性关系并不好。当且仅当角GOF趋向于零时,弧线GF等于线段EF。
又扯到高数了,小伙伴们表示压力山大。
别紧张,我们收。
如果在距离O点一定距离的B点上布置一个激光,显然激光光轴与摄像头光轴距离OB。当激光打在A点后,反射光沿着光路AO进入相机。如果OB距离固定,那么只要知道角度AOC就可以算出距离了。
是不是很简单?
接下来,我们要把每个像素对应到具体的角度(或者说物体所在的弧度)。
首先,我们来测量一下RK3066板子的USB摄像头的角度分辨率。
我在距离摄像头1米的位置上放了个椅子,椅背正好距离摄像头1米。
然后,把尺放到椅背上去,采集图像。注意这个夹子,它标记了1米处的视角宽度。
把尺子取下,读长度,这个摄像头在1米时候的视角宽度大约是45厘米。
接着,我们来写一下代码,把每个像素对应的角度(图像弧度)宽度可以计算出来了。
double angle_per_pix;//角度分辨率
double length_at_1m = 0.45;//距离1米处的视角宽度 单位:米
double arc_length_at_1m;//1米处的弧长 单位:米
double angle_at_1m;//1米处的视角 单位:弧度
angle_at_1m = atan(length_at_1m / 1.0);//这是半径1米时候的特解
arc_length_at_1m = angle_at_1m;//这是半径1米时候的特解 弧长与角度的数值相等
angle_per_pix = arc_length_at_1m / my_col;//每像素对应的角度 单位:弧度
是不是很简单?:lol设置在1米位置做测量,巧妙地利用了数值乘除1以后值不变这个定理。
激光点的采集很简单,就是个opencv读摄像头然后找最亮点的过程,这个太简单我就不细讲了(后面给了程序,大伙儿自己读就能看懂的)。
由像素点的横轴位置得到角度,然后再由角度计算距离的算法如下。
//距离计算
pix_from_center = max_col - W / 2;
alph = pix_from_center * angle_per_pix;
range = laser_to_camera / tan(alph);
另外,有个很现实的问题,如果你使用的是红激光,怎么减少杂光的干扰呢?这儿我介绍个算法,叫做滤镜。我们来一起写个算法来模拟滤镜效果,把蓝光和绿光的比例减小到原来的4分之一。注意b和g的参数,我并没有除以4,而是除以8,是为了让照片进一步把绿色和蓝色弱化。
//数值滤镜
b = (frame.at(row, col) + frame.at(row, col) + frame.at(row, col) * 2) / 8;
g = (frame.at(row, col) + frame.at(row, col) + frame.at(row, col) * 2) / 8;
r = (frame.at(row, col) + frame.at(row, col) + frame.at(row, col) * 2) / 4;
frame.at(row, col) = b;
frame.at(row, col) = g;
frame.at(row, col) = r;
//计算亮度并找到最大点
tmp_val = frame.at(row, col) + frame.at(row, col) + frame.at(row, col);
程序运行截图如下,图像小是因为我做了像素裁剪,牺牲分辨率但提高速度。效果比较好的测量区间在0.1米到2米范围内(摄像机传感器外侧区域)。越靠近中心(中心是无穷远),值变化越快,所以10米到无穷远的分辨率有些低。这个用于工业控制有些不够精确,但做个小玩具机器人避障玩玩应该是够了。
我这次编译代码没有使用RK3066的gcc环境,而是使用了我比较顺手的windows端的Visual Studio。重点是在算法,请不要在意这些细节:lol:lol:lol。
最后给出全部代码。
// opencv32test.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "opencv2/opencv.hpp"
using namespace cv;
using namespace std;
int main(int argc, char** argv)
{
VideoCapture capture(1);//第二个USB摄像头
Mat frame_orig;//采集的原始图像
Mat frame;//处理过的用于测距的图像
int my_col = 128;//缩减后的图像宽度 单位;像素
int my_row = 32;//缩减后的图像高度 单位;像素
int W, H;//图像宽度和高度 单位:像素
H = my_row;
W = my_col;
int row, col;//循环时候计数第几行和第几列的
int max_row;//最亮点的行数
int max_col;//最亮点的列数
double max_val;//最亮点的数值
double tmp_val;//循环获取像素数值时用的临时变量
int b, g, r;//滤镜变量
double angle_per_pix;//角度分辨率
double length_at_1m = 0.45;//距离1米处的视角宽度 单位:米
double arc_length_at_1m;//1米处的弧长 单位:米
double angle_at_1m;//1米处的视角 单位:弧度
angle_at_1m = atan(length_at_1m / 1.0);//这是半径1米时候的特解
arc_length_at_1m = angle_at_1m;//这是半径1米时候的特解 弧长与角度的数值相等
angle_per_pix = arc_length_at_1m / my_col;//每像素对应的角度 单位:弧度
double laser_to_camera=0.1;//激光与摄像头的轴距 单位:米
int pix_from_center;//检测到的激光点的中心距 单位:像素
double alph;//检测到的激光点的中轴角度 单位:弧度
double range;//计算得到的距离
while (1) {
capture >> frame_orig;//采集
resize(frame_orig, frame, Size(my_col,my_row));//缩减像素
max_val = 0;
for (row = 0; row < H; row++) {
for (col = 0; col < W; col++) {
//数值滤镜
b = (frame.at(row, col) + frame.at(row, col) + frame.at(row, col) * 2) / 8;
g = (frame.at(row, col) + frame.at(row, col) + frame.at(row, col) * 2) / 8;
r = (frame.at(row, col) + frame.at(row, col) + frame.at(row, col) * 2) / 4;
frame.at(row, col) = b;
frame.at(row, col) = g;
frame.at(row, col) = r;
//计算亮度并找到最大点
tmp_val = frame.at(row, col) + frame.at(row, col) + frame.at(row, col);
if (tmp_val>= max_val)
{
max_val = tmp_val;
max_row = row;
max_col = col;
}
}
}
//画个十字线标注一下
for (row = 0; row < H; row++) {
for (col = 0; col < W; col++) {
if ((row == max_row) || (col == max_col))
{
frame.at(row, col) = 255;
frame.at(row, col) = 128;
frame.at(row, col) = 0;
}
}
}
//距离计算
pix_from_center = max_col - W / 2;
alph = pix_from_center * angle_per_pix;
range = laser_to_camera / tan(alph);
//终端显示
cout << " LASER x "<< max_col << ", range=" << range << ", Alph=" << alph << endl;
//窗口显示
imshow("MyTest", frame);
waitKey(1);
}
}
本帖最后由 xukejing 于 2017-4-23 21:46 编辑
这块RK3066板子附带了一个USB摄像头,我们今天来玩玩这个摄像头吧。我最近脑洞比较大,想出了一个超级有趣的视觉测距算法。
照相机成像的过程如下图所示。
如果有一束与摄像头光轴平行但相距一定距离的激光打在物体上并被摄像头采集到,我们就能在摄像头输出图像上捕捉到一个亮点。
那么,下一步能做些什么呢?
我举个松下激光位移传感器的例子,小伙伴们感受一下。
上面这个传感器的激光光轴离传感器太近了,所以有效量程很近。我们来山寨个0.5米到2米量程的。
当摄像头拍摄一个距离1米的物体时,它的光路示意图是这样的。
在上图中,半径R为1米。如果我们用尺测量的是AB这个长度,由解析几何可以计算得到圆弧CD的长度和角COD的大小。在角COD里面,每个像素点都一一对应了圆弧CD上的点。
讨论:虚线OG是视角COD内部的一条光路,当角GOF变化时,弧线GF的长度与角度是线性的;但线段EF的长度与角度的线性关系并不好。当且仅当角GOF趋向于零时,弧线GF等于线段EF。
又扯到高数了,小伙伴们表示压力山大。
别紧张,我们收。
如果在距离O点一定距离的B点上布置一个激光,显然激光光轴与摄像头光轴距离OB。当激光打在A点后,反射光沿着光路AO进入相机。如果OB距离固定,那么只要知道角度AOC就可以算出距离了。
是不是很简单?
接下来,我们要把每个像素对应到具体的角度(或者说物体所在的弧度)。
首先,我们来测量一下RK3066板子的USB摄像头的角度分辨率。
我在距离摄像头1米的位置上放了个椅子,椅背正好距离摄像头1米。
然后,把尺放到椅背上去,采集图像。注意这个夹子,它标记了1米处的视角宽度。
把尺子取下,读长度,这个摄像头在1米时候的视角宽度大约是45厘米。
接着,我们来写一下代码,把每个像素对应的角度(图像弧度)宽度可以计算出来了。
double angle_per_pix;//角度分辨率
double length_at_1m = 0.45;//距离1米处的视角宽度 单位:米
double arc_length_at_1m;//1米处的弧长 单位:米
double angle_at_1m;//1米处的视角 单位:弧度
angle_at_1m = atan(length_at_1m / 1.0);//这是半径1米时候的特解
arc_length_at_1m = angle_at_1m;//这是半径1米时候的特解 弧长与角度的数值相等
angle_per_pix = arc_length_at_1m / my_col;//每像素对应的角度 单位:弧度
是不是很简单?:lol设置在1米位置做测量,巧妙地利用了数值乘除1以后值不变这个定理。
激光点的采集很简单,就是个opencv读摄像头然后找最亮点的过程,这个太简单我就不细讲了(后面给了程序,大伙儿自己读就能看懂的)。
由像素点的横轴位置得到角度,然后再由角度计算距离的算法如下。
//距离计算
pix_from_center = max_col - W / 2;
alph = pix_from_center * angle_per_pix;
range = laser_to_camera / tan(alph);
另外,有个很现实的问题,如果你使用的是红激光,怎么减少杂光的干扰呢?这儿我介绍个算法,叫做滤镜。我们来一起写个算法来模拟滤镜效果,把蓝光和绿光的比例减小到原来的4分之一。注意b和g的参数,我并没有除以4,而是除以8,是为了让照片进一步把绿色和蓝色弱化。
//数值滤镜
b = (frame.at(row, col) + frame.at(row, col) + frame.at(row, col) * 2) / 8;
g = (frame.at(row, col) + frame.at(row, col) + frame.at(row, col) * 2) / 8;
r = (frame.at(row, col) + frame.at(row, col) + frame.at(row, col) * 2) / 4;
frame.at(row, col) = b;
frame.at(row, col) = g;
frame.at(row, col) = r;
//计算亮度并找到最大点
tmp_val = frame.at(row, col) + frame.at(row, col) + frame.at(row, col);
程序运行截图如下,图像小是因为我做了像素裁剪,牺牲分辨率但提高速度。效果比较好的测量区间在0.1米到2米范围内(摄像机传感器外侧区域)。越靠近中心(中心是无穷远),值变化越快,所以10米到无穷远的分辨率有些低。这个用于工业控制有些不够精确,但做个小玩具机器人避障玩玩应该是够了。
我这次编译代码没有使用RK3066的gcc环境,而是使用了我比较顺手的windows端的Visual Studio。重点是在算法,请不要在意这些细节:lol:lol:lol。
最后给出全部代码。
// opencv32test.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "opencv2/opencv.hpp"
using namespace cv;
using namespace std;
int main(int argc, char** argv)
{
VideoCapture capture(1);//第二个USB摄像头
Mat frame_orig;//采集的原始图像
Mat frame;//处理过的用于测距的图像
int my_col = 128;//缩减后的图像宽度 单位;像素
int my_row = 32;//缩减后的图像高度 单位;像素
int W, H;//图像宽度和高度 单位:像素
H = my_row;
W = my_col;
int row, col;//循环时候计数第几行和第几列的
int max_row;//最亮点的行数
int max_col;//最亮点的列数
double max_val;//最亮点的数值
double tmp_val;//循环获取像素数值时用的临时变量
int b, g, r;//滤镜变量
double angle_per_pix;//角度分辨率
double length_at_1m = 0.45;//距离1米处的视角宽度 单位:米
double arc_length_at_1m;//1米处的弧长 单位:米
double angle_at_1m;//1米处的视角 单位:弧度
angle_at_1m = atan(length_at_1m / 1.0);//这是半径1米时候的特解
arc_length_at_1m = angle_at_1m;//这是半径1米时候的特解 弧长与角度的数值相等
angle_per_pix = arc_length_at_1m / my_col;//每像素对应的角度 单位:弧度
double laser_to_camera=0.1;//激光与摄像头的轴距 单位:米
int pix_from_center;//检测到的激光点的中心距 单位:像素
double alph;//检测到的激光点的中轴角度 单位:弧度
double range;//计算得到的距离
while (1) {
capture >> frame_orig;//采集
resize(frame_orig, frame, Size(my_col,my_row));//缩减像素
max_val = 0;
for (row = 0; row < H; row++) {
for (col = 0; col < W; col++) {
//数值滤镜
b = (frame.at(row, col) + frame.at(row, col) + frame.at(row, col) * 2) / 8;
g = (frame.at(row, col) + frame.at(row, col) + frame.at(row, col) * 2) / 8;
r = (frame.at(row, col) + frame.at(row, col) + frame.at(row, col) * 2) / 4;
frame.at(row, col) = b;
frame.at(row, col) = g;
frame.at(row, col) = r;
//计算亮度并找到最大点
tmp_val = frame.at(row, col) + frame.at(row, col) + frame.at(row, col);
if (tmp_val>= max_val)
{
max_val = tmp_val;
max_row = row;
max_col = col;
}
}
}
//画个十字线标注一下
for (row = 0; row < H; row++) {
for (col = 0; col < W; col++) {
if ((row == max_row) || (col == max_col))
{
frame.at(row, col) = 255;
frame.at(row, col) = 128;
frame.at(row, col) = 0;
}
}
}
//距离计算
pix_from_center = max_col - W / 2;
alph = pix_from_center * angle_per_pix;
range = laser_to_camera / tan(alph);
//终端显示
cout << " LASER x "<< max_col << ", range=" << range << ", Alph=" << alph << endl;
//窗口显示
imshow("MyTest", frame);
waitKey(1);
}
}