ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

OpenVINO 运行 LaMa 大掩码图像修复:CPU 推理原理与工程实践

OpenVINO 运行 LaMa 大掩码图像修复:CPU 推理原理与工程实践 简介面向图像处理开发者、算法工程师以及正在学习模型部署与推理优化的初学者这份基于OpenVINO的LaMa图像修复演示工程提供了从模型到应用的完整参考实现可在本地环境快速体验图像划痕、遮挡区域的高质量修复效果。压缩包共383个文件总大小约831.62MB主要文件类型包括dll运行库、xml与config配置文件、txt说明文档、onnx模型权重以及C#工程源码含cs/csproj/sln等既支撑直接运行也便于阅读和二次开发。目前已有252人学习下载。资源中封装了演示程序、预训练模型和运行依赖目录划分清楚适合对照理解OpenVINO的推理流程、LaMa网络结构以及图像修复任务的前后处理细节也适合在此基础上替换模型或集成到自己的项目中或进一步优化推理性能从而实现更灵活的部署。1. 用 OpenVINO 跑 LaMa 修复大块遮挡图像处理的低成本切入点一张老照片的边角缺损了拳头大小的一块或者扫描件上横了一条粗水印这种“遮罩面积占画面比例很大”的修复需求交给 LaMa 这类大掩码修复模型处理效果比传统补洞算法自然得多。而这套 Demo 的价值在于模型、推理脚本和示例图都打在一个 .rar 包里你用 OpenVINO 在普通 CPU 上就能跑512×512 分辨率一次推理实测 1~2 秒不依赖独立显卡。适合两类人一是想快速评估 LaMa 修复效果验证它能不能扛住你手上那批图二是想把图像修复能力嵌进后台服务或批处理脚本的后端开发者先用这份 Demo 打通输入输出链路再替换成自己的生产代码。打开包之前先记住一句实话里面是一个可执行的模型推理示例不是要装进 OpenVINO 的插件。2. 拆开 LaMa 模型与 Demo 包修复原理、文件布局和版本边界直接拿着包就跑容易翻车因为 LaMa 不是那种“输入一张图点个按钮”的傻瓜工具它要求你把图像和掩码分别准备好再按模型约定的张量格式喂进去。先把原理和包的内部结构看完后面所有报错你都能自己定位。2.1 LaMa 的结构4 通道输入与傅里叶卷积单元LaMa 全称 Large Mask Inpainting重点解决“掩码很大”时修复结果糊成一片的问题。传统基于 CNN 的修复网络比如普通 U-Net 结构每一层卷积只能看到局部感受野虽然网络越深感受野越大但要覆盖一个占画面 30% 的大窟窿需要很多层堆叠而且远处的上下文信息在逐层传递过程中会衰减。LaMa 的主干用 ResNet 搭配 Fast Fourier ConvolutionFFC模块在频域做全局卷积让每一个输出位置都能直接感知整张图的低频结构和高频纹理。直观感受就是修复大面积墙面、天空、草地这类重复纹理时LaMa 补出来的区域不会出现明显分界线。从工程视角看你只需要关注三点。第一模型的输入通常是 4 通道张量即 RGB 三通道加上一个单通道掩码形状是 1×4×H×W也有导出版本把 image 和 mask 拆成两个输入分别是 1×3×H×W 和 1×1×H×W。第二掩码通道有约定白色像素值 1 或 255代表需要修复的区域黑色代表保留区域反了的话模型会把你不想动的细节全抹掉。第三输入输出的颜色顺序、归一化区间跟导出时的预处理绑定后面第四章会讲怎么从 Demo 脚本里确认这些值而不是靠猜。这个模型跟 OpenVINO 的关系也值得说清楚。OpenVINO 在这里扮演的是推理引擎它加载 LaMa 导出的中间表示文件IR在 CPU、GPU 等设备上编译执行。模型本身的参数在训练阶段已经把修复能力固化好了推理阶段不更新权重所以别指望在 Demo 里调“学习率”这类参数。你要调的是输入尺寸、掩码膨胀半径、后处理融合程度这几个工程参数它们的优先级比模型超参高得多。2.2 Demo 压缩包的常见文件布局与执行入口解压后先不要急着装环境先把目录结构过一遍。常见做法是下面三层模型层lama.xml和lama.bin成对出现这是 OpenVINO 的 IR 模型如果只有.onnx文件说明需要你自己转 IR转换命令在第三章。代码层主脚本通常叫main.py或demo.py附带一个预处理/后处理工具模块负责读图、抠掩码、灰度归一化这些脏活。数据层示例原图和掩码图一般放在images/或samples/文件夹命名上会有_mask后缀来标识掩码文件。执行入口的确认方法是先看代码层里有没有if __name__ __main__块有就跑那一段。有些包会要求先修改脚本顶部的路径常量比如MODEL_PATH和IMAGE_PATH改成你解压后的实际路径。最稳妥的做法是把整个 .rar 解压到纯英文绝对路径下比如D:\lama_demo目录里不要出现中文和空格。Windows 下 OpenVINO 读取中文路径偶尔会报编码错误这属于最常见的低级坑浪费过不少人半小时。如果包内还有requirements.txt优先按里面的版本装原因是 Demo 脚本可能调用了一些旧 API比如openvino.inference_engine。此时你贸然装一个最新版 runtime很可能在 import 阶段就卡住。2.3 “插件”概念的误解与 IR 版本兼容边界搜“OpenVINO 软件/插件 LaMa”的人经常会误以为 LaMa 是 OpenVINO 的一个插件装上去之后所有软件都能直接调用修复能力。实际上这是两个层级的东西。OpenVINO 早期把后端设备称为插件比如 CPU 插件、GPU 插件但从 2022 版本开始这个概念已经被“设备device”取代你在代码里给compile_model传字符串CPU就是在选设备。LaMa 是一个深度学习模型它下载下来之后要经过 OpenVINO 的加载和编译才能成为可用的推理对象。你不需要向 OpenVINO 注册任何第三方插件这份 .rar 是演示工程不是扩展安装包。关于版本兼容还有一个现实问题IR 文件虽然一般向后兼容但旧版模型在加载到新版运行时可能会有一次隐式升级升级过程多数时候没感知偶尔会触发输入形状或者布局层面的意外报错。处理原则很简单先看代码里的 import 行。如果写的是from openvino.runtime import Core说明是按 2022 之后的新 API 写的装新版 runtime 没问题如果写的是from openvino.inference_engine import IECore说明是旧版语法我的习惯是不降级安装而是直接把这几行改成新版写法通常改动量很小换掉 import 和core.read_network那部分就能跑通。3. 搭建 OpenVINO 推理环境安装、模型转换和输入形状对齐这个 Demo 包的运行依赖其实很少核心只有 OpenVINO 运行时和一个 Python 环境。怕的是装了一堆重框架比如把整套训练框架也装进来白白占用十几个 GB 磁盘空间。按下面这套走磁盘占用可以控制在 2GB 以内。3.1 用虚拟环境锁住 runtime 与转换工具先建一个虚拟环境避免 OpenVINO 的依赖跟你已有的项目互相污染mkdir lama_demo cd lama_demo python -m venv venv source venv/bin/activate # Windows 下执行 venv\Scripts\activate pip install openvino2024.0,2025.0 openvino-dev2024.0,2025.0 python -c import openvino.runtime as rt; print(rt.__version__)逻辑说明第一条python -m venv venv创建独立环境后面的source venv/bin/activate切换进这个环境pip install安装两个包openvino是推理运行时openvino-dev里包含模型转换工具安装后才能在终端里使用ovc命令。最后一行打印版本号目的是确认安装成功也方便你记下环境版本。参数说明如果 Demo 包里已经提供了 IR 文件openvino-dev其实可以省掉只装openvino就能跑推理。但我建议还是装上因为后续如果你想调整输入形状甚至自己转模型ovc会频繁用到。版本范围写成 2024.x 是为了避开旧版 API 跟新代码不匹配的问题如果你机器上的 Python 是 3.122024.x 系列支持得比较完整。3.2 把 PyTorch/ONNX 权重转成 IR 参数如果包里只有.onnx文件或者你想换一个自己导出的 LaMa 权重就需要转换成 OpenVINO 的 IR 格式。转换命令如下ovc lama.onnx \ --output_dir model_out \ --input image:1,3,512,512 \ --input mask:1,1,512,512 \ --output output如果你的 ONNX 模型把掩码和图像拼成了一个 4 通道输入则用简化写法ovc lama.onnx \ --input input:1,4,512,512逻辑说明ovc是 OpenVINO 提供的模型转换命令它读取 ONNX 模型并导出成 XML 与 BIN 两个文件。第一个命令适用于 image 和 mask 分开输入的版本--input参数里的image:1,3,512,512含义是“名为 image 的输入形状为 1×3×512×512”第二个命令适用于拼接输入第 4 个通道就是掩码。参数说明形状里的 512 不能随便拍脑袋大部分 LaMa 权重按 512 尺寸训练如果拉到 1024 或更高修复质量不一定更好反而因为分辨率超出训练分布容易出现斑块甚至噪点。固定成 512 还有一个隐藏收益形状固定后OpenVINO 在 CPU 上会做形状专用优化动态形状比固定形状每次推理慢 20%~40%。如果源模型输入名不叫image或input先用 Netron 工具打开 ONNX 文件看一下实际输入名命令再照着改否则转换时会报找不到输入的错。3.3 统一输入输出形状一张表对齐张量推理前把张量约定写成一张表贴在显示屏旁边能救你好几次。常见 LaMa 导出版本的输入输出如下张量名形状数值范围说明image1,3,512,512[0,1] 或 [-1,1]BGR 图转 RGB 后归一化mask1,1,512,512{0,1}1 表示待修复区域output1,3,512,512与输入归一化一致修复后的 RGB 图注意数值范围这一列。LaMa 原始实现常用 [-1,1] 做输入OpenVINO 的某些转换示例又把它改成 [0,1]这两种写法差一个线性变换。如果 Demo 脚本里已经写好了预处理函数直接用它的归一化方式别自己另写一套。验证方法很简单先用示例图跑一次看输出有没有偏灰偏暗如果整体偏暗说明输入应该用 [-1,1] 你用了 [0,1]改一下归一化分母就能解决。4. 跑通一次 LaMa 修复预处理、掩码膨胀与后处理参数环境就绪后接下来这份 Demo 的核心链路就是读图 → 构造掩码 → 缩放对齐 → 推理 → 融合回原图。每一步都有参数需要根据你的实际图片调整照着下面写不只能跑通还能帮你理解为什么要这么做。4.1 Letterbox 预处理与插值细节现实里的图片长宽比五花八门直接拉伸到 512×512 会让建筑、人脸变形修复出来的结构也会跟着歪。所以标准做法是 letterbox长边缩放到 512短边按同比例缩放剩下的区域用 0 填充。代码def letterbox(img, mask, size512): h, w img.shape[:2] scale min(size / h, size / w) nh, nw int(h * scale), int(w * scale) img cv2.resize(img, (nw, nh), interpolationcv2.INTER_LINEAR) mask cv2.resize(mask, (nw, nh), interpolationcv2.INTER_NEAREST) canvas_img np.zeros((size, size, 3), dtypenp.float32) canvas_mask np.zeros((size, size), dtypenp.float32) y0, x0 (size - nh) // 2, (size - nw) // 2 canvas_img[y0:y0 nh, x0:x0 nw] img canvas_mask[y0:y0 nh, x0:x0 nw] mask return canvas_img, canvas_mask, (x0, y0, nw, nh)逻辑说明scale取宽高缩放比例中的较小值保证长边不超过 512 且不变形。cv2.resize分别处理图像和掩码注意掩码用的是INTER_NEAREST图像用INTER_LINEAR。最后把缩放后的图贴到 512×512 画布中央返回的(x0, y0, nw, nh)记录了原始内容在画布上的位置供后处理时裁剪。参数说明size512要跟模型输入尺寸严格一致模型是 512 你就传 512。掩码插值方式千万不能用线性插值否则掩码边缘会出现一圈灰色渐变模型会把这个模糊边界当真实内容处理修复结果会带一圈浅色轮廓。如果原图分辨率很高比如 4000×3000这里直接把整图缩到 512 会丢失大量细节这种情况请先看第五章的高分辨率处理建议。4.2 构造四通道输入并执行推理预处理完成后把图像和掩码拼成模型需要的张量。以单输入 4 通道版本为例from openvino.runtime import Core core Core() model core.read_model(model_out/lama.xml) compiled core.compile_model(model, CPU, config{PERFORMANCE_HINT: LATENCY}) infer_request compiled.create_infer_request() def run_inpaint(rgb_f, mask_f): chw np.concatenate([rgb_f, mask_f[..., None]], axis-1) blob np.expand_dims(chw.transpose(2, 0, 1), 0).astype(np.float32) output infer_request.infer([blob]) out next(iter(output.values())) return out[0].transpose(1, 2, 0)逻辑说明chw把 RGB 和掩码在通道维度上拼起来形状是 H×W×4transpose(2, 0, 1)把它变成 4×H×W再expand_dims在最前面补一个批次维度最终得到 1×4×H×W这就是模型的输入张量。推理后用next(iter(output.values()))取出输出再转回 H×W×3 方便后处理。参数说明PERFORMANCE_HINT传LATENCY表示低延迟优先适合单张图交互式修复如果你要批量处理几百张图改成THROUGHPUT能提高整体吞吐。这里用infer_request.infer([blob])的写法比较直观但每次都会做一次输入张量拷贝。性能紧张时可以用infer_request.get_input_tensor(0)直接写数据代码改成input_tensor infer_request.get_input_tensor(0) input_tensor.data[:] blob infer_request.infer() out infer_request.get_output_tensor(0).data这样能省掉一次张量拷贝CPU 上整套流程大概能快 10%~15%后面第六章批量修复时会用到这个写法。4.3 后处理裁回 letterbox 区域并按掩码融合模型输出的是一整张 512×512 的修复结果不能直接拿去找用户交差因为里面有 letterbox 补的黑边而且非掩码区域理论上是原图复制但实际会有轻微颜色偏移。正确做法是只把掩码区域从结果里抠出来贴回原图def postprocess(original, pred_rgb, box, mask_orig, feather3): x0, y0, nw, nh box pred_crop pred_rgb[y0:y0 nh, x0:x0 nw] pred_bgr np.clip(pred_crop[..., ::-1] * 255, 0, 255).astype(np.uint8) pred_bgr cv2.resize(pred_bgr, mask_orig.shape[::-1], interpolationcv2.INTER_LINEAR) m (mask_orig 127).astype(np.float32) if feather 0: m cv2.GaussianBlur(m, (0, 0), feather) m m[..., None] out original.astype(np.float32) * (1 - m) pred_bgr.astype(np.float32) * m return np.clip(out, 0, 255).astype(np.uint8)逻辑说明先把模型的 RGB 输出裁剪到 letterbox 内的有效区域再转成 BGR 并缩放到原始图尺寸然后根据原始掩码生成融合权重m掩码区域用修复结果非掩码区域用原图。feather控制边界的羽化半径避免修复区域和原图之间出现一条硬边。参数说明feather3一般够用主要消除接缝但如果你修复的是边缘锐利的物体比如建筑轮廓线羽化太大会让物体边缘发虚这时候把feather调到 0 或者 1。融合前务必确认掩码方向和模型要求一致白色代表修复区域如果发现模型把不该动的地方改了把掩码反相再跑一次。5. 避坑指南LaMa OpenVINO 推理中的 5 个常见问题这个 Demo 跑通不难但想跑得稳、跑得快下面几个坑十有八九会遇到。每一条都是我实际踩过或者帮别人排查过的按“现象 → 原因 → 解决”写方便直接对照。5.1 修复结果整体发灰像蒙了一层雾现象掩码区域确实被填上了但颜色整体偏灰、偏暗跟原图色彩完全不搭。 原因归一化区间不匹配。模型训练时输入范围是 [-1,1]你给了 [0,1] 的数据或者反过来。另一个常见原因是通道顺序错误把 OpenCV 的 BGR 图直接喂给了需要 RGB 的模型。 解决先用示例图定位问题。如果只是发暗把输入从float32 / 255.0改成(float32 / 127.5) - 1.0输出对应的反归一化改成(pred 1.0) * 127.5。如果颜色是青紫相间、不是单纯的暗检查cv2.COLOR_BGR2RGB有没有做。5.2 掩码边缘出现一圈浅色描边现象修复区域和原图交界处有一条细线颜色介于原图和填充色之间远看像描边。 原因处理掩码时用了cv2.INTER_LINEAR插值掩码边缘从纯白渐变到纯黑模型把灰色边界当成了半透明物体修复时对它做了错误猜测。 解决掩码 resize 必须用cv2.INTER_NEAREST。如果这条边仍然存在另一个可能是掩码本身没有完全覆盖要修的物体边缘把白色区域边缘漏掉了几个像素。处理办法是推理前先对掩码做膨胀mask cv2.dilate(mask, np.ones((3, 3), np.uint8), iterations1)让修复范围主动覆盖到边缘内侧。5.3 第二次推理反而比第一次慢 30% 以上现象程序刚启动时第一次推理很快后面几次反而变慢且 CPU 占用率下降。 原因模型输入是动态形状。OpenVINO 第一次收到新形状时要做形状推导和重新编译如果你的代码每次传入的图片尺寸不同就会反复触发这个过程。加上 CPU 变频调度表现就是时快时慢。 解决在ovc转换时就把输入固定成1,4,512,512。如果手头只有 IR 文件可以用model.reshape()在加载后重塑一次固定到你要的尺寸再compile_model。还有一个隐藏因素infer_request每次调用都重新分配输出张量改用第四章末尾的get_output_tensor(0).data方式读取能减少内存分配抖动。5.4 原图 4K 分辨率修复结果纹理模糊成一片现象缩略图看着还行放大到原始分辨率后修复区域没有和周围细节对齐树杈、砖墙纹理全糊了。 原因整张图被等比压到 512×512模型输出也只有 512 分辨率再放大回 4K 当然丢失细节。LaMa 按 512 尺寸训练直接改成 1024 输入不一定有效还可能触发频域卷积的形状限制。 解决改成局部修复。先对大图原区域的掩码膨胀找到包含掩码的外接矩形把这个矩形区域单独裁出来等比缩放到 512 以内跑推理再把修复结果贴回原图。这样模型看到的仍然是目标区域的纹理尺度不会因为全图压缩而丢失细节。注意外接矩形要留一圈上下文一般往外扩展 30~50 像素给模型足够参考信息。5.5 路径带中文或空格模型加载报 FileNotFoundError现象在 Windows 上把 .rar 解压到C:\用户\下载\laMa修复Demo运行脚本时报FileNotFoundError或者编码相关异常。 原因OpenVINO 底层读取模型文件走的是系统编码中文路径在某些 locale 下会解析失败空格有时也会让脚本里的相对路径拼接出错。 解决把整个工程放到纯英文路径比如D:\lama_demo。同时检查代码里是不是用字符串拼接路径如果是改成os.path.join或Path处理。这个坑跟模型本身无关但往往最容易卡住新手而且报错信息比较隐蔽看起来像模型文件损坏实际只是路径问题。6. 批量修复与性能验证缓存模型对象、固定形状和基准测试习惯Demo 跑通只是第一步真正要拿到生产环境用需要把单张推理扩展成批量处理并且建立自己的性能基准。这里几个习惯是我每次接到类似修复项目都强制自己走一遍的。先说批量循环。read_model和compile_model只能在程序启动时执行一次不要放进循环里。正确做法是循环外编译好循环内复用同一个infer_request把每次的输入直接拷进预分配的输入张量compiled core.compile_model(model, CPU, config{PERFORMANCE_HINT: THROUGHPUT}) request compiled.create_infer_request() input_tensor request.get_input_tensor(0) output_tensor request.get_output_tensor(0) for img_path, mask_path in sample_pairs: img, mask load_image_mask(img_path, mask_path) blob build_blob(img, mask) input_tensor.data[:] blob t0 time.perf_counter() request.infer() dt time.perf_counter() - t0 pred output_tensor.data save_result(img_path, pred)性能统计别用平均值我一般取连续 20 次推理的中位数然后把前 2 次结果丢掉因为前 2 次涉及缓存预热和 CPU 频率调整会明显拉高耗时。验证修复质量也不要只看眼睛。我自己的习惯是准备一张无缺陷的干净图人为画一个假掩码跑完修复后用 PSNR 比较修复区域和原始内容。PSNR 低于 30dB 时先检查归一化再检查掩码方向最后才考虑模型权重问题。这个方法能帮你把“玄学效果”变成可重复的量化指标。另一个建议是动手改 Demo 前先跑一遍原始脚本的样例记录输出图和耗时再改任何参数。从那以后我每次拿到新的修复模型包第一件事就是固定输入形状、清掉所有中文路径、跑假掩码做基线三件事做完再谈效果和优化。希望帮到你。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进