主题
在本节中我们将描述一种称为图像修复的区域填充算法。
这种图片修复算法的作用是可以通过使用OpenCV模块来进行图片上异常划痕或斑点等噪线、噪点的修复,而且代码相对其他的图片修复算法而言要稍微简单一些。(最后效果类似于PhotoShop)
图像修复算法是计算机仿人视觉中的一类基本算法,算法的主要目标是填充图像或视频内的区域,该区域主要使用二进制掩模来进行标识,填充通常根据需要我们来填充的区域周边的边界信息来完成。
图像修复的最常见应用是用来恢复照片中产生的一些小的噪线等,当然图像修复还可以用于删除图像中的小的不需要的对象,这种时候只需要我们把不需要的对象看作一种特殊的噪线即可。
在本节中,关于图片的修复,我们会简要的讨论在机器仿人视觉中较为常用的两种修复算法,分别是INPAINT_NS和INPAINT_TELEA。
INPAINT_NS
这里我们依靠图片来进行这种算法的说明,原图如下图左边所示,现在假设我们图片破损了,破损区域如下图右
边所示。
那么我们现在的问题就是我们该如何填补这个黑色区域的问题。现在我们想要的一条约束黑线,他应该有两个这样的特征:
(1)我们想要这样一条约束黑线,约束黑线的起点是从下边缘进入黑色区域,然后从上边沿处脱离黑色区域。
(2)曲线的右边的区域应该为蓝色,而约束黑线的左边区域应该为白色。
通过以上简述的两个约束条件,我们可以得知的是,这种算法要求我们得到的约束黑线有以下两个特点:保留原有的边缘特征,以及一条能够继续在平滑区域中传播颜色信息的约束黑线。
这个算法的创始人通过建立了一个偏微分方程来更新具有上述约束的区域内的图像强度,算法比较复杂,所以我们就不再在这里进行过多的描述了,这里给出对应的论文的网站。
INPAINT_TELEA
第二种算法与之前那种算法的区别在于:它不使用拉普拉斯算子作为平滑度的估计(前面那种算法需要使用拉普拉斯算子来进行平滑度的估计)。这种算法使用的是:依靠像素的已知图像邻域边上的加权平均值来对已经破损的图像进行补绘。这里的补绘指的是用我们已知的邻域像素和图像梯度来帮忙估计要修复的像素的颜色,通过这种估计可以来进行图像的修复。
提出这种算法的论文如下所示,感兴趣的读者可以自行前往如下网址。
cv2.inpaint函数
在我们进行图像的修复的时候,我们需要使用到的函数为:cv2.inpaint,这个函数的语法如下所示:
dst = cv2.inpaint(src, inpaintMask, inpaintRadius, flags)
·src:我们要修复的图像。
·inpaintMask:二进制的掩码,这里指的是我们要修复的像素。
·inpaintRadius:表示我们要对图片进行修复的半径。
·flags:我们要选用的修复算法,这里我们使用的主要有上面说过的两种:
(1)cv2.INPAINT_NS
(2)cv2. INPAINT_TELEA
·dst:最后我们得到的结果图像。
INPAINT_NS修复
首先我们先来进行cv2.INPAINT_NS的修复,我们的原图如代码下方图中最左边的所示,示例代码如下所示。
import numpy as np
import cv2
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()
cv2.setMouseCallback(self.windowname, self.on_mouse)
def show(self):
cv2.imshow(self.windowname, self.dests[0])
cv2.imshow(self.windowname + ": mask", self.dests[1])
# 设置相对应的鼠标事件
def on_mouse(self, event, x, y, flags, param):
pt = (x, y)
if event == cv2.EVENT_LBUTTONDOWN:
self.prev_pt = pt
elif event == cv2.EVENT_LBUTTONUP:
self.prev_pt = None
if self.prev_pt and flags & cv2.EVENT_FLAG_LBUTTON:
for dst, color in zip(self.dests, self.colors_func()):
cv2.line(dst, self.prev_pt, pt, color, 5)
self.dirty = True
self.prev_pt = pt
self.show()
def main():
# 读取照片
img = cv2.imread("1.jpg")
# 如果没有打开图片,直接返回
if img is None:
return
# 创造一个原图的复制出来,方便后面显示
img_mask = img.copy()
# 创建一个黑色的掩膜
inpaintMask = np.zeros(img.shape[:2], np.uint8)
sketch = Sketcher('image', [img_mask, inpaintMask], lambda : ((255, 255, 255), 255))
while True:
ch = cv2.waitKey()
if ch == ord('q'):
break
if ch == ord('n'):
# 使用图像修复算法
res = cv2.inpaint(src=img_mask, inpaintMask=inpaintMask, inpaintRadius=3, flags=cv2.INPAINT_NS)
cv2.imshow('output', res)
if ch == ord('r'):
img_mask[:] = img
inpaintMask[:] = 0
sketch.show()
print('Completed')
if __name__ == '__main__':
main()
cv2.destroyAllWindows()
运行上述代码,我们先左键拖动我们的鼠标,通过鼠标事件来在我们的图片上进行画图(将我们需要修复的部分用白色部分覆盖)我们鼠标拖动的白色部分其实也就是我们的掩膜,因此会显示在mask的窗口上,(每次进行算法前必须都提前准备好与我们的噪线相对应的掩膜)
通过鼠标拖动将损坏区域完全覆盖以后,我们可以按下q键来进行画布的退出,也可以按下n键来进行NS算法的画面修补,如果需要再查看原图,也按下r键来查看;下图中间的部分表示的是已经被我们画出的白色掩膜包含的图片,下图右侧的图片表示的是通过NS修补后的图片。
可以看到的是,经过NS算法修复之后,原本图中上方的蓝色划线字样已经完全消失了。
注意:如果在运行程序时将噪线完全覆盖后运行NS算法还有残留部分,那么可以增大在原图中残留部分周边的白色部分的面积,通过增大掩膜的方式增大计算,从而做到更好效果的NS修改算法效果。
INPAINT_TELEA修复
在上面的例子中我们已经对有“划痕”的图片进行了INPAINT_NS算法的修复,下面我们来尝试INPAINT_TELEA的修复算法并将两种算法来进行一些简单的比较。我们还是采用上图来进行修复,示例代码如下所示。
import numpy as np
import cv2
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()
cv2.setMouseCallback(self.windowname, self.on_mouse)
def show(self):
cv2.imshow(self.windowname, self.dests[0])
cv2.imshow(self.windowname + ": mask", self.dests[1])
# 设置相对应的鼠标事件
def on_mouse(self, event, x, y, flags, param):
pt = (x, y)
if event == cv2.EVENT_LBUTTONDOWN:
self.prev_pt = pt
elif event == cv2.EVENT_LBUTTONUP:
self.prev_pt = None
if self.prev_pt and flags & cv2.EVENT_FLAG_LBUTTON:
for dst, color in zip(self.dests, self.colors_func()):
cv2.line(dst, self.prev_pt, pt, color, 5)
self.dirty = True
self.prev_pt = pt
self.show()
def main():
# 读取照片
img = cv2.imread("1.jpg")
# 如果没有打开图片,直接返回
if img is None:
return
# 创造一个原图的复制出来,方便后面显示
img_mask = img.copy()
# 创建一个黑色的掩膜
inpaintMask = np.zeros(img.shape[:2], np.uint8)
sketch = Sketcher('image', [img_mask, inpaintMask], lambda : ((255, 255, 255), 255))
while True:
ch = cv2.waitKey()
if ch == ord('q'):
break
if ch == ord('t'):
res=cv2.inpaint(src=img_mask,inpaintMask=inpaintMask,inpaintRadius=3, flags=cv2.INPAINT_TELEA)
cv2.imshow('output', res)
if ch == ord('r'):
img_mask[:] = img
inpaintMask[:] = 0
sketch.show()
print('Completed')
if __name__ == '__main__':
main()
cv2.destroyAllWindows()
与前面一样的,这里我们可以使用q键来进行画布的退出,可以按下t键来进行TELEA算法的画面修补,按下r键可以查看我们的原图,运行上述代码后,与之前NS算法一样的操作之后,我们可以得到如下两张图所示的效果,其分别对应:掩膜图以及TELEA修复图。
这里我们可以通过尝试分别在两块代码中加入以下代码显示各自修复的时间来进行比较两种修复算法的修复快慢。
Start=time.time()
#中间过程
......
print(time.time()-Start)
这个时候我们可以发现的是,就上面两个例子而言NS算法所耗费的时间略比TELEA算法的时间要短一些,当然这也与我们选择的掩膜有关:可以看到的是我们在NS中画出的白色掩膜比起我们在TELEA中的掩膜面积要小一些,这个也对我们代码中NS算法最后得出较快的运行结果有关。
感兴趣的读者可以自行对这两种算法进行时间上的比较,就理论上而言,TELEA算法在与NS达到相同的效果的前提下,其所消耗的时间应该要比NS算法要短一些,但我们在实际运用的过程中,我们往往会发现的是,NS算法更能节约一些时间,且在相同计算量下做得比TELEA算法更好。
注意:TELEA算法因为基于的原理是快速匹配算法(Fast Marching Method based),所以我们常常也称其为FMM算法。
总结
(前一段日子在赶着写书,被催稿(吐)导致的本系列咕咕咕了,之后可能还会咕,但是还是会尽量快的更新,谢谢大家支持啦!)