【快速阅读三】使用泊松融合实现单幅图的无缝拼贴及消除两幅图片直接的拼接缝隙。
在【快速阅读二】从OpenCv的代码中扣取泊松融合算子(Poisson Image Editing)并稍作优化 一文的最后,我曾经提到有个使用泊松融合来来实现Seamless Tiling的效果,我自己尝试去实现,暂时没有获取正确的结果,论文里给出的效果如下:
一开始我没怎么看这个tinling的意思,总是以为算法的目的是左图通过泊松融合的处理,能够处理成右图的效果,所以怎么测试也打不到真确的结果。
后面又看了几篇文章,原来他并不是这个意思,注意到上面左图里上下共有2*3个相同的块,如下图所示:
他的意思是通过修改某种边界条件对这个图使用泊松融合,得到后的结果图,再进行拼接就可以得到那种无缝的效果了,即先泊松,再拼接,而不是先拼接,后泊松。我们去看看Tiling这个单词的意思也明白这个道理了。
那么在原文中他说只需要 we have chosen fnorth = fsouth = 0:5(gnorth + gsouth), and similarly for the eastand west borders.就可以了。
后面我在泊松图像编辑(Possion Image Edit)原理、实现与应用 这位仁兄的博客的尾部也看到了关于这个过程的一个较为详细的注释,直接复制他博客里的图吧(谢谢)。
即用原图的梯度场,用修改后边界后的图作为融合的前景图进行融合,这样得到的融合的结果图,一个简单的代码如下所示:
void IM_SeamlessTiling(unsigned char* Src, unsigned char* Dest, int Width, int Height, int Stride) { int Channel = Stride / Width; unsigned char* Fore = (unsigned char*)malloc(Height * Stride * sizeof(unsigned char)); unsigned char* Mask = (unsigned char*)calloc(Height * Width, sizeof(unsigned char)); memcpy(Fore, Src, Height * Stride); // 上下边界和左右边界设为原图像的边界和的一半。 for (int X = 0; X < Width * Channel; X++) { Fore[X] = (Src[X] + Src[(Height - 1) * Stride + X]) / 2; Fore[(Height - 1) * Stride + X] = Fore[X]; } for (int Y = 1; Y < Height - 1; Y++) { for (int C = 0; C < Channel; C++) { Fore[Y * Stride + C] = (Src[Y * Stride + C] + Src[Y * Stride + (Width - 1) * Channel + C]) / 2; Fore[Y * Stride + (Width - 1) * Channel + C] = Fore[Y * Stride + C]; } } memset(Mask , 255, Width * Height); // 然后引导向量场仍然使用原图像的梯度场。这样得到的结果就可以用来无缝拼接 // 最后一个参数为0,倒数第二个参数也为,Mask设置为全255,则就是直接使用Src的梯度场了 IM_NormalClone(Fore, Src, Mask, Dest, Width, Height, Stride, 0, 0); free(Fore); free(Mask); }
在原始的opencv代码里,对于蒙版在内部有个如下的操作:
Mat Kernel(Size(3, 3), CV_8UC1); Kernel.setTo(Scalar(1)); erode(binaryMask, binaryMask, Kernel, Point(-1,-1), 3);
我感觉我在上一篇文章里说到他的作用是错误的,按理说不应该需要这个的,但是实际测试就是用了这个对于非规则的图还是有一定好处的,他融合的更为自然,但是在这里的Tiling的应用中,就不应该需要了。
这个简单的操作的效果确实还是蛮好玩的,除去论文里那个图,我们有弄了几副测试图,效果如下:
直接拼贴的效果 泊松融合后拼接的效果
以下是拼接过程中使用到的小图。
可见进行泊松融合后的图,弥合的非常自然。
还是在同一篇博客里,作者还对这个想法进行了扩展,因为前面的融合是对单幅图四个边进行融合,另外一个更为常见的情形是对两个不同的图片在某一条边进行融合,这个情况再图像拼接, 多福图像合成等等方面也有着较为常见的应用。
比如两幅图在水平方向进行拼接,这个时候就可以用如下的思路来解决问题(也是直接拷贝的作者的原图,谢谢):
这个图的思路也是很简单的,左右两幅图拼接,则在左侧图的最右侧那一列以及右侧图最左侧那一列,都用两幅图的平均值代替,然后分别用两幅图各自的梯度场进行泊松融合,得到的两幅图再进行拼接,就显得自然了。
一个简单的代码如下所示:
void IM_InnerSeamlessStitching(HBitmap Src1, HBitmap Src2, HBitmap Dest1, HBitmap Dest2, int Direction) { int Channel = Src1.Stride / Src1.Width; if (Src1.PixelFormat != Src1.PixelFormat) return; if ((Direction == 0) && (Src1.Height != Src2.Height)) return; if ((Direction == 1) && (Src1.Width != Src2.Width)) return; unsigned char* Fore1 = (unsigned char*)malloc(Src1.Height * Src1.Stride * sizeof(unsigned char)); unsigned char* Fore2 = (unsigned char*)malloc(Src2.Height * Src2.Stride * sizeof(unsigned char)); unsigned char* Mask1 = (unsigned char*)calloc(Src1.Height * Src1.Width, sizeof(unsigned char)); unsigned char* Mask2 = (unsigned char*)calloc(Src2.Height * Src2.Width, sizeof(unsigned char)); memcpy(Fore1, Src1.Scan0, Src1.Height * Src1.Stride); memcpy(Fore2, Src2.Scan0, Src2.Height * Src2.Stride); // 水平方向融合 if (Direction == 0) { for (int Y = 0; Y < Src1.Height; Y++) { if (Channel == 1) { int Avg = (Src1.Scan0[Y * Src1.Stride + Src1.Width - 1] + Src2.Scan0[Y * Src2.Stride]) / 2; Fore1[Y * Src1.Stride + Src1.Width - 1] = Avg; Fore2[Y * Src2.Stride] = Avg; } else { int AvgB = (Src1.Scan0[Y * Src1.Stride + (Src1.Width - 1) * 3 + 0] + Src2.Scan0[Y * Src2.Stride + 0]) / 2; int AvgG = (Src1.Scan0[Y * Src1.Stride + (Src1.Width - 1) * 3 + 1] + Src2.Scan0[Y * Src2.Stride + 1]) / 2; int AvgR = (Src1.Scan0[Y * Src1.Stride + (Src1.Width - 1) * 3 + 2] + Src2.Scan0[Y * Src2.Stride + 2]) / 2; Fore1[Y * Src1.Stride + (Src1.Width - 1) * 3 + 0] = AvgB; Fore1[Y * Src1.Stride + (Src1.Width - 1) * 3 + 1] = AvgG; Fore1[Y * Src1.Stride + (Src1.Width - 1) * 3 + 2] = AvgR; Fore2[Y * Src2.Stride + 0] = AvgB; Fore2[Y * Src2.Stride + 1] = AvgG; Fore2[Y * Src2.Stride + 2] = AvgR; } } } else if (Direction == 1) { for (int X = 0; X < Src1.Width * Channel; X++) { // 上下两个可以采用不一样的值,但是做点融合也是不错的,实测还是有点小问题 // Fore1[(Src1.Height - 1) * Src1.Stride + X] = (Src1.Scan0[(Src1.Height - 1) * Src1.Stride + X] * 77 + Src2.Scan0[X] * 51) / 128; // Fore2[X] = (Src1.Scan0[(Src1.Height - 1) * Src1.Stride + X] * 51 + Src2.Scan0[X] * 77) / 128;*/ int Avg = (Src1.Scan0[(Src1.Height - 1) * Src1.Stride + X] + Src2.Scan0[X]) / 2; Fore1[(Src1.Height - 1) * Src1.Stride + X] = Avg; Fore2[X] = Avg; } } memset(Mask1, 255, Src1.Width * Src1.Height); memset(Mask2, 255, Src2.Width * Src2.Height); // 然后引导向量场仍然使用原图像的梯度场。这样得到的结果就可以用来无缝拼接 // 最后一个参数为0,倒数第二个参数也为,Mask设置为全255,则就是直接使用Src的梯度场了 IM_NormalClone(Fore1, Src1.Scan0, Mask1, Dest1.Scan0, Src1.Width, Src1.Height, Src1.Stride, 0, 0); IM_NormalClone(Fore2, Src2.Scan0, Mask2, Dest2.Scan0, Src2.Width, Src2.Height, Src2.Stride, 0, 0); free(Fore1); free(Fore2); free(Mask1); free(Mask2); }
我手上没有合适的图片,就自己随意处理了结果图,感觉还有那么一点用处:
可以看到,使用泊松融合后,已经没有明显的分解线了。
这个算法有一个很好的用法就是,对于某些大图像,需要分块进行处理,但是分块后必然会出现块于块之间出现过渡不自然的情况,直接拼接在一起有明显的痕迹,用上面的泊松融合则可以解决这个问题。
另外, 实际中可能还会遇到分界线不是垂直或者水平的现象,这种,个人感觉也是可以通过泊松融合来解决,只要能确定相应的分解mask,在分解线处求平均值,就一样能把边缘值的影响平均到图像内部,这个暂时还没有去研究。可以留待后续继续处理。
另外,提一个算法优化方面的问题,如果图像比较大,直接处理会打来很大的算法效率瓶颈,那么在这里其实是没有必要全图处理的,可以在相邻的区域取一定范围的像素进行融合就可以了,而且这个一定范围也可以做些固化,比如我们知道在OpenCV里不同长度的FFT其运算效率是不同的,对于有些数据,效率很高,因此,我们可以取这些值来做运算。、
对应的测试Demo和测试图片已经更新,下载地址: https://files.cnblogs.com/files/Imageshop/PossionBlending.rar?t=1705395766&download=true
如果想时刻关注本人的最新文章,也可关注公众号或者添加本人微信: laviewpbt
热门相关:大明武夫 惹火甜妻:老公大人,宠上瘾! 少林武僧在异界 少林武僧在异界 顺明