系列内容:OpenCV概述与环境配置,OpenCV基础知识和绘制图形,图像的算数与位运算,图像视频的加载和显示,图像基本变换,滤波器,形态学,图像轮廓,图像直方图,车辆统计项目,特征检测和匹配,图像查找和拼接,虚拟计算器项目,信用卡识别项目,图像的分割与修复,人脸检测与车牌识别,目标追踪,答题卡识别判卷与文档ocr扫描识别,光流估计
(一)分水岭算法理论
1.图像分割的基本概念
- 图像分割: 将前景物体从背景中分离出来.
- 图像分割分为传统图像分割和基于深度学习的图像分割方法.
- 传统图像分割就是使用OpenCV进行的图像分割
传统图像分割方法有:
- (1)分水岭法
- (2)GrabCut法
- (3)MeanShift法
- (4)背景扣除
2.分水岭法
分水岭分割方法是基于图像形态学和图像结构来实现的一种图像分割方法.
现实中我们可以或者说可以想象有山有湖的景象,那么那一定是水绕山,山围水的情形。当然在需要的时候,要人工构筑分水岭,以防集水盆之间的互相穿透。而区分高山(plateaus)与水的界线,以及湖与湖之间的间隔或都是连通的关系,就是分水岭(watershed)。
我们绘制灰度图像的梯度图,可以得到近似下图的梯度走势.梯度低的地方我们可以认为是低洼区或者山谷, 梯度高的地方可以认为是山峰. 我们往山谷中注水, 为了防止山谷中的水溢出汇合我们可以在汇合的地方筑起堤坝, 可将堤坝看做是对图像分割后形成的边界. 这就是分水岭算法的基本原理.
分水岭算法示意图,如图15.1所示:
15.1-分水岭算法示意图
分水岭算法的问题,如图15.2所示:
15.2-分水岭算法的问题
OpenCv中的分水岭法已经解决此问题
分水岭法涉及的API
(1)distanceTransform(img, distanceType, maskSize)计算img中非零值到距离它最近的0值
之间的距离,其中:
- img 要处理的图像
- distanceType 计算距离的方式: DIST_L1, DIST_L2
- maskSize:进行扫描时的kernel的大小, L1用3, L2用5
(2)connectedComponents(image[, labels[, connectivity[, ltype]]]) 求连通域, 用0标记图像的背景,用大于0的整数标记其他对象,其中:
- connectivity: 4, 8(默认)
(3)watershed(image, markers) 执行分水岭法
markers: 它是一个与原始图像大小相同的矩阵,int32数据类型,表示哪些是背景哪些是前景。分水岭算法将标记的0的区域视为不确定区域,将标记为1的区域视为背景区域,将标记大于1的正整数表示我们想得到的前景。
(二)分水岭算法实战
分水岭算法处理硬币边缘
示例代码:
python
import cv2
import numpy as np
import matplotlib.pyplot as plt
#封装显示图片的函数
def cv_show(name,img):
cv2.imshow(name, img)
cv2.waitKey()
cv2.destroyAllWindows()
img=cv2.imread('./coins01.png')
gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# cv_show('gray',gray)
#这是一个典型的双峰结构,我们使用大津算法进行二值化处理
_ =plt.hist(gray.ravel(),bins=256,range=[0,255])
#二值化处理,
_,thresh=cv2.threshold(gray,0,255,cv2.THRESH_BINARY_INV|cv2.THRESH_OTSU)
#发现二值化后的图片,存在毛边和一些小的噪点
cv_show('thresh',thresh)
#做一个开运算
kernel=cv2.getStructuringElement(cv2.MORPH_RECT,(3,3))
opening=cv2.morphologyEx(thresh,cv2.MORPH_OPEN,kernel,iterations=2)
# cv_show('opening',opening)
#想办法找到背景和前景
#对opening进行膨胀
bg=cv2.dilate(opening,kernel,iterations=2)
cv_show('bg',np.hstack((opening,bg)))
#腐蚀
fg=cv2.erode(opening,kernel,iterations=2)
cv_show('fg',np.hstack((opening,bg,fg)))
#剩下的区域(硬币边界区域)不能确定是背景还是前景
#可以通过膨胀之后的图减去腐蚀之后的图,得到未知区域的大学
unknown=cv2.subtract(bg,fg)
cv_show('unknown',np.hstack((opening,bg,fg,unknown)))
#因为硬币之间是彼此接触的,导致腐蚀之后的前景图不太对,硬币和硬币之间形成了通道
#腐蚀在这里不适合
#distanceTransform来确定前景
dist_transform=cv2.distanceTransform(opening,cv2.DIST_L2,5)
# print(dist_transform.max())
# cv_show('dist_transform',dist_transform)
#对dist_transform进行归一化方便展示结果
cv2.normalize(dist_transform,dist_transform,0,1.0,cv2.NORM_MINMAX)
cv_show('dist_transform',dist_transform)
#对dist_transform做二值化处理
_,fg=cv2.threshold(dist_transform,0.5*dist_transform.max(),255,cv2.THRESH_BINARY)
cv_show('fg',fg)
fg=np.uint8(fg)
#把位置区域算出来
unknown=cv2.subtract(bg,fg)
cv_show('gg',np.hstack((bg,fg,unknown)))
#connectedComponents要求输入的图片是个八位的单通道图片,即单通道的0-255的图片
#connectedComponents用0来标记背景,用大于零的整数标记前景
_,markers=cv2.connectedComponents(fg)
print(markers.max())
print(markers.min())
#因为watershed中0认为是不确定区域,1是背景,大于1是前景
#markers+1把原来的0变成1
markers+=1
#从markes中筛选未知区域,赋值为0
markers[unknown==255]=0
#展示markers
markers_copy=markers.copy()
#未知区域
markers_copy[markers==0]=150
#背景
markers_copy[markers==1]=0
#前景
markers_copy[markers>1]=255
markers_copy=np.uint8(markers_copy)
cv_show('markers_copy',markers_copy)
#到现在,我们的markers就已经生成好了
#watershed返回的markers已经做了修改,边界区域已经标记为-1了
markers=cv2.watershed(img,markers)
print(markers.min(),markers.max())
#显示一下前景图片的边缘
img[markers==-1]=[0,0,255]
#单独取出或标记出前景
img[markers>1]=[0,255,0]
cv_show('img',img)
运行结果:
硬币原图,如图15.3所示:
15.3-硬币原图
灰度化图,如图15.4所示:
15.4-灰度化图
灰度化后存在噪点图,如图15.5所示:
15.5-灰度化后存在噪点图
灰度化后去噪点图(开运算),如图15.6所示:
15.6-灰度化后去噪点图(开运算)
对opening进行膨胀,如图15.7所示:
15.7-对opening进行膨胀
对opening膨胀后进行腐蚀,如图15.8所示:
15.8-对opening膨胀后进行腐蚀
膨胀之后的图减去腐蚀之后的图,得到未知区域的,如图15.9所示:
15.9-膨胀之后的图减去腐蚀之后的图,得到未知区域
用distanceTransform来确定前景,如图15.10所示:
15.10-
用distanceTransform来确定前景
对dist_transform做二值化处理,如图15.11所示:
15.11-对dist_transform做二值化处理
把未知区域算出来,如图15.12所示:
15.12-未知区域算出来
把二值化后的区域减去已知区域,将未知区域灰度化表示,如图15.13所示:
15.13-把二值化后的区域减去已知区域,将未知区域灰度化表示
watershed返回的markers已经做了修改,边界区域标记为-1,显示一下前景图片的边缘,单独取出或标记出前景,如图15.14所示:
15.14-显示一下前景图片的边缘
(三)分水岭算法抠图以及和Canny,findContourns的对比
(1)分水岭算法抠图(硬币)
python
import cv2
import numpy as np
import matplotlib.pyplot as plt
#封装显示图片的函数
def cv_show(name,img):
cv2.imshow(name, img)
cv2.waitKey()
cv2.destroyAllWindows()
img=cv2.imread('./coins01.png')
cv_show('img',img)
gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# cv_show('gray',gray)
#这是一个典型的双峰结构,我们使用大津算法进行二值化处理
_ =plt.hist(gray.ravel(),bins=256,range=[0,255])
#二值化处理,
_,thresh=cv2.threshold(gray,0,255,cv2.THRESH_BINARY_INV|cv2.THRESH_OTSU)
#发现二值化后的图片,存在毛边和一些小的噪点
# cv_show('thresh',thresh)
#做一个开运算
kernel=cv2.getStructuringElement(cv2.MORPH_RECT,(3,3))
opening=cv2.morphologyEx(thresh,cv2.MORPH_OPEN,kernel,iterations=2)
# cv_show('opening',opening)
#想办法找到背景和前景
#对opening进行膨胀
bg=cv2.dilate(opening,kernel,iterations=2)
# cv_show('bg',np.hstack((opening,bg)))
#腐蚀
fg=cv2.erode(opening,kernel,iterations=2)
# cv_show('fg',np.hstack((opening,bg,fg)))
#剩下的区域(硬币边界区域)不能确定是背景还是前景
#可以通过膨胀之后的图减去腐蚀之后的图,得到未知区域的大学
unknown=cv2.subtract(bg,fg)
# cv_show('unknown',np.hstack((opening,bg,fg,unknown)))
#因为硬币之间是彼此接触的,导致腐蚀之后的前景图不太对,硬币和硬币之间形成了通道
#腐蚀在这里不适合
#distanceTransform来确定前景
dist_transform=cv2.distanceTransform(opening,cv2.DIST_L2,5)
# print(dist_transform.max())
# cv_show('dist_transform',dist_transform)
#对dist_transform进行归一化方便展示结果
cv2.normalize(dist_transform,dist_transform,0,1.0,cv2.NORM_MINMAX)
# cv_show('dist_transform',dist_transform)
#对dist_transform做二值化处理
_,fg=cv2.threshold(dist_transform,0.5*dist_transform.max(),255,cv2.THRESH_BINARY)
# cv_show('fg',fg)
fg=np.uint8(fg)
#把位置区域算出来
unknown=cv2.subtract(bg,fg)
# cv_show('gg',np.hstack((bg,fg,unknown)))
#connectedComponents要求输入的图片是个八位的单通道图片,即单通道的0-255的图片
#connectedComponents用0来标记背景,用大于零的整数标记前景
_,markers=cv2.connectedComponents(fg)
print(markers.max())
print(markers.min())
#因为watershed中0认为是不确定区域,1是背景,大于1是前景
#markers+1把原来的0变成1
markers+=1
#从markes中筛选未知区域,赋值为0
markers[unknown==255]=0
#展示markers
markers_copy=markers.copy()
#未知区域
markers_copy[markers==0]=150
#背景
markers_copy[markers==1]=0
#前景
markers_copy[markers>1]=255
markers_copy=np.uint8(markers_copy)
# cv_show('markers_copy',markers_copy)
#到现在,我们的markers就已经生成好了
#watershed返回的markers已经做了修改,边界区域已经标记为-1了
markers=cv2.watershed(img,markers)
print(markers.min(),markers.max())
#显示一下前景图片的边缘
# img[markers==-1]=[0,0,255]
#单独取出或标记出前景
# img[markers>1]=[0,255,0]
#coins=img[markers>1].copy()
#扣出硬币
#mask把要抠图的地方赋值255,其他位置,即背景赋值为0
mask=np.zeros(shape=img.shape[:2],dtype=np.uint8)
mask[markers>1]=255
coins=cv2.bitwise_and(img,img,mask=mask)
cv_show('coins',coins)
cv_show('img',img)
硬币抠图运行结果如图15.15所示:
15.15-硬币抠图运行结果
(2)和canny对比
python
#分水岭算法抠图以及和Canny,findContourns的对比
#和canny对比
img_canny=cv2.Canny(img,100,150)
cv_show('img_canny',img_canny)
# print(img_canny)
和canny抠图结果,如图15.16所示:
15.16-canny抠图结果
(3)和findContourns的对比
python
#和findContourns的对比
#把img变成灰度图
img=cv2.imread('./coins01.png')
gray=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
#二值化
_,thresh=cv2.threshold(gray,0,255,cv2.THRESH_BINARY_INV|cv2.THRESH_OTSU)
cv_show('gray',np.hstack((gray,thresh)))
#findContours要求单通道,0-255的整数的图片,最好是二值化的图片
image,contours,_=cv2.findContours(thresh,mode=cv2.RETR_TREE,method=cv2.CHAIN_APPROX_SIMPLE)
#显示轮廓,会直接修改img
cv2.drawContours(img,contours,-1,(0,0,255),3)
cv_show('contours',img)
和findContourns的对比,如图15.17所示:
15.17-和findContourns的对比
(四)GrabCut原理和使用
通过交互的方式获得前景物体。
- (1)用户指定前景的大体区域,剩下的为背景区域。
- (2)用户可以明确指定某些地方为前景或背景。
- (3)GrabCut采用分段迭代的方法分析前景物体,形成模型树。
- (4)最后根据权重决定某个像素是前景还是背景。
这里不去介绍GrabCut算法的具体数学原理,感兴趣可以阅读GrabCut原论文:GrabCut" --- Interactive Foreground Extraction using Iterated Graph Cuts
GrabCut算法主要基于以下知识:
- (1)k均值聚类
- (2)高斯混合模型建模(GMM)
- (3)max flow/min cut
这里介绍一些GrabCut算法的实现步骤
- (1)在图片中定义(一个或者多个)包含物体的矩形。
- (2)矩形外的区域被自动认为是背景。
- (3)对于用户定义的矩形区域,可用背景中的数据来区分它里面的前景和背景区域。
- (4)用高斯混合模型(GMM)来对背景和前景建模,并将未定义的像素标记为可能的前景或者背景。
- (5)图像中的每一个像素都被看做通过虚拟边与周围像素相连接,而每条边都有一个属于前景或者背景的概率,这是基于它与周边像素颜色上的相似性。
- (6)每一个像素(即算法中的节点)会与一个前景或背景节点连接。
- (7)在节点完成连接后(可能与背景或前景连接),若节点之间的边属于不同终端(即一个节点属于前景,另一个节点属于背景),则会切断他们之间的边,这就能将图像各部分分割出来。
grabCut(img, mask, rect, bgdModel, fgdModel, iterCount[, mode]) -> mask, bgdModel, fgdModel
其中:
- (1)img------待分割的源图像,必须是8位3通道,在处理的过程中不会被修改
- (2)mask------掩码图像,如果使用掩码进行初始化,那么mask保存初始化掩码信息;在执行分割的时候,也可以将用户交互所设定的前景与背景保存到mask中,然后再传入grabCut函数;在处理结束之后,mask中会保存结果。mask只能取以下四种值:
- GCD_BGD(=0),背景;
- GCD_FGD(=1),前景;
- GCD_PR_BGD(=2),可能的背景;
- GCD_PR_FGD(=3),可能的前景。
如果没有手工标记GCD_BGD或者GCD_FGD,那么结果只会有GCD\_PR\_BGD或GCD_PR_FGD;
- (3)rect------用于限定需要进行分割的图像范围,只有该矩形窗口内的图像部分才被处理;
- (4)bgdModel------背景模型,如果为None,函数内部会自动创建一个bgdModel;bgdModel必须是单通道浮点型图像,且行数只能为1,列数只能为13x5;
- (5)fgdModel------前景模型,如果为None,函数内部会自动创建一个fgdModel;fgdModel必须是单通道浮点型图像,且行数只能为1,列数只能为13x5;
- (6)iterCount------迭代次数,必须大于0;
- (7)mode------用于指示grabCut函数进行什么操作,可选的值有:
GC_INIT_WITH_RECT(=0),用矩形窗初始化GrabCut;
GC_INIT_WITH_MASK(=1),用掩码图像初始化GrabCut;
GC_EVAL(=2),执行分割。
用grabcut进行抠图,示例代码:
python
import cv2
import numpy as np
import matplotlib.pyplot as plt
#封装显示图片的函数
def cv_show(name,img):
cv2.imshow(name, img)
cv2.waitKey()
cv2.destroyAllWindows()
img=cv2.imread('./lena1.png')
plt.imshow(img)
#取出矩形框x,y,w,h
rect=(200,170,180,210)
mask=np.zeros(img.shape[:2],dtype=np.uint8)
#mark是grabcut保存分割结果的矩阵,分割完之后有一下四种值
# GCD\_BGD(=0),背景;
# GCD\_FGD(=1),前景;
# GCD\_PR\_BGD(=2),可能的背景;
# GCD\_PR\_FGD(=3),可能的前景。
#第一次使用grabcut是用户指定rect
cv2.grabCut(img,mask,rect,None,None,5,mode=cv2.GC_INIT_WITH_RECT)
#处理结果放在mask
#我们把所有的前景抠出来
mask1=np.where((mask==1)|(mask==3),255,0).astype(np.uint8)
#使用与运算
output1=cv2.bitwise_and(img,img,mask=mask1)
cv2.rectangle(img,(200,170),(200+180,170+210),(0,0,255),3)
cv_show('output1',np.hstack((img,output1)))
#第二次使用grabcut,对mask进行修改
mask[110:170,200:380]=1
cv2.grabCut(img,mask,None,None,None,5,mode=cv2.GC_INIT_WITH_MASK)
mask2=np.where((mask==1)|(mask==3),255,0).astype(np.uint8)
#使用与运算
output2=cv2.bitwise_and(img,img,mask=mask2)
cv2.rectangle(img,(200,110),(200+180,110+210),(0,0,0),3)
cv_show('output2',np.hstack((img,output1,output2)))
运行结果:
第一次使用grabcut是用户指定rect,如图15.18所示:
15.18-第一次使用grabcut是用户指定rect
第二次使用grabcut,对mask进行修改,如图15.19所示:
15.19-第二次使用grabcut,对mask进行修改
(五)GrabCut交互式前景分割
python
import cv2
import numpy as np
#面向对象:把grabcut进行交互式抠图的功能封装成一个类
class App:
def __init__(self,image):
self.image=image
self.img=cv2.imread(self.image)
self.img2=self.img.copy()
self.start_x=0
self.start_y=0
#是否需要绘制矩形的标志
self.rect_flag=False
self.rect=(0,0,0,0)
self.mask=np.zeros(shape=self.img.shape[:2],dtype=np.uint8)
#输出
self.output=np.zeros(shape=self.img.shape[:2],dtype=np.uint8)
#实例方法,第一个参数一定是self
#staticmethod默认类和实例对象不会自动传参数(self,cls)
# @staticmethod 静态方法 @classmethod
def on_mouse(self,event,x,y,flags,param):
#按下左键,开始框选前景区域
if event==cv2.EVENT_LBUTTONDOWN:
#记录起始坐标
self.start_x=x
self.start_y=y
self.rect_flag=True
elif event==cv2.EVENT_LBUTTONUP:
self.rect_flag=False
#记录用户的矩形大小
self.rect=(min(self.start_x,x),min(self.start_y,y),
abs(self.start_x-x),abs(self.start_y-y))
cv2.rectangle(self.img,(self.start_x,self.start_y),(x,y),(0,0,255),2)
elif event==cv2.EVENT_MOUSEMOVE and self.rect_flag==True:
#画矩形
self.img=self.img2.copy()
cv2.rectangle(self.img,(self.start_x,self.start_y),(x,y),(0,255,0),2)
#核心逻辑:窗口 回调函数 窗口
def run(self):
cv2.namedWindow('img')
#绑定鼠标事件
cv2.setMouseCallback('img',self.on_mouse)
while True:
cv2.imshow('img',self.img)
cv2.imshow('output',self.output)
key=cv2.waitKey(1)
if key==27:
break
elif key==ord('g'):
#进行切图
cv2.grabCut(self.img2,self.mask,self.rect,None,None,5,
mode=cv2.GC_INIT_WITH_RECT)
#把前景或者可能是前景的位置设置为255
mask2=np.where((self.mask==1)|(self.mask==3),255,0).astype(np.uint8)
#使用与运算
self.output=cv2.bitwise_and(self.img2,self.img2,mask=mask2)
cv2.destroyAllWindows()
app=App('./lena1.png')
app.run()
运行结果:
原图和输出图初始化,如图15.20所示:
15.20-原图和输出图初始化
动态图像分割(1),如图15.21所示:
15.21-动态图像分割(1)
动态图像分割(2),如图15.22所示:
15.22-动态图像分割(2)
(六)MeanShift图像分割
MeanShift严格来说并不是用来对图像进行分割的,而是在色彩层面进行平滑滤波的。它会中和色彩分布相近的颜色,平滑色彩细节,侵蚀掉面积较小的颜色区域。
它以图像上任一点p为圆心,半径为sp,色彩幅值为sr进行不断迭代。经过迭代,将收敛点的像素值代替原来的像素值,从而去除了局部相似的纹理,同时保留了边缘等差异较大的特征。
MeanShift平滑滤波,如图15.23所示:
15.23-MeanShift平滑滤波
实例代码(1):
python
import cv2
import numpy as np
img=cv2.imread('./folors.jpg')
mean_img=cv2.pyrMeanShiftFiltering(img,20,30)
cv2.imshow('img',np.hstack((img,mean_img)))
cv2.waitKey(0)
cv2.destroyAllWindows()
对花坛平滑滤波结果,如图15.24所示:
15.24-对花坛平滑滤波结果
对钥匙平滑滤波结果,如图15.25所示:
15.25-对钥匙平滑滤波结果
实例代码(2):
python
import cv2
import numpy as np
img=cv2.imread('./key.jpg')
img_mean=cv2.pyrMeanShiftFiltering(img,20,30)
img_canny=cv2.Canny(img_mean,150,300)
img_canny2=cv2.Canny(img,150,300)
#找轮廓
_,contours,_=cv2.findContours(img_canny,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(img,contours,-1,(0,0,255),2)
cv2.imshow('img',np.hstack((img,img_mean)))
cv2.imshow('img_canny',np.hstack((img_canny,img_canny2)))
cv2.waitKey(0)
cv2.destroyAllWindows()
运行结果:
找钥匙的轮廓,如图15.26所示:
15.26-找钥匙的轮廓
找花坛的轮廓,如图15.27所示:
15.27-找花坛的轮廓
(七)视频的前后景分离
视频是一组连续的帧(一幅幅图片组成)
帧与帧之间关系密切(GOP: group of pictures)
在GOP中,背景几乎是不变的.
混合高斯模型为基础的前景/背景分割算法
createBackgroundSubtractorMOG([, history[, nmixtures[, backgroundRatio[, noiseSigma]]]])
参数:
- (1)history: 进行建模的时候需要多长时间的参考帧, 默认是200ms
- (2)nmixtures: 高斯范围值, 默认为5.
- (3)backgroundRatio: 背景比例, 默认0.7
- (4)noiseSigma:降噪, 默认为0, 表示自动降噪.
以上参数一般不需要修改, 默认即可.
算法:
1.MOG*(2001年以前)
示例代码:
python
import cv2
import numpy as np
cap=cv2.VideoCapture('./road01.mp4')
mog=cv2.bgsegm.createBackgroundSubtractorMOG()
while(True):
ret,frame=cap.read()
if not ret:
break
fgmask=mog.apply(frame)
cv2.imshow('img',fgmask)
k=cv2.waitKey(10)
if k==27:
break
cap.release()
cv2.destroyAllWindows()
结果:
MOG算法,如图15.28所示:
15.28-MOG算法
2.MOG2(2004): 同MOG类似, 不过对亮度产生的阴影有更好的识别, 缺点是会产生很多细小的噪点.
createBackgroundSubtractorMOG2,参数:
- history: 默认500毫秒
- detectShadows: 是否检测阴影, 默认True
示例代码:
python
import cv2
import numpy as np
cap=cv2.VideoCapture('./road01.mp4')
mog=cv2.createBackgroundSubtractorMOG2()
while(True):
ret,frame=cap.read()
if not ret:
break
fgmask=mog.apply(frame)
cv2.imshow('img',fgmask)
k=cv2.waitKey(10)
if k==27:
break
cap.release()
cv2.destroyAllWindows()
运行结果:
MOG2算法,如图15.29所示:
15.29-MOG2算法
3.GMG(2012)去背景: 静态背景图像估计和每个像素的贝叶斯分割, 抗噪性更强.
createBackgroundSubtractorGMG,参数:
- initializationFrames: 初始化帧数, 默认120帧.
示例代码:
python
import cv2
import numpy as np
cap=cv2.VideoCapture('./road01.mp4')
mog=cv2.bgsegm.createBackgroundSubtractorGMG()
while(True):
ret,frame=cap.read()
if not ret:
break
fgmask=mog.apply(frame)
cv2.imshow('img',fgmask)
k=cv2.waitKey(10)
if k==27:
break
cap.release()
cv2.destroyAllWindows()
运行结果:
GMG算法,如图15.30所示:
15.30-GMG算法
(八)图像修复
OpenCV中图像修复的技术------基本思想很简单:用相邻像素替换这些坏标记,使其看起来像邻近。
inpaint(src, inpaintMask, inpaintRadius, flags[, dst])
- (1)src要修复的图片
- (2)inpaintMask: 图像的掩码,单通道图像,大小跟原图像一致,inpaintMask图像上除了需要修复的部分之外其他部分的像素值全部为0
- (3)inpaintRadius: 每个点的圆心邻域半径.
- (4)flags: 修复的使用的算法. INPAINT_NS, INPAINT_TELEA
(a)cv2.INPAINT_NS (Fluid Dynamics Method 流体力学算法)
(b)cv2.INPAINT_TELEA (Fast Marching Method 快速行进算法)
python
import cv2
import numpy as np
# 读取原始图像
img = cv2.imread('inpaint.png')
# 读取掩码图像,其中需要修复的区域为非零值,其他区域为0
mask = cv2.imread('inpaint_mask.png', 0)
cv2.imshow('mask',mask)
# 使用Telea算法进行图像修复
dst = cv2.inpaint(img, mask, 5, flags=cv2.INPAINT_TELEA)
# 显示修复后的图像,将原始图像和修复后的图像水平堆叠
cv2.imshow('dst', np.hstack((img,dst)))
cv2.waitKey(0)
cv2.destroyAllWindows()
运行结果:
mask和原图,如图15.31所示:
15.31-mask和原图
原图和修复图,如图15.32所示:
15.32-原图和修复图