自制三维激光扫描建模

看图片就是我做的东西,很炫酷是不是。

好吧,开玩笑,这是电影普罗米修斯的截图。

当初看这个电影的时候就感觉这东西好眩酷,我能不能做出来。最近借着帮做毕业设计的机会我也做了一个。

就是这个丑丑的东西啦~




首先感谢来自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
效果如图,还是挺好的哇~~~~~只是我还没怎么会用那些点云渲染的引擎,就直接扫了一段数据后用散点图画出来了,哈哈哈哈哈~

(这是我之前拍摄的工作视频截取得画面)


最后就祝大家万事胜意吧,嘻嘻。

猜你喜欢

转载自blog.csdn.net/qingelife/article/details/80215379