有2到3年没有逛CodeProject了,上班一时无聊,就翻翻这个比较有名的国外网站,在其Articles » Multimedia » General Graphics » Graphics一栏看到exture Transfer using Efros & Freeman's Image Quilting Algorithm顿感兴趣,其效果颇有特点,又激起了我很久没写代码的心,想一想在这个算法上大概前先后后思索了1个多星期,虽然最终仍是没有获得我想要的结果,但并不全无收获,至此国庆佳节加班之际抽空总结一下,以便慰藉我孤独的心灵。html
纹理图像的合成算法在早期的Photoshop中我记得是有一个单独的功能的,在后来的版本中不知道为何被取消了,印象中他能将只有几颗小草的图片生长成不少小草,而且基本看不出什么瑕疵和不天然的地方,那么CodeProject上的这个Quilting算法也有相似的能力,首先贴几张处理的结果图欣赏一下。web
原始纹理图像 由小纹理合成的大纹理图像算法
那么简单的描述下这篇文章所使用的算法的过程吧。编程
算法须要的输入:原始的纹理图像(W * H),块的大小TileSize,重叠部分的大小Overlap。函数
第一步:咱们从原始的纹理图像中一个随机的抽取一个小块,放到目标图像的左上角。post
无需解释,其中黑色的部分表示目标图像还没有处理的部分。大数据
第二步:按照从左到右,从上到下,抽取出块重叠的部分的数据,并计算这部分数据和原始纹理图像中各块的类似度。优化
水平重叠 垂直重叠 水平和垂直部分具备重叠网站
要达到合成天然,在有了第一个块以后,其周边的块应该从原图中选择和其最类似的部位, 所以 ,咱们从已经合成的块中抽取部分重叠的数据,如上图所示,红色方框内,没有红色网格线的部分即为重叠的内容,在整个的处理过程当中,会有三种状况出现,分别如上图所示,即(1)只有垂直方向有部分重叠;(2)只有水平发方向有部分重叠;(3)水平和垂直方向都有重叠。ui
为了选择合适的下一个块,咱们结算这些重叠的块和原图中的全部块的类似度,块和块之间的类似度能够有不少准则计算,最经常使用的莫过于SSD(Sum of Squared Distance),这里咱们也采用这种准则。
对于第一种只有垂直方向有部分重叠的状况,块的可能性有 (W - Overlap)* (H - TileSize)种,而只有水平发方向有部分重叠时,块的可能性有(W - TileSize)* (H - Overlap)种,当水平和垂直方向都有重叠时,只有(W - TileSize)* (H - TileSize)种块的选择方案。
对于水平和垂直有交叉的状况,须要分别计算水平重叠的类似性A,垂直重叠的类似性B以及交叉部位的类似性AB,最终的类似性由A + B - AB决定。
计算完类似性后,通常状况下,咱们可取类似度最小的块为下一个块,固然为了随机性更强,也能够取类似性序列中前N个最小值种的某个块为选中的块。
第三步:若是对选中的块直接进行拼贴到目标图中,则很明显两个块之间由过渡不会太天然,一种较好的方式就是在选中的块和重叠的部分找到一条路径,在该路径的两侧像素的距离和最小,若是使用暴力的方式去寻找这条路径,则是很是耗时和没必要要的,文章介绍使用了贪心算法来寻找这条路径。
咱们拿水平重叠的块来讲明问题,首先找到第一行最小距离和的像素的位置,假定为(x,1),接着咱们搜索第二行的(x - 1, 2), (x, 2), (x + 1, 2)三个位置的距离和的最小值,假定是 (x - 1, 2)是第二行的最小值,则第三行须要搜索的位置为(x - 2, 3), (x - 1, 3), (x, 3),依类类推直到最后一行。
其实路径必然是连续的,所以这种只搜索向下的三领域的作法是彻底合理的。
通常状况下,作图像算法会按照从左到右,从上到下的顺序进行处理,这样可以充分利用cache line的优点,若是按照从上到下,而后在从左到右的方式来处理,虽然逻辑和结果是同样的,一般会须要更多的时间,所以,在计算垂直的块的路径时,咱们能够借用水平块的算法,只要对垂直的块的数据先进行转置,处理转置的数据获得对应的数据,而后在把这个数据转置就获得了垂直块的结果。注意这里是用的转置,而不是旋转,由于旋转对于该问题是的到的结果是镜像的,因此必须注意。
在水平和垂直部分具备重叠的块的计算时,咱们是分别计算垂直和水平的路径,而后取两个路径的交集,计算过程分别以下图。
第四步: 按照路径的位置贴入新的数据。
编程技巧:
(1) 在整个的计算中,计算SSD是最为耗时的,所以对其优化是调高程序效率的关键。
咱们看一段matlab的代码,以下所示:
%function Z = ssd(X, Y)
%Computes the sum of squared distances between X and Y for each possible
% overlap of Y on X. Y is thus smaller than X
%
%Inputs:
% X - larger image
% Y - smaller image
%
%Outputs:
% Each pixel of Z contains the ssd for Y overlaid on X at that pixel
function Z = ssd(X, Y)
K = ones(size(Y,1), size(Y,2));
for k=1:size(X,3),
A = X(:,:,k);
B = Y(:,:,k);
a2 = filter2(K, A.^2, 'valid');
b2 = sum(sum(B.^2));
ab = filter2(B, A, 'valid').*2;
if( k == 1 )
Z = ((a2 - ab) + b2);
else
Z = Z + ((a2 - ab) + b2);
end;
end;
这里的优化时经过卷积实现的,由于SSD的计算就是(a-b)^2的累积和,展开为a^2 + b^2 - 2ab,其中a为固定的图像,那么a^2则为必定值,b^2是不断变化的,可是也能够用积分图之类的算法实现快速效果,惟一的耗时时2ab项,能够用卷积来实现。
快速的卷积算法能够经过FFT来实现,也能够借鉴我在图像处理中任意核卷积(matlab中conv2函数)的快速实现 一文中提到的用SSE的方式实现,鉴于这里的卷积更有其特殊性,他是2幅图像之间的卷积,所以参与计算的都是byte类型,则能够经过选择更为合适的SSE函数来进一步提升效率,这里要感谢有关高手提供的一段SSE代码。
/// <summary> /// 基于SSE的字节数据的乘法。 /// </summary> /// <param name="Kernel">须要卷积的核矩阵。</param> /// <param name="Conv">卷积矩阵。</param> /// <param name="Length">矩阵全部元素的长度。</param> /// <remarks> 1: 使用了SSE优化。 /// <remarks> 2: 感谢采石工(544617183)提供的SSE代码<。</remarks> /// https://msdn.microsoft.com/zh-cn/library/t5h7783k(v=vs.90).aspx int MultiplySSE(unsigned char* Kernel, unsigned char * Conv, int Length) { int Y, Sum; __m128i vsum = _mm_set1_epi32(0); __m128i vk0 = _mm_set1_epi8(0); for (Y = 0; Y <= Length - 16; Y += 16) { __m128i v0 = _mm_loadu_si128((__m128i*)(Kernel + Y)); // 对应movdqu指令,不须要16字节对齐 __m128i v0l = _mm_unpacklo_epi8(v0, vk0); __m128i v0h = _mm_unpackhi_epi8(v0, vk0); // 此两句的做用是把他们分别加载到两个128位寄存器中,供下面的_mm_madd_epi16的16位SSE函数调用(vk0的做用主要是把高8位置0) __m128i v1 = _mm_loadu_si128((__m128i*)(Conv + Y)); __m128i v1l = _mm_unpacklo_epi8(v1, vk0); __m128i v1h = _mm_unpackhi_epi8(v1, vk0); vsum = _mm_add_epi32(vsum, _mm_madd_epi16(v0l, v1l)); // _mm_madd_epi16 能够一次性进行8个16位数的乘法,而后把两个16的结果在加起来,放到一个32数中, r0 := (a0 * b0) + (a1 * b1),详见 https://msdn.microsoft.com/zh-cn/library/yht36sa6(v=vs.90).aspx vsum = _mm_add_epi32(vsum, _mm_madd_epi16(v0h, v1h)); } for (; Y <= Length - 8; Y += 8) { __m128i v0 = _mm_loadl_epi64((__m128i*)(Kernel + Y)); __m128i v0l = _mm_unpacklo_epi8(v0, vk0); __m128i v1 = _mm_loadl_epi64((__m128i*)(Conv + Y)); __m128i v1l = _mm_unpacklo_epi8(v1, vk0); vsum = _mm_add_epi32(vsum, _mm_madd_epi16(v0l, v1l)); } vsum = _mm_add_epi32(vsum, _mm_srli_si128(vsum, 8)); vsum = _mm_add_epi32(vsum, _mm_srli_si128(vsum, 4)); Sum = _mm_cvtsi128_si32(vsum); // MOVD函数,Moves the least significant 32 bits of a to a 32-bit integer: r := a0 for ( ; Y < Length; Y++) { Sum += Kernel[Y] * Conv[Y]; } return Sum; }
以上代码在大数据量时能够一次性完成16个字节数据的乘法及累加,大大的提升了效率。
以上计算SSD的方式也能够帮助提升标准的模板匹配算法的速度,这个我想有心的人应该不难实现。
可是即便采用了这种快速的计算,整个纹理合成的速度仍是至关的慢,要使得该算法具备实际的实用价值,还的寻找快速的块类似度平价方法。
因为程序速度问题,我对纹理传输的编写已经失去了信心,纹理传输过程总的和纹理合成和相似,只是在计算类似度时还要考虑目标的诸如亮度或者模糊只方面的信息,速度会比这个纹理合成还要慢,有兴趣的朋友能够看看我提供的一些连接,里面基本都有参考代码。
不过纹理传输产生的效果有的时候确实比较酷:
那天心血来潮我仍是去实现下这个效果吧。
纹理合成完整的工程下载:
http://files.cnblogs.com/files/Imageshop/ImageQuilting.rar
参考资料:
http://web.engr.illinois.edu/~vrgsslv2/cs498dwh/proj2/
http://www.codeproject.com/Articles/24172/Texture-Transfer-using-Efros-Freeman-s-Image-Quilt
http://mesh.brown.edu/dlanman/courses.html
****************************做者: laviewpbt 时间: 2015.10.1 联系QQ: 33184777 转载请保留本行信息**********************