利用OpenCV-Python 进行简单图像修复

引言

这两天看了Learn OpenCV 上关于图像修复(Image Inpainting)的相关文章,图像修复可能听起来是一项很难的技术,实际上确实如此。

有时相片上出现了划痕,我们第一时间可能想到的修复方式可能就是PS,然而利用Opencv写几十行代码就可以轻松的修复简单的划痕。

图像修复是什么

图像修复时计算机视觉中一种利用某种算法填充图像或者视频内的区域的方法,我们用二进制mask标识要修复的区域,并用其周边区域的信息完成修复。

修复的算法

文章中介绍了两种算法

cv2.INPAINT_NS: 基于Navier-Stokes的修复

如果你学过流体力学,NS方程一定是你绕不开的一个方程。

在这里插入图片描述

它极难寻找解析解,因此我们通常要进行一些假设消去某些项。

例如我们会做出流体不可压的假设,有时会忽略流体的黏性等等。

具体如何把牛顿流体与图像降噪进行类比,可以参考论文:

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()即可。

原图:在这里插入图片描述
用鼠标确定修复范围:在这里插入图片描述
最终结果对比:
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值