草庐IT

[OpenCV实战]34 使用OpenCV进行图像修复

liferecords 2023-03-28 原文
本文将描述一类称为图像修复的区域填充算法。想象一下找一张旧的家庭照片。你扫描它,它看起来很棒,除了一些划痕。当然,你可以在photoshop中加载照片并修复划痕。除此之外可以编写10行代码以使用OpenCV中的修复算法来解决问题。

1 什么是图像修复

图像修复是计算机视觉中的一类算法,其目标是填充图像或视频内的区域。该区域使用二进制掩模进行标识,填充通常根据需要填充的区域边界信息来完成。图像修复的最常见应用是恢复旧的扫描照片。它还用于删除图像中的小的不需要的对象。

在本节中,我们将简要讨论在OpenCV中实现的两种修复算法。

1.1 INPAINT_NS : Navier-Stokes based Inpainting

该方法于2001年发表在题为Navier-Stokes, Fluid Dynamics, and Image and Video Inpainting的论文。论文见:

有时我觉得计算机视觉领域是一个来自其他领域的移民领域,如电子工程,计算机科学,物理和数学。他们将自己的想法带到现场,以非常有趣和独特的方式解决同样的问题。电气工程师可以将图像看作2D信号,并应用信号处理理论来解决计算机视觉问题。另一方面,数学家可以将图像看作连通图并使用图论解决计算机视觉问题。因此,为流体动力学开发的理论也可以用于计算机视觉,这并不奇怪。在下图中,我们的目标是填充暗区并获得一个看起来像右边的图像。

我们如何填补这个黑色区域?我们想要的一个约束是边缘进入点A应该连接边缘离开点B。我们可能想要的另一个约束是连接A和B的曲线右边的区域应该是白色,而左边的区域应该是蓝色的。

以上两个约束基本上要求:保留渐变(即边缘特征)和继续在平滑区域中传播颜色信息。

作者建立了一个偏微分方程(PDE)来更新具有上述约束的区域内的图像强度。

1.2 INPAINT_TELEA : Fast Marching Method based

该方法基于论文An Image Inpainting Technique Based on the Fast Marching Method。论文作者Alexandru Telea。论文见:

该方法实现使用不同的技术解决了相同的约束。作者不使用图像拉普拉斯算子作为平滑度的估计,而是使用像素的已知图像邻域上的加权平均值来补绘。已知的邻域像素和梯度用于估计要修复的像素的颜色。

1.3 方法比较与函数实现

根据理论和论文,基于Navier-Stokes的修复应该更慢,并且倾向于产生比fast marching method的方法更模糊的结果。在实践中,我们没有发现这种情况。INPAINT_NS在我们的测试中产生了更好的结果,速度也略高于INPAINT_TELEA。

在OpenCV中,使用函数inpaint实现了修复算法。函数接口如下:

C++:

void inpaint( const Mat& src, const Mat& inpaintMask, Mat& dst, double inpaintRange, int flags ); Python:

dst = cv2.inpaint(src, inpaintMask, inpaintRadius, flags) Src:源图像

inpaintMask:二进制掩码,指示要修复的像素。

Dst:结果图像

inpaintRadius:表示修复的半径

flags : 修复算法,主要有INPAINT_NS (Navier-Stokes based method) or INPAINT_TELEA (Fast marching based method)

2 结果与代码

2.1 结果

让我们来看看对林肯总统的历史形象进行修复的结果。这张照片背后有一段引人入胜的历史,我从维基百科借来的:

1865年2月5日星期日,在华盛顿特区的加德纳画廊,亚历山大·加德纳拍摄了几张总统的多镜头照片。在本届会议结束之前,加德纳要求总统最后一个姿势。他把相机拉得更近,拍了一张林肯头部,肩膀和胸部的照片。神秘的玻璃板破裂。加德纳小心翼翼地将它带到了他的黑暗房间,并且能够制作一张印刷品,但在林肯的脸上有一个不祥的裂缝。在这个印刷品完全破碎并被弃用。但这种印刷品,即O-118,至今仍然存在。多年来,许多人将这一裂缝与10周后等待林肯的刺客子弹的象征性预言联系在一起。

修复结果:上图左边的第一个图像是输入图像,第二个图像是掩模,第三个图像是INPAINT_TELEA的结果,第四个图像是INPAINT_NS的结果。

让我们来看一个更复杂的例子。我们已经在一个花园的图像上草草写了很多,但是结果仍然非常引人注目。结果如下:

上图中,左:带有潦草文字的原始图像。中:使用INPAINT_TELEA方法修复,右:使用INPAINT_NS。

2.2 代码

所有代码见:

两种算法修复效果都还不错,但是都需要事先准备修复模板的掩模mask,也就是inpaintMask 这个参数。例子里面用鼠标在图片上划线,划线的结果就是mask,而真正应用的时候需要事先设计好这个mask。例子程序中在划线确定mask后,不同按键有不同效果。按t选择INPAINT_TELEA处理,按n选择INPAINT_NS处理,按r查看原图,按ESC退出。

具体代码如下:

C++:

#include "pch.h" #include <opencv2/opencv.hpp> #include <opencv2/photo.hpp> #include <iostream> using namespace cv; using namespace std; // Declare Mat objects for original image and mask for inpainting Mat img, inpaintMask; // Mat object for result output Mat res; Point prevPt(-1, -1); // onMouse function for Mouse Handling // Used to draw regions required to inpaint // 调用鼠标事件 static void onMouse(int event, int x, int y, int flags, void*) { if (event == EVENT_LBUTTONUP || !(flags & EVENT_FLAG_LBUTTON)) prevPt = Point(-1, -1); else if (event == EVENT_LBUTTONDOWN) prevPt = Point(x, y); else if (event == EVENT_MOUSEMOVE && (flags & EVENT_FLAG_LBUTTON)) { Point pt(x, y); if (prevPt.x < 0) prevPt = pt; line(inpaintMask, prevPt, pt, Scalar::all(255), 5, 8, 0); line(img, prevPt, pt, Scalar::all(255), 5, 8, 0); prevPt = pt; imshow("image", img); imshow("image: mask", inpaintMask); } } int main() { string filename = "./image/flower-garden.jpg"; // Read image in color mode 读图 img = imread(filename); Mat img_mask; // Return error if image not read properly if (img.empty()) { cout << "Failed to load image: " << filename << endl; return 0; } namedWindow("image"); // Create a copy for the original image 复制原图像 img_mask = img.clone(); // Initialize mask (black image) inpaintMask = Mat::zeros(img_mask.size(), CV_8U); // Show the original image imshow("image", img); //调用鼠标在图像上画圈 setMouseCallback("image", onMouse, NULL); for (;;) { char c = (char)waitKey(); //按t选择INPAINT_TELEA处理 if (c == 't') { // Use Algorithm proposed by Alexendra Telea inpaint(img, inpaintMask, res, 3, INPAINT_TELEA); imshow("Inpaint Output using FMM", res); } //按n选择INPAINT_NS处理 if (c == 'n') { // Use Algorithm proposed by Bertalmio et. al. inpaint(img, inpaintMask, res, 3, INPAINT_NS); imshow("Inpaint Output using NS Technique", res); } //按r查看原图 if (c == 'r') { inpaintMask = Scalar::all(0); img_mask.copyTo(img); imshow("image", inpaintMask); } //按ESC退出 if (c == 27) { break; } } return 0; } Python:

import numpy as np import cv2 as cv # OpenCV Utility Class for Mouse Handling class Sketcher: def __init__(self, windowname, dests, colors_func): self.prev_pt = None self.windowname = windowname self.dests = dests self.colors_func = colors_func self.dirty = False self.show() cv.setMouseCallback(self.windowname, self.on_mouse) def show(self): cv.imshow(self.windowname, self.dests[0]) cv.imshow(self.windowname + ": mask", self.dests[1]) # onMouse function for Mouse Handling def on_mouse(self, event, x, y, flags, param): pt = (x, y) if event == cv.EVENT_LBUTTONDOWN: self.prev_pt = pt elif event == cv.EVENT_LBUTTONUP: self.prev_pt = None if self.prev_pt and flags & cv.EVENT_FLAG_LBUTTON: for dst, color in zip(self.dests, self.colors_func()): cv.line(dst, self.prev_pt, pt, color, 5) self.dirty = True self.prev_pt = pt self.show() def main(): print("Usage: python inpaint <image_path>") print("Keys: ") print("t - inpaint using FMM") print("n - inpaint using NS technique") print("r - reset the inpainting mask") print("ESC - exit") # Read image in color mode img = cv.imread("./image/Lincoln.jpg") # If image is not read properly, return error if img is None: return # Create a copy of original image img_mask = img.copy() # Create a black copy of original image # Acts as a mask inpaintMask = np.zeros(img.shape[:2], np.uint8) # Create sketch using OpenCV Utility Class: Sketcher sketch = Sketcher('image', [img_mask, inpaintMask], lambda : ((255, 255, 255), 255)) while True: ch = cv.waitKey() if ch == 27: break if ch == ord('t'): # Use Algorithm proposed by Alexendra Telea: Fast Marching Method (2004) # Reference: https://pdfs.semanticscholar.org/622d/5f432e515da69f8f220fb92b17c8426d0427.pdf res = cv.inpaint(src=img_mask, inpaintMask=inpaintMask, inpaintRadius=3, flags=cv.INPAINT_TELEA) cv.imshow('Inpaint Output using FMM', res) if ch == ord('n'): # Use Algorithm proposed by Bertalmio, Marcelo, Andrea L. Bertozzi, and Guillermo Sapiro: Navier-Stokes, Fluid Dynamics, and Image and Video Inpainting (2001) res = cv.inpaint(src=img_mask, inpaintMask=inpaintMask, inpaintRadius=3, flags=cv.INPAINT_NS) cv.imshow('Inpaint Output using NS Technique', res) if ch == ord('r'): img_mask[:] = img inpaintMask[:] = 0 sketch.show() print('Completed') if __name__ == '__main__': main() cv.destroyAllWindows()

有关[OpenCV实战]34 使用OpenCV进行图像修复的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  4. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  5. ruby - 在 Ruby 中使用匿名模块 - 2

    假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

  6. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

    我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

  7. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  8. ruby-on-rails - rails : "missing partial" when calling 'render' in RSpec test - 2

    我正在尝试测试是否存在表单。我是Rails新手。我的new.html.erb_spec.rb文件的内容是:require'spec_helper'describe"messages/new.html.erb"doit"shouldrendertheform"dorender'/messages/new.html.erb'reponse.shouldhave_form_putting_to(@message)with_submit_buttonendendView本身,new.html.erb,有代码:当我运行rspec时,它失败了:1)messages/new.html.erbshou

  9. ruby-on-rails - 由于 "wkhtmltopdf",PDFKIT 显然无法正常工作 - 2

    我在从html页面生成PDF时遇到问题。我正在使用PDFkit。在安装它的过程中,我注意到我需要wkhtmltopdf。所以我也安装了它。我做了PDFkit的文档所说的一切......现在我在尝试加载PDF时遇到了这个错误。这里是错误:commandfailed:"/usr/local/bin/wkhtmltopdf""--margin-right""0.75in""--page-size""Letter""--margin-top""0.75in""--margin-bottom""0.75in""--encoding""UTF-8""--margin-left""0.75in""-

  10. ruby-on-rails - 按天对 Mongoid 对象进行分组 - 2

    在控制台中反复尝试之后,我想到了这种方法,可以按发生日期对类似activerecord的(Mongoid)对象进行分组。我不确定这是完成此任务的最佳方法,但它确实有效。有没有人有更好的建议,或者这是一个很好的方法?#eventsisanarrayofactiverecord-likeobjectsthatincludeatimeattributeevents.map{|event|#converteventsarrayintoanarrayofhasheswiththedayofthemonthandtheevent{:number=>event.time.day,:event=>ev

随机推荐