暑期做的一个项目,开始并不是很熟悉,在网上查找的资料也不是很具体,但是自习学习了理论知识之后还是比较容易的做出来这个项目,现在开源整个项目,由于篇幅有限,本文适合稍微有点点基础的朋友。源码见底部

先显示下最后结果:

一、硬件设计

主要有步进电机(建议精度高点,这涉及到后面的精确度问题,一般42电机即可)、步进电机驱动、彩色相机、一字型激光头、arduino、各部分的供电单元;基本的硬件框架包括能够固定在步进电机上的旋转台,以及一些整体的固定装置。我的效果如下(不一定和我一样,完成相关功能即可,我也是利用实验室有限资源):

二、整体设计思路

本设计为线结构光三维扫描,是一种基于光学三角法的非接触式物体表面轮廓成像技术,利用线激光投影到被测物体表面,工业相机采集受到物体表面高度调制的形变条纹,经过计算得到表面轮廓三维数据。很多相关设计采用了数学上的几何关系来解算,例如之前参考过一个方法是结合镜头光轴与激光线的距离b以及夹角c0,再用镜头光轴垂直于镜头光心与激光头的连线,结合基本的几何关系以及正弦定理推算出物体距离深度信息,个人认为这种方法确实十分好的利用了激光三角法的基本原理,但是对于装置的硬件要求过高,例如实际上角度和距离并不是很准确的能够控制,加上是光心到激光头中心的这种不易测量的距离。随本设计采用标准的标定的方式,后面能够发现对硬件要求很低。但是由于我也是为了简单的模型设计,项目本身并没有实际参数的要求,故实际测量等对有些变量并没有很好地测量与控制,这并不影响我们对线激光三维成像原理与方法的理解;实际来看实验效果依旧不错,大家可以根据实际情况对硬件加以更严谨的测量和控制。

三、标定

相机标定:    相机的标定为的是获取相机的内外参数,有一点需要指出,就是标定后相机的内参矩阵只有一个,但是外参矩阵的数目是和标定板是一样的,因为每一个标定板的平面都是被视为一个世界坐标系的XoY面(Z为0),相片摆放不同即表示各个世界坐标系不同,而内参矩阵是代表相机坐标系与世界坐标系的转换关系,相机坐标系是一直变化的,随着世界坐标系的变化外部转移矩阵自然一直变化。为简化标定过程,相机标定直接采用matlab相机标定工具箱,具体教程可参考如下链接  MATLAB--相机标定教程。得到的Intrinsic Matrix为内参矩阵,RotationMatrix 何Translation Vector 即为外部矩阵对应的旋转矩阵和平移向量;注意,我验证过,这里得到的矩阵结果,如代入下面的公式计算需要进行转置,程序上有体现。标定时我默认为倒数第二张标定板图片为唯一的世界坐标系(即后面的计算以此世界坐标系为基础),倒数第一张标定板图片我用来作为后面的激光光平面的标定的临时坐标系;最后两张标定板照片采集后分别保持不动,打开激光,并分别采集激光打在标定平面上的图片,以备后面光平面标定使用。

%%  获取标定结果  MATLAB 获取 经计算 需转置矩阵
load('cameraParams.mat');
intriMatrix = cameraParams.IntrinsicMatrix';
R = cameraParams.RotationMatrices(:, :, 21)';
T = cameraParams.TranslationVectors(21, :)';
%  Temporary coordinate system
R_t = cameraParams.RotationMatrices(:, :, 22)';
T_t = cameraParams.TranslationVectors(22, :)';

 光平面标定:  本设计目前发现的对硬件结构有一些基本的要求,主要体现在1、要保证旋转台水平;2、尽量保证激光线垂直于旋转台中心(这个我只是目测简单的校准,或许通过算法的改变可以解决这个硬件约束,但是硬件上稍微的一点约束,程序上的数学处理会少很多)。光平面的主要思路是,在唯一世界坐标系上的激光线(前述的激光打在标定板的图片)上找到两个点,再在临时坐标系上找到一点,利用相机坐标系不变的原理,将临时坐标系上的光点通过转换关系(外部矩阵)先转到相机坐标系,再从相机坐标系转到唯一世界坐标;光点的像素坐标获取主要使用steger算法和交比不变性:

%%  计算光平面方程
close all;
%   世界坐标系
chessPoint = cameraParams.ReprojectedPoints(:, :, 21);
second_row = chessPoint(2, 2);
fourth_row = chessPoint(4, 2);
delta_row = sqrt((chessPoint(1, 1) - chessPoint(2, 1))^2 + (chessPoint(1, 2) - chessPoint(2, 2))^2);
frame = imread('F:\旋转式3d激光扫描\Image\Calibration\cali21_laser.jpg');
background = imread('F:\旋转式3d激光扫描\Image\Calibration\cali21.jpg');
laserPixel = findLaserCenter(frame - background, 1, 480, 325, 450, 0);
[row, column, channel] = size(frame);
image_1=zeros(row,column);      % 等大的全黑背景
max = length(laserPixel);
for index = 1 : maximage_1(laserPixel(index, 2), laserPixel(index, 1)) = 255;
end
%  find the first point on the light:  pixel coordinate ---> (column_index, row_index)
for row_index = round(second_row - delta_row/2) : round(second_row)column_index = find(image_1(row_index, :) == 255);if length(column_index) == 1  break;end
end
x1 = (chessPoint(1, 1) - column_index)/delta_row*15;
y1 = (chessPoint(1, 2) - row_index)/delta_row*15;
z1 = 0;
%  find the second point on the light:  pixel coordinate ---> (column_index, row_index)
for row_index = round(fourth_row - delta_row/2) : round(fourth_row)column_index = find(image_1(row_index, :) == 255);if length(column_index) == 1break;end
end
x2 = (chessPoint(1, 1) - column_index)/delta_row*15;
y2 = (chessPoint(1, 2) - row_index)/delta_row*15;
z2 = 0;
%%   临时坐标系
chessPoint = cameraParams.ReprojectedPoints(:, :, 22);
second_row = chessPoint(2, 2);
fourth_row = chessPoint(4, 2);
delta_row = sqrt((chessPoint(1, 1) - chessPoint(2, 1))^2 + (chessPoint(1, 2) - chessPoint(2, 2))^2);
frame = imread('F:\旋转式3d激光扫描\Image\Calibration\cali22_laser.jpg');
background = imread('F:\旋转式3d激光扫描\Image\Calibration\cali22.jpg');
laserPixel = findLaserCenter(frame - background, 1, 480, 325, 450, 0);
[row, column, channel] = size(frame);
image_1=zeros(row,column);      % 等大的全黑背景
max = length(laserPixel);
for index = 1 : maximage_1(laserPixel(index, 2), laserPixel(index, 1)) = 255;
end
%  find the first point on the light:  pixel coordinate ---> (column_index, row_index)
for row_index = round(second_row - delta_row/2) : round(second_row)column_index = find(image_1(row_index, :) == 255);if length(column_index) == 1  break;end
endx3_t = (chessPoint(1, 1) - column_index)/delta_row*15;
y3_t = (chessPoint(1, 2) - row_index)/delta_row*15;
z3_t = 0;
CCS3 = R_t * [x3_t; y3_t; z3_t] + T_t;
WCS3 = pinv(R) * (CCS3 - T);
x3 = WCS3(1, 1); y3 = WCS3(2, 1); z3 = WCS3(3, 1);
[a, b, c, d] = createLightPlane(x1, y1, z1, x2, y2, z2, x3, y3, z3, 0);  %  后面加上非0参数即可显示

 旋转平面的标定:即确定步进电机驱动脉冲与相机的帧的关系;我使用matlab与arduino通信的方式,一定的脉冲之后采集一张图片。关于matlab与arduino通信有很多博客提供了方法    激光扫描三维重建——3.matlab和arduino通信 。此处为了后面的色彩匹配与方便提取激光中线,采用的是第一圈开激光,第二圈关闭激光的方式(我的激光器是直连的,没有激光驱动,故采用分别亮灭的方式,但是这样不一定很好的保证两张图片的对应,因为步进电机的一圈不一定是完整的一圈);最好采用控制激光亮灭的方式拍两张图片,分别作为image和background;

Uno = arduino('COM5');
Uno.pinMode(7, 'output');video = videoinput('winvideo', 2, 'YUY2_640x480');
triggerconfig(video, 'manual');
start(video);
pause(2);
fprintf('   Catch Frame...... \n');laser = 0; %是否有激光
for i = 1 : 320OneStep(Uno, 0.005);frame = getsnapshot(video);frame = ycbcr2rgb(frame);if laser == 1imwrite(frame,strcat('F:\旋转式3d激光扫描\Image\NongfuSpring\Reconstruct\','image',num2str(i,'%d'),'.jpg'),'jpg');% 保存帧elseimwrite(frame,strcat('F:\旋转式3d激光扫描\Image\NongfuSpring\Background\','background',num2str(i,'%d'),'.jpg'),'jpg');% 保存帧endend
fprintf('   Complete!!! \n');delete(instrfind({'Port'}, {'COM5'}));
stop(video);
delete(video);

四、三维点云获取

对每张照片处理的时候,采用背景减除 image - background,将直接得到激光区域,减少了环境的干扰;对激光区域使用steger算法求出激光中心;分别对每一帧上的每一个激光中心进行三维坐标的解算;具体如下:

由标定原理:

由上面的公式得到:

              (1)

由光平面方程:

                                                ( 2 )

由(1)和(2)联列,四个方程,四个未知数Xw, Yw, Zw, Zc  可解出我们输入像素点(u,v)的 三维坐标(Xw, Yw, Zw)。

求出单个像素点的空间坐标后,需要结合旋转台的旋转参数来解算出对应的唯一世界坐标系下的坐标;由于前述要求了激光线垂直于圆台中心,对于旋转体,核心是旋转半径 r = Xw^2 + Yw^2以及旋转角(与相片索引和旋转角分辨率相关);

色彩匹配即利用刚刚求出的各激光中心点对应的像素坐标,对应在background相片中提取色彩信息即可!

%%  生成点云
picture_num = 320;  %
WCS = [];          % 所有点的坐标
color = fopen('apple.txt','w');
for frame_index = 1 : picture_numframe = imread(['F:\旋转式3d激光扫描\Image\Reconstruct\image',num2str(frame_index),'.jpg']);background = imread(['F:\旋转式3d激光扫描\Image\Background\background',num2str(frame_index),'.jpg']);fprintf('   Processing %d th image...\n', frame_index);laserPixel = findLaserCenter(frame - background, 1, 480, 325, 450, 0);for pixel_index = 1 : length(laserPixel)[Xw, Yw, Zw] = pcs2wcs(laserPixel(pixel_index, 1), laserPixel(pixel_index,2), intriMatrix, R, T, a, b, c, d, 0);r = sqrt((Xw - 40)^2 + Zw^2);theta = atan2(-Zw, 40 - Xw);Xw = r * cos(theta - frame_index/picture_num * 2 * pi);Zw = r * sin(theta - frame_index/picture_num * 2 * pi);red = background(laserPixel(pixel_index, 2), laserPixel(pixel_index, 1), 1);green = background(laserPixel(pixel_index, 2), laserPixel(pixel_index, 1), 2);blue = background(laserPixel(pixel_index, 2), laserPixel(pixel_index, 1), 3);WCS = [WCS; Xw Yw Zw];fprintf(color,'%d  %d  %d  %d  %d  %d\n',Xw, Yw, Zw, red, green, blue);end
end
%save('apple.txt', 'WCS', '-ascii');
fprintf('   Processing Complete! Saved as ‘apple.txt‘ \n');

五、实际效果

点云图像:

加上彩色信息的点云数据:

meshlab处理后的彩色信息:方法参考   使用Meshlab软件将点云(Point Cloud)转换为模型(Mesh)

六、总结

由于篇幅和个人水平有限,博客中代码段均只给出主函数部分,详细全部代码可以在评论处留下邮箱!欢迎各位同仁指正!!!

鉴于最近经常有小伙伴找资料,在这里建个群,资料群里有,欢迎大家交流!

QQ群号  :   519692535           

基于MATLAB的线激光三维彩色扫描仪相关推荐

  1. fib matlab,基于MATLAB的FIB制备三维原子探针样品过程的模拟方法与流程

    本发明属于FIB加工领域,尤其是涉及一种基于MATLAB的使用聚焦离子束(FIB)进行三维原子探针(APT)样品加工过程的模拟方法. 背景技术: APT可以定量的给出材料中不同元素原子在三维空间中的分 ...

  2. 【RRT三维路径规划】基于matlab RRT算法无人机三维路径规划【含Matlab源码 155期】

    一.获取代码方式 获取代码方式1: 通过订阅紫极神光博客付费专栏,凭支付凭证,私信博主,可获得此代码. 获取代码方式2: 完整代码已上传我的资源:[三维路径规划]基于matlab RRT算法无人机三维 ...

  3. 【RRT三维路径规划】基于matlab RRT算法无人机三维路径规划【含Matlab源码 1363期】

    一.获取代码方式 获取代码方式1: 通过订阅紫极神光博客付费专栏,凭支付凭证,私信博主,可获得此代码. 获取代码方式2: 完整代码已上传我的资源:[三维路径规划]基于matlab RRT算法无人机三维 ...

  4. 【BFS三维路径规划】基于matlab广度优先搜索算法无人机三维路径规划【含Matlab源码 270期】

    一.获取代码方式 获取代码方式1: 通过订阅紫极神光博客付费专栏,凭支付凭证,私信博主,可获得此代码. 获取代码方式2: 完整代码已上传我的资源:[三维路径规划]基于matlab广度优先搜索算法无人机 ...

  5. 单目-线激光三维扫描系统中光刀平面的标定

    单目-线激光三维扫描系统中光刀平面的标定 线结构光有结构简单.精度高.测速快等特点,被广泛应用于三维重建领域.线结构光三维扫描系统由一个相机和一个线结构光投射器构成,线结构光投射器将线结构光投射到被测 ...

  6. 【飞行器】基于matlab四旋翼飞行器三维动态仿真【含Matlab源码 269期】

    一.获取代码方式 获取代码方式1: 完整代码已上传我的资源:[飞行器]基于matlab四旋翼飞行器三维动态仿真[含Matlab源码 269期] 点击上面蓝色字体,直接付费下载,即可. 获取代码方式2: ...

  7. 基于点、线数据三维地质建模方法

    ** 基于点.线数据三维地质建模方法 ** 1.前言   作者本人计算机出身,近一年多负责公司地质建模项目项目工作,项目关联计算机.采矿行业相关技术.本文章主要介绍采矿行业地质建模及模型更新相关数字化 ...

  8. matlab三维海浪代码,基于Matlab的不规则海浪三维仿真

    Vol. 15 No. 7 系 统 仿 真 学 报 July 2003 JOURNAL OF SYSTEM SIMULATION • 1057 • 基于 Matlab 的不规则海浪三维仿真 李 晖 1 ...

  9. 基于栅格地图的路径规划(一)基于Matlab二维、三维栅格地图的构建

    基于栅格地图的路径规划(一)基于Matlab二维.三维栅格地图的构建 前言 1.二维栅格地图的创建 1.1.二维栅格地图构建原理 1.2.二维栅格地图构建例程 2.三维栅格地图的创建 2.1.三维栅格 ...

最新文章

  1. 【错误记录】Flutter 报错 ( Android Studio 中 main.dart 左侧不显示设备栏 )
  2. 《研磨设计模式》chap14 迭代器模式(2)算工资举例
  3. 特征值和特征矩阵的意义和应用
  4. 如何用AnySDK快速接入SDK上线
  5. 演讲(2)----十大范畴法
  6. jQuery实现影院选座订座效果
  7. 湖南省计算机二级tc,湖南计算机二级考试大纲,重点内容谢谢!
  8. java 日志框架 详解_springboot日志详解
  9. 模式识别的几种基本算法
  10. RS-274drillIPC-2581三种PCB Gerber文件的观察软件
  11. win10键盘失效问题
  12. GHO文件转iso文件能启动安装
  13. python最大公约数计算_Python怎样求得最大公约数
  14. 【数据库学习笔记】Day03 - SQL语言基础及数据库定义功能
  15. 今日头条是怎么挣钱的
  16. 计算机组装机配置单2019,2019年电脑主流配置
  17. ATJ2157内存篇【炬芯音频芯片】---sct语法
  18. 2018年刑侦科推理试题php版,2018年刑侦科目推理试题完整版分享
  19. Console线连接交换机路由器等设备
  20. html5 scrollheight,scrollHeight和scrollWidth,获取网页内容高度和宽度不正确

热门文章

  1. 电工与电子技术实验——叠加定理与戴维南定理
  2. 黄飞130702010037第二次作业
  3. FD650B数码管驱动
  4. FISCO-BCOS应用实战:区块链实战应用开发分享
  5. JSON反序列化失败:although at least one Creator exists): can only instantiate non-static inner class by usi
  6. 模块例化是怎么进行的?——FPGA学习笔记(四)
  7. bi 建模流程图_搞懂PowerBI的数据建模
  8. android多文件上传错误,微信多图上传解决android多图上传失败问题
  9. CANoe不能自动识别串口号?那就封装个DLL让它必须行
  10. 测绘工程和计算机专业哪个好,测绘工程专业就业前景如何