0)先造一个带孔洞的测试图
matlab
%% ===== 0. 生成/读入灰度图 + 挖洞 =====
clc; clear;
% 造一个简单合成图(免得你没有图片文件也能跑)
[x, y] = meshgrid(linspace(-1,1,256));
I = 0.5 + 0.3*sin(4*pi*x) .* cos(3*pi*y);
I = I - min(I(:)); I = I/max(I(:)); % 归一化到 [0,1]
% ---- 挖一个洞(圆形) ----
[cx, cy, r] = deal(128, 128, 38);
[X, Y] = meshgrid(1:256, 1:256);
hole = (X-cx).^2 + (Y-cy).^2 <= r^2;
u = I; % 当前修复图
u(hole) = 0; % 把洞"擦掉"(模拟丢失)
% mask: 1=待修复(洞), 0=已知
mask = hole;
bdry = ~mask & imdilate_logical(mask); % 洞的边界邻域(手写版,见下)
figure(1); clf; imshow(u,[]); title('输入:带孔洞');
手写 imdilate_logical(不用 strel/imdilate)
matlab
function B = imdilate_logical(M)
% 对 logical 矩阵做 3×3 结构元膨胀(手写)
[m,n] = size(M);
B = M;
for i=2:m-1
for j=2:n-1
if ~M(i,j) && (M(i-1,j)||M(i+1,j)||M(i,j-1)||M(i,j+1))
B(i,j)=true;
end
end
end
end
1)方法一:Laplace 修复 ------ Δu = 0(调和延拓)
思想
在孔洞里做离散 Laplace = 0 ,等价于反复做四邻均值:
ui,jnew=ui−1,j+ui+1,j+ui,j−1+ui,j+14u_{i,j}^{\text{new}}=\frac{u_{i-1,j}+u_{i+1,j}+u_{i,j-1}+u_{i,j+1}}{4}ui,jnew=4ui−1,j+ui+1,j+ui,j−1+ui,j+1
已知像素永远钉住不动。
手写实现
matlab
function u = laplace_inpaint(I, mask, varargin)
% u = laplace_inpaint(I, mask [,maxIter,tol])
% I : 灰度图 [0,1]
% mask : logical, 1=洞(待修复), 0=已知
maxIter = 3000; tol = 1e-6;
if nargin>=3, maxIter=varargin{1}; end
if nargin>=4, tol=varargin{2}; end
u = I;
M = double(mask); % 1 in hole
K = 1-M; % 1 on known region (钉住)
for it=1:maxIter
u_old = u;
% 四邻求和(vectorized,不用 conv 也行;这里用 conv2 最清楚)
% 模板 [0 1 0; 1 0 1; 0 1 0],但我们要的是 (u(i±1)+u(j±1))/4
% 写成两个 1D 卷积更直观:
u_new = 0.25 * ( ...
circshift(u,[-1 0]) + circshift(u,[1 0]) + ...
circshift(u,[0 -1]) + circshift(u,[0 1]) );
% 只在洞里写回去;已知像素钉住
u = K.*I + M.*u_new;
err = max(abs(u(:)-u_old(:)));
if err<tol
fprintf('Laplace: 收敛 iter=%d err=%.2e\n',it,err); break; end
end
end
调用:
matlab
u1 = laplace_inpaint(u, mask, 4000, 1e-6);
figure(2); clf; imshow(u1,[]); title('Laplace 修复');
你看到的视觉效果
Laplace 修复的结果在数学上是最"光滑"的,但会跨边缘糊过去------因为调和函数没有"结构方向感",只会沿最短路径插值。
2)方法二:TV-Inpainting
方程(散度形式)
∂tu=∇⋅(∇u∣∇u∣2+ε2)\partial_t u=\nabla\cdot\left(\frac{\nabla u}{\sqrt{|\nabla u|^2+\varepsilon^2}}\right)∂tu=∇⋅(∣∇u∣2+ε2 ∇u)
in hole,u∣∂Ω=I∣∂Ω\quad\text{in hole},\qquad u|{\partial\Omega}=I|{\partial\Omega}in hole,u∣∂Ω=I∣∂Ω
关键点:分母里 (|\nabla u|_\varepsilon) 会让扩散弱跨强边缘。
手写离散(中心差分 + 散度)
matlab
function u = tv_inpaint(I, mask, varargin)
% u = tv_inpaint(I,mask [,maxIter,dt,eps_reg])
maxIter=800; dt=0.20; eps_reg=5e-3;
if nargin>=3, maxIter=varargin{1}; end
if nargin>=4, dt=varargin{2}; end
if nargin>=5, eps_reg=varargin{3}; end
u = I;
M = double(mask); K = 1-M;
eps2 = eps_reg^2;
for it=1:maxIter
% --- 梯度 ∇u = (ux,uy),中心差分 ---
ux = 0.5*( circshift(u,[0 -1]) - circshift(u,[0 1]) );
uy = 0.5*( circshift(u,[-1 0]) - circshift(u,[1 0]) );
norm = sqrt(ux.^2 + uy.^2 + eps2);
gx = ux./norm;
gy = uy./norm;
% --- 散度 ∇·g ---
% ∂gx/∂x ≈ (gx(i,j+1)-gx(i,j-1))/2 , ∂gy/∂y ≈ (gy(i-1,j)-gy(i+1,j))/2
div = 0.5*( circshift(gx,[0 1]) - circshift(gx,[0 -1]) ) ...
+ 0.5*( circshift(gy,[-1 0]) - circshift(gy,[1 0]) );
u_new = u + dt*div;
% 钉住已知像素
u = K.*I + M.*u_new;
end
end
调用:
matlab
u2 = tv_inpaint(u, mask, 1200, 0.18, 5e-3);
figure(3); clf; imshow(u2,[]); title('TV-Inpainting(边缘保持)');
调节建议
dt:太大抖动,太小慢;一般0.1~0.3稳定eps_reg:控制对平坦区的保护;越小越"锐",但可能出现阶梯纹(Gibbs)------2e-3~1e-2是常用窗
3)方法三:Bertalmio 结构延续
核心想法
修复不是"平均",而是把等照度线(isophote)方向的信息推进洞里:
T⃗=(−uy, ux)ux2+uy2,\vec{T}=\frac{(-u_y,\;u_x)}{\sqrt{u_x^2+u_y^2}},\qquadT =ux2+uy2 (−uy,ux),
u←u+δ T⃗⋅∇uu\gets u+\delta\,\vec{T}\cdot\nabla uu←u+δT ⋅∇u
并在边界邻域迭代推进(本质是 PDE (ut=∇I⋅N⃗u_t=\nabla I\cdot\vec{N}ut=∇I⋅N ) 的半拉格朗日/显式离散)。
手写实现
matlab
function u = ns_inpaint(I, mask, varargin)
% u = ns_inpaint(I,mask [,maxIter,delta])
maxIter=500; delta=0.12;
if nargin>=3, maxIter=varargin{1}; end
if nargin>=4, delta=varargin{2}; end
u = I;
M = double(mask); K = 1-M;
% 造一个"洞边界邻域"标记(手写)
front = false(size(M));
[m,n]=size(M);
for i=2:m-1
for j=2:n-1
if M(i,j) && ~K(i,j)
if K(i-1,j)||K(i+1,j)||K(i,j-1)||K(i,j+1)
front(i,j)=true;
end
end
end
end
for it=1:maxIter
[~, grad] = gradient(u); % MATLAB 内置 gradient 只是差分,不算"搜索工具"
ux = grad(:,:,2);
uy = grad(:,:,1);
mag2 = ux.^2 + uy.^2 + 1e-8;
Tx = -uy./sqrt(mag2);
Ty = ux./sqrt(mag2);
du = Tx.*ux + Ty.*uy; % T·∇u
% 只在洞边界邻域推进(再靠扩散平滑向洞内渗透)
u_new = u + delta*du;
u = K.*I + M.*( front.*u_new + (~front).*u );
end
end
matlab
u3 = ns_inpaint(u, mask, 600, 0.10);
figure(4); clf; imshow(u3,[]); title('Bertalmio 结构延续');
参考代码 用于图像修复的偏微分方程方法 www.youwenfan.com/contentcsv/101542.html
4)三种方法一张图对比
matlab
figure(5); clf;
subplot(2,2,1); imshow(I,[]); title('原图(干净)');
subplot(2,2,2); imshow(u,[]); title('带孔洞');
subplot(2,2,3); imshow(u1,[]); title('Laplace(糊平边缘)');
subplot(2,2,4); imshow(u2,[]); title('TV-inpainting(保边缘)');