1. 需求
本文基于某手环(拆机打磨处理)做的验证性工作,为了项目保密性,只截取部分数据进行测试。
这里使用的是海康3D线激光轮廓相机+直线电机的方式进行的高度数据采集,我们拿到的是高度图+亮度图数据。
提取手环上的胶路信息,检测胶路的胶宽、胶高、断胶等信息。
2.实现思路
- 扫描没有涂胶水的手环作为模板数据,包含高度图+亮度图;
- 扫描带胶水的手环作为被测数据,包含高度图+亮度图;
- 分割模板数据高度图中一个公共部分作为匹配模板;
- 高度图转点云图,利用模板点云信息,和被测数据的点云进行匹配靠模;
- 匹配后的点云转高度图;
- 高度图差分,只保留胶路数据;
- 进行胶路分析。
3.图像格式分析
3.1高度图
海康相机返回的高度图是tiff格式,具体含义如下:
- 图像高:直线电机移动中会触发拍照,每拍一次生成一个轮廓,高度对应了本次扫描拍摄的轮廓数。每行的间隔对应了直线电机的触发拍照两次间隔移动的距离。
- 图像宽:和相机的X轴轮廓点数+X轴采样间隔有关,注意这里不等于轮廓点数,需要调节合适的参数,保证图像的行间隔和列间隔实际距离一致。
- 像素值:代表高度信息,单位um。
本次测试调节的每个像素宽高都为40um。
3.2亮度图
最终提取的胶路,可以在亮度图上进行对比查看。
亮度图还可以进行缺陷检测、2D匹配定位等功能,但注意本验证中,亮度图不能用来匹配,因为手环放置的位姿可能会出现倾斜透视,需转到点云上进行3D位姿匹配。
4.代码实现
4.1 胶水和无胶水图像信息
无胶水高度图:
无胶水亮度图:
带胶水高度图:
带胶水亮度图:
4.2 读取数据
read_image(HeightImg_no_glue, height_img_no_glue_path)
read_image (LumImage_no_glue, lum_img_no_glue_path)
read_image(HeightImg_glue, height_img_glue_path)
read_image (LumImage_glue, lum_img_glue_path)
get_image_type (HeightImg_no_glue, ImgType) //海康是uint2
get_image_size (LumImage_no_glue, Width, Height)
* 裁剪图片
crop_row_begin := 700
crop_row_end := 1060
gen_rectangle1 (ROI_Crop, crop_row_begin, 0, crop_row_end, Width)
reduce_domain (HeightImg_no_glue, ROI_Crop, HeightImg_no_glue)
reduce_domain (LumImage_no_glue, ROI_Crop, LumImage_no_glue)
reduce_domain (HeightImg_glue, ROI_Crop, HeightImg_glue)
reduce_domain (LumImage_glue, ROI_Crop, LumImage_glue)
get_image_size (LumImage_no_glue, Width, Height)
dev_open_window (0, 0, Width/2,Height/2, 'black', WindowHandle)
* 高度图像素间隔 单位um
step := 40
4.3 高度图预处理
高度图中干扰信息太多,比如底平面、高位杂质等,通过设定高度阈值,去除干扰的信息,聚焦胶水和模板位置的信息。
* 对高度图阈值分割
height_min := 3000
height_max := 7500
threshold (HeightImg_no_glue, region, height_min, height_max)
reduce_domain (HeightImg_no_glue, region, HeightImg_no_glue)
threshold (HeightImg_glue, region, height_min, height_max)
reduce_domain (HeightImg_glue, region, HeightImg_glue)
预处理结果:
4.4 模板位置提取
在高度图中,尽心如下位置的提取:
* 分割模板区域
thr_min := 3500
thr_max := 4500
gen_rectangle1 (ROI_1, 937, 118, 1060, 220) //选择这个两个孔的位置作为模板
reduce_domain (HeightImg_no_glue, ROI_1, HeightImg_no_glue_roi)
threshold (HeightImg_no_glue_roi, HeightImg_no_glue_roi_region, thr_min, thr_max)//缩小高度范围,聚焦模板平面
reduce_domain (HeightImg_no_glue, HeightImg_no_glue_roi_region, img_mod) //分割模板位置区域,注意是在高度图中分割,这样后面创建的模板3D图像的坐标系和原先的高度图是一样的
提取的模板如下:
4.5 高度图转点云图
要想进行点云匹配,必须首先将高度图转为点云图。halcon中没有该算子(可能没发现??),这里自己实现了一个算子height_to_3d_obj,可以将高度图转为halcon点云对象。
核心是构建X坐标图、Y坐标图和Z坐标图,然后调用halcon的xyz_to_object_model_3d算子,转为3D图。
三个图的宽高和原始的高度图一致:
- X图:每个像素存储的是对应高度图相同行列像素的X世界坐标,使用间隔step(高度图像素间隔)构建。可能结果为[(第一行)0 40 80 120 …(第二行)…]
- Y图:和X图类似,存储的是对应高度图相同行列像素的Y世界坐标,也使用step构建。可能的结果为[(第一行)0 0 0 … (第二行)40 40… ]
- Z图:就是高度图。
最后需要使用reduce_domain算子只保留原高度图中分割的部分。
gen_image_const (ImageY, 'real', Width, Height)
gen_image_const (ImageZ, 'real', Width, Height)
* 获取高度图中的region区域
get_domain (HeightImg, DomainH)
* x图
tupleX :=[]
tuple_gen_sequence (0, (Width-1)*step, step, row_x) //一行
for Index := 0 to Height-1 by 1 //多行拼接成图像数据
tupleX :=[tupleX,row_x]
endfor
get_domain(ImageX,domainX)
get_region_points(domainX,rows,cols)
set_grayval (ImageX, rows, cols, tupleX) //技巧,通过获取domain定义域,直接将序列赋值为像素
* 整体赋值完毕后再抠出region
reduce_domain (ImageX, DomainH, ImageX)
* y图
tupleY :=[]
for Index := 0 to Height-1 by 1
tuple_gen_const (Width, Index*step, row_y)
tupleY :=[tupleY, row_y]
endfor
set_grayval (ImageY, rows, cols, tupleY)
reduce_domain (ImageY, DomainH, ImageY)
* z图 - 转为real格式的图,原高度图非real格式
get_region_points(DomainH,rows_Z,cols_Z)
get_grayval(HeightImg,rows_Z,cols_Z,tupleZ)
zv0 := tupleZ
set_grayval (ImageZ, rows_Z, cols_Z, zv0) //ImageZ在前面是real格式
reduce_domain (ImageZ, DomainH, ImageZ)
xyz_to_object_model_3d (ImageX, ImageY, ImageZ, ObjectModel3D3)
return ()
最后显示三个高度图的点云图像:
* 转3D对象模型查看
height_to_3d_obj (HeightImg_no_glue, step, obj3D_hk_no_glue)
height_to_3d_obj (HeightImg_glue, step, obj3D_hk_glue)
height_to_3d_obj (img_mod, step, obj3D_mod)
visualize_object_model_3d (WindowHandle, obj3D_hk_no_glue, [], [], ['lut','color_attrib','disp_pose'], ['color1','coord_z','true'], [], [], [], PoseOut)
visualize_object_model_3d (WindowHandle, obj3D_hk_glue, [], [], ['lut','color_attrib','disp_pose'], ['color1','coord_z','true'], [], [], [], PoseOut)
visualize_object_model_3d (WindowHandle, obj3D_mod, [], [], ['lut','color_attrib','disp_pose'], ['color1','coord_z','true'], [], [], [], PoseOut)
无胶水:
胶水:
模板:
4.6 点云匹配
先上代码:
* 点云匹配校准
create_surface_model (obj3D_mod, 0.03, [], [], SurfaceModel)//创建surface模型,因为上面的模型非点云型,需进行采样
MinScore := 0.1
* find返回的Pose指的是模型到目标的位姿变换关系,可以用rigid_trans_object_model_3d将模板转换到目标附近靠模
find_surface_model (SurfaceModel, obj3D_hk_glue, 0.03, 0.1, MinScore, 'true', ['num_matches', 'use_3d_edges'], [1, 'false'], Pose_glue, Score, SurfaceMatchingResult)
get_surface_matching_result (SurfaceMatchingResult, 'sampled_scene', [], SampledScene)
get_surface_matching_result (SurfaceMatchingResult, 'key_points', [], KeyPoints)
* 胶路点云对齐到模型,注意rigid_trans_object_model_3d算子是将模型转变到目标场景中
* pose_invert (Pose_glue, Pose_glue_Invert)
rigid_trans_object_model_3d (obj3D_hk_no_glue, Pose_glue, obj3D_hk_no_glue_t)
visualize_object_model_3d (WindowHandle, [obj3D_hk_glue,obj3D_modTrans,obj3D_hk_no_glue_t], [], [], \
['color_' + [0, 1, 2],'point_size_' + [0, 1, 2],'disp_pose'], ['red', 'blue', 'yellow', 1.0, 3.0, 5.0,'true'], [], [], [], PoseOut)
- 首先create_surface_model 进行采样,作为基准;
- find_surface_model 进行点云匹配,返回的Pose是模型到目标的位姿变换关系,这里的参数不再详细解释,需自己实验调节;
- rigid_trans_object_model_3d 靠模,这里是将模型(无胶水点云)转变到目标场景中(胶水点云),用这个算子就可以进行点云对齐,为后面的高度图差分做准备。
其中红色的带胶的,黄色是无胶水,蓝色是无胶水的模板。
4.7 点云转高度图
现在需要将靠模后的无胶水点云转为高度图,这里又是自己封装的算子:
* 将3D对象中的xyz点云,转为z向高度图
get_object_model_3d_params (object_3d, 'point_coord_x', point_x_no_glue_t)
get_object_model_3d_params (object_3d, 'point_coord_y', point_y_no_glue_t)
get_object_model_3d_params (object_3d, 'point_coord_z', point_z_no_glue_t)
cols := int(point_x_no_glue_t/step) //在高度图中的行列坐标,cols[index]和rows[index]是一对。
rows := int(point_y_no_glue_t/step)
* 高度图赋值
for Index := 0 to |cols|-1 by 1
* 注意其中可能有负数!下面将负数都转移到0,0点
* 可能有越界的点,都转移到0点
if (cols[Index] < 0 or cols[Index]>=Width)
cols[Index] :=0
rows[Index] :=0
point_z_no_glue_t[Index] :=0
endif
if (rows[Index] < 0 or rows[Index]>=Height)
cols[Index] :=0
rows[Index] :=0
point_z_no_glue_t[Index] :=0
endif
endfor
gen_image_const (Height_img, img_type, Width, Height)
set_grayval (Height_img, rows, cols, point_z_no_glue_t) //将对应行列的,设置为对应的z值,比较绕这里。
return ()
实际代码:
* x y z的实际坐标是变了,所以深度图需要由这三个实际坐标来重新构建
obj3D_to_height (Height_img_no_glue_t, obj3D_hk_no_glue_t, step, Width, Height, ImgType)
带胶水的高度图:
转换之后的高度图(无胶水):
转换之前的高度图(无胶水):
4.8 差分
* 差分
sub_image (HeightImg_glue, Height_img_no_glue_t, ImageSub, 1, 0)
threshold (ImageSub, Image_Glue, 80, 1000)
背景为带胶水的亮度图,红色的为差分出的胶路Region:
5. 胶路分析
略。