Criminisi图像修复MATLAB实现,这是目前纹理/结构型破损(划痕、物体移除、大面积缺损)的经典算法,比传统的TV/Inpainting模型更擅长保持纹理连续性,也是Adobe Photoshop“内容识别填充”的早期核心算法之一。
一、Criminisi算法核心逻辑
Criminisi是基于纹理合成的优先级修复算法,核心解决“先修哪里、用什么修”两个问题:
- 优先级计算:优先修复结构明显(梯度强)且周围已知信息可靠的区域,符合人类视觉“先看轮廓、再看纹理”的习惯。
- 块匹配:从完好区域找和待修复块最相似的纹理块,复制到破损区域,保证纹理一致性。
核心公式
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);
五、算法优缺点
优点
- 擅长修复大面积纹理/结构破损(如移除照片中的路人、修复划痕)
- 能保持结构的连续性,修复结果更符合人类视觉习惯
- 无需训练数据,通用性强
缺点
- 计算量大(全图搜索匹配块,512×512图像约需1-5分钟)
- 平滑区域容易出现块效应
- 对复杂语义(如人脸五官)修复效果差
六、与其他修复算法的对比
| 算法 | 适用场景 | 优缺点 |
|---|---|---|
| Criminisi | 大面积纹理/结构破损 | 结构保持好,速度慢 |
| TV-Inpainting | 小面积平滑区域破损 | 速度快,纹理模糊 |
| PatchMatch | 纹理合成 | 速度快,结构保持弱 |
| 深度学习(LaMa/Stable Diffusion) | 复杂语义破损 | 效果好,需要训练数据 |
如果你的破损区域是不规则形状(如划痕、文字遮挡),可以告诉我,我帮你修改掩码生成代码;如果是彩色图像/实时性要求高,也可以给你对应的优化版本。