引言
这两天看了Learn OpenCV 上关于图像修复(Image Inpainting)的相关文章,图像修复可能听起来是一项很难的技术,实际上确实如此。
有时相片上出现了划痕,我们第一时间可能想到的修复方式可能就是PS,然而利用Opencv写几十行代码就可以轻松的修复简单的划痕。
图像修复是什么
图像修复时计算机视觉中一种利用某种算法填充图像或者视频内的区域的方法,我们用二进制mask标识要修复的区域,并用其周边区域的信息完成修复。
修复的算法
文章中介绍了两种算法
cv2.INPAINT_NS: 基于Navier-Stokes的修复
如果你学过流体力学,NS方程一定是你绕不开的一个方程。
它极难寻找解析解,因此我们通常要进行一些假设消去某些项。
例如我们会做出流体不可压的假设,有时会忽略流体的黏性等等。
具体如何把牛顿流体与图像降噪进行类比,可以参考论文:
我们来看图像与牛顿流体的类比
论文中提到了该类比表,其中将图像灰度类比为流函数,图像的等照度线(等灰度线?)类比为流体的流速(流函数的梯度),光滑度类比为流体旋涡,各向异性的散射类比为流体黏性。
论文在最后证明了利用NS方程进行图像降噪方法的解的存在与唯一性。
cv.INPAINT_TELEA: 基于快速行进方法
该算法利用图像邻域的加权平均修补去计算平滑度,理论上比上述NS方法利用拉普拉斯算子的方法更快速。
然而实际上NS效果稍好,速度也稍好。
openCV图像修复函数
dst = cv2.inpaint(
src,
inpaintMask,
inpaintRadius,
flags)
其中inpaintMask代表要修复的区域的掩码,
inpaintRadius代表像素邻域修补半径
flags有上述的NS,TELEA两种方法可选
下面来看代码:
import numpy as np
import cv2 as cv
import sys
# Use time to compare NS and TELEA
import time
# opencv Class for Mouse (Version for python) setmouse...
class Sketcher:
def __init__(self,windowname,dests,colors_func):
self.prev_point=None
self.windowname=windowname
# dests is a set of images: copy & mask
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])
# on mouse function
def on_mouse(self,event,x,y,flags,param):
# point store the current position of the mouse
point=(x,y)
if event==cv.EVENT_LBUTTONDOWN:
# assignment of previous point
self.prev_point=point
elif event==cv.EVENT_LBUTTONUP:
self.prev_point=None
# cv.EVENT_FLAG_LBUTTON & flags 代表按住左键拖拽
if self.prev_point and flags & cv.EVENT_FLAG_LBUTTON:
# zip 把前后参数打包为元组
for dst,color in zip (self.dests,self.colors_func()):
cv.line(dst,self.prev_point,point,color,5)
# Record this dirt
self.dirty=True
self.prev_point=point
self.show()
def main():
print("Usage: python inpaint <image_path>")
print("Keys: ")
print("t - inpaint using FMM")# Fast Marching method
print("n - inpaint using NS technique")
print("r - reset the inpainting mask")
print("ESC - exit")
# Read image in color mode
img=cv.imread(sys.argv[1],cv.IMREAD_COLOR)
# Return error if failed to read the image
if img is None:
print("Failed to read the image")
return
# Create the copy of the original image
img_mask=img.copy()
# Create a black mask of the image
inpaintMask = np.zeros(img.shape[:2],np.uint8)
# Create a Sketch
# dests= img_mask, inpaintMask
# color_func is a tuple : white with BGR and white on gray
sketch=Sketcher('image',[img_mask,inpaintMask],lambda : ((255,255,255),255))
while True:
ch=cv.waitKey()
# Esc
if ch==27:
break
if ch == ord('t'):
t1 = time.time()
res=cv.inpaint(src=img_mask,inpaintMask=inpaintMask,inpaintRadius=3, flags=cv.INPAINT_TELEA)
res=np.hstack((img,res))
t2 = time.time()
print("Time: FMM = {} ms".format((t2-t1)*1000))
cv.imshow('Inpaint with FMM',res)
cv.imwrite("FMM-eye.png",res)
if ch ==ord('n'):
t1 = time.time()
res=cv.inpaint(src=img_mask,inpaintMask=inpaintMask,inpaintRadius=3,flags=cv.INPAINT_NS)
res=np.hstack((img,res))
t2 = time.time()
cv.imshow('Inpaint Output using NS Technique', res)
cv.imwrite("NS-eye.png",res)
# type r to reset the image
if ch== ord('r'):
# The reason for which we copied image
img_mask[:]=img
inpaintMask[:]=0
sketch.show()
print('Completed')
if __name__ == '__main__':
main()
cv.destroyAllWindows()
可以看到,这个程序中sketch类用于绘制记录草图,dests是图片数组,分别存储要操作的图片的副本和要修复区域的掩码,color_func用于存储该草图的颜色数据。dirt用于储存是否已经被修改。附加了setMousecallback()的cv鼠标回调函数。prev_point用于储存前一个位置。
可以看到后面的onMouse函数中定义了鼠标左键操作记录点坐标,值得注意的是flags and cv.EVENT_FLAG_LBUTTON代表记录左键按下并移动的操作。
后面传入类时可以看到传入 ((255,255,255),255)分别赋值给彩图和二值掩码。
我们用np.hstack水平拼接原图和复原后的图片,做一个对比。
其余注释已经比较详尽,最后调用main()即可。
原图:
用鼠标确定修复范围:
最终结果对比: