文章目录
- 1. Opencv 环境配置
-
- 1.1 创建opencv虚拟环境
- 1.2 让notebook 进入虚拟环境
- 1.3 虚拟环境导入pycharm
- 2. 图像的基本操作
-
- 2.1 读取图片
-
- 2.1.1 基本操作
- 2.1.2 颜色通道提取
- 2.2 读取视频
- 2.3 边界填充
- 2.4 图像融合
-
- 2.4.1 数值计算
- 2.4.2 图像融合
- 2.5 灰度图
-
- 2.6 HSV
- 3. 阈值和平滑处理
-
- 3.1 图像阈值
- 3.2 图像平滑
- 4. 图像形态学操作
-
- 4.1 腐蚀操作
- 4.2 膨胀操作
- 4.3 开运算和闭运算
- 4.4 梯度运算
- 4.5 礼帽与黑帽
- 5. 图像梯度计算
-
- 5.1 Sobel 算子
- 5.2 scharr 和lapkacian 算子
- 6. 边缘检测
- 7. 图像金字塔与轮廓检测
-
- 7.1 图像金字塔定义
- 7.2 图像金字塔制作
- 7.3 图像轮廓
- 7.4 轮廓特征与近似
- 7.5 模板匹配
- 8. 直方图和傅里叶变换
-
- 8.1 直方图定义
- 8.2 直方图均衡化
- 8.3 自适应直方图均衡化
- 9. 图像特征
-
- 9.1 图像特征harris
- 图像特征SIFT
- 10. 特征匹配
-
- 10.1 Brute-Force匹配
- 10.2 随机抽样一致算法(RANAC)
- 11. 光流估计
-
- 11.1 原理介绍
- 11.2 实战演练
- 12. 背景建模
-
- 12.1 原理介绍
- 12.2 实战演练
- 13. 项目实战
1. Opencv 环境配置
为了避免依赖的冲突和python版本的不匹配,建议重新建立一个虚拟环境。
要确保python版本和opencv版本匹配。在这里,我采用的是opencv 3.8.8 和 opencv 4.5.3,并且已经配置好Anaconda。
1.1 创建opencv虚拟环境
-
创建虚拟环境(其中opencvEnv是自己起的名字,也可以用其他的名字)
Anaconda Prompt 中键入
conda create -n opencvEnv python=3.8.8
- 进入虚拟环境
activate opencvEnv
- 安装opencv
pip install opencv-python
pip install opencv-contrib-python
- 安装matplotlib库(后续画图需要)
pip install opencv-python
1.2 让notebook 进入虚拟环境
- 在当前虚拟环境下安装
conda install ipykernel
- 将当前环境写入notebook中
python -m ipykernel install --user --name opencvEnv --display-name opencvEnv
- 如果没有安装notebook ,则键入
pip install notebook
- 打开notebook
ipython notebook
- 切换当前环境为新建立的虚拟环境opencvEnv(在导航栏中点击“kernel","change kernel ",“opencvEnv”)
1.3 虚拟环境导入pycharm
- 点击"File" ,“setting”, “project”,“python interpreter”
- 点击
Opencv学习笔记——基础知识1. Opencv 环境配置2. 图像的基本操作3. 阈值和平滑处理4. 图像形态学操作5. 图像梯度计算6. 边缘检测7. 图像金字塔与轮廓检测8. 直方图和傅里叶变换9. 图像特征10. 特征匹配11. 光流估计12. 背景建模13. 项目实战 - 点击"add"
- 点击"Conda Environment",“Existing environment”
- 选择“opencvEnv”,“OK”
2. 图像的基本操作
图片数据大小 h e i g h t ∗ w i d t h ∗ 3 height*width*3 height∗width∗3
h e i g h t ∗ w i d t h height*width height∗width: 像素点的个数
3 3 3:R,G,B三个颜色通道
因此图片可以有三个大小为height*width的矩阵构成,矩阵中的元素值表示当前颜色通道的亮度大小,取值范围是[0,255], 数值越大,表明该区域越亮。
2.1 读取图片
2.1.1 基本操作
- 导入库
import cv2
import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline
-
读取图片
opencv的读取格式是BGR, 读取格式是
, 参数是图片位置cv2.imread()
img=cv2.imread('cat.jpg') # 图片与源程序处于同一个目录下 img # 打印img的矩阵格式
- 显示图片
第一个参数是图片名字,第二个参数是已经读取完的图片cv2.imshow( )
cv2.imshow('image',img) cv2.waitKey(0) # 等待时间,毫秒级,0表示任意键终止 cv2.destroyAllWindows()
定义显示图片的函数
def cv_show(name,img):
cv2.imshow(name,img)
cv2.waitKey(0)
cv2.destroyAllWindows()
- 图片的大小
img.shape
- 灰度图片
cv2.imread(' ',cv2.IMREAD_GRAYSCALE)
- cv2.IMREAD_COLOR:彩色图像
- cv2.IMREAD_GRAYSCALE:灰度图像
显示图片img=cv2.imread('cat.jpg',cv2.IMREAD_GRAYSCALE) img.shape #(414,500) 灰度图片只有单通道,表示亮度
cv2.imshow('image',img) cv2.waitKey(10000) #等待10000毫秒后自动关闭 cv2.destroyAllWindows()
- 保存图片
参数一:保存的文件名;参数二:保存的图片cv2.imwrite(' ', )
-
图片的类别
图片在计算机中一ndarray的数据结构保存
- 图片大小
- 保存图片的数据类型
- 截取部分图像数据
img=cv2.imread('cat.jpg') cat=img[0:50,0:200] cv_show('cat',cat)
2.1.2 颜色通道提取
- 颜色通道提取
, 参数是图片,返回值分别为b,g,rcv2.split()
1. b,g,r=cv2.split(img) r.shape #(414,500)
- 颜色通道合成
, 参数是颜色通道组成的元组,返回值是图片cv2.merge()
img=cv2.merge((b,g,r)) img.shape # (414, 500, 3)
-
保留单通道
只保留R
cur_img = img.copy() cur_img[:,:,0] = 0 #b通道置为0 cur_img[:,:,1] = 0 #g通道置为0 cv_show('R',cur_img)
只保留G
# 只保留G
cur_img = img.copy()
cur_img[:,:,0] = 0
cur_img[:,:,2] = 0
cv_show('G',cur_img)
只保留B
cur_img = img.copy()
cur_img[:,:,1] = 0
cur_img[:,:,2] = 0
cv_show('B',cur_img)
2.2 读取视频
- cv2.VideoCapture可以捕获摄像头,用数字来控制不同的设备,例如0,1。
- 如果是视频文件,直接指定好路径即可。
- 读视频
cv2.VideoCapture( )
参数是视屏路径
检查打开的是否正确,其中open 是bool类型的数据,为True表示可以正确打开,反之则不能; frame表示读取视频中的一帧数据。
if vc.isOpened(): oepn, frame = vc.read() else: open = False
while open: ret, frame = vc.read() if frame is None: #如果读取完,则退出循环 break if ret == True: gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) #将每一帧图片转化为灰度图片 cv2.imshow('result', gray) # 显示每一帧的灰度图片 if cv2.waitKey(100) & 0xFF == 27: #相邻两个帧展示间隔时间为100ms, 0xFF 是退出键 break vc.release() cv2.destroyAllWindows()
2.3 边界填充
cv2.copyMakeBorder()
参数是待处理的图片,上下左右边框大小,填充方式
- BORDER_REPLICATE:复制法,也就是复制最边缘像素。
- BORDER_REFLECT:反射法,对感兴趣的图像中的像素在两边进行复制例如:fedcba|abcdefgh|hgfedcb
- BORDER_REFLECT_101:反射法,也就是以最边缘像素为轴,对称,gfedcb|abcdefgh|gfedcba
- BORDER_WRAP:外包装法cdefgh|abcdefgh|abcdefg
- BORDER_CONSTANT:常量法,常数值填充。
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()
2.4 图像融合
2.4.1 数值计算
img_cat=cv2.imread('cat.jpg')
img_dog=cv2.imread('dog.jpg')
img_cat2= img_cat +10 # 相当于在每一个像素点位置加10
img_cat[:5,:,0]
'''
array([[142, 146, 151, ..., 156, 155, 154],
[108, 112, 118, ..., 155, 154, 153],
[108, 110, 118, ..., 156, 155, 154],
[139, 141, 148, ..., 156, 155, 154],
[153, 156, 163, ..., 160, 159, 158]], dtype=uint8)
'''
img_cat2[:5,:,0]
'''
array([[152, 156, 161, ..., 166, 165, 164],
[118, 122, 128, ..., 165, 164, 163],
[118, 120, 128, ..., 166, 165, 164],
[149, 151, 158, ..., 166, 165, 164],
[163, 166, 173, ..., 170, 169, 168]], dtype=uint8)
'''
(img_cat + img_cat2)[:5,:,0] #相当于% 256
'''
array([[ 38, 46, 56, ..., 66, 64, 62],
[226, 234, 246, ..., 64, 62, 60],
[226, 230, 246, ..., 66, 64, 62],
[ 32, 36, 50, ..., 66, 64, 62],
[ 60, 66, 80, ..., 74, 72, 70]], dtype=uint8)
'''
cv2.add
将两张图片相加时,当和超多255时,用255代替。
cv2.add(img_cat,img_cat2)[:5,:,0]
'''
array([[255, 255, 255, ..., 255, 255, 255],
[226, 234, 246, ..., 255, 255, 255],
[226, 230, 246, ..., 255, 255, 255],
[255, 255, 255, ..., 255, 255, 255],
[255, 255, 255, ..., 255, 255, 255]], dtype=uint8)
'''
2.4.2 图像融合
当大小不同的两张图片相加会报错。
- 调整图片大小
参数是待处理的图片,和期望的宽高。cv2.resize()
还可以按照比例缩放img_dog = cv2.resize(img_dog, (500, 414)) img_dog.shape # (414, 500, 3)
res = cv2.resize(img, (0, 0), fx=1, fy=3) plt.imshow(res)
- 按照权重融合
参数是待处理的两张图片和各自的权重cv2.addWeighted()
res = cv2.addWeighted(img_cat, 0.4, img_dog, 0.6, 0) plt.imshow(res)
2.5 灰度图
cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
import cv2 #opencv读取的格式是BGR
import numpy as np
import matplotlib.pyplot as plt#Matplotlib是RGB
%matplotlib inline
img=cv2.imread('cat.jpg')
img_gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
img_gray.shape #(414, 500)
展示图片
cv2.imshow("img_gray", img_gray)
cv2.waitKey(0)
cv2.destroyAllWindows()
2.6 HSV
- H - 色调(主波长)。
- S - 饱和度(纯度/颜色的阴影)。
- V值(强度)
cv2.cvtColor(img,cv2.COLOR_BGR2HSV)
hsv=cv2.cvtColor(img,cv2.COLOR_BGR2HSV)
cv2.imshow("hsv", hsv)
cv2.waitKey(0)
cv2.destroyAllWindows()
3. 阈值和平滑处理
3.1 图像阈值
ret, dst = cv2.threshold(src, thresh, maxval, type)
- src: 输入图,只能输入单通道图像,通常来说为灰度图
- dst: 输出图
- thresh: 阈值
- maxval: 当像素值超过了阈值(或者小于阈值,根据type来决定),所赋予的值
- type:二值化操作的类型,包含以下5种类型: cv2.THRESH_BINARY; cv2.THRESH_BINARY_INV; cv2.THRESH_TRUNC; cv2.THRESH_TOZERO;cv2.THRESH_TOZERO_INV
- cv2.THRESH_BINARY 超过阈值部分取maxval(最大值),否则取0
- cv2.THRESH_BINARY_INV THRESH_BINARY的反转
- cv2.THRESH_TRUNC 大于阈值部分设为阈值,否则不变
- cv2.THRESH_TOZERO 大于阈值部分不改变,否则设为0
- cv2.THRESH_TOZERO_INV THRESH_TOZERO的反转
ret, thresh1 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_BINARY)
ret, thresh2 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_BINARY_INV)
ret, thresh3 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_TRUNC)
ret, thresh4 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_TOZERO)
ret, thresh5 = cv2.threshold(img_gray, 127, 255, cv2.THRESH_TOZERO_INV)
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()
3.2 图像平滑
图像平滑的目的是降低噪声点的影响
img = cv2.imread('lenaNoise.png')
cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
滤波实际上时进行卷积
均值滤波:简单的平均卷积操作
方框滤波: 基本和均值一样,可以选择归一化
box = cv2.boxFilter(img,-1,(3,3), normalize=True)
高斯滤波:高斯模糊的卷积核里的数值是满足高斯分布,相当于更重视中间
中值滤波:相当于用中值代替
展示所有的处理效果图
res = np.hstack((blur,aussian,median))
cv2.imshow('median vs average', res)
cv2.waitKey(0)
cv2.destroyAllWindows()
4. 图像形态学操作
4.1 腐蚀操作
读入原始图片
img = cv2.imread('dige.png')
cv2.imshow('img', i
mg)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.erode()
, 参数分别是待处理的图片,卷积核,迭代次数
kernel = np.ones((3,3),np.uint8)
erosion = cv2.erode(img,kernel,iterations = 1)
cv2.imshow('erosion', erosion)
cv2.waitKey(0)
cv2.destroyAllWindows()
读入原始图片
pie = cv2.imread('pie.png')
cv2.imshow('pie', pie)
cv2.waitKey(0)
cv2.destroyAllWindows()
观察迭代次数对于腐蚀的影响
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)
res = np.hstack((erosion_1,erosion_2,erosion_3))
cv2.imshow('res', res)
cv2.waitKey(0)
cv2.destroyAllWindows()
4.2 膨胀操作
展示原始图片
img = cv2.imread('dige.png')
cv2.imshow('img', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
先进行腐蚀操作
kernel = np.ones((3,3),np.uint8)
dige_erosion = cv2.erode(img,kernel,iterations = 1)
cv2.imshow('erosion', erosion)
cv2.waitKey(0)
cv2.destroyAllWindows()
在进行膨胀操作
cv2.dilate()
膨胀操作, 参数是图片,卷积核,迭代次数
kernel = np.ones((3,3),np.uint8)
dige_dilate = cv2.dilate(dige_erosion,kernel,iterations = 1)
cv2.imshow('dilate', dige_dilate)
cv2.waitKey(0)
cv2.destroyAllWindows()
观察迭代次数对于膨胀操作的影响
pie = cv2.imread('pie.png')
kernel = np.ones((30,30),np.uint8)
dilate_1 = cv2.dilate(pie,kernel,iterations = 1)
dilate_2 = cv2.dilate(pie,kernel,iterations = 2)
dilate_3 = cv2.dilate(pie,kernel,iterations = 3)
res = np.hstack((dilate_1,dilate_2,dilate_3))
cv2.imshow('res', res)
cv2.waitKey(0)
cv2.destroyAllWindows()
4.3 开运算和闭运算
开:先腐蚀,再膨胀
img = cv2.imread('dige.png')
kernel = np.ones((5,5),np.uint8)
opening = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
cv2.imshow('opening', opening)
cv2.waitKey(0)
cv2.destroyAllWindows()
闭:先膨胀,再腐蚀
img = cv2.imread('dige.png')
kernel = np.ones((5,5),np.uint8)
closing = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
cv2.imshow('closing', closing)
cv2.waitKey(0)
cv2.destroyAllWindows()
4.4 梯度运算
梯度=膨胀-腐蚀
pie = cv2.imread('pie.png')
kernel = np.ones((7,7),np.uint8)
dilate = cv2.dilate(pie,kernel,iterations = 5)
erosion = cv2.erode(pie,kernel,iterations = 5)
res = np.hstack((dilate,erosion))
cv2.imshow('res', res)
cv2.waitKey(0)
cv2.destroyAllWindows()
gradient = cv2.morphologyEx(pie, cv2.MORPH_GRADIENT, kernel) #计算梯度
cv2.imshow('gradient', gradient)
cv2.waitKey(0)
cv2.destroyAllWindows()
4.5 礼帽与黑帽
- 礼帽 = 原始输入-开运算结果
- 黑帽 = 闭运算-原始输入
礼帽
img = cv2.imread('dige.png')
tophat = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel)
cv2.imshow('tophat', tophat)
cv2.waitKey(0)
cv2.destroyAllWindows()
黑帽
img = cv2.imread('dige.png')
blackhat = cv2.morphologyEx(img,cv2.MORPH_BLACKHAT, kernel)
cv2.imshow('blackhat ', blackhat )
cv2.waitKey(0)
cv2.destroyAllWindows()
5. 图像梯度计算
5.1 Sobel 算子
展示原始图片
img = cv2.imread('pie.png',cv2.IMREAD_GRAYSCALE)
cv2.imshow("img",img)
cv2.waitKey()
cv2.destroyAllWindows()
定义展示图片的函数
def cv_show(img,name):
cv2.imshow(name,img)
cv2.waitKey()
cv2.destroyAllWindows()
dst = cv2.Sobel(src, ddepth, dx, dy, ksize)
- ddepth:图像的深度
- dx和dy分别表示水平和竖直方向
- ksize是Sobel算子的大小
sobelx = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=3)
cv_show(sobelx,'sobelx')
白到黑是正数,黑到白就是负数了,所有的负数会被截断成0,所以要取绝对值
cv2.convertScaleAbs()
sobelx = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=3) # 1,0
sobelx = cv2.convertScaleAbs(sobelx)
cv_show(sobelx,'sobelx')
sobely = cv2.Sobel(img,cv2.CV_64F,0,1,ksize=3) # 0,1
sobely = cv2.convertScaleAbs(sobely)
cv_show(sobely,'sobely')
分别计算x和y,再求和
sobelxy = cv2.addWeighted(sobelx,0.5,sobely,0.5,0)
cv_show(sobelxy,'sobelxy')
分别计算x和y,再求和
sobelxy = cv2.addWeighted(sobelx,0.5,sobely,0.5,0)
cv_show(sobelxy,'sobelxy')
不建议直接计算
sobelxy=cv2.Sobel(img,cv2.CV_64F,1,1,ksize=3)
sobelxy = cv2.convertScaleAbs(sobelxy)
cv_show(sobelxy,'sobelxy')
img = cv2.imread('lena.jpg',cv2.IMREAD_GRAYSCALE)
cv_show(img,'img')
推荐方法
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)
cv_show(sobelxy,'sobelxy')
5.2 scharr 和lapkacian 算子
不同算子的差异
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))
cv_show(res,'res')
6. 边缘检测
- 使用高斯滤波器,以平滑图像,滤除噪声。
- 计算图像中每个像素点的梯度强度和方向。
- 应用非极大值(Non-Maximum Suppression)抑制,以消除边缘检测带来的杂散响应。
- 应用双阈值(Double-Threshold)检测来确定真实的和潜在的边缘。
- 通过抑制孤立的弱边缘最终完成边缘检测。
-
高斯滤波器
先进行归一化处理
H = [ 0.0924 0.1192 0.0924 0.1192 0.1538 0.1192 0.0924 0.1192 0.0924 ] H=\left[\begin{array}{lll} 0.0924 & 0.1192 & 0.0924 \\ 0.1192 & 0.1538 & 0.1192 \\ 0.0924 & 0.1192 & 0.0924 \end{array}\right] H=⎣⎡0.09240.11920.09240.11920.15380.11920.09240.11920.0924⎦⎤
e = H ∗ A = [ h 11 h 12 h 13 h 21 h 22 h 23 h 31 h 32 h 33 ] ∗ [ a b c d e f g h i ] = sum ( [ a × h 11 b × h 12 c × h 13 d × h 21 e × h 22 f × h 23 g × h 31 h × h 32 i × h 33 ] ) e=H * A=\left[\begin{array}{lll} \mathrm{h}_{11} & \mathrm{~h}_{12} & \mathrm{~h}_{13} \\ \mathrm{~h}_{21} & \mathrm{~h}_{22} & \mathrm{~h}_{23} \\ \mathrm{~h}_{31} & \mathrm{~h}_{32} & \mathrm{~h}_{33} \end{array}\right] *\left[\begin{array}{lll} a & b & c \\ d & e & f \\ g & h & i \end{array}\right]=\operatorname{sum}\left(\left[\begin{array}{ccc} \mathrm{a} \times \mathrm{h}_{11} & \mathrm{~b} \times \mathrm{h}_{12} & \mathrm{c} \times \mathrm{h}_{13} \\ \mathrm{~d} \times \mathrm{h}_{21} & \mathrm{e} \times \mathrm{h}_{22} & \mathrm{f} \times \mathrm{h}_{23} \\ \mathrm{~g} \times \mathrm{h}_{31} & \mathrm{~h} \times \mathrm{h}_{32} & \mathrm{i} \times \mathrm{h}_{33} \end{array}\right]\right) e=H∗A=⎣⎡h11 h21 h31 h12 h22 h32 h13 h23 h33⎦⎤∗⎣⎡adgbehcfi⎦⎤=sum⎝⎛⎣⎡a×h11 d×h21 g×h31 b×h12e×h22 h×h32c×h13f×h23i×h33⎦⎤⎠⎞
-
梯度和方向
G = G x 2 + G y 2 θ = arctan ( G y / G x ) S x = [ − 1 0 1 − 2 0 2 − 1 0 1 ] S y = [ 1 2 1 0 0 0 − 1 − 2 − 1 ] G x = S x ∗ A = [ − 1 0 1 − 2 0 2 − 1 0 1 ] ∗ [ a b c d e f g h i ] = sum ( [ − a 0 c − 2 d 0 2 f − g 0 i ] ) G y = S y ∗ A = [ 1 2 1 0 0 0 − 1 − 2 − 1 ] ∗ [ a b c d e f g h i ] = sum ( [ a 2 b c 0 0 0 − g − 2 h − i ] ) . \begin{aligned} &\begin{array}{l} G=\sqrt{G_{x}^{2}+G_{y}^{2}} \\ \theta=\arctan \left(G_{y} / G_{x}\right) \end{array} \quad S_{x}=\left[\begin{array}{ccc} -1 & 0 & 1 \\ -2 & 0 & 2 \\ -1 & 0 & 1 \end{array}\right] \quad S_{y}=\left[\begin{array}{ccc} 1 & 2 & 1 \\ 0 & 0 & 0 \\ -1 & -2 & -1 \end{array}\right] \\ &G_{x}=S_{x} * A=\left[\begin{array}{lll} -1 & 0 & 1 \\ -2 & 0 & 2 \\ -1 & 0 & 1 \end{array}\right] *\left[\begin{array}{ccc} a & b & c \\ d & e & f \\ g & h & i \end{array}\right]=\operatorname{sum}\left(\left[\begin{array}{ccc} -a & 0 & c \\ -2 d & 0 & 2 f \\ -g & 0 & i \end{array}\right]\right) \\ &G_{y}=S_{y} * A=\left[\begin{array}{ccc} 1 & 2 & 1 \\ 0 & 0 & 0 \\ -1 & -2 & -1 \end{array}\right] *\left[\begin{array}{ccc} a & b & c \\ d & e & f \\ g & h & i \end{array}\right]=\operatorname{sum}\left(\left[\begin{array}{ccc} a & 2 b & c \\ 0 & 0 & 0 \\ -g & -2 h & -i \end{array}\right]\right) . \end{aligned} G=Gx2+Gy2
θ=arctan(Gy/Gx)Sx=⎣⎡−1−2−1000121⎦⎤Sy=⎣⎡10−120−210−1⎦⎤Gx=Sx∗A=⎣⎡−1−2−1000121⎦⎤∗⎣⎡adgbehcfi⎦⎤=sum⎝⎛⎣⎡−a−2d−g000c2fi⎦⎤⎠⎞Gy=Sy∗A=⎣⎡10−120−210−1⎦⎤∗⎣⎡adgbehcfi⎦⎤=sum⎝⎛⎣⎡a0−g2b0−2hc0−i⎦⎤⎠⎞.
- 非极大值抑制
4. 双阈值检测
img=cv2.imread("lena.jpg",cv2.IMREAD_GRAYSCALE)
v1=cv2.Canny(img,80,150)
v2=cv2.Canny(img,50,100)
res = np.hstack((v1,v2))
cv_show(res,'res')
img=cv2.imread("car.png",cv2.IMREAD_GRAYSCALE)
v1=cv2.Canny(img,120,250)
v2=cv2.Canny(img,50,100)
res = np.hstack((v1,v2))
cv_show(res,'res')
7. 图像金字塔与轮廓检测
7.1 图像金字塔定义
- 高斯金字塔
- 拉普拉斯金字塔
7.2 图像金字塔制作
读取图片
img=cv2.imread("AM.png")
cv_show(img,'img')
print (img.shape) # (442, 340, 3)
向上采样法
cv2.pyrUp
up=cv2.pyrUp(img)
cv_show(up,'up')
print (up.shape) #(884, 680, 3)
向下采样法
cv2.pyrDown
down=cv2.pyrDown(img)
cv_show(down,'down')
print (down.shape) # (221, 170, 3)
up=cv2.pyrUp(img)
up_down=cv2.pyrDown(up)
cv_show(img-up_down,'img-up_down')
down=cv2.pyrDown(img)
down_up=cv2.pyrUp(down)
l_1=img-down_up
cv_show(l_1,'l_1')
7.3 图像轮廓
cv2.findContours(img,mode,method)
mode:轮廓检索模式
- RETR_EXTERNAL :只检索最外面的轮廓;
- RETR_LIST:检索所有的轮廓,并将其保存到一条链表当中;
- RETR_CCOMP:检索所有的轮廓,并将他们组织为两层:顶层是各部分的外部边界,第二层是空洞的边界;
- RETR_TREE:检索所有的轮廓,并重构嵌套轮廓的整个层次;
method:轮廓逼近方法
- CHAIN_APPROX_NONE:以Freeman链码的方式输出轮廓,所有其他方法输出多边形(顶点的序列)。
- CHAIN_APPROX_SIMPLE:压缩水平的、垂直的和斜的部分,也就是,函数只保留他们的终点部分。
为了更高的准确率,使用二值图像。
img = cv2.imread('contours.png')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
cv_show(thresh,'thresh')
绘制轮廓
drawContours()
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,绘制所有轮廓
cv_show(res,'res')
draw_img = img.copy()
res = cv2.drawContours(draw_img, contours, 0, (0, 0, 255), 2) # 参数为0,绘制第一个外轮廓
cv_show(res,'res')
7.4 轮廓特征与近似
轮廓特征
面积:
cv2.contourArea
;
周长:
cv2.arcLength()
cnt = contours[0]
cv2.contourArea(cnt) # 轮廓面积 8500.5
cv2.arcLength(cnt,True # 周长,True表示闭合的 437.9482651948929
轮廓近似
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)
cv_show(res,'res')
epsilon = 0.15*cv2.arcLength(cnt,True)
approx = cv2.approxPolyDP(cnt,epsilon,True)
draw_img = img.copy()
res = cv2.drawContours(draw_img, [approx], -1, (0, 0, 255), 2)
cv_show(res,'res')
边界矩形
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)
cv_show(img,'img')
area = cv2.contourArea(cnt)
x, y, w, h = cv2.boundingRect(cnt)
rect_area = w * h
extent = float(area) / rect_area
print ('轮廓面积与边界矩形比',extent) # 轮廓面积与边界矩形比 0.5154317244724715
外接圆
(x,y),radius = cv2.minEnclosingCircle(cnt)
center = (int(x),int(y))
radius = int(radius)
img = cv2.circle(img,center,radius,(0,255,0),2)
cv_show(img,'img')
7.5 模板匹配
模板匹配和卷积原理很像,模板在原图像上从原点开始滑动,计算模板与(图像被模板覆盖的地方)的差别程度,这个差别程度的计算方法在opencv里有6种,然后将每次计算的结果放入一个矩阵里,作为结果输出。假如原图形是AxB大小,而模板是axb大小,则输出结果的矩阵是(A-a+1)x(B-b+1)
读入待处理的图片和模板
img = cv2.imread('lena.jpg', 0)
template = cv2.imread('face.jpg', 0)
h, w = template.shape[:2]
img.shape #(263, 263)
template.shape # (110,85)
- TM_SQDIFF:计算平方不同,计算出来的值越小,越相关
- TM_CCORR:计算相关性,计算出来的值越大,越相关
- TM_CCOEFF:计算相关系数,计算出来的值越大,越相关
- TM_SQDIFF_NORMED:计算归一化平方不同,计算出来的值越接近0,越相关
- TM_CCORR_NORMED:计算归一化相关性,计算出来的值越接近1,越相关
- TM_CCOEFF_NORMED:计算归一化相关系数,计算出来的值越接近1,越相关
methods = ['cv2.TM_CCOEFF', 'cv2.TM_CCOEFF_NORMED', 'cv2.TM_CCORR',
'cv2.TM_CCORR_NORMED', 'cv2.TM_SQDIFF', 'cv2.TM_SQDIFF_NORMED']
res = cv2.matchTemplate(img, template, cv2.TM_SQDIFF)
res.shape # (154, 179)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
min_val # 39168.0
max_val # 74403584.0
min_loc # (107, 89)
max_loc # (159, 62)
人脸匹配
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)
8. 直方图和傅里叶变换
8.1 直方图定义
cv2.calcHist(images,channels,mask,histSize,ranges)
- images: 原图像图像格式为 uint8 或 float32。当传入函数时应 用中括号 [] 括来例如[img]
- channels: 同样用中括号括来它会告函数我们统幅图 像的直方图。如果入图像是灰度图它的值就是[0] 如果是彩色图像 的传入的参数可以是 [0],[1],[2] 它们分别对应着 BGR。
- mask: 掩模图像。统整幅图像的直方图就把它为 None。但是如 果你想统图像某一分的直方图的你就制作一个掩模图像并 使用它。
- histSize:BIN 的数目。也应用中括号括来
- ranges: 像素值范围常为 [0256]
img = cv2.imread('cat.jpg',0) #0表示灰度图
hist = cv2.calcHist([img],[0],None,[256],[0,256])
hist.shape #(256, 1)
plt.hist(img.ravel(),256);
plt.show()
img = cv2.imread('cat.jpg')
color = ('b','g','r')
for i,col in enumerate(color):
histr = cv2.calcHist([img],[i],None,[256],[0,256])
plt.plot(histr,color = col)
plt.xlim([0,256])
mask操作
mask = np.zeros(img.shape[:2], np.uint8)
print (mask.shape)
mask[100:300, 100:400] = 255
cv_show(mask,'mask') # (414, 500)
img = cv2.imread('cat.jpg', 0)
cv_show(img,'img')
masked_img = cv2.bitwise_and(img,mask=mask)
cv_show(masked_img,'masked_img')
hist_full = cv2.calcHist([img], [0], None, [256], [0, 256])
hist_mask = cv2.calcHist([img], [0], mask, [256], [0, 256])
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()
8.2 直方图均衡化
img = cv2.imread('clahe.jpg',0) #0表示灰度图 #clahe
plt.hist(img.ravel(),256);
plt.show()
equ = cv2.equalizeHist(img)
plt.hist(equ.ravel(),256)
plt.show()
res = np.hstack((img,equ))
cv_show(res,'res')
8.3 自适应直方图均衡化
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
res_clahe = clahe.apply(img)
res = np.hstack((img,equ,res_clahe))
cv_show(res,'res')
9. 图像特征
9.1 图像特征harris
cv2.cornerHarris()
img: 数据类型为 float32 的入图像
blockSize: 角点检测中指定区域的大小
ksize: Sobel求导中使用的窗口大小
k: 取值参数为 [0,04,0.06]
import cv2
import numpy as np
img = cv2.imread('test_1.jpg')
print ('img.shape:',img.shape)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# gray = np.float32(gray)
dst = cv2.cornerHarris(gray, 2, 3, 0.04)
print ('dst.shape:',dst.shape)
'''
img.shape: (800, 1200, 3)
dst.shape: (800, 1200)
'''
img[dst>0.01*dst.max()]=[0,0,255]
cv2.imshow('dst',img)
cv2.waitKey(0)
cv2.destroyAllWindows()
图像特征SIFT
在一定的范围内,无论物体是大还是小,人眼都可以分辨出来,然而计算机要有相同的能力却很难,所以要让机器能够对物体在不同尺度下有一个统一的认知,就需要考虑图像在不同的尺度下都存在的特点。
尺度空间的获取通常使用高斯模糊来实现
不同σ的高斯函数决定了对图像的平滑程度,越大的σ值对应的图像越模糊
每个特征点可以得到三个信息(x,y,σ,θ),即位置、尺度和方向。具有多个方向的关键点可以被复制成多份,然后将方向值分别赋给复制后的特征点,一个特征点就产生了多个坐标、尺度相等,但是方向不同的特征点。
import cv2
import numpy as np
img = cv2.imread('test_1.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 得到特征点
sift = cv2.xfeatures2d.SIFT_create()
kp = sift.detect(gray, None)
# 画特征点
img = cv2.drawKeypoints(gray, kp, img)
cv2.imshow('drawKeypoints', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
# 计算特征
kp, des = sift.compute(gray, kp)
print (np.array(kp).shape) # (6809,)
des.shape #(6809, 128)
10. 特征匹配
10.1 Brute-Force匹配
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)
def cv_show(name,img):
cv2.imshow(name, img)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv_show('img1',img1)
cv_show('img2',img2)
sift = cv2.xfeatures2d.SIFT_create()
kp1, des1 = sift.detectAndCompute(img1, None)
kp2, des2 = sift.detectAndCompute(img2, None)
# crossCheck表示两个特征点要互相匹,例如A中的第i个特征点与B中的第j个特征点最近的,并且B中的第j个特征点到A中的第i个特征点也是
#NORM_L2: 归一化数组的(欧几里德距离),如果其他特征计算方法需要考虑不同的匹配计算方式
bf = cv2.BFMatcher(crossCheck=True)
一对一的匹配
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)
cv_show('img3',img3)
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])
img3 = cv2.drawMatchesKnn(img1,kp1,img2,kp2,good,None,flags=2)
cv_show('img3',img3)
如果需要更快速完成操作,可以尝试使用cv2.FlannBasedMatcher
10.2 随机抽样一致算法(RANAC)
11. 光流估计
11.1 原理介绍
光流是空间运动物体在观测成像平面上的像素运动的“瞬时速度”,根据各个像素点的速度矢量特征,可以对图像进行动态分析,例如目标跟踪。
亮度恒定:同一点随着时间的变化,其亮度不会发生改变。
小运动:随着时间的变化不会引起位置的剧烈变化,只有小运动情况下才能用前后帧之间单位位置变化引起的灰度变化去近似灰度对位置的偏导数。
空间一致:一个场景上邻近的点投影到图像上也是邻近点,且邻近点速度一致。因为光流法基本方程约束只有一个,而要求x,y方向的速度,有两个未知变量。所以需要连立n多个方程求解。
11.2 实战演练
cv2.calcOpticalFlowPyrLK()
参数:
prevImage 前一帧图像
nextImage 当前帧图像
prevPts 待跟踪的特征点向量
winSize 搜索窗口的大小
maxLevel 最大的金字塔层数
返回:
nextPts 输出跟踪特征点向量
status 特征点是否找到,找到的状态为1,未找到的状态为0
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)
# 创建一个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, (int(a),int(b)),(int(c),int(d)), color[i].tolist(), 2)
frame = cv2.circle(frame,(int(a),int(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()
12. 背景建模
12.1 原理介绍
帧差法
由于场景中的目标在运动,目标的影像在不同图像帧中的位置不同。该类算法对时间上连续的两帧图像进行差分运算,不同帧对应的像素点相减,判断灰度差的绝对值,当绝对值超过一定阈值时,即可判断为运动目标,从而实现目标的检测功能。
帧差法非常简单,但是会引入噪音和空洞问题
混合高斯模型
在进行前景检测前,先对背景进行训练,对图像中每个背景采用一个混合高斯模型进行模拟,每个背景的混合高斯的个数可以自适应。然后在测试阶段,对新来的像素进行GMM匹配,如果该像素值能够匹配其中一个高斯,则认为是背景,否则认为是前景。由于整个过程GMM模型在不断更新学习中,所以对动态背景有一定的鲁棒性。最后通过对一个有树枝摇摆的动态背景进行前景检测,取得了较好的效果。
在视频中对于像素点的变化情况应当是符合高斯分布
混合高斯模型学习方法
1.首先初始化每个高斯模型矩阵参数。
2.取视频中T帧数据图像用来训练高斯混合模型。来了第一个像素之后用它来当做第一个高斯分布。
3.当后面来的像素值时,与前面已有的高斯的均值比较,如果该像素点的值与其模型均值差在3倍的方差内,则属于该分布,并对其进行参数更新。
4.如果下一次来的像素不满足当前高斯分布,用它来创建一个新的高斯分布。
混合高斯模型测试方法
在测试阶段,对新来像素点的值与混合高斯模型中的每一个均值进行比较,如果其差值在2倍的方差之间的话,则认为是背景,否则认为是前景。将前景赋值为255,背景赋值为0。这样就形成了一副前景二值图。
12.2 实战演练
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)
#形态学开运算去噪点
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()
运行结果
13. 项目实战
opencv项目实战(1)——信用卡数字识别
opencv项目实战(2)——文档扫描OCR识别
opencv项目实战(3)——全景图像拼接
opencv项目实战(4)——答题卡识别判卷
opencv项目实战(5)——DNN模块
opencv项目实战(6)—— 多目标追踪
opencv项目实战 (7) —— 基于dlib和ssd的追踪
opencv项目实战(8)—— 多进程目标追踪
opencv项目实战(9)——人脸五官检测
opencv项目实战(10)—— 疲劳检测