{
  "title": "Practice: CPU-Only Image Inpainting — Three Models Benchmarked: LaMa / MI-GAN / Telea Selection and Engineering Details",
  "url": "/en/posts/practice-erase-cpu-benchmark/",
  "permalink": "https://hackcv.com/en/posts/practice-erase-cpu-benchmark/",
  "date": "2026-08-23",
  "lastmod": "2026-08-23",
  "author": "hackcv",
  "description": "Real measurements on an Intel Mac with pure CPU + ONNX Runtime: LaMa/MI-GAN/Telea speed characteristics, crop-vs-resize strategy, mask dilation/feathering, and a three-tier selection matrix.",
  "categories": ["Research Brief"],
  "tags": ["AI","Image Inpainting","ONNX Runtime","LaMa","MI-GAN","Practice"],
  "cover": "https://picsum.photos/seed/practice-cpu-only-image-inpainting-three-models-benchmarked-lama-/-mi-gan-/-telea-selection-and-engineering-details/1200/675",
  "readingTime": 3,
  "wordCount": 672,
  "content": "\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003eOne-line takeaway\u003c/strong\u003e: Production-grade image inpainting works without a GPU. \u003cstrong\u003eLaMa at 2.1s/image for final output, MI-GAN at 0.8s for quick preview, OpenCV Telea under 50ms for flat backgrounds\u003c/strong\u003e, all switchable in one process. Two key engineering findings: ① inference time is \u003cstrong\u003eroughly independent of input resolution\u003c/strong\u003e (2.1s/0.8s constant); ② \u003cstrong\u003elocal cropping (crop) clearly beats whole-image resizing (resize)\u003c/strong\u003e — preserving high-frequency details like mountains.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"background--motivation\"\u003eBackground \u0026amp; Motivation\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eScenario\u003c/strong\u003e: remove watermarks, objects, text on a CPU-only machine (Intel Mac x86_64, 12 cores / 16GB), no NVIDIA GPU\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eHard constraint\u003c/strong\u003e: PyTorch stopped shipping \u003cstrong\u003emacOS x86_64 wheels at 2.3\u003c/strong\u003e — both GPU and PyTorch routes are dead; the only viable path is \u003cstrong\u003eONNX Runtime (CPU inference)\u003c/strong\u003e\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eExcluded\u003c/strong\u003e: diffusion models (SD Inpainting / BrushNet / FLUX) take 30s–minutes on CPU — unusable on Intel Mac, ruled out\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"three-model-benchmarks\"\u003eThree-Model Benchmarks\u003c/h2\u003e\n\u003ctable\u003e\n\t\u003cthead\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003cth\u003eModel\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003eSource\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003eSize\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003eMeasured time\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003eCharacter\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003eBest for\u003c/th\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/thead\u003e\n\t\u003ctbody\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003cstrong\u003eLaMa\u003c/strong\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eWACV 2022 (IOPaint default)\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e~198 MB\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e\u003cstrong\u003e2.1 s\u003c/strong\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eStrongest with large masks \u0026amp; textures\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eGeneral inpainting, architecture/nature textures, final output\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003cstrong\u003eMI-GAN\u003c/strong\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eICCV 2023 (Picsart)\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e~27 MB\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e\u003cstrong\u003e0.8 s\u003c/strong\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eFast, light; slightly soft on fine texture\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eQuick preview, mobile\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003e\u003cstrong\u003eTelea/NS\u003c/strong\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eOpenCV built-in\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e0 MB\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e\u003cstrong\u003e\u0026lt;50 ms\u003c/strong\u003e\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eDiffusion interpolation, simple backgrounds\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eFlat backgrounds, watermarks\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003e\u003cstrong\u003eKey observation: time does not scale linearly with resolution\u003c/strong\u003e — LaMa stays at 2.1s, MI-GAN at 0.8s within normal sizes. The model internally normalizes the input; resolution mainly affects preprocessing, not the inference core. So \u0026ldquo;small preview first, full-size output later\u0026rdquo; costs almost nothing.\u003c/p\u003e\n\u003ch2 id=\"engineering-details-three-things-that-decide-quality\"\u003eEngineering Details (Three Things That Decide Quality)\u003c/h2\u003e\n\u003ch3 id=\"1-backend-abstraction--automatic-strategy-selection\"\u003e1. Backend abstraction + automatic strategy selection\u003c/h3\u003e\n\u003cp\u003eThe three models are unified as separate backends (\u003ccode\u003eeraser/backends/\u003c/code\u003e: \u003ccode\u003elama.py\u003c/code\u003e / \u003ccode\u003emigan.py\u003c/code\u003e / \u003ccode\u003eclassic.py\u003c/code\u003e) sharing the same mask input and post-processing. \u003ccode\u003e_pick_strategy\u003c/code\u003e in \u003ccode\u003epipeline.py\u003c/code\u003e chooses automatically from \u003cstrong\u003einput size + mask extent\u003c/strong\u003e:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003ecrop (local crop)\u003c/strong\u003e: crop the region around the mask bounding box and infer locally — preserves original high-frequency detail\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eresize (whole-image)\u003c/strong\u003e: squeeze the full image to model input size — faster, but \u003cstrong\u003emountains, fabric and other high-frequency texture get smeared\u003c/strong\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eMeasured: crop is clearly better (more detail retained).\u003c/p\u003e\n\u003ch3 id=\"2-large-image-tiling-overlap--ramp-feathering\"\u003e2. Large-image tiling: overlap + ramp feathering\u003c/h3\u003e\n\u003cp\u003eVery long images are tiled and stitched (\u003ccode\u003eeraser/tiling.py\u003c/code\u003e):\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eTile size \u003ccode\u003etile\u003c/code\u003e + \u003cstrong\u003e\u003ccode\u003eoverlap\u003c/code\u003e pixels\u003c/strong\u003e between neighbors (step = tile − overlap; last tile edge-aligned so nothing is missed or gapped)\u003c/li\u003e\n\u003cli\u003eOverlap zones are blended with a \u003ccode\u003e_ramp\u003c/code\u003e linear-weight ramp — this is what makes \u0026ldquo;no visible tile seams on big images\u0026rdquo;\u003c/li\u003e\n\u003cli\u003eMulti-box masks are merged via \u003ccode\u003e_merge_boxes\u003c/code\u003e (IoU-based) to avoid re-erasing the same object across tiles\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"3-mask-post-processing-dilation--feathering\"\u003e3. Mask post-processing: dilation + feathering\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# Dilate mask 12px (cover edge residue) + feather 5px (soft transition)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epython erase.py -i photo.jpg -m mask.png -o out.png --model lama --dilate \u003cspan style=\"color:#ae81ff\"\u003e12\u003c/span\u003e --feather \u003cspan style=\"color:#ae81ff\"\u003e5\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eDilation\u003c/strong\u003e: the mask must be slightly larger than the object, or edges leave ghosting — \u003ccode\u003e--dilate 12\u003c/code\u003e is a safe value\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eFeathering\u003c/strong\u003e: hard mask edges create visible seams; feathering softens the transition\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"4-three-tier-switching-costs--0\"\u003e4. Three-tier switching costs ≈ 0\u003c/h3\u003e\n\u003cp\u003eSame mask, same process, switch models: preview with MI-GAN (0.8s to check composition), final with LaMa (2.1s), auto-degrade to Telea for simple watermarks — the whole three-tier experience is ≈ 2s.\u003c/p\u003e\n\u003ch2 id=\"measured-data\"\u003eMeasured Data\u003c/h2\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" style=\"color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003e\u003cspan style=\"color:#75715e\"\u003e# Environment: Python 3.13 + onnxruntime, models under models/\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epython erase.py -i photo.jpg -m mask.png -o out.png --model lama --strategy crop    \u003cspan style=\"color:#75715e\"\u003e# 2.1s\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epython erase.py -i photo.jpg -m mask.png -o out.png --model migan --strategy resize \u003cspan style=\"color:#75715e\"\u003e# 0.8s\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan style=\"display:flex;\"\u003e\u003cspan\u003epython erase.py -i photo.jpg -m mask.png -o out.png --model telea --dilate \u003cspan style=\"color:#ae81ff\"\u003e12\u003c/span\u003e --feather \u003cspan style=\"color:#ae81ff\"\u003e5\u003c/span\u003e \u003cspan style=\"color:#75715e\"\u003e# \u0026lt;50ms\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003ctable\u003e\n\t\u003cthead\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003cth\u003eMetric\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003eLaMa\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003eMI-GAN\u003c/th\u003e\n\t\t\t\t\t\u003cth\u003eTelea\u003c/th\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/thead\u003e\n\t\u003ctbody\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003eTime (1024² mask)\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e2.1 s\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e0.8 s\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003e0.05 s\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003eTexture detail\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eHighest\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eSlightly soft\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eFlat backgrounds only\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\t\t\u003ctr\u003e\n\t\t\t\t\t\u003ctd\u003eLarge masks (\u0026gt;1/4 image)\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eGood\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003eFair\u003c/td\u003e\n\t\t\t\t\t\u003ctd\u003ePoor\u003c/td\u003e\n\t\t\t\u003c/tr\u003e\n\t\u003c/tbody\u003e\n\u003c/table\u003e\n\u003ch2 id=\"scope\"\u003eScope\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eFits\u003c/strong\u003e: general object removal, watermarks/subtitles, GPU-less local toolchains, batch processing\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eDoesn\u0026rsquo;t fit\u003c/strong\u003e: \u0026gt;half-image masks (use specialized models for semantic completion); millisecond-latency batch workloads (get a GPU)\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eTrade-off\u003c/strong\u003e: skip diffusion — on CPU, LaMa\u0026rsquo;s 2.1s vs diffusion\u0026rsquo;s 30s+ isn\u0026rsquo;t worth 15× the wait for the quality gap\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"reproduction-notes\"\u003eReproduction Notes\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003eModel source: IOPaint official ONNX weights (LaMa big_lama_dyn, MI-GAN)\u003c/li\u003e\n\u003cli\u003eThreads: onnxruntime defaults are fine on 12 cores; use multi-process parallelism for batches\u003c/li\u003e\n\u003cli\u003eReference: the project README ships smoke/pipeline/benchmark comparison images to check against\u003c/li\u003e\n\u003c/ul\u003e\n",
  "summary": " One-line takeaway: Production-grade image inpainting works without a GPU. LaMa at 2.1s/image for final output, MI-GAN at 0.8s for quick preview, OpenCV Telea under 50ms for flat backgrounds, all switchable in one process. Two key engineering findings: ① inference time is roughly independent of input resolution (2.1s/0.8s constant); ② local cropping (crop) clearly beats whole-image resizing (resize) — preserving high-frequency details like mountains.\nBackground \u0026amp; Motivation Scenario: remove watermarks, objects, text on a CPU-only machine (Intel Mac x86_64, 12 cores / 16GB), no NVIDIA GPU Hard constraint: PyTorch stopped shipping macOS x86_64 wheels at 2.3 — both GPU and PyTorch routes are dead; the only viable path is ONNX Runtime (CPU inference) Excluded: diffusion models (SD Inpainting / BrushNet / FLUX) take 30s–minutes on CPU — unusable on Intel Mac, ruled out Three-Model Benchmarks Model Source Size Measured time Character Best for LaMa WACV 2022 (IOPaint default) ~198 MB 2.1 s Strongest with large masks \u0026amp; textures General inpainting, architecture/nature textures, final output MI-GAN ICCV 2023 (Picsart) ~27 MB 0.8 s Fast, light; slightly soft on fine texture Quick preview, mobile Telea/NS OpenCV built-in 0 MB \u0026lt;50 ms Diffusion interpolation, simple backgrounds Flat backgrounds, watermarks Key observation: time does not scale linearly with resolution — LaMa stays at 2.1s, MI-GAN at 0.8s within normal sizes. The model internally normalizes the input; resolution mainly affects preprocessing, not the inference core. So \u0026ldquo;small preview first, full-size output later\u0026rdquo; costs almost nothing.\n"
}
