
简介该资源面向图像修复与重构方向的研究人员和开发者提供在Matlab环境下对经典Criminisi算法的改进实现。核心思路是用P-Laplacian算子替代原算法中的数据项借助其非线性扩散特性强化边缘保持与局部信息权重从而提升优先级计算的精确度减少修复过程中的噪声传播与模糊失真。压缩包共5个文件约6KB包含3个m脚本、1个c源码和1个dll动态库分别承担算法主流程、辅助函数与底层加速等职责并附带PSNR计算脚本便于定量评估修复质量。资源已有212人学习下载适合具备一定图像处理基础、希望对比改进前后效果或将其迁移到去噪、去雾、图像拼接等场景的读者参考可据此快速复现实验、分析边界处理与性能开销并借助峰值信噪比指标验证算法优劣。1. Criminisi 算法在 MATLAB 里的真实定位从 p-laplace.rar 说起如果你手头有一个叫p-laplace.rar的压缩包里面塞着 Criminisi 算法的 MATLAB 实现还带着“改进”两个字那你大概率正卡在同一个地方原版 Criminisi 跑小图还行一上真实照片纹理区域就开始糊结构边缘开始歪修复块之间的接缝像补丁一样明显。这不是你参数没调好而是算法本身的优先级函数在复杂区域会失效。Criminisi 算法本质是一种基于样例的图像修复方法核心思路是沿等照度线方向优先填充结构区域再用纹理合成补全剩余部分。它适合做划痕去除、文字遮挡修复、老照片缺损填补这类任务不适合做大面积语义缺失的生成式补全。p-laplace 这个词出现在标题里通常意味着有人在扩散项或优先级计算里引入了 p-Laplace 算子用来增强边缘保持能力。MATLAB 则是这类算法最常用的验证平台因为矩阵操作天然适合块匹配和像素级迭代。这一章不展开公式只帮你判断一件事你手里的 Criminisi 改进版到底值不值得花时间跑通、调参、再改。2. 原版 Criminisi 的优先级函数为什么会在纹理区翻车2.1 置信度项与数据项的实际计算方式原版 Criminisi 的优先级函数是P(p) C(p) * D(p)其中C(p)是置信度项D(p)是数据项。置信度项衡量像素点周围已知信息的可靠程度初始时已知区域为 1待修复区域为 0每填充一个块就把新填充像素的置信度设为当前块的平均置信度。数据项衡量等照度线的强度用梯度方向和法向量的点积绝对值表示。问题出在乘法上当置信度项快速衰减到接近 0 时数据项再大也拉不回优先级导致算法在纹理密集区域反复选择低置信度的块填充顺序混乱。我一般会在调试时把C(p)和D(p)分别可视化出来如果看到C(p)在纹理区一片暗而D(p)的亮线断断续续那就说明优先级函数已经失效了。2.2 用 MATLAB 复现优先级计算的最小代码% 计算置信度项 C(p) 和数据项 D(p) % 输入img 为当前图像mask 为待修复区域掩码1 表示待修复 % 输出priority 为每个边界点的优先级值 function [priority, C, D] compute_priority(img, mask, patch_size) % 获取待修复区域的边界 boundary bwperim(mask); [by, bx] find(boundary); C zeros(size(mask)); D zeros(size(mask)); priority zeros(size(mask)); % 已知区域置信度初始化为 1 C(~mask) 1; % 计算图像梯度 [gx, gy] gradient(double(rgb2gray(img))); for k 1:length(by) y by(k); x bx(k); % 提取以 (y,x) 为中心的 patch 窗口 r (patch_size - 1) / 2; y1 max(1, y-r); y2 min(size(mask,1), yr); x1 max(1, x-r); x2 min(size(mask,2), xr); % 置信度项窗口内已知像素的置信度均值 patch_C C(y1:y2, x1:x2); patch_mask mask(y1:y2, x1:x2); C(y,x) sum(patch_C(~patch_mask)) / sum(~patch_mask(:)); % 数据项等照度线方向与法向量的点积 % 法向量由边界梯度得到 [ny, nx] gradient(double(boundary)); n [ny(y,x), nx(y,x)]; n n / (norm(n) eps); % 等照度线方向垂直于梯度 grad [gx(y,x), gy(y,x)]; grad grad / (norm(grad) eps); D(y,x) abs(dot(grad, n)); % 优先级 priority(y,x) C(y,x) * D(y,x); end end这段代码的关键在于C(y,x)的计算方式它取的是窗口内所有已知像素置信度的平均值而不是只取中心点。D(y,x)用的是梯度方向与边界法向量的点积绝对值法向量由边界掩码的梯度得到。参数patch_size通常取 9 或 11太小会导致匹配块信息不足太大会让优先级计算变慢且模糊边缘。运行后如果发现priority在纹理区几乎全为零说明置信度项已经衰减到无法区分候选块了这时候就需要引入改进策略。2.3 纹理区失效的根因与 p-Laplace 的介入点纹理区的置信度衰减快是因为原版算法每填充一个块就把整个块内所有像素的置信度设为该块的平均值这个平均值通常低于周围已知区域的置信度。迭代几十次后待修复区域内部的置信度趋近于零优先级函数退化成只看数据项。而数据项在纹理区本身就不稳定梯度方向杂乱点积结果忽大忽小。p-Laplace 算子的介入点通常有两个一是替换数据项中的梯度计算用 p-Laplace 各向异性扩散来平滑梯度方向让等照度线更连续二是直接修改优先级函数把C(p) * D(p)改成C(p) * D(p) lambda * L(p)其中L(p)是 p-Laplace 响应用来在纹理区提供额外的优先级信号。常见做法是取p1.5到2.5之间p2退化为普通 Laplacep越小边缘保持越强但噪声也越敏感。3. 把 p-Laplace 改进版跑起来从解压到第一张修复图3.1 压缩包目录结构与入口脚本识别拿到p-laplace.rar后先别急着全选运行。我一般会先看目录结构典型的 Criminisi MATLAB 包会有这几个部分main.m或demo.m作为入口criminisi_core.m或类似名字的核心迭代函数compute_priority.m优先级计算find_best_match.m块匹配update_confidence.m置信度更新可能还有一个p_laplace_filter.m专门做 p-Laplace 扩散。如果压缩包里只有一个大文件所有函数都塞在一起那说明作者可能没做模块化调试起来会更麻烦。先确认入口脚本里读图的路径和掩码生成方式很多包默认用交互式roipoly让用户手动画掩码这在批量测试时很不方便我通常会改成从.mat文件加载掩码。3.2 最小可运行配置与参数初始化% Criminisi 主循环的最小配置 % 读入图像和掩码 img imread(damaged.jpg); load(mask.mat); % mask 为二值图像1 表示待修复区域 % 参数设置 patch_size 9; % 匹配块大小通常 7~11 search_window 50; % 搜索窗口半径越大越慢但匹配越好 p_value 1.8; % p-Laplace 的 p 值 max_iter 2000; % 最大迭代次数防止死循环 % 初始化 mask logical(mask); img double(img); fill_region mask; confidence double(~mask); % 主循环 iter 0; while any(fill_region(:)) iter max_iter iter iter 1; % 计算优先级 [priority, C, D] compute_priority(img, fill_region, patch_size); % 如果启用了 p-Laplace 改进叠加 L(p) 项 if p_value 0 L p_laplace_response(img, fill_region, p_value); priority priority 0.3 * L; % 权重 0.3 是经验值 end % 找到优先级最高的边界点 [~, idx] max(priority(:)); [py, px] ind2sub(size(priority), idx); % 提取以该点为中心的待填充块 target_patch extract_patch(img, py, px, patch_size); % 在已知区域搜索最佳匹配块 [match_patch, match_pos] find_best_match(img, fill_region, target_patch, py, px, search_window); % 填充 img fill_patch(img, fill_region, match_patch, py, px, patch_size); % 更新掩码和置信度 fill_region update_mask(fill_region, py, px, patch_size); confidence update_confidence(confidence, py, px, patch_size, match_patch); end这段主循环里p_laplace_response是改进版新增的函数用来计算当前待修复区域在 p-Laplace 算子下的响应强度。权重0.3是我在几组老照片上试出来的经验值太大比如 1.0会让优先级完全被 p-Laplace 主导结构边缘反而被忽略太小比如 0.05则几乎没效果。search_window设为 50 意味着在每个待填充块周围 50 像素半径内搜索最佳匹配对于 512x512 的图像这个半径能覆盖大部分纹理重复区域再大就只是增加计算量。max_iter设 2000 是防止掩码更新逻辑有 bug 时无限循环正常一张 256x256 的图待修复区域占 10% 左右迭代 300 到 500 次就能结束。3.3 第一张修复图的验证方法与常见异常跑完第一张图后不要只看最终结果要分阶段保存中间结果。我习惯每 50 次迭代保存一次当前图像和掩码用imwrite存成序列然后快速翻看。如果发现修复区域出现大面积模糊说明块匹配的搜索窗口太小或者patch_size太大如果出现明显的块状接缝说明置信度更新太快可以尝试把置信度更新改为取窗口内已知像素的最小值而不是平均值如果结构边缘断裂说明数据项权重不够可以临时把优先级函数改成C(p) * D(p)^2来放大数据项的影响。p-Laplace 改进版最常见的异常是边缘过增强表现为修复区域周围出现一圈亮边或暗边这是p_value太小导致扩散过于各向异性把p_value调到 2.0 以上通常能缓解。4. 避坑与排查Criminisi 改进版在 MATLAB 里的五个血泪教训4.1 现象修复区域全黑或全白掩码逻辑反了原因MATLAB 里logical类型和double类型混用时1和0的含义容易搞反。有些包用1表示已知区域有些用1表示待修复区域。解决在入口脚本里加一行mask ~mask;并注释清楚或者统一用mask 0表示待修复。我一般会在读入掩码后立刻用imshowpair(img, mask)看一眼确认白色区域确实是你要修的地方。4.2 现象迭代到一半报错“索引超出矩阵范围”原因边界点提取时用了bwperim但掩码更新后边界可能紧贴图像边缘提取 patch 时y-r或x-r小于 1。解决在extract_patch函数里做边界裁剪用max(1, y-r)和min(size(img,1), yr)限制索引范围。这个坑在修复区域靠近图像边缘时必现尤其是做老照片边框修复时。4.3 现象修复速度极慢一张 256x256 的图跑半小时原因块匹配用了三重循环逐像素计算 SSD没有向量化。解决把find_best_match里的 SSD 计算改成矩阵运算用im2col把搜索窗口内所有候选块拉成列向量然后一次性计算欧氏距离。我实测过向量化后速度能提升 20 到 50 倍。另一个提速点是限制搜索窗口不要全图搜索search_window设 30 到 50 足够。4.4 现象p-Laplace 改进后修复结果反而不如原版原因p_value设得太小比如 1.0 或 1.2导致扩散过度纹理区域被强行平滑成结构区域优先级函数完全被L(p)项主导。解决先把p_value设为 2.0 跑一遍作为基线然后逐步降到 1.5每次降 0.1观察修复区域是否出现过度平滑。如果原版结果更好说明这张图的纹理重复性不强p-Laplace 改进不适合直接关掉L(p)项即可。4.5 现象修复后的图像颜色和原图不一致出现色偏原因在 RGB 三个通道上分别独立跑 Criminisi每个通道的块匹配结果不同导致颜色通道错位。解决把块匹配的 SSD 计算改成三通道求和即sum((patch1(:) - patch2(:)).^2)而不是逐通道匹配。填充时三个通道用同一个匹配块的位置。这个坑在修复彩色照片时特别明显灰度图不会遇到。5. 进阶技巧用 p-Laplace 响应图做自适应优先级权重5.1 从固定权重到自适应权重的切换逻辑前面主循环里priority priority 0.3 * L用的是固定权重实际项目中更稳的做法是让权重随迭代次数和局部纹理复杂度自适应。具体思路是在迭代初期置信度项还比较可靠权重设小一点比如 0.1迭代到中后期置信度衰减到 0.3 以下时把权重提高到 0.5 甚至 0.8让 p-Laplace 响应接管优先级决策。同时局部纹理复杂度可以用窗口内的梯度方差来衡量方差大说明纹理密集权重再乘一个 1.2 的系数方差小说明是平坦区域权重乘 0.8。这样做的效果是在纹理区更依赖 p-Laplace 的结构保持能力在平坦区仍然信任置信度项。5.2 自适应权重的 MATLAB 实现与参数表% 自适应权重计算 % 输入confidence 当前置信度图img 当前图像fill_region 待修复掩码 % 输出weight 自适应权重 function weight adaptive_weight(confidence, img, fill_region, iter, max_iter) % 基础权重随迭代次数线性增长 base_weight 0.1 0.7 * (iter / max_iter); % 计算当前待修复区域的平均置信度 mean_conf mean(confidence(fill_region)); % 置信度越低权重越高 if mean_conf 0.3 base_weight base_weight * 1.5; elseif mean_conf 0.6 base_weight base_weight * 0.7; end % 计算局部纹理复杂度梯度方差 [gx, gy] gradient(double(rgb2gray(img))); grad_mag sqrt(gx.^2 gy.^2); % 只统计待修复区域边界附近的梯度 boundary bwperim(fill_region); local_grad grad_mag(boundary); if ~isempty(local_grad) grad_var var(local_grad); if grad_var 100 % 纹理密集 base_weight base_weight * 1.2; else base_weight base_weight * 0.8; end end weight min(base_weight, 1.0); % 上限 1.0 end这段代码里base_weight从 0.1 线性增长到 0.8然后根据平均置信度和梯度方差做二次调整。grad_var 100这个阈值是针对 0 到 255 灰度图的经验值如果图像归一化到 0 到 1阈值要相应改成 0.01 左右。min(base_weight, 1.0)是防止权重超过 1 后完全压制置信度项。实际使用时把主循环里的priority priority 0.3 * L替换成weight adaptive_weight(...); priority priority weight * L;即可。5.3 验证自适应效果的三组对比实验我一般会跑三组对比第一组原版 Criminisi第二组固定权重 p-Laplace 改进第三组自适应权重 p-Laplace 改进。评价指标不用太复杂肉眼观察加两个简单指标就够了一是修复区域与周围区域的梯度方向一致性用sum(abs(dot(grad_fill, grad_around)))粗略衡量二是修复区域的块状接缝数量用edge检测后统计修复区域内的边缘像素占比。三组跑下来自适应权重通常在纹理密集的老照片上比固定权重提升 10% 到 20% 的方向一致性但在平坦区域可能略差于原版因为 p-Laplace 响应在平坦区本身就很弱自适应权重反而引入了噪声。所以我的习惯是如果修复区域以纹理为主开自适应如果以平坦区域为主直接关掉 p-Laplace 项用原版跑更快更稳。这个判断没有公式就是跑几组图看一眼比任何理论分析都直接。希望帮到你。本文还有配套的精品资源点击获取