MATLAB 图像修复 — 偏微分方程方法

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(保边缘)');
相关推荐
Cthy_hy1 小时前
Python算法竞赛:排列组合核心用法
开发语言·python·算法
大圣编程1 小时前
面向对象深度理解
java·开发语言·算法
爱喝水的鱼丶1 小时前
SAP-ABAP:SAP 简单报表输出开发系列(共6篇) 第四篇:SAP 报表异常处理机制:数据校验与消息提示规范落地
开发语言·数据库·学习·算法·sap·abap
wabs6662 小时前
关于贪心算法【划分字母区间】的问题总结(C++语法)
算法·贪心算法
啦啦啦啦啦zzzz2 小时前
数据结构:二叉树的线索化
数据结构·算法
2401_872418783 小时前
算法入门:并查集(Disjoint Set / Union-Find):连通性问题的利器
算法
luj_17683 小时前
R语言生态优势与学习曲线分析
c语言·开发语言·网络·经验分享·算法
计算机安禾3 小时前
【算法分析与设计】第36篇:计算几何基础:凸包问题的分治与扫描线解法
大数据·人工智能·算法·机器学习·剪枝
货拉拉技术3 小时前
飞速发展的计算机视觉
人工智能·算法