图像拼接在实际的应用场景很广,比如无人机航拍,遥感图像等等,图像拼接是进一步做图像理解基础步骤,拼接效果的好坏直接影响接下来的工作,所以一个好的图像拼接算法非常重要。
如按下图是将两张楼房图片拼接成一个图像。
1 拼接步骤
要实现图像拼接,简单来说要实现以下步骤:
-
输入图像
-
图像几何校正
-
图像预处理
-
对每幅图进行特征点提取
-
对特征点进行匹配
-
进行图像配准
-
图像融合
-
对重叠边界进行特殊处理
2 拼接条件
图像的拼接要具备以下几个条件:
-
图像应具有一定的特征。
-
图像要有重叠部分,一般重叠部分占总图像的1/4以上较合适。
-
图像的背景亮度差异不能太大,应该低于10个灰度值,否则难以拼接成功。
-
图像的方位差异不能太大,图像应该来源同一方位。
-
拼合边界过渡应平滑,以消除接拼痕迹。
3 特征点提取
基于SRUF 的特征点的提取与匹配
为了使拼接具有良好的精度和鲁棒性,同时又使其具有较好的实时性,本实验采用SURF 算法完成图像序列特征点的提取。
SURF 算法又称快速鲁棒特征,借鉴了SIFT 中简化近似的思想,将DoH 中的高斯二阶微分模板进行了近似简化,使得模板对图像的滤波只需要进行几个简单的加减法运算,并且这种运算与滤波模板的尺寸无关。实验证明,SURF 算法较SIFT 在运算速度上要快3 倍左右,综合性能要优于SIFT 算法。
SURF 特征点提取与描述主要包含4 个步骤:
-
检测尺度空间极值。
-
精炼特征点位置。
-
计算特征点的描述信息。
-
生成描述特征点的特征向量。
halcon特征点特取的算子如下:
select_obj (Images, ImageF, F)
select_obj (Images, ImageT, T)
* 提取两幅图像中的点。
points_foerstner (ImageF, 1, 2, 3, 200, 0.3, 'gauss', 'false', RowJunctionsF, ColJunctionsF, CoRRJunctionsF, CoRCJunctionsF, CoCCJunctionsF, RowAreaF, ColAreaF, CoRRAreaF, CoRCAreaF, CoCCAreaF)
points_foerstner (ImageT, 1, 2, 3, 200, 0.3, 'gauss', 'false', RowJunctionsT, ColJunctionsT, CoRRJunctionsT, CoRCJunctionsT, CoCCJunctionsT, RowAreaT, ColAreaT, CoRRAreaT, CoRCAreaT, CoCCAreaT)
4 图像配准
图像配准是一种确定待拼接图像间的重叠区域以及重叠位置的技术,它是整个图像拼接的核心。本节采用的是基于特征点的图像配准方法,即通过匹配点对构建图像序列之间的变换矩阵,从而完成全景图像的拼接。
变换矩阵H求解是图像配准的核心,其求解的算法流程如下。
-
检测每幅图像中特征点。
-
计算特征点之间的匹配。
-
计算图像间变换矩阵的初始值。
-
迭代精炼H变换矩阵。
-
引导匹配。用估计的H去定义对极线附近的搜索区域,进一步确定特征点的对应。
-
重复迭代4)和5)直到对应点的数目稳定为止。
图像配准算子如下:
* 确定当前图像对的点匹配和变换。
proj_match_points_ransac (ImageF, ImageT, RowJunctionsF, ColJunctionsF, RowJunctionsT, ColJunctionsT, 'ncc', 21, 0, 0, 480, 640, 0, 0.5, 'gold_standard', 1, 4364537, ProjMatrix, Points1, Points2)
* 累加变换矩阵。
ProjMatrices := [ProjMatrices,ProjMatrix]
* 累积点数匹配和点数匹配。
Rows1 := [Rows1,subset(RowJunctionsF,Points1)]
Cols1 := [Cols1,subset(ColJunctionsF,Points1)]
Rows2 := [Rows2,subset(RowJunctionsT,Points2)]
Cols2 := [Cols2,subset(ColJunctionsT,Points2)]
NumMatches := [NumMatches,|Points1|]
* 生成表示平铺图像中提取点的十字。
* 请注意,我们必须考虑平铺图像中图像的行偏移。
gen_cross_contour_xld (PointsF, RowJunctionsF + (F - 1) * 500, ColJunctionsF, 6, rad(45))
gen_cross_contour_xld (PointsT, RowJunctionsT + (T - 1) * 500, ColJunctionsT, 6, rad(45))
* 生成匹配点对的线表示。我们从线条中创建XLD轮廓,以便放大图形窗口,更仔细地查看匹配。
RowF := subset(RowJunctionsF,Points1) + (F - 1) * 500
ColF := subset(ColJunctionsF,Points1)
RowT := subset(RowJunctionsT,Points2) + (T - 1) * 500
ColT := subset(ColJunctionsT,Points2)
gen_empty_obj (Matches)
for K := 0 to |RowF| - 1 by 1
gen_contour_polygon_xld (Match, [RowF[K],RowT[K]], [ColF[K],ColT[K]])
concat_obj (Matches, Match, Matches)
endfor
5 图像融合
根据仿射变换矩阵进行图像融合。
关闭窗口后重新打开
gen_projective_mosaic (Images, MosaicImage, 2, From, To, ProjMatrices, 'default', 'false', MosaicMatrices2D)
get_image_size (MosaicImage, Width, Height)
6 图像合并
单纯将多张图片按几行几列合并成一张大图并显示。
* 关闭窗口后重新打开
dev_close_window()
dev_open_window (0, 0, 600, 400, 'black', WindowHandle)
* 创建一张空白图片
gen_empty_obj (Images)
* 遍历文件夹
list_files ('C:/Users/Administrator/Desktop/test', ['files','follow_links'], ImageFiles)
tuple_regexp_select (ImageFiles, ['\\.(tif|tiff|gif|bmp|jpg|jpeg|jp2|png|pcx|pgm|ppm|pbm|xwd|ima|hobj)$','ignore_case'], ImageFiles)
for Index := 0 to |ImageFiles| - 1 by 1
read_image (Image, ImageFiles[Index])
* 缩放图片到图片统一大小
zoom_image_size (Image, ImageZoom, 200, 200, 'constant')
concat_obj (Images, ImageZoom, Images)
endfor
* 合并图片(按行填充,一行填满4张图片后再填充下一行)
tile_images (Images, TiledImage, 4, 'horizontal')
dev_display (TiledImage)
图像效果如下:
7 拼接示例
示例如图:
代码如下:
dev_update_off ()
dev_close_window ()
dev_open_window (0, 0, 640, 480, 'white', WindowHandle)
dev_set_color ('green')
set_display_font (WindowHandle, 14, 'mono', 'true', 'false')
* Read in the images and show them one-by-one. Please not the fold-like
* degradations running across the PCB.
gen_empty_obj (Images)
for J := 1 to 6 by 1
read_image (Image, 'mosaic/pcb_' + J$'02')
concat_obj (Images, Image, Images)
dev_display (Image)
disp_message (WindowHandle, 'Image ' + J$'d', 'window', 12, 12, 'black', 'true')
*wait_seconds (1)
endfor
disp_continue_message (WindowHandle, 'black', 'true')
stop ()
*显示用于计算投影的点匹配
*在图像之间进行转换,我们将以大屏幕显示所有图像
*平铺图像在图像之间留有一定的空间,以便
*这些图像中的大部分很容易看到。
dev_set_window_extents (-1, -1, 640 / 4, 2980 / 4)
tile_images_offset (Images, TiledImage, [0, 500, 1000, 1500, 2000, 2500], [0, 0, 0, 0, 0, 0], [-1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1], 640, 2980)
dev_clear_window ()
dev_display (TiledImage)
disp_message (WindowHandle, 'All 6 images', 'window', 12, 12, 'black', 'true')
disp_message (WindowHandle, 'Click \'Run\'\nto continue', 'window', 2980 / 4 - 50, 12, 'black', 'true')
stop ()
*现在我们计算五对图像之间的点匹配,并以此计算图像对之间的投影变换。
*请注意,下面的代码为每个图像对调用点运算符。由于图像形成一条带,
*只需稍加簿记,我们就可以通过保存上一次迭代的点来提高过程的效率
*(J对中的ImageT将与J+1对中的ImageF相同)。这里没有这样做,
*因为在一般情况下,这样的优化会非常麻烦,因为图像可能位于无法用条带表示的一般配置中。
dev_clear_window ()
dev_display (TiledImage)
disp_message (WindowHandle, 'Point matches', 'window', 12, 3, 'black', 'true')
*我们定义了图像对,即哪个图像应该映射到哪个图像。
From := [1, 2, 3, 4, 5]
To := [2, 3, 4, 5, 6]
Num := |From|
* 我们需要一个变量来累加投影变换矩阵。
ProjMatrices := []
*此外,因为我们想要在下面创建一个刚性马赛克,所以我们需要累积所有点对应和匹配图像对的数量。
Rows1 := []
Cols1 := []
Rows2 := []
Cols2 := []
NumMatches := []
* 现在我们可以确定五个图像对之间的转换。
for J := 0 to Num - 1 by 1
F := From[J]
T := To[J]
select_obj (Images, ImageF, F)
select_obj (Images, ImageT, T)
* 提取两幅图像中的点。
points_foerstner (ImageF, 1, 2, 3, 200, 0.3, 'gauss', 'false', RowJunctionsF, ColJunctionsF, CoRRJunctionsF, CoRCJunctionsF, CoCCJunctionsF, RowAreaF, ColAreaF, CoRRAreaF, CoRCAreaF, CoCCAreaF)
points_foerstner (ImageT, 1, 2, 3, 200, 0.3, 'gauss', 'false', RowJunctionsT, ColJunctionsT, CoRRJunctionsT, CoRCJunctionsT, CoCCJunctionsT, RowAreaT, ColAreaT, CoRRAreaT, CoRCAreaT, CoCCAreaT)
* 确定当前图像对的点匹配和变换。
proj_match_points_ransac (ImageF, ImageT, RowJunctionsF, ColJunctionsF, RowJunctionsT, ColJunctionsT, 'ncc', 21, 0, 0, 480, 640, 0, 0.5, 'gold_standard', 1, 4364537, ProjMatrix, Points1, Points2)
* 累加变换矩阵。
ProjMatrices := [ProjMatrices,ProjMatrix]
* 累积点数匹配和点数匹配。
Rows1 := [Rows1,subset(RowJunctionsF,Points1)]
Cols1 := [Cols1,subset(ColJunctionsF,Points1)]
Rows2 := [Rows2,subset(RowJunctionsT,Points2)]
Cols2 := [Cols2,subset(ColJunctionsT,Points2)]
NumMatches := [NumMatches,|Points1|]
* 生成表示平铺图像中提取点的十字。
* 请注意,我们必须考虑平铺图像中图像的行偏移。
gen_cross_contour_xld (PointsF, RowJunctionsF + (F - 1) * 500, ColJunctionsF, 6, rad(45))
gen_cross_contour_xld (PointsT, RowJunctionsT + (T - 1) * 500, ColJunctionsT, 6, rad(45))
* 生成匹配点对的线表示。我们从线条中创建XLD轮廓,以便放大图形窗口,更仔细地查看匹配。
RowF := subset(RowJunctionsF,Points1) + (F - 1) * 500
ColF := subset(ColJunctionsF,Points1)
RowT := subset(RowJunctionsT,Points2) + (T - 1) * 500
ColT := subset(ColJunctionsT,Points2)
gen_empty_obj (Matches)
for K := 0 to |RowF| - 1 by 1
gen_contour_polygon_xld (Match, [RowF[K],RowT[K]], [ColF[K],ColT[K]])
concat_obj (Matches, Match, Matches)
endfor
* 现在显示提取的数据。
dev_set_color ('blue')
dev_display (Matches)
dev_set_color ('green')
dev_display (PointsF)
dev_display (PointsT)
endfor
disp_message (WindowHandle, 'Click \'Run\'\nto continue', 'window', 2980 / 4 - 50, 12, 'black', 'true')
stop ()
* 最后,我们可以从投影变换生成马赛克图像。
gen_projective_mosaic (Images, MosaicImage, 2, From, To, ProjMatrices, 'default', 'false', MosaicMatrices2D)
get_image_size (MosaicImage, Width, Height)
dev_set_window_extents (-1, -1, Width / 3, Height / 3)
dev_clear_window ()
dev_display (MosaicImage)
disp_message (WindowHandle, 'Projective mosaic', 'window', 12, 12, 'black', 'true')
disp_message (WindowHandle, 'Click \'Run\'\nto continue', 'window', Height / 3 - 50, 12, 'black', 'true')
stop ()
* 为了更清楚地显示图像中可见的褶皱不是马赛克造成的,我们在马赛克图像中显示图像之间的接缝。
*这可以通过创建包含图像边界的图像、从中生成马赛克并分割生成的马赛克图像来实现。
get_image_size (Image, Width, Height)
gen_image_const (ImageBlank, 'byte', Width, Height)
gen_rectangle1 (Rectangle, 0, 0, Height - 1, Width - 1)
paint_region (Rectangle, ImageBlank, ImageBorder, 255, 'margin')
gen_empty_obj (ImagesBorder)
for J := 1 to 6 by 1
concat_obj (ImagesBorder, ImageBorder, ImagesBorder)
endfor
gen_projective_mosaic (ImagesBorder, MosaicImageBorder, 2, From, To, ProjMatrices, 'default', 'false', MosaicMatrices2D)
threshold (MosaicImageBorder, Seams, 128, 255)
dev_clear_window ()
dev_display (MosaicImage)
disp_message (WindowHandle, 'Seams between the\nimages', 'window', 12, 12, 'black', 'true')
dev_set_color ('yellow')
dev_display (Seams)
disp_message (WindowHandle, 'Click \'Run\'\nto continue', 'window', 550, 12, 'black', 'true')
stop ()
*如果你仔细观察上面的投影马赛克,你可能会注意到
*马赛克中有一个非常轻微的投影失真。这种情况会发生
*因为变换不能精确地确定
*因为噪声导致的点坐标误差非常小。因为
*在条形结构中,基本上是图像之间的重叠区域
*成对的图像可以像一个铰链一样绕着它旋转,离开图像平面。
*在这个例子中,我们知道图像之间的映射必须是刚性变换。如果我们想强制转换为刚性,
*我们可以简单地使用bundle_adjust_mosaic。
bundle_adjust_mosaic (6, 1, From, To, ProjMatrices, Rows1, Cols1, Rows2, Cols2, NumMatches, 'rigid', MosaicMatrices2D, Rows, Cols, Error)
* 现在,我们可以从刚性变换生成马赛克图像。
gen_bundle_adjusted_mosaic (Images, MosaicImageRigid, MosaicMatrices2D, 'default', 'false', TransMatrix2D)
get_image_size (MosaicImageRigid, Width, Height)
dev_set_window_extents (-1, -1, Width / 3, Height / 3)
dev_clear_window ()
dev_display (MosaicImageRigid)
disp_message (WindowHandle, 'Rigid mosaic', 'window', 12, 12, 'black', 'true')
近期评论