Opencv的使用教程,opencv比较全的基础教程
分类专栏: 深度学习理论基础 文章标签: opencv 图像处理 视频处理
版权
场景一:Opencv对图像视频的基本使用
场景二:Opencv对图像视频进一步处理
场景三:Opencv对图像视频高级处理
.
.
.
课程视频
课程视频2
ubuntu18下采用源码编译的方式安裝opencv / 简单方式安装
常见头导入
import cv2
import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline
…
场景一:Opencv对图像视频的基本使用
知识点一: 操作图片
1.1数据读取方式-图像
1.2 Opencv处理图片的基本操作
#读取图片
img=cv2.imread("图片位置")
img=cv2.imread("图片位置",cv2.IMREAD_GRAYSCALE)
#第二个参数表示以灰度图像处理的方式,还可以 cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)的方式
#展示图片
cv2.imshow("展示的名字",img)
cv2.waitKey(0) #等待时间,毫秒级,0表示可以任意键结束
cv2.destroyAllWindows()
#保存图片
cv2.imwrite("指定保存的名字",img)
#img的相关操作
img.shape #输出(hwc)长宽和图片类型
img.dtype #输出图片类型,如dtype('uint8')
type(img) #如输出numpy.ndarray
img.size #输出像素点大小,如总像素点大小为207000
res = np.hstack((blur,aussian,median))
#可以混合多张图片展示blur,aussian,median都是图片
1.3截取部分图像数据
#截取部分图像数据
img =cv2.imread("要处理的图像")
xxx=img[0:50,0:200] #截取宽50,长200的位置
#展示图片
cv2.imshow("展示的名字",xxx)
cv2.waitKey(0)#等待时间,毫秒级,0表示可以任意键结束
cv2.destroyAllWindow()
1.4颜色通道截取
> 一个通道由三个颜色组成,bgr
b,g,r=cv2.split(img)#切分
b.shape
img=cv2.merge((b,g,r))#组合
1.5 图片尺寸的改变
#固定尺寸的改变
img1= cv2.resize(img, (500, 414))#可以指定原img生成为固定大小的img1
#倍数放大法
img1= cv2.resize(img, (0, 0), fx=4, fy=4) 这样就是倍数即4倍长宽扩大
知识点二: 操作视频
1.1数据读取方式-视频
1.2 Opencv处理视频的基本操作
#读取视频
vc= cv2.VideoCapture("要读取的视频")
#判断视频是否读取成功
if vc.isOpened():
oepn,frame=vc.read() #vc.read代表视频从第一帧开始一帧一帧的开始读
# oepn是Boolean类型,返回true 或false frame表示帧的结果
else:
open=False
#如果视频读取成功
while open:
ret,frame=vc.read() # oepn是Boolean类型,返回true 或false frame表示帧的结果
if frame is None: #如果读取第一帧是否为空
break
if ret==True:
gray=cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)#最简单的操作,把视频每一帧图片转换为灰度图
cv2.imshow("展示的名字",gray)
if cv2.waitKey(100)& 0xFF==27; # 27代表键盘的退出键esc,视频展示的时候可以按esc退出
break
vc.release()
cv2.destroyAllWindows()
cap.grap() 从设备或者视频上获取一帧,获取成功返回true
cap.retrieve(frame) 在grap()后使用,对获取到的帧进行解码 ,也返回true或者false
cap.read( frame ) 结合grap和retrieve的功能,抓取下一帧并解码
知识点三: 图像边界填充
top_size,bottom_size,left_size,right_size = (50,50,50,50)
replicate = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, right_size, borderType=cv2.BORDER_REPLICATE)
reflect = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, right_size,cv2.BORDER_REFLECT)
reflect101 = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, right_size, cv2.BORDER_REFLECT_101)
wrap = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, right_size, cv2.BORDER_WRAP)
constant = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, right_size,cv2.BORDER_CONSTANT, value=0)
import matplotlib.pyplot as plt
plt.subplot(231), plt.imshow(img, 'gray'), plt.title('ORIGINAL') #原图
plt.subplot(232), plt.imshow(replicate, 'gray'), plt.title('REPLICATE')#复制
plt.subplot(233), plt.imshow(reflect, 'gray'), plt.title('REFLECT')#反射法一
plt.subplot(234), plt.imshow(reflect101, 'gray'), plt.title('REFLECT_101')
plt.subplot(235), plt.imshow(wrap, 'gray'), plt.title('WRAP')
plt.subplot(236), plt.imshow(constant, 'gray'), plt.title('CONSTANT')
plt.show()
知识点四: 图像数值计算
img_cat=cv2.imread('cat.jpg')
img_dog=cv2.imread('dog.jpg')
img_cat2= img_cat +10
> 其中img_cat部分数据长这样
> 进行每一个像素点加10以后img_cat2部分数据长这样
img_cat + img_cat2 #相当于142+152=294 294/256=38
cv2.add(img_cat,img_cat2) #add函数像这样 294>255,然后就取255
知识点五: 图像融合
> 图像融合的基础是图像尺寸要一样.
img.shape #可以查看图像的尺寸
img1= cv2.resize(img, (500, 414))#可以指定原img生成为固定大小的img1
#cv2.resize(img, (0, 0), fx=4, fy=4) 这样就是倍数即4倍长宽扩大
res = cv2.addWeighted(img_cat, 0.4, img_dog, 0.6,0)#融合 0.4,0.6相当于权重,0代表偏置向b ,例如 c= x1*0.4+ x2*0.6+b ; x1,x2是两张图片某个点的像素值,c是融合以后的点的像素值
plt.imshow(res)#展示
…
场景二:Opencv对图像视频进一步处理
知识点六:HSV
OpenCV学习笔记——HSV颜色空间超极详解
利用OPENCV+PYTHON进行HSV颜色识别,并结合滑动条动态改变目标颜色
作用 : 进行颜色分割和颜色识别等,看最下面案例一
#将RGB-->转换为HSV
hsv=cv2.cvtColor(img,cv2.COLOR_BGR2HSV)
#下面通过一个具体的案例实现图像内黄色信息的识别,并加入滑动条的功能,可以让用户更直观地体验HSV颜色空间。
import cv2
import numpy as np
#定义窗口名称
winName='Colors'
def nothing(x):
pass
img1=cv2.imread('166.png')
#颜色空间的转换
img_hsv=cv2.cvtColor(img1,cv2.COLOR_BGR2HSV)
#新建窗口
cv2.namedWindow(winName)
#新建6个滑动条,表示颜色范围的上下边界,这里滑动条的初始化位置即为黄色的颜色范围
cv2.createTrackbar('LowerbH',winName,9,255,nothing)
cv2.createTrackbar('LowerbS',winName,160,255,nothing)
cv2.createTrackbar('LowerbV',winName,215,255,nothing)
cv2.createTrackbar('UpperbH',winName,83,255,nothing)
cv2.createTrackbar('UpperbS',winName,255,255,nothing)
cv2.createTrackbar('UpperbV',winName,255,255,nothing)
while(1):
#函数cv2.getTrackbarPos()范围当前滑块对应的值
lowerbH=cv2.getTrackbarPos('LowerbH',winName)
lowerbS=cv2.getTrackbarPos('LowerbS',winName)
lowerbV=cv2.getTrackbarPos('LowerbV',winName)
upperbH=cv2.getTrackbarPos('UpperbH',winName)
upperbS=cv2.getTrackbarPos('UpperbS',winName)
upperbV=cv2.getTrackbarPos('UpperbV',winName)
#得到目标颜色的二值图像,用作cv2.bitwise_and()的掩模
img_target=cv2.inRange(img1,(lowerbH,lowerbS,lowerbV),(upperbH,upperbS,upperbV))
#输入图像与输入图像在掩模条件下按位与,得到掩模范围内的原图像
img_specifiedColor=cv2.bitwise_and(img1,img1,mask=img_target)
cv2.imshow(winName,img_specifiedColor)
if cv2.waitKey(1)==ord('q'):
break
cv2.destroyAllWindows()
知识点七:图像阈值
> 例如以下的
img=cv2.imread('cat.jpg')
img_gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret, thresh1 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_BINARY)
#如下图中,像素点大于127的地方为255,显示白,亮; 像素点小于127的地方为0,显黑
ret, thresh2 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_BINARY_INV)
#如下图cv2.THRESH_BINARY_INV是 THRESH_BINARY的颜色反转
ret, thresh3 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_TRUNC)
#如下图中,像素点大于127的地方为127, 像素点小于127的地方不变
ret, thresh4 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_TOZERO)
#如下图中,像素点大于127的地方为不变, 像素点小于127的地方为0
ret, thresh5 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_TOZERO_INV)
#如下图中,像素点大于127的地方为0, 像素点小于127的地方为不变
titles = ['Original Image', 'BINARY', 'BINARY_INV', 'TRUNC', 'TOZERO', 'TOZERO_INV']
images = [img, thresh1, thresh2, thresh3, thresh4, thresh5]
for i in range(6):
plt.subplot(2, 3, i + 1), plt.imshow(images[i], 'gray')
plt.title(titles[i])
plt.xticks([]), plt.yticks([])
plt.show()
> 效果如下:
>>自动阈值 cv2.THRESH_OTSU
ret2,th2 = cv2.threshold(img,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
知识点八:图像平滑
> 图片有噪声,进行光滑处理。
img = cv2.imread('要处理的图片')
# 均值滤波 ---->简单取指定大小方框的平均值 ,如下面3X3中取平均值
blur = cv2.blur(img, (3, 3))
#然后展示三连
# 方框滤波
# 基本和均值一样,可以选择归一化,
#比如上面的(3,3)中的 9个数加起来/9 是true的情况,和均值滤波基本一样
box = cv2.boxFilter(img,-1,(3,3), normalize=True)
# 方框滤波
# 基本和均值一样,可以不选择归一化,不选折 / 9 那么容易越界,相加的和 >255 ,那么会做为255
box = cv2.boxFilter(img,-1,(3,3), normalize=False)
# 高斯滤波
# 高斯模糊的卷积核里的数值是满足高斯分布,相当于更重视中间的
aussian = cv2.GaussianBlur(img, (3, 3), 1)
#如下面的(3,3)中,我们重视离中间204 更近的 75,24,104,113,给与权重0.8,离比较远的剩余的121,78,154.235给与权重0.6
# 中值滤波
# 相当于用中值代替
# 上面的3x3里面24,75,78,104 113 121,154,204,235 中值滤波指的是选中间的113
median = cv2.medianBlur(img, 5) # 中值滤波
# 展示所有的
res = np.hstack((blur,aussian,median))
#print (res)
知识点九:形态学
1. 腐蚀操作
img = cv2.imread('dige.png')
cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
#腐蚀操作
#腐蚀操作的前提一般要求图片是二值的,如黑白
kernel = np.ones((3,3),np.uint8) #3x3大小范围去腐蚀
erosion = cv2.erode(img,kernel,iterations = 1) #腐蚀操作
cv2.imshow('erosion', erosion)
cv2.waitKey(0)
cv2.destroyAllWindows()
#迭代次数不同的影响
import cv2 #opencv读取的格式是BGR
import numpy as np
import matplotlib.pyplot as plt#Matplotlib是RGB
%matplotlib inline
pie = cv2.imread('pie.png')
kernel = np.ones((30,30),np.uint8)
erosion_1 = cv2.erode(pie,kernel,iterations = 1)
erosion_2 = cv2.erode(pie,kernel,iterations = 2)
erosion_3 = cv2.erode(pie,kernel,iterations = 3)
plt.subplot(221), plt.imshow(pie, 'gray'), plt.title('原图')
plt.subplot(222), plt.imshow(erosion_1, 'gray'), plt.title('one')
plt.subplot(223), plt.imshow(erosion_2, 'gray'), plt.title('two')
plt.subplot(224), plt.imshow(erosion_2, 'gray'), plt.title('three')
2. 膨胀操作
img = cv2.imread('dige.png')
#膨胀4次
kernel = np.ones((3,3),np.uint8)
dige_dilate1 = cv2.dilate(img,kernel,iterations = 1)
dige_dilate2 = cv2.dilate(dige_dilate1,kernel,iterations = 1)
dige_dilate3 = cv2.dilate(dige_dilate2,kernel,iterations = 1)
dige_dilate4 = cv2.dilate(dige_dilate3,kernel,iterations = 1)
cv2.imshow('dilate', dige_dilate4)
cv2.waitKey(0)
cv2.destroyAllWindows()
3. 开运算与闭运算
opencv中的开运算,闭运算,形态学梯度,顶帽和黑帽简介
4. 梯度运算
5. 顶帽与黑帽
OpenCV-Python系列之顶帽与黑帽操作(二十二)
…
场景三:Opencv对图像视频高级处理
知识点十:边缘检测技术(带方向的)—图像梯度=sobel算子/scharr算子/laplasian算子
> 原始图片:
#原始图片
img = cv2.imread('pie.png',cv2.IMREAD_GRAYSCALE)
cv2.imshow("img",img)
cv2.waitKey()
cv2.destroyAllWindows()
> Sobel函数效果
sobelx = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=3) #ksize必须为1,3,5,7
'''
CV_8UC1,CV_8SC1,CV_32FC1
如果你现在创建了一个存储--灰度图片的Mat对象,这个图像的大小为宽100,高100,
那么,现在这张灰度图片中有10000个像素点,它每一个像素点在内存空间所占的空间大小是8bite,
8位--所以它对应的就是CV_8。
S|U|F:
S--代表---signed int---有符号整形
U--代表--unsigned int--无符号整形
F--代表--float---------单精度浮点型
C----代表---一张图片的通道数,比如:
channels = 1:灰度图片--grayImg---是--单通道图像
channels = 3:RGB彩色图像---------是--3通道图像
channels = 4:带Alph通道的RGB图像--是--4通道图像
imshow函数在显示图像时,会将各种类型的数据都映射到[0, 255]。
如下:
· 如果载入的图像是8位无符号类型(8-bit unsigned),就显示图像本来的样子。
· 如果图像是16位无符号类型(16-bit unsigned)或32位整型(32-bit integer,有符号位),便用像素值除以256。也就是说,值的范围是 [0,255 x 256]映射到[0,255]。
· 如果图像是32位或64位浮点型(32-bit floating-point or 64-bit floating-point),像素值便要乘以255。也就是说,该值的范围是 [0,1]映射到[0,255]。
如:CV_8U的灰度或BGR图像的颜色分量都在0~255之间。直接imshow可以显示图像。 CV_32F或者CV_64F取值范围为0~1.0,imshow的时候会把图像乘以255后再显示。
'''
#展示三连
sobelx = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=3)
sobelx = cv2.convertScaleAbs(sobelx)
'''
在经过处理后,需要用convertScaleAbs()函数将其转回原来的uint8形式,
否则将无法显示图像,而只是一副灰色的窗口。
'''
#展示三连
#竖直方向
sobely = cv2.Sobel(img,cv2.CV_64F,0,1,ksize=3)
sobely = cv2.convertScaleAbs(sobely)
#展示三连
> 应用: 相当于可以画轮廓
img = cv2.imread('lena.jpg',cv2.IMREAD_GRAYSCALE)
sobelx = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=3)
sobelx = cv2.convertScaleAbs(sobelx)
sobely = cv2.Sobel(img,cv2.CV_64F,0,1,ksize=3)
sobely = cv2.convertScaleAbs(sobely)
sobelxy = cv2.addWeighted(sobelx,0.5,sobely,0.5,0)
cv2.imshow('sobelxy',sobelxy)
cv2.waitKey()
cv2.destroyAllWindows()
> 不同算子
#不同算子的差异
img = cv2.imread('lena.jpg',cv2.IMREAD_GRAYSCALE)
sobelx = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=3) #水平方向求梯度
sobely = cv2.Sobel(img,cv2.CV_64F,0,1,ksize=3)#竖直方向
sobelx = cv2.convertScaleAbs(sobelx)
sobely = cv2.convertScaleAbs(sobely) #取绝对值
sobelxy = cv2.addWeighted(sobelx,0.5,sobely,0.5,0) #图像融合
scharrx = cv2.Scharr(img,cv2.CV_64F,1,0)
scharry = cv2.Scharr(img,cv2.CV_64F,0,1)
scharrx = cv2.convertScaleAbs(scharrx)
scharry = cv2.convertScaleAbs(scharry)
scharrxy = cv2.addWeighted(scharrx,0.5,scharry,0.5,0)
laplacian = cv2.Laplacian(img,cv2.CV_64F)
laplacian = cv2.convertScaleAbs(laplacian)
res = np.hstack((sobelxy,scharrxy,laplacian))
cv2.imshow('res',res)
cv2.waitKey()
cv2.destroyAllWindows()
拉普拉斯 Laplacian: 先过滤噪声,再进行边缘检测
知识点十一: Canny边缘检测
1.1边缘检测基本流程
> 双阈值
1.2应用 : cv2.Canny( img , minval, maxval )
img=cv2.imread("要处理的图片",cv2.IMREAD_GRAYSCALE)
v1=cv2.Canny(img,80,150)
v2=cv2.Canny(img,50,100) #50,100分别代表着双阈值:minVal和maxVal
res = np.hstack((v1,v2))
cv2.imshow('res',res)
cv2.waitKey()
cv2.destroyAllWindows()
知识点十二: 图像金字塔
1.1高斯金字塔
> 应用:
#向上采样
img=cv2.imread("AM.png")
up=cv2.pyrUp(img)
cv2.imshow('up',up)
cv2.waitKey()
cv2.destroyAllWindows()
#向下采样
img=cv2.imread("AM.png")
down=cv2.pyrDown(img)
cv2.imshow('down',down)
cv2.waitKey()
cv2.destroyAllWindows()
1.2拉普拉斯金字塔
> 应用:原有的-现有的先下后上
img=cv2.imread("AM.png")
down=cv2.pyrDown(img)
down_up=cv2.pyrUp(down)
l_1=img-down_up
cv2.imshow('l_1',l_1)
cv2.waitKey()
cv2.destroyAllWindows()
知识点十三: 图像轮廓
1.1绘图描轮廓
contours, hierarchy = cv2.findContours( 阈值进行二值处理的图片, mode, method)
hierarchy: 层次 结构
res = cv2.drawContours(原图拷贝图, 轮廓集合 , -1 , 颜色线条, 线条粗细 )
> 为了更高的准确率,使用二值图像。
img = cv2.imread('car.png') #读取一张图片
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)#进行灰度转换
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)#进行2值处理
binary, contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
#第一个值binary就是二值处理的结果,第二个值contours是轮廓点的信息,
#需要注意的是,旧版本的opencv返回上面三个值,新版本的opencv已经不再返回binary的值了
#contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
#绘制轮廓
#传入绘制图像,轮廓,轮廓索引,颜色模式,线条厚度
# 注意需要copy,要不原图会变。。。
draw_img = img.copy() #图片拷贝
res = cv2.drawContours(draw_img, contours, -1, (0, 0, 255), 2)
#第三个参数告诉它要画第几个轮廓,其中-1代表着画出所有的轮廓
#(0,0,255)代表用什么颜色去画轮廓,(b,g,r)颜色通道,这里表示用红色去画
#最后一个参数是线条的宽度
cv2.imshow('res',res)
cv2.waitKey(0)
cv2.destroyAllWindows()
cnt = contours[0]#表示第0个轮廓
#算面积
cv2.contourArea(cnt)
#算周长,True表示闭合的
cv2.arcLength(cnt,True)
1.2 轮廓层级
1.3轮廓近似
#原轮廓展示
img = cv2.imread('contours2.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
cnt = contours[0]
''' 原轮廓展示
draw_img = img.copy()
res = cv2.drawContours(draw_img, [cnt], -1, (0, 0, 255), 2)
'''
#轮廓近似处理
epsilon = 0.1*cv2.arcLength(cnt,True)
#0.1这里进行调节可以改变,这里用0.1的周长做阈值
approx = cv2.approxPolyDP(cnt,epsilon,True)
#第一个参数代表要处理那个轮廓
#第二个参数就是阈值
# 得到一个近似轮廓点结合
draw_img = img.copy()
res = cv2.drawContours(draw_img, [approx], -1, (0, 0, 255), 2)
cv2.imshow('res',res)
cv2.waitKey(0)
cv2.destroyAllWindows()
1.4边界矩形
img = cv2.imread('contours.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
cnt = contours[0]
#外接矩形
x,y,w,h = cv2.boundingRect(cnt)
img = cv2.rectangle(img,(x,y),(x+w,y+h),(0,255,0),2)
# x,y是矩阵左上点的坐标,w,h是矩阵的宽和高, (x+w,y+h)右下角坐标
'''
area = cv2.contourArea(cnt)
x, y, w, h = cv2.boundingRect(cnt)
rect_area = w * h
extent = float(area) / rect_area
print ('轮廓面积与边界矩形比',extent)
'''
'''
#外接圆
(x,y),radius = cv2.minEnclosingCircle(cnt)
center = (int(x),int(y))
radius = int(radius)
img = cv2.circle(img,center,radius,(0,255,0),2)
'''
cv2.imshow('img',img)
cv2.waitKey(0)
cv2.destroyAllWindows()
知识点十四: 模板匹配
模板匹配简单的来说,上图A在B中的位置。
原理:
模板匹配和卷积原理很像,模板在原图像上从原点开始滑动,计算模板与(图像被模板覆盖的地方)的差别程度,这个差别程度的计算方法在opencv里有6种,然后将每次计算的结果放入一个矩阵里,作为结果输出。假如原图形是AxB大小,而模板是axb大小,则输出结果的矩阵是(A-a+1)x(B-b+1)
函数功能:假设有一个矩阵a,现在需要求这个矩阵的最小值,最大值,并得到最大值,最小值的索引。咋一看感觉很复杂,但使用这个cv2.minMaxLoc()函数就可全部解决。函数返回的四个值就是上述所要得到的。
应用
# 模板匹配
img = cv2.imread('lena.jpg', 0) #b图
template = cv2.imread('face.jpg', 0) #a图
h, w = template.shape[:2] #a图宽长
res = cv2.matchTemplate(img, template, cv2.TM_SQDIFF)
#第一个参数是b图 第二个是a图 第三个参数表示采取用什么方法去计算匹配
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
#用这四个值得到 在b图中 a的四个点
top_left = min_loc #如果采取cv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMED 即上面红框2种
#top_left = max_loc #采用另外四种计算方式
bottom_right = (top_left[0] + w, top_left[1] + h)
# 画矩形
cv2.rectangle(img2, top_left, bottom_right, 255, 2)
cv2.imshow('img2', img2)
cv2.waitKey(0)
六种计算方式对比
# 模板匹配
img = cv2.imread('lena.jpg', 0)
template = cv2.imread('face.jpg', 0)
h, w = template.shape[:2]
methods = ['cv2.TM_CCOEFF', 'cv2.TM_CCOEFF_NORMED', 'cv2.TM_CCORR',
'cv2.TM_CCORR_NORMED', 'cv2.TM_SQDIFF', 'cv2.TM_SQDIFF_NORMED']
for meth in methods:
img2 = img.copy()
# 匹配方法的真值
method = eval(meth)
print(method)
res = cv2.matchTemplate(img, template, method)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
# 如果是平方差匹配TM_SQDIFF或归一化平方差匹配TM_SQDIFF_NORMED,取最小值
if method in [cv2.TM_SQDIFF, cv2.TM_SQDIFF_NORMED]:
top_left = min_loc
else:
top_left = max_loc
bottom_right = (top_left[0] + w, top_left[1] + h)
# 画矩形
cv2.rectangle(img2, top_left, bottom_right, 255, 2)
plt.subplot(121), plt.imshow(res, cmap='gray')
plt.xticks([]), plt.yticks([]) # 隐藏坐标轴
plt.subplot(122), plt.imshow(img2, cmap='gray')
plt.xticks([]), plt.yticks([])
plt.suptitle(meth)
plt.show()
匹配多个对象
img_rgb = cv2.imread('mario.jpg')
img_gray = cv2.cvtColor(img_rgb, cv2.COLOR_BGR2GRAY)
template = cv2.imread('mario_coin.jpg', 0)
h, w = template.shape[:2]
res = cv2.matchTemplate(img_gray, template, cv2.TM_CCOEFF_NORMED)
threshold = 0.8
# 取匹配程度大于%80的坐标
loc = np.where(res >= threshold)
for pt in zip(*loc[::-1]): # *号表示可选参数
bottom_right = (pt[0] + w, pt[1] + h)
cv2.rectangle(img_rgb, pt, bottom_right, (0, 0, 255), 2)
cv2.imshow('img_rgb', img_rgb)
cv2.waitKey(0)
知识点十五: 图像特征检测
一个讲的非常好的图像特征的博文
1.1图像特征提取-harris角点检测
对于1,3,e,f属于角点
对于2,4,c,d属于边界
ab属于内部
计算机是怎么识别这三种情况的
.
对于角点而言,在焦点部位进行xy轴上小范围平移,角点图像在x,y下的变化下像素变化的都非常大。
对于边界来说,在边界部位进行x或y轴上小范围平移,边界部位在x或y下的变化下只有一种情况下像素变化的非常大。
在opencv中
cv2.cornerHarris(img, blockSize, ksize, k)
应用
import cv2
import numpy as np
img = cv2.imread('2.png')
#print ('img.shape:',img.shape)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# gray = np.float32(gray)
dst = cv2.cornerHarris(gray, 2, 3, 0.04) #gray是图像,3是默认
img[dst>0.01*dst.max()]=[0,0,255]
#这里的0.01可以去调节,然后过滤点的多少,[0,0,255]表示用红色标记
cv2.imshow('dst',img)
cv2.waitKey(0)
cv2.destroyAllWindows()
1.2图像特征-sift尺度不变特征检测法 关键点提取(专利保护)
在一定的范围内,无论物体是大还是小,人眼都可以分辨出来,然而计算机要有相同的能力却很难,所以要让机器能够对物体在不同尺度下有一个统一的认知,就需要考虑图像在不同的尺度下都存在的特点。
原理介绍:
高斯滤波的特点是越靠近中间值越大。对图像做一个高斯模糊变换。
不同σ的高斯函数决定了对图像的平滑程度,越大的σ值对应的图像越模糊。
多分辨率金字塔
对于同一张图片,不仅要在大的时候要进行特征提取,还希望在小的时候进行特征提取,即构建图片的多分辨率(尺寸大小)的金字塔,在图片大的时候,做高斯模糊,在图片小的时候,同样做相同数量的高斯模糊。
第一步:读入图片
第二步:进行灰度化
第三步:使用cv2.xfeatures2d.SIFT_create() 实例化sift函数
第四步:使用sift.detect(gray, None) 生成关键点
第五步:使用cv2.drawKeypoints( grap, kp ,输出的图片) 进行画图操作
第六步:使用sift.compute(gray, kp) 求得关键点对应的128个特征向量
import cv2
import numpy as np
img = cv2.imread('test_1.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
cv2.__version__
#opencv3.4.1向上的版本,有些函数被进人申请了专利保护,高版本的就用不了了.不过在低版本中还是可以用的
#3.4.1.15 pip install opencv-python==3.4.1.15 pip install opencv-contrib-python==3.4.1.15
sift = cv2.xfeatures2d.SIFT_create()
kp = sift.detect(gray, None) # 找出图像中的关键点
img = cv2.drawKeypoints(gray, kp, img) # 在图中画出关键点
# gray表示输入图片, kp表示关键点,img表示输出的图片
cv2.imshow('drawKeypoints', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
#计算特征
kp, des = sift.compute(gray, kp)
print (np.array(kp).shape)
des.shape
des[0]
1.3特征匹配(可以做全景图拼接----函数被专利保护)
目标是在img2中找出img1,思想是以img1的特征去在img中进行特征匹配。
import cv2
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
img1 = cv2.imread('box.png', 0)
img2 = cv2.imread('box_in_scene.png', 0)
#第一步, 分别得到2张图的特征点
sift = cv2.xfeatures2d.SIFT_create()
kp1, des1 = sift.detectAndCompute(img1, None)
kp2, des2 = sift.detectAndCompute(img2, None)
'''
print(np.array(kp1).shape)
print(np.array(des1).shape)
print(np.array(kp2).shape)
print(np.array(des2).shape)
#可以得到结果:img1中有603个特征点,img2中有969个特征点,
因为从img1和img2本身的图像来看,img2中是包含img1的,
所以进行特征匹配的时候只会匹配img1和img2共有的603个特征点。
ps:当然,这里也可以分开来做,即先检测特征点再计算特征。即
kp1 = sift.detect(img1, None)
kp2 = sift.detect(img2, None)
kp1, des1 = sift.compute(img1, kp1)
'''
#第二步 创建BF匹配器对象
# crossCheck表示两个特征点要互相匹,例如A中的第i个特征点与B中的第j个特征点最近的,并且B中的第j个特征点到A中的第i个特征点也是
#NORM_L2: 归一化数组的(欧几里德距离),如果其他特征计算方法需要考虑不同的匹配计算方式
bf = cv2.BFMatcher(crossCheck=True)
#第三步 匹配
#1对1的匹配
matches = bf.match(des1, des2)
matches = sorted(matches, key=lambda x: x.distance)
img3 = cv2.drawMatches(img1, kp1, img2, kp2, matches[:10], None,flags=2) #这一步相当于连接两个图片的关键点
#找出最匹配的十个特征点并将它们用线连起来,flags表示有几个图像
cv2.imshow('img3', img3)可以查看img3长什么样
cv2.waitKey(0)
#k对最佳匹配
bf = cv2.BFMatcher()
matches = bf.knnMatch(des1, des2, k=2)
good = []
for m, n in matches:
if m.distance < 0.75 * n.distance:
good.append([m])
img4 = cv2.drawMatchesKnn(img1,kp1,img2,kp2,good,None,flags=2)
cv2.imshow('img4', img4)可以查看img4长什么样
cv2.waitKey(0)
cv2.destroyAllWindows()
全景拼接效果展示
知识点十六: 直方图 (均衡化,可以提亮)
Opencv-Python学习笔记七——图像直方图 calcHist,直方图均衡化equalizeHist
左边是图片的像素点组成,在0-255之间,需要做的事情就是统计各个像素点数值一样的次数。
函数介绍:cv2.calcHist(images,channels,mask,histSize,ranges)
histogram :直方图的意思
> imaes:输入的图像
> channels:选择图像的通道
> mask:掩膜,是一个大小和image一样的np数组,其中把需要处理的部分指定为1,不需要处理的部分指定为0,一般设置为None,表示处理整幅图像
> histSize:使用多少个bin(柱子),一般为256 ranges:像素值的范围,一般为[0,255]表示0~255
> 后面两个参数基本不用管。 注意,除了mask,其他四个参数都要带[]号。
应用
img = cv2.imread('cat.jpg',0) #0表示灰度图
hist = cv2.calcHist([img],[0],None,[256],[0,256])
#[256]一般取值256,最后一个参数取值范围一般是[0,256],第三个参数mask相当于确定想要统计的范围
plt.hist(img.ravel(),256);
plt.show()
Mask操作
# 创建mast
#创建与img一样大小的全0 mask,然后把一部分设置为255
mask = np.zeros(img.shape[:2], np.uint8)
mask[100:300, 100:400] = 255
img = cv2.imread('cat.jpg', 0)
masked_img = cv2.bitwise_and(img, img, mask=mask)#与操作,将mask在原始图片上作用
#单词 bitsize 位运算 , 按位操作
hist_full = cv2.calcHist([img], [0], None, [256], [0, 256]) #原始图
hist_mask = cv2.calcHist([img], [0], mask, [256], [0, 256]) #有mask
plt.subplot(221), plt.imshow(img, 'gray')
plt.subplot(222), plt.imshow(mask, 'gray')
plt.subplot(223), plt.imshow(masked_img, 'gray')
plt.subplot(224), plt.plot(hist_full), plt.plot(hist_mask)
plt.xlim([0, 256])
plt.show()
均衡化: 使左边的图向右边一样,进行均衡一些。缺点是细节丢失
原理: 图像像素累计概率 * 255得到新像素的大小
应用:给图片颜色提亮
img = cv2.imread('clahe.jpg',0) #0表示灰度图 #clahe
#均衡化 缺点是细节丢失
equ = cv2.equalizeHist(img)
'''
上述的直方图均衡化可以可能到是一种全局意义上的均衡化,但是有的时候这种操作并不是很好,
会把某些不该调整的部分给调整了。Opencv中还有一种直方图均衡化,它是一种局部局部来的均衡化,
也就是是说把整个图像分成许多小块(比如按10*10作为一个小块),那么对每个小块进行均衡化。
这种方法主要对于图像直方图不是那么单一的(比如存在多峰情况)图像比较实用。
Opencv中将这种方法称之为CLAHE,CLAHE 是一种非常经典的直方图均衡化算法,
使用到的函数就是cv2.createCLAHE(),一个实例如下:
'''
#自适应直方图均衡化
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
res_clahe = clahe.apply(img)
res = np.hstack((img,equ,res_clahe))
cv2.imshow('res', res)
cv2.waitKey(0)
cv2.destroyAllWindows()
结果:
对应的像素直方图变化
知识点十七: 傅里叶变换 (可以进行模糊处理,边界)
在数字图像处理中,有两个经典的变换被广泛使用——傅里叶变换和霍夫变换。傅里叶变换是将时间域上的信号转变为频率域上的信号,进而进行图像去噪、图像增强等处理。
傅里叶变换(Fourier Transform,FT)后,对同一事物的观看角度随之改变,可以从频域里发现一些从时域里不易察觉的特征。某些在时域内不好处理的地方,在频域内可以容易地处理。
傅里叶定理:“ 任何连续周期信号都可以表示成(或者无限逼近)一系列正弦信号的叠加。”
公式原理参考博文
import numpy as np
import cv2
from matplotlib import pyplot as plt
img = cv2.imread('lena.jpg',0)
img_float32 = np.float32(img) #转成opencv要求的格式
dft = cv2.dft(img_float32, flags = cv2.DFT_COMPLEX_OUTPUT)
dft_shift = np.fft.fftshift(dft)
rows, cols = img.shape
crow, ccol = int(rows/2) , int(cols/2) # 中心位置
# 低通滤波
mask = np.zeros((rows, cols, 2), np.uint8)
mask[crow-30:crow+30, ccol-30:ccol+30] = 1
'''
# 高通滤波
mask = np.ones((rows, cols, 2), np.uint8)
mask[crow-30:crow+30, ccol-30:ccol+30] = 0
'''
# IDFT
fshift = dft_shift*mask
f_ishift = np.fft.ifftshift(fshift)
img_back = cv2.idft(f_ishift)
img_back = cv2.magnitude(img_back[:,:,0],img_back[:,:,1])
plt.subplot(121),plt.imshow(img, cmap = 'gray')
plt.title('Input Image'), plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(img_back, cmap = 'gray')
plt.title('Result'), plt.xticks([]), plt.yticks([])
plt.show()
高通滤波:保留了边界信息。
低通滤波:变模糊处理
知识点十八: 背景建模 (目标运动 )
前提: 固定的摄像头,也就是背景基本没有发生变化
方法一:帧差法
原理:用的不多,思想简单。
由于场景中的目标在运动,目标的影像在不同图像帧中的位置不同。该类算法对时间上连续的两帧图像进行差分运算,不同帧对应的像素点相减,判断灰度差的绝对值,当绝对值超过一定阈值时,即可判断为运动目标,从而实现目标的检测功能。
帧差法非常简单,但是会引入噪音和空洞问题。
空洞问题:人的某些细节缺失,如下图上的人身体变黑为空洞,二是容易引入噪音,假如风吹草动问题.
方法二:混合高斯模型
思想:
在进行前景检测前,先对背景进行训练,对图像中每个背景采用一个混合高斯模型进行模拟,每个背景的混合高斯的个数可以自适应。然后在测试阶段,对新来的像素进行GMM匹配,如果该像素值能够匹配其中一个高斯,则认为是背景,否则认为是前景。由于整个过程GMM模型在不断更新学习中,所以对动态背景有一定的鲁棒性。最后通过对一个有树枝摇摆的动态背景进行前景检测,取得了较好的效果。
运动的物体 符合某种高斯分布,背景符合某种高斯分布. 一个像素点进来了,判断它属于哪个分布的.
.
混合高斯分布是这样:一个固定的摄像头,假如在一个路口,背景的红绿灯服从某些高斯分布,路边的桥,或者路,或者是天都符合不同的高斯分布.
.
再比如判断一个人,只是评判一个点是远远不够的,需要进行混合评判.
如上面,假如第一帧某一个像素点为100,初始值方差标准为5, 当下一帧(200帧)来的时候,这个像素点变为了105 ,105-100 =5 < 3倍的方差标准. 那么我们认为它属于同一个高斯分布.更新.
105+100)/2 =102.5 计算新的方差 , 当下一个判断帧进来再重复上面操作
.
下一个点 180,超过方差标准了,进行创建一个新的高斯分布模型
高斯分布一般选取3-5种就够了。
应用:运动检测
.
这里使用了 createBackgroundSubtractorMOG2()方法, 更多方法详情
.
第一步: 读取视频
第二步: 创建混合高斯模型用于背景建模
第三步: 将模型应用到每一帧图片建立前景255和背景0
第四步: 将每一帧进行形态学开运算降噪
第五步: 将降噪后的帧帧图片进行轮廓检测,画出轮廓
import numpy as np
import cv2
#经典的测试视频
cap = cv2.VideoCapture('test.avi') #捕捉视频
#形态学操作需要使用
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(3,3))
#创建混合高斯模型用于背景建模
fgbg = cv2.createBackgroundSubtractorMOG2()
while(True):
ret, frame = cap.read()
fgmask = fgbg.apply(frame)#用建成的高斯模型来应用读取的每一帧
'''
对新来像素点的值与混合高斯模型中的每一个均值进行比较,
如果其差值在2倍的方差之间的话,则认为是背景,否则认为是前景。
将前景赋值为255,背景赋值为0。
'''
#形态学开运算去噪点
fgmask = cv2.morphologyEx(fgmask, cv2.MORPH_OPEN, kernel)
#寻找视频中的轮廓
contours, hierarchy = cv2.findContours(fgmask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
for c in contours:
#计算各轮廓的周长
perimeter = cv2.arcLength(c,True)
if perimeter > 188:
#找到一个直矩形(不会旋转)
x,y,w,h = cv2.boundingRect(c)
#画出这个矩形
cv2.rectangle(frame,(x,y),(x+w,y+h),(0,255,0),2)
cv2.imshow('frame',frame)
cv2.imshow('fgmask', fgmask)
k = cv2.waitKey(150) & 0xff
if k == 27:
break
cap.release()
cv2.destroyAllWindows()
知识点十九:光流估计
当做光流估计的时候,角点可逆满足数学方程求解要求,因此当做光流估计,一般选取角点作为输入。
光流估计 函数:
nextPts ,status,err=cv2.calcOpticalFlowPyrLK
(prevImage,nextImage,prevPts, winSize,maxLevel)
应用:
第一步: 做角点检测
第二步: 光流估计函数
第三步: 更新角点状态
第四步: 绘制轨迹
import numpy as np
import cv2
cap = cv2.VideoCapture('test.avi')
# 角点检测所需参数
feature_params = dict( maxCorners = 100,
qualityLevel = 0.3,
minDistance = 7)
# lucas kanade参数
lk_params = dict( winSize = (15,15),
maxLevel = 2)
# 随机颜色条
color = np.random.randint(0,255,(100,3))
# 拿到第一帧图像
ret, old_frame = cap.read()
old_gray = cv2.cvtColor(old_frame, cv2.COLOR_BGR2GRAY)#进行灰度图转换
#生成第一帧角点
p0 = cv2.goodFeaturesToTrack(old_gray, mask = None, **feature_params)
# 返回所有检测特征点,需要输入图像,角点最大数量(效率),品质因子(特征值越大的越好,来筛选)
# 距离相当于这区间有比这个角点强的,就不要这个弱的了,p0就是待跟踪的特征向量,也就是包含角点信息。
# 创建一个mask
mask = np.zeros_like(old_frame)
while(True):
ret,frame = cap.read()
frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 需要传入前一帧和当前图像以及前一帧检测到的角点
#光流估计函数
p1, st, err = cv2.calcOpticalFlowPyrLK(old_gray, frame_gray, p0, None, **lk_params)
# st=1表示在下一帧图片中仍然找到了上一帧对应的特征点,即角点
good_new = p1[st==1]
good_old = p0[st==1]
# 绘制轨迹
for i,(new,old) in enumerate(zip(good_new,good_old)):
a,b = new.ravel()
c,d = old.ravel()
mask = cv2.line(mask, (a,b),(c,d), color[i].tolist(), 2)
frame = cv2.circle(frame,(a,b),5,color[i].tolist(),-1)
img = cv2.add(frame,mask)
cv2.imshow('frame',img)
k = cv2.waitKey(150) & 0xff
if k == 27:
break
# 更新帧
old_gray = frame_gray.copy()
p0 = good_new.reshape(-1,1,2)
cv2.destroyAllWindows()
cap.release()
'''
上面代码在测试中,读取了第一帧,做了角点检测,然后角点信息传入下一帧,进行循环读入。
但是假如一开始的时候,并没有行人d,也就没有相应的d人物的角点,当d人物在后续过程中进入视频
并不会追踪d人物的行动路径,因为一开始就没有d人物的相关角点。
问题二:由于上面要检查角点的状态st,当st等于0,也就是下一帧找不到这个角点了,就放弃了追踪。例如在视频中,
一开始的人可能被其他物体在某一个时间挡住了角点,那么后续就没有这个人的追踪了。
改进方法,在每读取下一帧的时候,进行角点检测。不过要进行适当的角点过滤,要不然角点太多。
'''
…
更新于2021/6/10 基础知识补充
知识点二十 : 鼠标事件回调函数 cv2.setMouseCallcack()
> 创建窗口命令: cv2.namedWindow( winname='drawing' )
import cv2
# 编写回调函数
def draw_rectangle(event, x, y, flags, param):
# 鼠标左键按下去,蓝色框
if event == cv2.EVENT_LBUTTONDOWN:
cv2.rectangle(img1, (x+5 , y+5 ), (x-5, y-5),(255, 0, 0),1)
# 鼠标右键按下去,绿色框
elif event == cv2.EVENT_RBUTTONDOWN:
cv2.rectangle(img1, (x+5 , y+5 ), (x-5, y-5),(0, 255, 0),1)
img = cv2.imread('2.png')
img1 = img.copy()
cv2.namedWindow(winname='drawing')
cv2.setMouseCallback('drawing', draw_rectangle)
while True:
cv2.imshow('drawing', img1)
# 按 q 键退出
if cv2.waitKey(1) & 0xFF == 27:
break
cv2.destroyAllWindows()
知识点二十一: 基本绘图函数和图像逻辑运算
> 除了上面已经使用过的 cv2.rectangle( )
画矩形 cv2.cicrcle( )
画圆 ,还有以下函数.
图像按位逻辑运算
import numpy as np
import cv2
rectangle = np.zeros((300,300),dtype="uint8")
cv2.rectangle(rectangle,(25,25),(275,275),255,-1)
cv2.imshow("Rectangle",rectangle)
circle = np.zeros((300,300),dtype="uint8")
cv2.circle(circle,(150,150),150,255,-1)
cv2.imshow("Circle",circle)
cv2.waitKey(0)
bitwiseAnd = cv2.bitwise_and(rectangle,circle)
cv2.imshow("And",bitwiseAnd)
cv2.waitKey(0)
bitwiseOr = cv2.bitwise_or(rectangle,circle)
cv2.imshow("OR",bitwiseOr)
cv2.waitKey(0)
bitwiseXor = cv2.bitwise_xor(rectangle,circle)
cv2.imshow("XOR",bitwiseXor)
cv2.waitKey(0)
bitwiseNot = cv2.bitwise_not(rectangle)
cv2.imshow("Not",bitwiseNot)
cv2.waitKey(0)
and()是对二进制数据进行“与”操作,即对图像(灰度图像或彩色图像均可)每个像素值进行二进制“与”操作,1&1=1,1&0=0,0&1=0,0&0=0 上面表现为有黑则黑
例如我项想把下面的图像放在另一个图像上面,如果使用前面的混合选项,会造成两个图像变得透明(变得范白不清楚),而我只想把logo部分添加上去其他部分不需要。
import cv2
import numpy as np
img1 = cv2.imread('222.png')
img2 = cv2.imread('2.png')
rows, cols, channels = img1.shape #获取img1的 行列和通道
roi = img2[0:rows, 0:cols] #在img2上 截取对应大小的位置
img1gray = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY) #灰度化img1
ret, Mask = cv2.threshold(img1gray, 170, 255, cv2.THRESH_BINARY)
Mask_inv = cv2.bitwise_not(Mask)
a= np.hstack( ( img1gray,Mask,Mask_inv ) )
cv2.imshow('r', a)
img1_bg = cv2.bitwise_and(roi, roi, mask=Mask) #这一步相当于将img1的黑白二值图映在了 图片二对应区域上,得到了图片二的底色
img1_fg = cv2.bitwise_and(img1, img1, mask=Mask_inv) #这一步相当于得到img1的前景色
dst = cv2.add(img1_bg, img1_fg)
#查看这三种情况
b= np.hstack( ( img1_bg , img1_fg , dst ) )
cv2.imshow('r1', b)
img2[0:rows, 0:cols] = dst #图二相应位置赋值
cv2.imshow('das', img2)
cv2.waitKey(0)
cv2.destroyAllWindows()
知识点二十二: 图像的几何变换 cv2.warpAffine( )
前文中,我们介绍了图像尺寸变换,截取部分区域等操作,而图片的平移|旋转|翻转却很少提到,难不成是opencv没有提供类似的方法吗??? 而是大部分这操作 大家都可以使用imutils包中的方法代替.
图像平移
img1= imutils.translate(img,x,y)
图像缩放
img 2= imutils.resize(img,width=200)
图像旋转
img1= imutils.rotate(img,90)逆时针方向
img2= imutils.rotate_bound(img, 90) 顺时针旋转
平移
# 声明变换矩阵 向右平移10个像素, 向下平移30个像素 右下方向为正
m = np.float32( [ [1, 0, 10], [0, 1, 30] ] )
# 声明变换矩阵 向左平移10个像素, 向上平移30个像素
m1 = np.float32( [ [1, 0, -10], [0, 1, -30] ])
# 进行2D 仿射变换
shifted = cv2.warpAffine( img, m, (width, height) )
旋转
def getRotationMatrix2D(theta):
# 角度值转换为弧度值
# 因为图像的左上角是原点 需要×-1
theta = math.radians(-1*theta)
M = np.float32([
[math.cos(theta), -math.sin(theta), 0],
[math.sin(theta), math.cos(theta), 0]])
return M
# 进行2D 仿射变换
# 围绕原点 顺时针旋转30度
M = getRotationMatrix2D(30)
rotated_30 = cv2.warpAffine(img, M, (width, height))
翻转
方式一: 利用 warpAffine( ) 函数进行翻转
# 水平翻转
M1 = np.float32([[-1, 0, width], [0, 1, 0]])
flip_h = cv2.warpAffine(img, M1, (width, height))
# 垂直翻转
M2 = np.float32([[1, 0, 0], [0, -1, height]])
flip_v = cv2.warpAffine(img, M2, (width, height))
# 水平垂直同时翻转
M3 = np.float32([[-1, 0, width], [0, -1, height]])
flip_hv = cv2.warpAffine(img, M3, (width, height))
方式二:利用 cv2.flip( )函数
# 水平翻转
flip_h = cv2.flip(img, 1)
# 垂直翻转
flip_v = cv2.flip(img, 0)
# 同时水平翻转与垂直翻转
flip_hv = cv2.flip(img, -1)
方式三: 利用np数组实现
# 水平翻转
flip_h = img[:,::-1]
# 垂直翻转
flip_v = img[::-1]
# 水平垂直同时翻转
flip_hv = img[::-1, ::-1]
知识点二十三: 霍夫变换--->检测图像中的几何形状
更多理论详细参考
import cv2
import numpy as np
img = cv2.imread('1111.png',1)
img1 = img.copy()
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray,20,150,apertureSize = 3)
lines = cv2.HoughLines(edges,1,np.pi/180,150)
#计算得两点
for line in lines:
rho,theta = line[0] #rho = line[0][0] theta =line[0][1]
a = np.cos(theta)
b = np.sin(theta)
x0 = a*rho
y0 = b*rho
x1 = int(x0 + 1000*(-b))
y1 = int(y0 + 1000*(a))
x2 = int(x0 - 1000*(-b))
y2 = int(y0 - 1000*(a))
cv2.line(img1,(x1,y1),(x2,y2),(0,0,255),1)
result = np.hstack( (img,img1))
cv2.imshow('1',result)
cv2.waitKey(0)
概率霍夫直线变换 cv2.HoughLinesP( )
import cv2
import numpy as np
img = cv2.imread('1111.png')
img1 = img.copy()
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray,50,150,apertureSize = 3)
minLineLength =10
maxLineGap = 15
lines = cv2.HoughLinesP(edges,1,np.pi/180,20,minLineLength,maxLineGap)
for line in lines:
x1,y1,x2,y2 = line[0]
cv2.line(img1,(x1,y1),(x2,y2),(0,255,0),2)
result = np.hstack( (img,img1))
cv2.imshow('1',result)
cv2.waitKey(0)
#不同的参数检测展现的效果也不一样
霍夫圆检测 cv2.HoughCircles( )
import cv2
import numpy as np
img = cv2.imread('11122.jpg',0)
img = cv2.medianBlur(img,5)
img1 = cv2.cvtColor(img,cv2.COLOR_GRAY2BGR)
circles = cv2.HoughCircles(img,cv2.HOUGH_GRADIENT,1,100,param1=50,param2=30,minRadius=0,maxRadius=0)
#circles = cv2.HoughCircles(img,cv2.HOUGH_GRADIENT,1,100, 50, 30, 0, 0)
circles = np.uint16(np.around(circles))
for i in circles[0,:]:
# draw the outer circle
cv2.circle(img1,(i[0],i[1]),i[2],(0,255,0),2)
# draw the center of the circle
cv2.circle(img1,(i[0],i[1]),2,(0,0,255),3)
cv2.imshow('1',img1)
cv2.waitKey(0)
cv2.destroyAllWindows()
知识点二十四: 图像直方图应用补充
更多用法
import cv2
import numpy as np
from matplotlib import pyplot as plt
def create_rgb_hist(img): #创建RGB三通道直方图(直方图矩阵)
h,w,c = img.shape
rgbhist = np.zeros([16 * 16 * 16, 1], np.float32)#创建一个(16*16*16,1)的初始矩阵,作为直方图矩阵,16*16*16的意思为三通道每通道有16个bins
bsize = 256 / 16 #每一个bins说表示的范围大小
for row in range(h):
for col in range(w):
b = img[row, col, 0]
g = img[row, col, 1]
r = img[row, col, 2]
# 人为构建直方图矩阵的索引,该索引是通过每一个像素点的三通道值进行构建
index = int(b / bsize) * 16 * 16 + int(g / bsize) * 16 + int(r / bsize)
# 该处形成的矩阵即为直方图矩阵
rgbhist[int(index), 0] += 1
plt.ylim([0, 10000])
plt.grid(color='r', linestyle='--', linewidth=0.5, alpha=0.3)
return rgbhist
def hist_compare(img1, img2): #直方图比较函数
hist1 = create_rgb_hist(img1) # 创建第一幅图的rgb三通道直方图(直方图矩阵)
hist2 = create_rgb_hist(img2)# 创建第二幅图的rgb三通道直方图(直方图矩阵)
#进行三种方式的直方图比较
match1 = cv2.compareHist(hist1, hist2, cv2.HISTCMP_BHATTACHARYYA) #巴氏距离
match2 = cv2.compareHist(hist1, hist2, cv2.HISTCMP_CORREL) #相关性
match3 = cv2.compareHist(hist1, hist2, cv2.HISTCMP_CHISQR) #卡方
print("巴氏距离:%s, 相关性:%s, 卡方:%s" %(match1, match2, match3))
#读取图片
img1 = cv2.imread('2.png')
img2 = cv2.imread('22.png')
a = np.hstack( (img1,img2) )
cv2.imshow("img1 v img2", a)
#比较
hist_compare(img1, img2)
#展示2张图的三通道直方图
plt.subplot(1,2,1)
plt.title("img1")
plt.plot(create_rgb_hist(img1))
plt.subplot(1,2,2)
plt.title("img2")
plt.plot(create_rgb_hist(img2))
plt.show()
cv2.waitKey(0)
cv2.destroyAllWindows()
拓展
.
OpenCV附带了一个函数cv2.matchShapes(),它使我们能够比较两个形状或两个轮廓,并返回一个显示相似性的度量。 结果越低,匹配就越好.它是根据hu-moment值计算的.
import cv2
import numpy as np
img = cv2.imread('2.png',0)
img2 = cv2.imread('22.png',0)
ret, thresh = cv2.threshold(img, 127, 255,0)
ret, thresh2 = cv2.threshold(img2, 127, 255,0)
contours1,hierarchy = cv2.findContours(thresh,2,1)
cnt1 = contours1[0]
contours2,hierarchy = cv2.findContours(thresh2,2,1)
cnt2 = contours2[0]
ret = cv2.matchShapes(cnt1,cnt2,1,0.0)
print( ret )
直方图反向(反射)投影
import cv2
import numpy as np
template= cv2.imread('61.png') # 想要寻找的图片
target = cv2.imread('612.png') # 目标搜索图片
hsv = cv2.cvtColor(template, cv2.COLOR_BGR2HSV)
target_hsvt = cv2.cvtColor(target, cv2.COLOR_BGR2HSV)
# 计算template直方图
roihist = cv2.calcHist([hsv], [0, 1], None, [180, 256], [0, 180, 0, 256])
cv2.normalize(roihist, roihist, 0, 255, cv2.NORM_MINMAX)
dst = cv2.calcBackProject([target_hsvt], [0,1],roihist, [0, 180, 0, 256], 1)
# 此处卷积可以把分散的点连在一起
disc = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5))
dst = cv2.filter2D(dst, -1, disc)
ret, thresh = cv2.threshold(dst, 50, 255, 0)
#使用merge变成通道图像
thresh = cv2.merge((thresh,thresh,thresh))
# 按位操作
res = cv2.bitwise_and(target, thresh)
a = np.hstack((target, thresh,res))
# 显示图像
cv2.imshow('1', a)
cv2.waitKey(0)
cv2.destroyAllWindows()
知识点二十五: 图像轮廓应用补充
在前面我们学习了轮廓逼近cv2.approxPolyDP( cnt, eplison,True )
学习了x,y, w,h = cv2.boundingRect( cnt )
, cv2.minEnclosingCircle( cnt )
这里再补充几个
凸包
凸包的底层计算方法有:卷包裹算法(Gift Wrapping Algorithm),Graham扫描法(Graham Scan
Algorithm),快速凸包算法(Quickhull Algorithm)等。
凸包原理
import cv2
# 读取图片并转至灰度模式
img = cv2.imread( '2213.png' , 1)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 二值化
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
# 图片轮廓
contours, hierarchy = cv2.findContours(thresh, 2, 1)
cnt = contours[0]
# 寻找凸包并绘制凸包(轮廓)
hull = cv2.convexHull(cnt)
length = len(hull)
for i in range(len(hull)):
cv2.line(img, tuple(hull[i][0]), tuple(hull[(i+1)%length][0]), (0,255,255), 2)
# 显示图片
cv2.imshow('line', img)
cv2.waitKey()
import cv2
# 读取图片并转至灰度模式
img = cv2.imread('2211.png', 1)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 二值化,取阈值为235
ret, thresh = cv2.threshold(gray, 235, 255, cv2.THRESH_BINARY)
# 寻找图像中的轮廓
contours, hierarchy = cv2.findContours(thresh, 2, 1)
# 寻找物体的凸包并绘制凸包的轮廓
for cnt in contours:
hull = cv2.convexHull(cnt)
length = len(hull)
# 如果凸包点集中的点个数大于20
if length > 20:
# 绘制图像凸包的轮廓
for i in range(length):
cv2.line(img, tuple(hull[i][0]), tuple(hull[(i+1)%length][0]), (0,0,255), 2)
cv2.imshow('finger', img)
cv2.waitKey()
虽然改变不同的参数会得到不同的效果,但结果表明并不是所有的图片,凸包处理的效果都很好
.
最小外接矩形
例如:
画一个任意四边形(任意多边形都可以)的最小外接矩形,那么点集 cnt 存放的就是该四边形的4个顶点坐标(点集里面有4个点)
cnt = np.array([[x1,y1],[x2,y2],[x3,y3],[x4,y4]]) # 必须是array数组的形式
rect = cv2.minAreaRect(cnt) # 得到最小外接矩形的(中心(x,y), (宽,高), 旋转角度)
box = cv2.boxPoints(rect) # 获取最小外接矩形的4个顶点坐标
box = np.int0(box)
# 画出来
cv2.drawContours(img, [box], 0, (255, 0, 0), 1)
cv2.imwrite('contours.png', img)
import cv2
# 读取图片并转至灰度模式
img = cv2.imread('1213.png', 1)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 二值化,取阈值为127
ret, thresh = cv2.threshold(gray,127, 255, cv2.THRESH_BINARY)
# 寻找图像中的轮廓
contours, hierarchy = cv2.findContours(thresh, 2, 1)
#最小外接矩形
cnt = contours[0]
rect = cv2.minAreaRect(cnt) # 得到最小外接矩形的(中心(x,y), (宽,高), 旋转角度)
box = cv2.boxPoints(rect) # 获取最小外接矩形的4个顶点坐标
box = np.int0(box)
# 画出来
a = cv2.drawContours(img, [box], 0, (255, 0, 0), 1)
#外接矩形
x,y,w,h = cv2.boundingRect(cnt)
b = cv2.rectangle( a,(x,y),(x+w,y+h),(0,255,0),2)
cv2.imshow('1', a)
cv2.waitKey()
应用一:估算物体的长宽
应用二: 旋转纠正
目标物体旋转纠正参考c++代码
.
外接椭圆
import cv2
# 读取图片并转至灰度模式
img = cv2.imread('1213.png', 1)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 二值化,取阈值为127
ret, thresh = cv2.threshold(gray,127, 255, cv2.THRESH_BINARY)
# 寻找图像中的轮廓
contours, hierarchy = cv2.findContours(thresh, 2, 1)
for i in range( len(contours)):
cnt = contours[i]
ellipse = cv2.fitEllipse( cnt )
a = cv2.ellipse( img , (ellipse[0] , ellipse[1], ellipse[2] ), ( 0,255,255 ) , 2 )
cv2.imshow('1', a)
cv2.waitKey()
实验结果表明:椭圆拟合对于一个比较闭合的图形来说,效果稍微好一些,但是没有其他的方法更好,对于像上面类似直线的图形,效果很鸡肋.
点和轮廓的距离与关系
知识点二十六: 分水岭算法 – 图像分割
代码重要参考
参考一
import cv2
import numpy as np
img = cv2.imread('252.png', 0)
ret, thresh = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
#去除噪点,可多迭代几次
#opening operator是先腐蚀后膨胀,可以消除一些细小的边界,消除噪声
kernel=np.ones((3,3),np.uint8)
opening=cv2.morphologyEx(thresh,cv2.MORPH_OPEN,kernel,iterations=1)
cv2.imshow('2', opening)
#对其使用膨胀,我们所得到的大部分区域都是背景部分
sure_bg=cv2.dilate(opening,kernel,iterations=3)
'''
距离变化 可用来实现目标细化、骨架提取、形状插值及匹配、粘连物体的分离等。距离变换是针对二值图像的一种变换。
在二维空间中,一幅二值图像可以认为仅仅包含目标和背景两种像素,目标的像素值为1,背景的像素值为0;
距离变换的结果不是另一幅二值图像,而是一幅灰度级图像,即距离图像,图像中每个像素的灰度值为该像素与距其最近的
背景像素间的距离(就是原二值图像中像素值为1的像素点与最近的像素点为0的像素点之间的距离)
由于掩码是一幅二值图像,所以经过距离变化后还需要将图像进行二值化这样子的结果图像中像素点为1的区域就一定是硬币的位置,即前景图.
'''
#确定前景区域
dist_transfrom=cv2.distanceTransform(opening,cv2.DIST_L2 ,5)
#这里可能看起来是二值图 但其实还是一种灰度变化不明显的灰度图。 然后我们使用阈值来将其转变为二值图:
ret,sure_fg=cv2.threshold(dist_transfrom,0.7*dist_transfrom.max(),255,0)
'''
之后因为之前用了dilate函数,所以前景和背景之间可能会有重合,所以此处我们需要进行这块区域的确认,
可以通过sure_bg与sure_fg相减得到这块未知的区域:
'''
sure_fg = np.uint8(sure_fg)
unknown = cv2.subtract(sure_bg,sure_fg)
cv2.imshow('img6',unknown)
'''
需要注意的是,下面我们会使用一个cv2.connectedComponents的函数,
该函数会创建一个图像与原图像大小相同,不过数据类型会变成int32,他会把图像背景标记为0,其他目标用从1开始标记;
但是分水岭算法会将标为0的位置认为是unknown,所以我们对里面所有的像素的值加一,这样原本为0的位置上的像素的值就是1了。
之后我们设置栅栏来阻止水汇聚,相对于把unknown的区域设置为栏杆,设置为0
(给定确定的前景区,其中一些节点会连在一起,而另一些没连在一起的,这意味着他们来自不同的山谷,所以他们之间需要设置一个栅栏)
'''
ret, markers = cv2.connectedComponents(sure_fg)
# 所有像素值+1
markers = markers+1
# 这里将所有未知区域的像素设置为0
markers[unknown==255] = 0
# 打开门,让水漫起来
markers = cv2.watershed(img,markers)
# 将原图中的栅栏位置的颜色设置为蓝色并进行显示
img[markers == -1] = [255,0,0]
cv2.imshow('img',img)
cv2.waitKey(0)
cv2.destroyAllWindows()
希望桌前的你把参考链接中的硬币换成下面的小饼干
参考二
#参考代码二:
import numpy as np
import cv2
#读入图片
img = cv2.imread('2.png')
#转换为灰度图片
gray_img = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
#canny边缘检测 函数返回一副二值图,其中包含检测出的边缘。
canny = cv2.Canny(gray_img,80,150)
cv2.imshow('Canny',canny)
#寻找图像轮廓 返回修改后的图像 图像的轮廓 以及它们的层次
contours,hierarchy = cv2.findContours(canny,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
#32位有符号整数类型,
marks = np.zeros(img.shape[:2],np.int32)
#findContours检测到的轮廓
imageContours = np.zeros(img.shape[:2],np.uint8)
#轮廓颜色
compCount = 0
index = 0
#绘制每一个轮廓
for index in range(len(contours)):
#对marks进行标记,对不同区域的轮廓使用不同的亮度绘制,相当于设置注水点,有多少个轮廓,就有多少个轮廓
#图像上不同线条的灰度值是不同的,底部略暗,越往上灰度越高
marks = cv2.drawContours(marks,contours,index,(index,index,index),1,8,hierarchy)
#绘制轮廓,亮度一样
imageContours = cv2.drawContours(imageContours,contours,index,(255,255,255),1,8,hierarchy)
#查看 使用线性变换转换输入数组元素成8位无符号整型。
markerShows = cv2.convertScaleAbs(marks)
cv2.imshow('markerShows',markerShows)
#cv2.imshow('imageContours',imageContours)
#使用分水岭算法
marks = cv2.watershed(img,marks)
afterWatershed = cv2.convertScaleAbs(marks)
cv2.imshow('afterWatershed',afterWatershed)
#生成随机颜色
colorTab = np.zeros((np.max(marks)+1,3))
#生成0~255之间的随机数
for i in range(len(colorTab)):
aa = np.random.uniform(0,255)
bb = np.random.uniform(0,255)
cc = np.random.uniform(0,255)
colorTab[i] = np.array([aa,bb,cc],np.uint8)
bgrImage = np.zeros(img.shape,np.uint8)
#遍历marks每一个元素值,对每一个区域进行颜色填充
for i in range(marks.shape[0]):
for j in range(marks.shape[1]):
#index值一样的像素表示在一个区域
index = marks[i][j]
#判断是不是区域与区域之间的分界,如果是边界(-1),则使用白色显示
if index == -1:
bgrImage[i][j] = np.array([255,255,255])
else:
bgrImage[i][j] = colorTab[index]
cv2.imshow('After ColorFill',bgrImage)
#填充后与原始图像融合
result = cv2.addWeighted(img,0.6,bgrImage,0.4,0)
cv2.imshow('addWeighted',result)
cv2.waitKey(0)
cv2.destroyAllWindows()
知识点二十七: 图像修补
我暂且把修复分为两种:
.
自动修复(不推荐): 这种就是知道mask,或者提取mask比较简单.
如我参考了好多代码
#代码一: 有mask图
import numpy as np
import cv2 as cv
img = cv.imread('messi_2.jpg')
mask = cv.imread('mask2.png',0)
dst = cv.inpaint(img,mask,3,cv.INPAINT_TELEA)
cv.imshow('dst',dst)
cv.waitKey(0)
cv.destroyAllWindows()
上面图一是要修复的图 ,图二是mask , 图三图四是两种falg的结果, 但我个人认为,实际意义不大.因为我们修复的话,有mask的图情况基本很少 .
代码二参考
#代码二: 提取mask简单
import numpy as np
import cv2 as cv
src_image = cv2.imread("data/1.jpg")
gray_image = cv2.cvtColor(src_image, cv2.COLOR_RGB2GRAY)
# 二值化
ret, binary = cv2.threshold(gray_image, 245, 255, cv2.THRESH_BINARY)
# 进行开操作,去除噪音小点。
kernel_3X3 = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
binary_after_open = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel_3X3)
restored = cv2.inpaint(src_image, binary_after_open, 9, cv2.INPAINT_NS)
cv2.imshow('img1',restored)
这里原图上面是白色的王,当进行二值阈值处理的时候,能够比较好的分离出王字,但是当这个字不是白色,是其他颜色,这样二值处理提取mask的效果就差强人意了.
再比如处理这张皮卡丘图
再比如这里去水印的
去水印的
貌似能处理马赛克的
手动修复:
.
手动滑动修复
代码一: 手动滑动修复
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 ")
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()
知识点二十八: 双边滤波 – 磨皮,美白,祛痘
import cv2
img = cv2.imread('2214.png',1)
dst = cv2.bilateralFilter(img,15,65,35)
cv2.imshow('src',img)
cv2.imshow('dst',dst)
cv2.waitKey(0)
知识点二十九: grabCut ( ) 和 floodFIll ( )算法 - 图像分割
参考博客
所谓图像分割指的是根据灰度、颜色、纹理和形状等特征把图像划分成若干互不交迭的区域,并使这些特征在同一区域内呈现出相似性,而在不同区域间呈现出明显的差异性。
grabCut( ) 分割
Grabcut是基于图割(graph cut)实现的图像分割算法,它需要用户输入一个boundingbox作为分割目标位置,实现对目标与背景的分离/分割。Grabcut分割速度快,效果好,支持交互操作,因此在很多APP图像分割/背景虚化的软件中经常使用。
#指定矩形位置
import cv2
import numpy as np
img = cv2.imread('2.png')
#设定矩形区域 作为ROI 矩形区域外作为背景
rect = (275, 120, 170, 320)
#img.shape[:2]得到img的row 和 col ,得到和img尺寸一样的掩模即mask ,然后用0填充
mask = np.zeros(img.shape[:2], np.uint8)
#创建以0填充的前景和背景模型, 输入必须是单通道的浮点型图像, 1行, 13x5 = 65的列 即(1,65)
bgModel = np.zeros((1,65), np.float64)
fgModel = np.zeros((1,65), np.float64)
#调用grabcut函数进行分割,输入图像img, mask, mode为 cv2.GC_INIT_WITH-RECT
cv2.grabCut(img, mask, rect, bgModel, fgModel, 5, cv2.GC_INIT_WITH_RECT)
##调用grabcut得到rect[0,1,2,3],将0,2合并为0, 即确定的背景区域和可能的背景区域合并, 1,3合并为1 存放于mask2中
mask2 = np.where((mask == 2) | (mask == 0), 0, 1).astype(np.uint8)
#得到输出图像
out = img * mask2[:, :, np.newaxis]
cv2.imshow('origin', img)
cv2.imshow('grabcut', out)
cv2.waitKey()
cv2.destroyAllWindows()
#鼠标滑动指定框的大小
import numpy as np
import cv2
#鼠标事件的回调函数
def on_mouse(event,x,y,flag,param):
global rect
global leftButtonDowm
global leftButtonUp
#鼠标左键按下
if event == cv2.EVENT_LBUTTONDOWN:
rect[0] = x
rect[2] = x
rect[1] = y
rect[3] = y
leftButtonDowm = True
leftButtonUp = False
#移动鼠标事件
if event == cv2.EVENT_MOUSEMOVE:
if leftButtonDowm and not leftButtonUp:
rect[2] = x
rect[3] = y
#鼠标左键松开
if event == cv2.EVENT_LBUTTONUP:
if leftButtonDowm and not leftButtonUp:
x_min = min(rect[0],rect[2])
y_min = min(rect[1],rect[3])
x_max = max(rect[0],rect[2])
y_max = max(rect[1],rect[3])
rect[0] = x_min
rect[1] = y_min
rect[2] = x_max
rect[3] = y_max
leftButtonDowm = False
leftButtonUp = True
#读入图片
img = cv2.imread('5.jpg')
#掩码图像,如果使用掩码进行初始化,那么mask保存初始化掩码信息;在执行分割的时候,也可以将用户交互所设定的前景与背景保存到mask中,然后再传入grabCut函数;在处理结束之后,mask中会保存结果
mask = np.zeros(img.shape[:2],np.uint8)
#背景模型,如果为None,函数内部会自动创建一个bgdModel;bgdModel必须是单通道浮点型图像,且行数只能为1,列数只能为13x5;
bgdModel = np.zeros((1,65),np.float64)
#fgdModel——前景模型,如果为None,函数内部会自动创建一个fgdModel;fgdModel必须是单通道浮点型图像,且行数只能为1,列数只能为13x5;
fgdModel = np.zeros((1,65),np.float64)
#用于限定需要进行分割的图像范围,只有该矩形窗口内的图像部分才被处理;
rect = [0,0,0,0]
#鼠标左键按下
leftButtonDowm = False
#鼠标左键松开
leftButtonUp = True
cv2.namedWindow('img')
cv2.setMouseCallback('img',on_mouse)
cv2.imshow('img',img)
#循环部分,当鼠标左键按下、没有松开则实时绘制矩形框。当左键松开、对图像进行分割。
while cv2.waitKey(2) == -1:
#左键按下,画矩阵
if leftButtonDowm and not leftButtonUp:
img_copy = img.copy()
#在img图像上,绘制矩形 线条颜色为green 线宽为2
cv2.rectangle(img_copy,(rect[0],rect[1]),(rect[2],rect[3]),(0,255,0),2)
#显示图片
cv2.imshow('img',img_copy)
#左键松开,矩形画好
elif not leftButtonDowm and leftButtonUp and rect[2] - rect[0] != 0 and rect[3] - rect[1] != 0:
#转换为宽度高度
rect[2] = rect[2]-rect[0]
rect[3] = rect[3]-rect[1]
rect_copy = tuple(rect.copy())
rect = [0,0,0,0]
#物体分割
cv2.grabCut(img,mask,rect_copy,bgdModel,fgdModel,5,cv2.GC_INIT_WITH_RECT)
mask2 = np.where((mask==2)|(mask==0),0,1).astype('uint8')
img_show = img*mask2[:,:,np.newaxis]
#显示图片分割后结果
cv2.imshow('grabcut',img_show)
#显示原图
cv2.imshow('img',img)
cv2.waitKey(0)
cv2.destroyAllWindows()
.
floodFill ( )
参数介绍代码
参数代码参考
#创建一个20*10像素的灰度图像,为了便于观察,我们以60个灰度为一个等级填充图片。
import cv2
import numpy as np
img = np.zeros((20,10), dtype=np.uint8)
#构建4个等级的灰度值 , 分别是 0 , 60 , 120 , 180
i = 0
for v in img:
v[:] = i//5 * 60
i += 1
cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
就是这样的一张图,现在 我们开始准备实行漫水填充,开始选取填充的起点seed(7, 7 ) ,填充颜色选为127
import cv2
import numpy as np
img = np.zeros((20,10), dtype=np.uint8)
i = 0
for v in img:
v[:] = i//5 * 60
i += 1
cv2.imwrite('img_init.png', img)
seed = (7, 7) #漫水填充的第三 个参数有了
#构建mask,根据mask参数的介绍,其size必须为宽img+2,高img+2
mask = np.zeros((img.shape[0]+2, img.shape[1] +2), dtype=np.uint8) #第二个参数
newVal = (127) #img fill的填充颜色 #第四个参数
mask_fill = 252 #mask的填充值
#floodFill充值标志
flags = 4|(mask_fill<<8)|cv2.FLOODFILL_FIXED_RANGE
#连通区范围设定 这个指 [ 填充位置像素值(7,7对应的60 ) - loDiff ,填充位置像素值(7,7对应的60 )+ upDIff ]范围之间 ,如果其他位置的像素值在这之间,都会被指定的newval 填充颜色给填充.
loDiff, upDiff = 20,20 # 也就是(40- -80)之间的像素点 都会被替换为指定的127
#执行floodFill操作
ret, image, mask, rect = cv2.floodFill(img, mask, seed, newVal,(loDiff), (upDiff), flags)
cv2.imwrite('img.png', img)
cv2.imwrite('img_mask.png', mask)
图像位置的颜色变了, 在(40 , 80 )之间的像素点都被替换成为 127
#改变 为 70 ,20
#相当于(60-70 , 60+20 ) = (-10 , 80) ,也就是像素点值在(0, 80)之间的位置都会变填充为指定的 newval ,如上文的127)
loDiff, upDiff = 70,20
#效果如下 , 原本为0的地方也被替换为127 ,
(但是如果在大于80的地方的120 下面还有在(0,80)之间, 假如上面的180位置的像素点被70替代, 漫水将会被120隔断,下面70不会改变. 待验证,懒得验证)
与此同时,当原图的位置被指定的填充,对应的mask的位置,也会被修改成为指定的值.这里mask为什么加2,将在下面细说
以上都是基于FLOODFILL_FIXED_RANGE这种填充方式的,下面我们对比一下使用FLOODFILL_MASK_ONLY方式来填充会有什么不一样。其他code保存不变,我们只需将
flags =4|(mask_fill<<8)|cv2.FLOODFILL_FIXED_RANGE
改为
flags =4|(mask_fill<<8)|cv2.FLOODFILL_MASK_ONLY
运行程序后,发现img图片第一、第二行的值均未发生变化:
但mask,对应的位置改变了.
故此,可以验证前面对FLOODFILL_MASK_ONLY的解释,只影响mask的输出,对image无影响。
相当于 图片是 人 ,mask是铠甲 , 人穿铠甲. 现在有不同粗细的不同类型的子弹,
一种是可以打穿铠甲的子弹,就意味着被子弹击中的位置 也会受到影响, 不同粗细的子弹,对应的受伤区域会不同. 粗细对应着loDiff, upDiff
另外这种子弹,很特别,只会打掉铠甲,人不会受伤. 就好像铠甲是火,子弹是水做的,火被灭了.人没事
至此 上面参数就学的差不多了, 还有就是mask为什么+2
漫水填充算法实现最常见有四邻域像素填充法,八邻域 像素填充法,基于扫描线的填充方法。根据代码实现方式又可以分为递归与非递归。
上面介绍了用什么子弹打铠甲,但是被击中的铠甲 ,怎么碎开的或者是人的部位,伤口是怎么扩大的.这就是像素填充法
#简单应用
#泛洪填充(彩色图像填充)
import cv2
import numpy as np
def fill_color_demo(image):
copyImg = image.copy()
h, w = image.shape[:2]
mask = np.zeros([h+2, w+2],np.uint8) #mask必须行和列都加2,且必须为uint8单通道阵列
cv2.floodFill(copyImg, mask, (220, 250), (0, 255, 255), (100, 100, 100), (50, 50 ,50), cv2.FLOODFILL_FIXED_RANGE) #这边是3通道 (100 ,100,100) ,(50,50,50)
cv2.imshow("fill_color_demo", copyImg)
src = cv2.imread('2.png')
cv2.namedWindow('input_image', cv2.WINDOW_AUTOSIZE)
cv2.imshow('input_image', src)
fill_color_demo(src)
cv2.waitKey(0)
cv2.destroyAllWindows()
# roi 即兴趣区域,对图像提取想要的部分
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
def roi_test(src):
face = src[100:510, 200:600] # 高度,宽度.
gray = cv.cvtColor(face, cv.COLOR_BGR2GRAY) # face彩色图片变成灰度图片
cv.imshow("gray", gray)
back_face = cv.cvtColor(gray, cv.COLOR_GRAY2BGR) # 将 R = G = B = gray,所以呈现出来的是灰色
cv.imshow("back_face", back_face)
src[100:510, 200:600] = back_face # back_face必须是三通道的
cv.imshow("face", src)
def fill_color_demo(image):
copyImg = image.copy()
h, w = image.shape[:2]
mask = np.zeros([h+2, w+2], np.uint8) # mask必须是一个单通道
# 参数:原图,mask图,起始点,起始点值减去该值作为最低值,起始点值加上该值作为最高值,彩色图模式
cv.floodFill(copyImg, mask, (30, 30), (0, 255, 255), (100,100,100), (50, 50, 50), cv.FLOODFILL_FIXED_RANGE)
cv.imshow("fill_color_demo", copyImg)
def fill_binary():
image = np.zeros([400, 400, 3], np.uint8)
image[100:300, 100:300, :] = 255 # 将这个范围的所有通道赋值
cv.imshow("fill_binary", image)
mask = np.ones([402, 402, 1], np.uint8)
mask[101:301, 101:301] = 0
cv.floodFill(image, mask, (200, 200), (100, 2, 255), cv.FLOODFILL_MASK_ONLY)
cv.imshow("filled binary", image)
if __name__ == '__main__':
src = cv.imread("2.jpg") # 读入图片放进src中
# cv.namedWindow("Crystal Liu") # 创建窗口
# cv.imshow("Crystal Liu", src) # 将src图片放入该创建的窗口中
# roi_test(src)
# fill_color_demo(src)
fill_binary()
# img = cv.imread("../images/opencv_logo.png")
# make_border(img)
cv.waitKey(0) # 等有键输入或者1000ms后自动将窗口消除,0表示只用键输入结束窗口
cv.destroyAllWindows() # 关闭所有窗口
.
知识点三十: 角点检测补充cv2.goodFeaturesToTrack() 与 cv2.cornerSubPix()
Shi-Tomasi角点检测— cv2.goodFeaturesToTrack()
Shi-Tomasi算法是Harris算法的改进, Harris 算法最原始的定义是将矩阵 M 的行 列式值与 M 的迹相减,
再将差值同预先给定的阈值进行比较。后来Shi和Tomasi提 出改进的方法, 若两个特征值中较小的一个大于最小阈值,则会得到强角点。
import numpy as np
import cv2
img = cv2.imread('5.png')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
corners = cv2.goodFeaturesToTrack(gray,25,0.01,10)
#corners = cv2.goodFeaturesToTrack(gray,maxCorners=200,qualityLevel=0.01,minDistance=3.0,useHarrisDetector=False,k=0.04,blockSize=3)
corners = np.int0(corners)
for i in corners:
x,y = i.ravel()
cv2.circle(img,(x,y),3,255,-1)
cv2.imshow('1', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
亚像素角点检测—cv2.cornerSubPix()
最大精度的角点检测
首先我们要找到 Harris角点,然后将角点的重心传给这个函数进行修正。Harris
角点用红色像素标出,绿色像素是修正后的像素。在使用这个函数是我们要定义一个迭代停止条件。当迭代次数达到或者精度条件满足后迭代就会停止。我们同样需要定义进行角点搜索的邻域大小。
import cv2
import numpy as np
img = cv2.imread( '2.png' )
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# 找到Harris角点
gray = np.float32(gray)
dst = cv2.cornerHarris(gray,2,3,0.04)
dst = cv2.dilate(dst,None)
ret, dst = cv2.threshold(dst,0.01*dst.max(),255,0)
dst = np.uint8(dst)
# 找到Harris角点重心
ret, labels, stats, centroids = cv2.connectedComponentsWithStats(dst)
# 定义迭代停止条件
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 100, 0.001)
corners = cv2.cornerSubPix(gray,np.float32(centroids),(5,5),(-1,-1),criteria)
# 绘制角点
res = np.hstack((centroids,corners))
res = np.int0(res)
img[res[:,1],res[:,0]]=[0,0,255]
img[res[:,3],res[:,2]] = [0,255,0]
cv2.imshow('1',img)
cv2.waitKey()
cv2.destroyAllWindows()
学会这些Python美图技巧,就等着女朋友夸你吧
…
案例一:识别颜色
识别下面图片上的发夹的颜色
OPENCV+PYTHON识别并打印HSV颜色
#参考代码
import cv2
import numpy as np
#这张作图的中间部分是 黄色,0,255,255
img=np.ones((240,320,3),dtype=np.uint8)*128
img[60:180,80:240]=[0,255,255]
def color_hist(img):
#RGB图像转为HSV图像色域
hsv=cv2.cvtColor(img,cv2.COLOR_BGR2HSV)
#创建和img同样大小mask,将255赋值给mask上位置b,这个位置b和 原图黄色地方一样,目的是为了统计这块mask区域的直方图
mask=np.zeros(img.shape[:2],dtype=np.uint8)
mask[60:180,80:240]=255
#统计mask这块区域的直方图,选取直方图中出现数量最多的值作为这块区域的HSV的代表值
hist_mask=cv2.calcHist([hsv],[0],mask,[180],[0,180])
object_H=np.where(hist_mask==np.max(hist_mask))
print(object_H[0])
return object_H[0]
#根据代表值进行判断属于什么颜色
def color_distinguish(object_H):
try:
if object_H>26 and object_H<34: color='yellow'
elif object_H>35 and object_H<77 : color='green'
elif object_H>78 and object_H<99:color='cyan-blue'
elif object_H>100 and object_H<124: color='blue'
elif object_H>156 and object_H<180 : color='red'
elif object_H>6 and object_H<15: color ='orange'
else: color='None'
print(color)
return color
except:
pass
if __name__=='__main__':
object_H=color_hist(img)
color_distinguish(object_H)
cv2.imshow('166.png',img)
cv2.waitKey(0)
#目标识别发夹是红色
import cv2
import numpy as np
img1=cv2.imread('166.png')
def color_hist(img):
#RGB图像转为HSV图像色域
hsv=cv2.cvtColor(img,cv2.COLOR_BGR2HSV)
#这里的pass应该是这样,进行阈值处理,过滤出发夹的颜色,然后画出发夹的轮廓,
#设置同样大小的mask,在mask的相信位置b上进行255赋值,然后进行下面的直方图统计
pass
#统计mask这块区域的直方图,选取直方图中出现数量最多的值作为这块区域的HSV的代表值
hist_mask=cv2.calcHist([hsv],[0],mask,[180],[0,180])
object_H=np.where(hist_mask==np.max(hist_mask))
print(object_H[0])
return object_H[0]
卷积池化
原图是32x32x3的,在32x32的化为3x3,5x5等小格,对每一个小格进行特征提取。计算特征值。
得到一个可能为28x28的特征图,这个操作就是卷积操作,然后可以对28x28的特征图同样操作划分小格,提取特征,不停卷积操作。
特征值计算
原图是7x7三个通道rgb三个通道,对于每一个通道划分成为每一个小格。如图中是先3x3,对于指定的的filter,同样有3个,还有一个个b,b的值在这里就是1。
f11=1x0+1x0+1x0 +0x(-1)+(-1)x1+1x0 + (-1)x0+1x1+1x0 =0
f12= 2
f13=0
则特征值为f11+f12+f13+b=0+2+0+1 =3
为什么最后是3x3?这是由stride的值决定的,相当于把原来指定的3x3每次向右平移2格,最后得到3x3的特征图。
有的像素点被利用了2次,有的像素点被利用了一次。比如边缘上的值就是被使用一次。
实际上原数据是5x5,但是要解决上述边缘点可能被利用一次,就需要对原数据进行处理,使得原来是边缘的点,加上一圈,成为内部点。加几圈是由+pad确定。用0填充是因为不会影响原数据。
池化层Pooling层:特征压缩操作
原特征图可能是224x224x64,下图是MAXpooLING,指定了2x2的filters,然后选取每一个2x2的小方框里面最大的值作为2x2值的代表,从而达到压缩的效果。当然也有取平均值的方法。