看图片就是我做的东西,很炫酷是不是。
好吧,开玩笑,这是电影普罗米修斯的截图。
当初看这个电影的时候就感觉这东西好眩酷,我能不能做出来。最近借着帮做毕业设计的机会我也做了一个。
就是这个丑丑的东西啦~
首先感谢来自CSK的低成本3D scanner。http://www.csksoft.net/blog/post/lowcost_3d_laser_ranger_1.html
本制作中大部分参考了他的原理,创意。
原理:
利用激光三角测距法的原理,计算出摄像头到激光点的距离,这样很多个激光点的距离得到,再结合角度信息,我们便可以得到他在三维坐标系下的坐标,从而渲染出来。
三角测距法:
Laser是激光器,Imager是针孔摄像机的模型,Object是物体,激光器发射的激光,经过的物体的反射进入摄像头,其中β角已知,S已知,光斑在摄像头成像的坐标我们可以得到,从而我们可以得到反射光线与S的夹角,从而三角形已知,解这个三角形,我们便可以得到d,也就是距离。
不过,这样只能得到一个点的信息,转一周我们也只能得到二维坐标系下的坐标。要想得到三维的坐标,要不装置可以增加一个维度的运动,要不我们可以采用一字线激光器。
在这个制作中我们采用了一字线激光器。
效果如图。
如何根据一字线激光点坐标得到距离(图像处理过程之后再提)
这样实际就成为了一个椎体,画面中心点的距离可以很方便求出,而其他地方点的坐标我们必须知道它的仰角,我们可以通过根据距离画面中心点的距离得到。实际上就是解立体几何啦~
具体的公式,可以参见我的代码。
对于摄像头的选择
理论上来说,640*480的分辨率的画面就可以很好的精度。我选择的是OV9710摄像头,通过USB获取数据。起初我还打算用STM32F4来进行图像处理,后来实在是太吃力了,RAM根本不够用的,所以就直接在电脑上使用OpenCV来进行图像处理了。
图像处理
整个制作最关键的部分就是图像处理了,起初我是对图像直接灰度然后二值化,这种效果很不理想,一遇到亮的地方就完蛋了。
后来我参考网上别人的做法,使用不可见光808NM的100MW的激光器,摄像头加装了一个窄带滤光片,效果的确不错,可是距离我的期望感觉还差得远。
这就是加装了滤光片后使用不可见光激光拍摄的画面。
后来我苦思冥想,想出来一个大招。把激光当作一个信息处理,那么原来我直接获取激光,是根本没有经过任何处理的获取信息,但如果我把激光调制呢,例如这一帧画面有激光,下一帧画面便没有激光,两幅图像异或!便可以得到很清晰的只有激光的画面。事实证明,我的这个想法很好。非常可行。
可以看到效果很好,得到的画面很干净。
控制
根据前面说的,我们整个装置要进行旋转,我选择了舵机,舵机要用PWM控制。
激光器要能够根据我的指令关闭和开启,所以我搭建了一个简单的MOS开关电路用来对激光器进行控制,控制信号采用的是高电平低电平信号。
要能够获取我们现在转动的角度,因此我添加了MPU6050模块,模块基于I2C协议进行通讯。
要与电脑进行通信,接收电脑的指令,我选择了串口作为通讯方式。
所以,基于以上,我使用了STM32F103作为下位机,接收上位机指令,控制激光器开关,获取MPU6050数据,驱动舵机运动。
程序实现
因为大三啦~要准备考研啦~(我还选择跨考计算机就更没怎么有时间了)所以我就用了Python+OpenCV来缩短编程的时间了。
程序主要就是图像处理部分和计算部分了。
这是图像处理以及控制部分程序(Python2.7+OpenCV)
ser.write('+\r\n'.encode()) #舵机转
str=ser.readline()
ser.write('ON\r\n'.encode()) #激光器打开
str=ser.readline()
ret,img1 = cap.read()
ret,img1 = cap.read()
ret,img1 = cap.read()
ret,img1 = cap.read() #多获取几次,感觉这样获得的画面会稳定点吧
ser.write('OFF\r\n'.encode()) #激光器关闭
str=ser.readline()
ret,img2 = cap.read()
ret,img2 = cap.read()
ret,img2 = cap.read()
ret,img2 = cap.read() #多获取几次,感觉这样获得的画面会稳定点吧
ser.write('Angle?\r\n'.encode()) #请求角度
Angle=ser.readline()#获取角度
img1=cv2.resize(img1,(800,600),interpolation=cv2.INTER_CUBIC)#图像缩放
img2=cv2.resize(img2,(800,600),interpolation=cv2.INTER_CUBIC)#图像缩放
gray1 = cv2.cvtColor(img1, 6)
gray2 = cv2.cvtColor(img2, 6)#图像灰度化
ret,TRE1 = cv2.threshold(gray1,215,255,cv2.THRESH_BINARY)
ret,TRE2 = cv2.threshold(gray2,215,255,cv2.THRESH_BINARY)#图像二值化
img=cv2.absdiff(TRE1,TRE2)#两幅图像找不同
x=[]
y=[] #用来存放坐标
x,y=findXY(img) #获取坐标
px=[]
py=[]
pz=[] #用来存放实际三维坐标
for i in range(len(x)):
dis,ang,pit=calcDistanceByPos(x[i],y[i],800)
p_x,p_y,p_z=getPLOTXYZ(dis,ang,pit,Angle) #计算坐标
px.append(p_x)
py.append(p_y)
pz.append(p_z)
计算(这部分程序参考网上帖子的计算方法~)
主要是先获取极坐标系下坐标再得到直角三维坐标
def calcDistanceByPos(x,y,img_height):
a=698.1 #f*baseline
b=4.354
c=0.006912 #PixelSize
d=-40.88
baseline=100.0
laser_angle=1.486407302
rotation_r=49.2
focal=a/baseline
center_distance=a/(b-c*x)
pitch_angle=math.atan(((y-img_height/2)*c)/(focal))
pitch_distance=center_distance/math.cos(pitch_angle)
laser_to_dist_pt = center_distance*math.tan(PI/2 - laser_angle)
laser_to_current_pt = math.sqrt(pitch_distance*pitch_distance + laser_to_dist_pt*laser_to_dist_pt)
laser_to_center_pt = math.sqrt(center_distance*center_distance + laser_to_dist_pt*laser_to_dist_pt)
real_center_distance = math.sqrt( (laser_to_dist_pt-rotation_r)*(laser_to_dist_pt-rotation_r)
+ center_distance*center_distance)
yaw_angle = PI/2 - math.acos((rotation_r*rotation_r + real_center_distance*real_center_distance
- laser_to_center_pt*laser_to_center_pt)/2.0/rotation_r/real_center_distance)
real_distance = math.sqrt( (laser_to_dist_pt-rotation_r)*(laser_to_dist_pt-rotation_r) + pitch_distance*pitch_distance)
return real_distance,yaw_angle,(pitch_angle-18.0*PI/180.0)
def getPLOTXYZ(distance,yaw,pitch,Angle):
px = distance * math.cos(pitch) * math.sin( yaw + Angle*PI/10.0/180.0)
py = distance * math.cos(pitch) * math.cos( yaw + Angle*PI/10.0/180.0)
pz = distance * math.sin(-pitch)
return px,py,pz
效果如图,还是挺好的哇~~~~~只是我还没怎么会用那些点云渲染的引擎,就直接扫了一段数据后用散点图画出来了,哈哈哈哈哈~
(这是我之前拍摄的工作视频截取得画面)
最后就祝大家万事胜意吧,嘻嘻。