之前发了数篇系列博文来仔细研究Poisson Image Editing算法,每次重新审视和深入,仿佛都能有更为深刻的认识和很大的收获。这应该算是我这个系列的完结篇,会用用Matlab代码一点一点的演示,原文作者到底是如何设计和实现他那个强大且影响深远的算法的。希望你在看本文之前务必参考一下文章来了解算法原理,本文将主要讲解编程实现的问题,对于前面讲过的内容,我不会深究。但我个人总体的感觉是,现在图像处理算法对数学的要求是越来越高了,像泊松融合、泊松抠图这样的算法如果没有偏微分方程(本算法中涉及Poisson Equation)、数值计算(需要高斯-塞德尔迭代法或者共轭梯度法)、场论(涉及散度、梯度和拉普拉斯算子)、矩阵论(涉及大型稀疏矩阵线性方程组)、数学物理方法(涉及带狄利克雷条件的椭圆曲线方程)、最优化理论(涉及到了欧拉-朗格拉日变分法)这些艰深的数学知识,要理解起来实在太困难了(每一步都是一个坎,我感觉有一个地方过不了,那篇经典论文的精华就无法领会,更别说编程实现了)。
素材是三张图片,如下
bear.jpg bear-mask.jpg
pool-target.jpg
首先,我们清理一下Matlab的环境:
然后,读入三张图片(注意需要把mask图以二值图的形式读入)
- TargetImg = imread('pool-target.jpg');
- SourceImg = imread('bear.jpg');
- SourceMask = im2bw(imread('bear-mask.jpg'));
用函数bwboundaries(W,CONN) 来获取二值图中对象的轮廓,其中CONN 为8或4,指示联通性采用4方向邻域点判别还是8方向邻域点判别,默认为8。
- [SrcBoundry, ~] = bwboundaries(SourceMask, 8);
然后我们把这个轮廓在SourceImg上绘制出来,显示出我们将要剪切的区域。参数 'r' 表示红色。
- figure, imshow(SourceImg), axis image
- hold on
- for k = 1:length(SrcBoundry)
- boundary = SrcBoundry{k};
- plot(boundary(:,2), boundary(:,1), 'r', 'LineWidth', 2)
- end
- title('Source image intended area for cutting from');
所得之结果如下图
设定source将要粘贴在target图中的具体位置,并获取TargetImg的长和宽。
- position_in_target = [10, 225];%xy
- [TargetRows, TargetCols, ~] = size(TargetImg);
函数find()的作用:b=find(X),X是一个矩阵,查询非零元素的位置,如果X是一个行向量,则返回一个行向量,否则,返回一个列向量。
- [row, col] = find(SourceMask);
然后来计算mask框在source图中的大小。
- start_pos = [min(col), min(row)];
- end_pos = [max(col), max(row)];
- frame_size = end_pos - start_pos;
如果在position_in_target的位置放置frame将超出Target图的范围,则改变position_in_target,以保证frame不会超出Target图的范围。
- if (frame_size(1) + position_in_target(1) > TargetCols)
- position_in_target(1) = TargetCols - frame_size(1);
- end
-
- if (frame_size(2) + position_in_target(2) > TargetRows)
- position_in_target(2) = TargetRows - frame_size(2);
- end
构建一个大小与Target图相等的新的Mask,然后在position_in_target的位置放入SourceMask。
- MaskTarget = zeros(TargetRows, TargetCols);
- MaskTarget(sub2ind([TargetRows, TargetCols], row - start_pos(2) + position_in_target(2), ...
- col - start_pos(1) + position_in_target(1))) = 1;
此时我们得到的新MaskTarget如下图所示
同前面一样,获取二值图像中对象的轮廓,然后把这个轮廓在TargetImg上绘制出来。
- TargBoundry = bwboundaries(MaskTarget, 8);
- figure, imshow(TargetImg), axis image
- hold on
- for k = 1:length(TargBoundry)
- boundary = TargBoundry{k};
- plot(boundary(:,2), boundary(:,1), 'r', 'LineWidth', 1)
- end
- title('Target Image with intended place for pasting Source');
结果如下图所示
根据文章所给出的算法我们知道,对于Mask轮廓的内部,我们是不考虑边界项的,此时计算梯度(G)的散度div,就是执行一个拉普拉斯算子,如下
div ( G( Source(x,y) ) ) = -4 f(x,y) + f(x-1,y) + f(x,y-1) + f(x+1,y) + f(x,y+1)
对SourceImg执行拉普拉斯算子,然后提取结果的R、G、B三个分量。
- templt = [0 -1 0; -1 4 -1; 0 -1 0];
- LaplacianSource = imfilter(double(SourceImg), templt, 'replicate');
- VR = LaplacianSource(:, :, 1);
- VG = LaplacianSource(:, :, 2);
- VB = LaplacianSource(:, :, 3);
然后根据Mask,把上述计算结果贴入TargetImg。
- TargetImgR = double(TargetImg(:, :, 1));
- TargetImgG = double(TargetImg(:, :, 2));
- TargetImgB = double(TargetImg(:, :, 3));
-
- TargetImgR(logical(MaskTarget(:))) = VR(SourceMask(:));
- TargetImgG(logical(MaskTarget(:))) = VG(SourceMask(:));
- TargetImgB(logical(MaskTarget(:))) = VB(SourceMask(:));
-
- TargetImgNew = cat(3, TargetImgR, TargetImgG, TargetImgB);
- figure, imagesc(uint8(TargetImgNew)), axis image, title('Target image with laplacian of source inserted');
结果如下图所示
然后,我们要来计算稀疏的临接矩阵。(这个地方需要参考前面给出的博文1)
如果简单从代码来分析,假设我们现在有这样一个mask
- mask =
-
- 0 0 0 0 0
- 0 1(1) 1(2) 0 0
- 0 1(3) 1(4) 1(5) 0
- 0 0 1(6) 1(7) 0
- 0 0 0 0 0
那么我们就要建立一个7×7的邻接矩阵,例如(1)是(2)邻接的,所以下面矩阵中[1,2]和[2,1]的位置就1。
- >> neighbors = calcAdjancency( mask );
- >> full(neighbors)
-
- ans =
-
- 0 1 1 0 0 0 0
- 1 0 0 1 0 0 0
- 1 0 0 1 0 0 0
- 0 1 1 0 1 1 0
- 0 0 0 1 0 0 1
- 0 0 0 1 0 0 1
- 0 0 0 0 1 1 0
下面我们给出函数calcAdjancency()的实现代码:
- function neighbors = calcAdjancency( Mask )
-
- [height, width] = size(Mask);
- [row_mask, col_mask] = find(Mask);
-
- neighbors = sparse(length(row_mask), length(row_mask), 0);
-
- %下标转索引
- roi_idxs = sub2ind([height, width], row_mask, col_mask);
-
- for k = 1:size(row_mask, 1),
- %4 邻接点
- connected_4 = [row_mask(k), col_mask(k)-1;%left
- row_mask(k), col_mask(k)+1;%right
- row_mask(k)-1, col_mask(k);%top
- row_mask(k)+1, col_mask(k)];%bottom
-
- ind_neighbors = sub2ind([height, width], connected_4(:, 1), connected_4(:, 2));
-
- %二分查找函数 i = ismembc2(t, X):返回t在X中的位置,其中X必须为递增的的数值向量
- for neighbor_idx = 1: 4, %number of neighbors,
- adjacent_pixel_idx = ismembc2(ind_neighbors(neighbor_idx), roi_idxs);
- if (adjacent_pixel_idx ~= 0)
- neighbors(k, adjacent_pixel_idx) = 1;
- end
- end
- end
- end
上述代码中第一个应该知道的地方是Matlab的二分查找函数ismembc2(),这一点我的注释已经比较明确,不再赘言。另一个地方是下标转索引的函数sub2ind()。下面这个例子说明了它的用法和意义:
- % % e.g. M =
- %
- % 11(1) 12(5) 13 14
- % 21 22(6) 23 24
- % 31 32(7) 33 34
- % 41 42 43 44
-
- % roi_idxs = sub2ind(size(M), [1, 1, 2, 3], [2, 1, 2, 2])
-
-
六六分期app的软件客服如何联系?不知道吗?加qq群【895510560】即可!标题:六六分期
阅读:19286|2023-10-27
-
今天小编告诉大家如何处理win10系统火狐flash插件总是崩溃的问题,可能很多用户都不知
阅读:10021|2022-11-06
-
今天小编告诉大家如何对win10系统删除桌面回收站图标进行设置,可能很多用户都不知道
阅读:8344|2022-11-06
-
今天小编告诉大家如何对win10系统电脑设置节能降温的设置方法,想必大家都遇到过需要
阅读:8713|2022-11-06
-
我们在使用xp系统的过程中,经常需要对xp系统无线网络安装向导设置进行设置,可能很多
阅读:8658|2022-11-06
-
今天小编告诉大家如何处理win7系统玩cf老是与主机连接不稳定的问题,可能很多用户都不
阅读:9688|2022-11-06
-
电脑对日常生活的重要性小编就不多说了,可是一旦碰到win7系统设置cf烟雾头的问题,很
阅读:8647|2022-11-06
-
我们在日常使用电脑的时候,有的小伙伴们可能在打开应用的时候会遇见提示应用程序无法
阅读:8014|2022-11-06
-
今天小编告诉大家如何对win7系统打开vcf文件进行设置,可能很多用户都不知道怎么对win
阅读:8683|2022-11-06
-
今天小编告诉大家如何对win10系统s4开启USB调试模式进行设置,可能很多用户都不知道怎
阅读:7550|2022-11-06
|
请发表评论