* This example program shows how several images of a PCB can be combined
* into a large mosaic image of the PCB. The program shows how to use
* proj_match_points_ransac and gen_projective_mosaic to achieve this.* Please note that the PCB has some degradations on its surface, which look
* like folds and may easily be mistaken as the seams between the images
* in the mosaic image. To show that this is not the case, the program
* also displays the true seams of the mosaic image.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 1read_image(Image,'mosaic/pcb_'+ J$'02')concat_obj(Images, Image, Images)dev_display(Image)disp_message(WindowHandle,'Image '+ J$'d','image',-1,-1,'black','true')wait_seconds(1)
endfor
disp_continue_message(WindowHandle,'black','true')stop()* To show the point matches that are used to compute the projective
* transformation between the images, we will show all images in a large
* tiled image with some space between the images so that the extents
* of the images are easily visible.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()* Now we compute point matches between the five pairs of images and with this* the projective transformation between the image pairs. Note that the code
* below calls the point operatorfor each image pair. Since the images form
* a strip, with a little book keeping we could make the process a little more
* efficient by saving the points from the last iteration(ImageT in pair J will
* be identical to ImageF in pair J+1). This is not done here because such an
* optimization would be quite cumbersome in the general case where the images
* can lie in a general configuration that cannot be represented by a strip.dev_clear_window()dev_display(TiledImage)disp_message(WindowHandle,'Point matches','window',12,3,'black','true')* We define the image pairs, i.e., which image should be mapped to which image.
From :=[1,2,3,4,5]
To :=[2,3,4,5,6]
Num :=|From|* We need a variable to accumulate the projective transformation matrices.
ProjMatrices :=[]* Furthermore, since we want to create a rigid mosaic below we need to
* accumulate all the point correspondences and the number of matches per
* image pair.
Rows1 :=[]
Cols1 :=[]
Rows2 :=[]
Cols2 :=[]
NumMatches :=[]* Now we can determine the transformations between the five image pairs.for J :=0 to Num -1 by 1
F := From[J]
T := To[J]select_obj(Images, ImageF, F)select_obj(Images, ImageT, T)* Extract the points in both images.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)* Determine the point matches and the transformation for the current
* image pair.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)* Accumulate the transformation matrix.
ProjMatrices :=[ProjMatrices,ProjMatrix]* Accumulate the point matches and number of point matches.
Rows1 :=[Rows1,subset(RowJunctionsF,Points1)]
Cols1 :=[Cols1,subset(ColJunctionsF,Points1)]
Rows2 :=[Rows2,subset(RowJunctionsT,Points2)]
Cols2 :=[Cols2,subset(ColJunctionsT,Points2)]
NumMatches :=[NumMatches,|Points1|]* Generate crosses that represent the extracted points in the tiled image.* Note that we have to take the row offsets of the images in the tiled image
* into account.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))* Generate a representation of the matched point pairs as lines. We create
* XLD contours from the lines so that we can zoom into the graphics window
* to take a closer look at the matches.
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 1gen_contour_polygon_xld(Match,[RowF[K],RowT[K]],[ColF[K],ColT[K]])concat_obj(Matches, Match, Matches)
endfor
* Now display the extracted data.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()* Finally, we can generate the mosaic image from the projective transformations.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()* To show more clearly that the folds visible in the image donot result from the
* mosaicking, we display the seams between the images in the mosaic image.* This can be done most easily by creating an image that contains the border
* of the images, generating a mosaic from it,and segmenting the resulting
* mosaic image.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 1concat_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()* If you look very closely at the projective mosaic above, you may note that
* there is a very slight projective distortion in the mosaic. This happens
* because the transformations cannot be determined with perfect accuracy
* because of very small errors in the point coordinates due to noise. Because
* of the strip configuration, essentially the overlapping area between the image
* pairs can act like a hinge around which the images may rotate out of the image
* plane. In this example, we know that the mapping between the images must
* be a rigid transformation. If we want to force the transformation to be rigid
* we can simply use bundle_adjust_mosaic.bundle_adjust_mosaic(6,1, From, To, ProjMatrices, Rows1, Cols1, Rows2, Cols2, NumMatches,'rigid', MosaicMatrices2D, Rows, Cols, Error)* Now, we can generate the mosaic image from the rigid transformations.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')