阅读原文
图像修复是计算机视觉中但一类算法,它但目标是填充图像或视频中的区域。它使用二值掩模来识别区域,填充的像素通过从需要填充的区域的边界或者其传播区域来获取。图像修复最常见的任务是旧照片的修复和删除图像中不需要的小对象。
在这一部分,我们将简要讨论在Opencv中实现的两个图像修复算法。
这个方法是2001年提出的,它所对应的论文是Navier-Stokes, Fluid Dynamics, and Image and Video Inpainting。有时候每个学科并不是完全分割开来的,例如,电子工程师们可能会将图像看成一个二维的信号,然后使用信号处理的方法来处理图像;而数学家们,可能会将图像看成连通图,进而用数学的方法来解决图像的问题。
令人惊讶的是,流体动力学的理论也被应用到计算机视觉中。在下方的图像中,我们的目标是填充左边图像中的黑暗区域,得到一个看起来想右边的图像。
我们该如何填充这个黑色的区域呢?一个简单的约束是,有这么一条曲线,它的一头链接着A,另一头链接着B。而另一个约束是,链接A和B的曲线的右边区域应该是白色,左边的区域应该是蓝色。可以看到,上述的两个约束本质上是状态:
为了实现这样的约束,该方法的作者建立了一个偏微分方差(PDE)来更新区域内的图像强度。利用图像拉普拉斯算子估计图像平滑度信息,并沿等照度线(等强度等值线)传播。等照度估计的方法是:通过将图像梯度旋转90度来获得。
作者表明,这些方程在形式上与二维不可压缩流体的Navier-Stokes方程密切相关。将问题简化为流体动力学问题的好处是,我们可以受益于成熟的理论分析和数值工具。
这个方法是An Image Inpainting Technique Based on the Fast Marching Method这篇论文提出的。
这个方法使用了与上述方法完全不同的技术方案解决了相同的问题。它不使用图像拉普拉斯算子作为平滑估计,作者使用加权平均在已知的图像领域的像素来进行修复。利用已知的领域像素和梯度来估计等待修复区域的像素。
在该方法中,一旦需要修复的区域中的像素被修复,那么边界就需要进行更新。作者将图像缺失区域作为水平集,采用快速步进的方法对边界进行更新。
根据理论和论文,基于Navier-Stokes的修复应该是比较慢的,并且会产生比基于Fast Marching方法更加模糊的修复结果。但实际情况可能会有出入。
此外,今年来深度学习但迅猛发展,使用深度学习来进行图像修复,取得了比传统方法更好但修复效果。
让我们来看看代码吧。代码比较简单,因为Opencv都已经对修复算法进行来封装,我们只需要调用并做好交互就好啦。
import numpy as np
import cv2 as cv
import sys
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])
# 鼠标触发函数
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()):
# 在图像和mask上绘制白色线条
cv.line(dst, self.prev_pt, pt, color, 5)
self.dirty = True
self.prev_pt = pt
self.show()
def main():
print("Usage: python inpaint " )
print("Keys: ")
print("t - inpaint using FMM")
print("n - inpaint using NS technique")
print("r - reset the inpainting mask")
print("ESC - exit")
# 读取测试图像
img = cv.imread('sample.jpeg', cv.IMREAD_COLOR)
# 判读图像是否为None
if img is None:
print('Failed to load image file: {}'.format(args["image"]))
return
# 使用深度拷贝创建一个愿图像副本
img_mask = img.copy()
# 创建一个全黑的mask,用来显示人为绘制的图像缺失区域
inpaintMask = np.zeros(img.shape[:2], np.uint8)
# 使用Opencv创建能够绘制缺失区域的草图
sketch = Sketcher('image', [img_mask, inpaintMask], lambda : ((255, 255, 255), 255))
while True:
ch = cv.waitKey()
# 根据不同的按键触发不同的效果
if ch == 27:
break
if ch == ord('t'):
# FMM算法
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'):
# NS算法
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
# 清空mask图
inpaintMask[:] = 0
sketch.show()
print('Completed')
if __name__ == '__main__':
main()
cv.destroyAllWindows()