简介:这份资源围绕Criminisi图像修复算法的改进实现展开,面向图像处理、图像修复方向的研究人员与开发者,尤其适合需要在Matlab环境下验证算法效果、评估修复质量的学习者。其核心思路是以P-Laplacian算子替代原始算法中的数据项,从而提升优先级计算的精确度,增强边缘保持能力并减少噪声传播。压缩包共5个文件,包含3个m脚本、1个c源码与1个dll动态库,整体约6KB,其中m文件承担主流程与PSNR计算,c与dll文件支撑底层辅助逻辑。资源提供了可直接运行的修复流程与PSNR定量评估功能,便于读者对比改进前后的修复效果,理解优先级计算、边界处理与性能优化等关键环节。目前已有212人学习下载,适合作为图像修复算法入门与改进实验的参考素材。
1. 拆开 p-laplace.rar:一份把 Criminisi 数据项换成 P-Laplacian 的 MATLAB 修复包
图像修复里有个绕不开的经典:Criminisi 算法。2004 年那篇论文把「先补哪块、从哪块抄」拆成优先级排序加样本块匹配两步,成了后来一大票修复方法的基线。但基线归基线,真拿它去补一条穿过纹理区域的划痕,边缘经常糊成一团,噪声还会顺着补丁往里爬。p-laplace.rar 这份资源干的事,就是把原算法里算优先级的那个数据项,从普通梯度换成 P-Laplacian 算子,让边缘和细节在填充过程中被更狠地保住。包里是 MATLAB 实现,核心文件是 Demo_inpaint.m、inpaint.m、bestexemplarhelper.c 及其编译产物 bestexemplarhelper.dll,外加一个 PSNR.m 用来量化修复质量。适合正在做图像修复、去划痕、老照片修补,或者拿 Criminisi 当对比基线的人——你不需要从零复现论文,但得知道它改了哪、怎么跑、哪里会翻车。
2. Criminisi 的优先级机制与 P-Laplacian 到底改了什么
2.1 原版 Criminisi 的优先级公式拆解
Criminisi 的核心是给破损区域边界上每个待补像素算一个优先级 P(p),挑最高的先补。公式是 P(p) = C(p) · D(p),其中 C(p) 是置信度项,衡量这个像素周围有多少已知信息;D(p) 是数据项,衡量等照度线(isophote)在边界上的强度,说白了就是「这个位置有没有明显的边缘方向」。
数据项 D(p) 的原始定义是:
D(p) = |∇I⊥(p) · n(p)| / α∇I⊥(p) 是 p 点梯度的正交方向(等照度线方向),n(p) 是边界法向量,α 是归一化因子(灰度图取 255)。这个式子的物理含义是:如果等照度线方向和边界法向越接近垂直,说明这里有强边缘穿过,应该优先补,这样纹理和结构才能延续进去。
问题出在 ∇I 上。原版用的是普通梯度,对噪声敏感,而且在线性结构交叉或者光照不均的区域,梯度方向容易算歪。一旦 D(p) 算错,优先级排序就乱,补丁顺序跟着错,最后修复结果要么边缘断裂,要么纹理错位。
2.2 P-Laplacian 算子为什么能顶替梯度
P-Laplacian 算子的定义是:
Δ_p u = div(|∇u|^(p-2) ∇u)当 p=2 时退化成普通 Laplacian,p>2 时对梯度大的区域(边缘)惩罚更重,p<2 时对梯度小的区域(平滑区)更敏感。在图像修复场景里,通常取 p 在 1 到 2 之间,目的是让算子在平坦区域做各向同性扩散,在边缘附近做各向异性扩散——也就是沿着边缘方向扩散、垂直边缘方向抑制。
把它塞进 Criminisi 的数据项,本质是用 P-Laplacian 的扩散通量替代原始梯度方向,让优先级计算对边缘的判定更稳。具体来说,改进后的数据项不再直接取 ∇I⊥,而是用 P-Laplacian 在局部窗口内的响应来加权邻域信息。这样做的直接好处是:噪声引起的伪梯度被压制,真实边缘的响应被放大,优先级排序更贴近人眼期望的「先补结构、再补纹理」。
2.3 包里文件的分工与调用关系
拿到压缩包先别急着跑,把文件角色理清楚:
| 文件 | 类型 | 作用 |
|---|---|---|
| Demo_inpaint.m | 脚本 | 入口,读图、设掩膜、调 inpaint、算 PSNR |
| inpaint.m | 函数 | Criminisi 主循环,含优先级计算和样本块搜索 |
| bestexemplarhelper.c | C 源码 | 样本块匹配的加速实现 |
| bestexemplarhelper.dll | 编译产物 | Windows 下供 MATLAB 调用的 MEX 文件 |
| PSNR.m | 函数 | 计算修复前后峰值信噪比 |
调用链是 Demo_inpaint → inpaint → bestexemplarhelper(通过 MEX 接口)。如果你在非 Windows 平台,dll 用不了,得自己用 mex 重新编译 bestexemplarhelper.c,这一步后面避坑章节会细说。
2.4 优先级计算里 P-Laplacian 的接入点
在 inpaint.m 里找优先级计算那段,通常会看到一个循环遍历破损边界上的像素。原版代码大概长这样:
% 原版数据项计算(示意) [Ix, Iy] = gradient(img); grad_perp = [-Iy, Ix]; % 梯度正交方向 normal = compute_normal(mask, p); D = abs(sum(grad_perp .* normal)) / 255;改进版会把 gradient 那步换成 P-Laplacian 响应:
% P-Laplacian 加权后的数据项(示意) p = 1.5; % P-Laplacian 阶数,常用 1.2~1.8 [Ix, Iy] = gradient(img); grad_mag = sqrt(Ix.^2 + Iy.^2) + eps; weight = grad_mag.^(p - 2); % P-Laplacian 非线性权重 Ix_w = weight .* Ix; Iy_w = weight .* Iy; grad_perp = [-Iy_w, Ix_w]; normal = compute_normal(mask, p); D = abs(sum(grad_perp .* normal)) / 255;逻辑说明:weight 这一项就是 P-Laplacian 的核心。当 p<2 时,grad_mag 越小(平坦区),weight 越大,扩散越强;grad_mag 越大(边缘),weight 越小,扩散被抑制。这样算出来的 grad_perp 在边缘处更干净,不会被噪声带偏。参数 p 是唯一需要调的,默认 1.5 是个稳妥起点,纹理密集的图可以往 1.2 压,结构为主的图可以往 1.8 提。
3. 跑通 Demo:从掩膜制作到 PSNR 输出
3.1 环境准备与 MEX 编译
MATLAB 版本没有硬性要求,但 bestexemplarhelper.dll 是预编译的,位数必须和你的 MATLAB 一致。64 位 MATLAB 配 64 位 dll,32 位配 32 位,混了直接报「invalid MEX-file」。如果你在 Windows 上且 dll 能用,跳过编译;否则在 MATLAB 命令行执行:
% 编译 MEX 文件,需提前配置好 C 编译器 mex -O bestexemplarhelper.c-O 开优化,编译完会生成 bestexemplarhelper.mexw64(Windows)或 .mexa64(Linux)。如果报「No supported compiler」,去 MATLAB 的附加功能里装一个 MinGW-w64 编译器,这是常见做法,装完再跑一次 mex -setup 选它。
3.2 制作掩膜:决定修复范围的关键输入
Criminisi 需要一个二值掩膜,白色(1)表示待修复区域,黑色(0)表示已知区域。掩膜质量直接决定修复效果,别拿画图工具随手涂。常见做法是用阈值分割加形态学处理:
% 从原图生成掩膜示例:假设修复区域是亮色划痕 img = imread('damaged.png'); gray = rgb2gray(img); mask = gray > 200; % 阈值提取亮色划痕 mask = imdilate(mask, strel('disk', 2)); % 膨胀 2 像素,留余量 imwrite(mask, 'mask.png');参数说明:阈值 200 是经验值,具体看划痕亮度和背景对比度,可以用 imtool 交互看灰度直方图定。膨胀半径 2 是防止掩膜边缘刚好压在真实边缘上,导致修复时把好像素也覆盖掉。掩膜宁可稍微大一点,也别漏掉破损边缘。
3.3 调用 inpaint 主函数
Demo_inpaint.m 里通常已经写好了调用模板,核心就几行:
% 主修复调用 img = imread('damaged.png'); mask = imread('mask.png'); mask = logical(mask); patch_size = 9; % 样本块大小,奇数 result = inpaint(img, mask, patch_size); imwrite(result, 'restored.png'); psnr_val = PSNR(img, result, mask); fprintf('PSNR = %.2f dB\n', psnr_val);逻辑说明:patch_size 是样本块边长,9 是 Criminisi 论文里的推荐值。块太小,纹理匹配容易碎;块太大,结构延续会拖沓。inpaint 内部会先算优先级、再搜最佳样本块、然后拷贝像素并更新置信度,循环直到掩膜区域填满。PSNR 计算时注意第三个参数 mask——只算修复区域的 PSNR 才有意义,全图算会被未修改区域拉高。
3.4 PSNR 评估的正确用法
PSNR.m 的调用签名一般是 PSNR(original, restored, mask)。如果你手上没有无损原图,PSNR 就没法算,这时候只能靠目视评估。有原图的情况下,修复区域的 PSNR 能到 30 dB 以上算不错,28 以下基本能看出明显瑕疵。但别迷信 PSNR,它和主观质量的相关性有限,边缘糊没糊、纹理断没断,还是得放大看。
提示:跑 Demo 前先把 MATLAB 当前目录切到解压后的文件夹,否则 inpaint 找不到 bestexemplarhelper,会报 undefined function。
4. 避坑与排查:dll 报错、掩膜越界、优先级反转
4.1 MEX 文件加载失败
现象:运行 inpaint 时报「Invalid MEX-file 'bestexemplarhelper.mexw64': 找不到指定的模块」或「不是有效的 Win32 应用程序」。
原因:dll 位数和 MATLAB 不匹配,或者缺少运行时依赖库。预编译的 dll 可能链接了特定版本的 MSVCRT。
解决:先确认 MATLAB 位数(computer('arch')),然后自己用 mex 重新编译 bestexemplarhelper.c。编译前装好 MinGW-w64,mex -setup 选它。如果还报缺库,用 Dependency Walker 查 dll 依赖,补上对应运行库。
4.2 掩膜全白或全黑导致死循环
现象:程序卡住不结束,或者瞬间跑完但结果没变化。
原因:掩膜全白意味着所有像素都待修复,没有已知信息可用,优先级计算里置信度全为零,样本块搜索找不到有效源;掩膜全黑则循环条件直接不成立。
解决:跑之前用 sum(mask(:)) 检查掩膜像素数,确保既不为 0 也不等于图像总像素数。正常掩膜占比在 5% 到 30% 之间比较合理,超过 50% 修复质量会急剧下降,这不是算法问题,是信息量不够。
4.3 优先级反转导致结构断裂
现象:修复结果里长边缘补到一半突然拐弯,或者纹理方向错乱。
原因:P-Laplacian 的 p 参数设得不对。p 太大(接近 2),退化成普通 Laplacian,边缘保持效果消失;p 太小(接近 1),扩散过强,平坦区被过度平滑,优先级排序被噪声主导。
解决:p 从 1.5 开始试,每次调 0.1,观察边缘连续性。另外检查 inpaint.m 里 P-Laplacian 权重那行有没有加 eps,没加的话 grad_mag 为零时会出现 Inf 或 NaN,直接污染整个优先级矩阵。
4.4 PSNR 计算出负值或异常大
现象:PSNR 输出 -Inf 或者 100 以上。
原因:-Inf 通常是修复结果和原图在掩膜区域完全一致(MSE=0),或者数据类型不匹配导致减法溢出。异常大则是掩膜区域选错了,比如把未修复区域也算进去。
解决:确认 PSNR.m 里先把图像转成 double 再算 MSE,uint8 相减会截断。检查掩膜是不是 logical 类型,double 类型的掩膜在索引时会被当成数值而不是逻辑,导致区域选错。
4.5 大图跑不动或内存爆掉
现象:处理 1024×1024 以上的图时 MATLAB 卡死或报 Out of Memory。
原因:Criminisi 的样本块搜索是暴力匹配,复杂度随图像尺寸和掩膜面积平方增长,加上 P-Laplacian 权重计算额外开销,内存占用比原版高。
解决:先把图降采样到 512 宽跑通流程,确认参数后再上原图。或者把掩膜分成若干连通区域,逐块修复再合并。bestexemplarhelper.c 本身就是加速用的,确保它被正确调用,别退回到纯 MATLAB 的慢速搜索。
5. 进阶调参与验证:把 P-Laplacian 阶数当成旋钮来拧
跑通 Demo 只是起点,这份资源真正的价值在于 P-Laplacian 阶数 p 给了你一个连续可调的边缘保持旋钮。我一般会做一组对比实验:固定掩膜和 patch_size,让 p 从 1.1 到 1.9 步进 0.2,每个值跑一遍,把修复区域的 PSNR 和目视效果都记下来。
% p 参数扫描脚本 p_list = 1.1:0.2:1.9; psnr_results = zeros(size(p_list)); for i = 1:length(p_list) result = inpaint(img, mask, 9, 'p', p_list(i)); psnr_results(i) = PSNR(img, result, mask); imwrite(result, sprintf('restored_p%.1f.png', p_list(i))); end plot(p_list, psnr_results, '-o'); xlabel('P-Laplacian order p'); ylabel('PSNR (dB)');这段脚本假设 inpaint 支持名值对参数传 p,如果你的版本里 p 是硬编码的,就去 inpaint.m 里把它提成函数参数。跑完看曲线,通常会看到一个先升后降的趋势,峰值对应的 p 就是这张图的最优值。纹理密集的图峰值偏左(p 小),结构为主的图峰值偏右(p 大)。
验证环节别只看 PSNR。把修复结果和原图在掩膜区域做差分,放大看边缘过渡带有没有振铃或模糊。P-Laplacian 的强项是保边,如果差分图里边缘处出现明显亮暗条纹,说明 p 太小、扩散过强;如果边缘处出现阶梯状伪影,说明 p 太大、退化成线性扩散了。
还有一个容易忽略的点:bestexemplarhelper.c 里的搜索窗口大小。默认可能是全图搜索,大图上极慢。常见做法是限制搜索半径,比如只在待补块周围 50 像素范围内找最佳样本,速度能快一个数量级,质量损失通常可接受。改完 C 源码记得重新 mex 编译。
从那以后我每次拿到一个新的修复算法包,都强制先跑一遍参数扫描再下结论,绝不拿默认参数的结果去对比别人的最优结果。希望这份拆解帮到你,少走点我当年踩过的弯路。
本文还有配套的精品资源,点击获取