Criminisi图像修复MATLAB实现

Criminisi图像修复MATLAB实现,这是目前纹理/结构型破损(划痕、物体移除、大面积缺损)的经典算法,比传统的TV/Inpainting模型更擅长保持纹理连续性,也是Adobe Photoshop“内容识别填充”的早期核心算法之一。


一、Criminisi算法核心逻辑

Criminisi是基于纹理合成的优先级修复算法,核心解决“先修哪里、用什么修”两个问题:

  1. 优先级计算:优先修复结构明显(梯度强)周围已知信息可靠的区域,符合人类视觉“先看轮廓、再看纹理”的习惯。
  2. 块匹配:从完好区域找和待修复块最相似的纹理块,复制到破损区域,保证纹理一致性。

核心公式

1. 优先级

2. 置信项更新

每次修复完一个块,块内新修复像素的置信度统一设为当前块的置信度


二、MATLAB实现

1. 主函数:criminisi_inpaint.m

function [inpainted_img, mask] = criminisi_inpaint(img, mask, patch_size)
% CRIMINISI_INPAINT 基于Criminisi算法的图像修复
%   img: 输入图像 (灰度图,uint8/ double)
%   mask: 破损掩码 (0=破损区域,255=完好区域,和img同尺寸)
%   patch_size: 修复块大小 (奇数,推荐9/11/15)
%   inpainted_img: 修复后图像
%   mask: 更新后的掩码

%% ========== 参数初始化 ==========
if nargin < 3, patch_size = 9; end
half_patch = floor(patch_size/2);
max_iter = 1000;  % 最大迭代次数,防止死循环
alpha = 1e-6;     % 归一化常数,避免除零

% 转double便于计算
if isa(img, 'uint8'), img = im2double(img); end
if isa(mask, 'uint8'), mask = im2double(mask); end
mask = im2bw(mask, 0.5);  % 确保mask是二值(0/1)

[rows, cols] = size(img);
% 置信度矩阵:完好区域=1,破损区域=0
C = double(mask);
% 破损区域标记:Φ=破损,Ω=完好
Phi = ~mask;  % 逻辑矩阵,破损为true
Omega = mask; % 逻辑矩阵,完好的为true

%% ========== 预计算图像梯度 ==========
% 用Sobel算子计算梯度(x/y方向)
[gx, gy] = imgradientxy(img, 'sobel');
[~, grad_mag] = imgradient(gx, gy);  % 梯度幅值
grad_mag_max = max(grad_mag(:));     % 梯度幅值最大值,用于归一化

%% ========== 主修复循环 ==========
fprintf('开始Criminisi修复...\n');
iter = 0;
while any(Phi(:)) && iter < max_iter
    iter = iter + 1;
    
    % ---------- 1. 找破损区域的边界δΦ ----------
    % 形态学操作:腐蚀完好区域,得到边界
    eroded_Omega = imerode(Omega, strel('disk', 1));
    boundary = Omega & ~eroded_Omega;  % 边界点:完好区域的外围
    [b_y, b_x] = find(boundary);      % 边界点的坐标
    if isempty(b_y), break; end       % 无边界,修复完成
    
    % ---------- 2. 计算每个边界点的优先级 ----------
    P = zeros(length(b_y), 1);  % 优先级数组
    for i = 1:length(b_y)
        p_y = b_y(i); p_x = b_x(i);
        
        % ---- 2.1 计算置信项C(p) ----
        % 取以p为中心的块,裁剪边界
        y_min = max(1, p_y - half_patch);
        y_max = min(rows, p_y + half_patch);
        x_min = max(1, p_x - half_patch);
        x_max = min(cols, p_x + half_patch);
        % 块内的已知像素的置信度平均值
        block_C = C(y_min:y_max, x_min:x_max);
        block_Omega = Omega(y_min:y_max, x_min:x_max);
        if sum(block_Omega(:)) == 0
            C_p = 0;
        else
            C_p = mean(block_C(block_Omega));
        end
        
        % ---- 2.2 计算数据项D(p) ----
        % 边界法向量n:指向破损区域内部(掩码梯度的反方向)
        % 用中心差分计算掩码梯度
        if p_x == 1, nx = mask(p_y, p_x+1) - mask(p_y, p_x);
        elseif p_x == cols, nx = mask(p_y, p_x) - mask(p_y, p_x-1);
        else nx = mask(p_y, p_x+1) - mask(p_y, p_x-1); end
        
        if p_y == 1, ny = mask(p_y+1, p_x) - mask(p_y, p_x);
        elseif p_y == rows, ny = mask(p_y, p_x) - mask(p_y-1, p_x);
        else ny = mask(p_y+1, p_x) - mask(p_y-1, p_x); end
        
        n_norm = sqrt(nx^2 + ny^2 + eps);
        nx = -nx / n_norm;  % 取反,指向破损内部
        ny = -ny / n_norm;
        
        % 图像梯度在法向量上的投影
        grad_dot_n = abs(gx(p_y, p_x)*nx + gy(p_y, p_x)*ny);
        D_p = grad_dot_n / (alpha + grad_mag_max);  % 归一化
        
        % ---- 2.3 总优先级 ----
        P(i) = C_p * D_p;
    end
    
    % ---------- 3. 选优先级最高的点 ----------
    [~, idx] = max(P);
    p_hat_y = b_y(idx); p_hat_x = b_x(idx);
    
    % ---------- 4. 取以待修复点为中心的块Ψ_p̂ ----------
    y_min = max(1, p_hat_y - half_patch);
    y_max = min(rows, p_hat_y + half_patch);
    x_min = max(1, p_hat_x - half_patch);
    x_max = min(cols, p_hat_x + half_patch);
    patch_phi = Phi(y_min:y_max, x_min:x_max);  % 块内的破损区域
    patch_omega = Omega(y_min:y_max, x_min:x_max);  % 块内的完好区域
    
    % ---------- 5. 在完好区域搜索最佳匹配块 ----------
    min_ssd = inf;
    best_q_y = p_hat_y; best_q_x = p_hat_x;
    
    % 遍历所有完好区域的可能块中心
    [omega_y, omega_x] = find(Omega);
    for i = 1:length(omega_y)
        q_y = omega_y(i); q_x = omega_x(i);
        % 块不能超出图像边界
        q_ymin = max(1, q_y - half_patch);
        q_ymax = min(rows, q_y + half_patch);
        q_xmin = max(1, q_x - half_patch);
        q_xmax = min(cols, q_x + half_patch);
        % 块尺寸必须和待修复块一致
        if (q_ymax - q_ymin) ~= (y_max - y_min) || ...
           (q_xmax - q_xmin) ~= (x_max - x_min)
            continue;
        end
        % 计算SSD(平方差和)
        src_block = img(q_ymin:q_ymax, q_xmin:q_xmax);
        dst_block = img(y_min:y_max, x_min:x_max);
        ssd = sum((src_block(patch_omega) - dst_block(patch_omega)).^2, 'all');
        if ssd < min_ssd
            min_ssd = ssd;
            best_q_y = q_y; best_q_x = q_x;
            best_q_ymin = q_ymin; best_q_ymax = q_ymax;
            best_q_xmin = q_xmin; best_q_xmax = q_xmax;
        end
    end
    
    % ---------- 6. 复制匹配块到破损区域 ----------
    src_block = img(best_q_ymin:best_q_ymax, best_q_xmin:best_q_xmax);
    % 只复制破损的部分
    img(y_min:y_max, x_min:x_max) = src_block .* double(patch_phi) + ...
                                     img(y_min:y_max, x_min:x_max) .* double(patch_omega);
    
    % ---------- 7. 更新置信度和掩码 ----------
    C(y_min:y_max, x_min:x_max) = C(y_min:y_max, x_min:x_max) .* double(patch_omega) + ...
                                   P(idx) .* double(patch_phi);
    Phi(y_min:y_max, x_min:x_max) = false;  % 修复的区域不再是破损
    Omega(y_min:y_max, x_min:x_max) = true;
    
    % 进度提示
    if mod(iter, 10) == 0
        fprintf('迭代 %d 次,剩余破损像素: %d\n', iter, sum(Phi(:)));
    end
end

fprintf('修复完成,总迭代次数: %d\n', iter);
inpainted_img = img;
mask = Omega;
end

2. 测试脚本:demo_criminisi.m

%% demo_criminisi.m Criminisi算法示例
clear; clc; close all;

%% ========== 1. 准备输入图像和破损掩码 ==========
% 读入测试图像(Lena图,确保路径正确)
img = imread('lena.png');
if size(img, 3) == 3
    img_gray = rgb2gray(img);  % 转为灰度图,彩色图见下文说明
else
    img_gray = img;
end

% 生成破损掩码:在图像中心创建一个50x50的矩形破损
mask = ones(size(img_gray));
rect_center = [size(img_gray,2)/2, size(img_gray,1)/2];  % [x, y]
rect_size = [50, 50];  % [宽, 高]
x1 = rect_center(1) - rect_size(1)/2;
x2 = rect_center(1) + rect_size(1)/2;
y1 = rect_center(2) - rect_size(2)/2;
y2 = rect_center(2) + rect_size(2)/2;
mask(y1:y2, x1:x2) = 0;  % 破损区域设为0

% 可视化破损图像
broken_img = img_gray;
broken_img(~mask) = 0;  % 破损区域显示为黑色

%% ========== 2. 运行Criminisi修复 ==========
patch_size = 9;  % 修复块大小,奇数,推荐9/11/15
[inpainted_img, ~] = criminisi_inpaint(img_gray, mask, patch_size);

%% ========== 3. 结果可视化 ==========
figure('Color','white','Position',[100 100 1200 400])
subplot(1,3,1)
imshow(img_gray)
title('原始图像')
subplot(1,3,2)
imshow(broken_img)
title('破损图像(中心矩形区域)')
subplot(1,3,3)
imshow(inpainted_img)
title(sprintf('Criminisi修复结果(块大小=%d)', patch_size))

%% ========== 4. (可选)计算修复质量指标 ==========
% 如果有原始完整图像,可以计算PSNR
psnr_val = psnr(inpainted_img, img_gray);
fprintf('修复图像PSNR: %.2f dB\n', psnr_val);

参考代码 图像修复算法 www.youwenfan.com/contentcsw/82890.html

三、关键参数说明

参数 作用 推荐值
patch_size 修复块大小 9/11/15(纹理越丰富,块越大;结构越简单,块越小)
max_iter 最大迭代次数 1000(破损区域大时适当增大)
掩码制作 定义破损区域 用画图工具手动标注,或代码生成(矩形/圆形/自定义形状)

四、彩色图像修复方法

上面的代码是灰度图版本,彩色图像修复有两种常用方案:

方案1:逐通道修复(简单但可能颜色失真)

img_color = imread('lena.png');
mask = imread('mask.png');
inpainted_color = zeros(size(img_color));
for c = 1:3
    [inpainted_color(:,:,c), ~] = criminisi_inpaint(img_color(:,:,c), mask, 9);
end

方案2:HSV空间修复(推荐,颜色更自然)

img_hsv = rgb2hsv(img_color);
% 只在V(明度)通道修复,H(色调)和S(饱和度)保持不变
[V_inpainted, ~] = criminisi_inpaint(img_hsv(:,:,3), mask, 9);
img_hsv(:,:,3) = V_inpainted;
inpainted_color = hsv2rgb(img_hsv);

五、算法优缺点

优点

缺点

 

六、与其他修复算法的对比

算法 适用场景 优缺点
Criminisi 大面积纹理/结构破损 结构保持好,速度慢
TV-Inpainting 小面积平滑区域破损 速度快,纹理模糊
PatchMatch 纹理合成 速度快,结构保持弱
深度学习(LaMa/Stable Diffusion) 复杂语义破损 效果好,需要训练数据

如果你的破损区域是不规则形状(如划痕、文字遮挡),可以告诉我,我帮你修改掩码生成代码;如果是彩色图像/实时性要求高,也可以给你对应的优化版本。

专注于matlab/simulink,电子电路,编程