创建显示窗口
namedWindow('name',cv2.window_nomal) 创建窗口
imshow('name',img) 显示窗口
destoryAllWindows() 释放窗口
resizeWindow('name', 800, 600) 设置大小
waitKey(0) 显示时间ms
# 02.win_api.py
import cv2
cv2.namedWindow('new', cv2.WINDOW_AUTOSIZE)
cv2.resizeWindow('new', 640, 480)
cv2.imshow('new', 0)
key = cv2.waitKey(0)
if(key == 'q'):
exit()
cv2.destroyAllWindows()
#03.reading_writing.py
import cv2
from sqlalchemy import true
cv2.namedWindow('img', cv2.WINDOW_NORMAL)
img = cv2.imread(
'G:\\BaiduNetdiskWorkspace\\Code From VSCode\OpenCV\\timg_.jpg')
while true:
cv2.imshow('img', img)
key = cv2.waitKey(0)
# if key == 'q':
if key & 0xff == ord('q'): # 退出
break
elif key & 0xff == ord('s'):
cv2.imwrite(
'G:\\BaiduNetdiskWorkspace\\Code From VSCode\OpenCV\\save.png', img)
else:
print(key, 'otherkeys')
cv2.destroyAllWindows()
#04.VideoCap.py
import cv2
from sqlalchemy import true
# 创建窗口
cv2.namedWindow('video', cv2.WINDOW_GUI_NORMAL)
# 设置大小
cv2.resizeWindow('video', 800, 600)
# 获取摄像头设备
cap = cv2.VideoCapture(0)
# 读取视频
# cap = cv2.VideoCapture(
# 'G:\\BaiduNetdiskWorkspace\\Code From VSCode\\OpenCV\\VLAN划分.mp4')
# 判断摄像头是否为打开状态
while cap.isOpened():
# 从摄像头读取视频帧
ret, frame = cap.read()
if ret == True:
# 将视频帧在窗口显示
cv2.imshow('video', frame)
# 重新设置窗口
cv2.resizeWindow('video', 640, 360)
# 等待键盘事件
key = cv2.waitKey(30)
if key & 0xff == ord('q'):
break
else:
break
# 释放VideoCapture
cap.release()
cv2.destroyAllWindows()
#05.VideoWrite.py
import cv2
# 创建VideoWriter为多媒体文件
fourcc = cv2.VideoWriter_fourcc(*'MJPG')
vw = cv2.VideoWriter('./out.mp4v', fourcc, 25, (1280, 720))
# 创建窗口
cv2.namedWindow('video', cv2.WINDOW_AUTOSIZE)
# 设置大小
# cv2.resizeWindow('video', 800, 600)
# 获取摄像头设备
cap = cv2.VideoCapture(0)
while True:
# 从摄像头读取视频帧
ret, frame = cap.read()
# 将视频帧在窗口显示
cv2.imshow('video', frame)
# 写数据到多媒体文件
vw.write(frame)
# 等待键盘事件
key = cv2.waitKey(30)
if key & 0xff == ord('q'):
break
# 释放VideoCapture
cap.release()
# 释放VideoWriter
vw.release()
cv2.destroyAllWindows()
创建trackbar creatTrackbar(tackbarname,winname,value,count)
获取trackbar值 getTrackbarPos(trackbarname,winname)
#06.trackbar.py
import cv2
import numpy as np
def callback():
pass
# 创建窗口
cv2.namedWindow('trackbar')
# 创建trackbar
cv2.createTrackbar('R', 'trackbar', 0, 255, callback)
cv2.createTrackbar('G', 'trackbar', 0, 255, callback)
cv2.createTrackbar('B', 'trackbar', 0, 255, callback)
# 创建一个背景图片
img = np.zeros((480, 640, 3), np.uint8)
while True:
# 获取当前trackbar的值
r = cv2.getTrackbarPos('R', 'trackbar')
g = cv2.getTrackbarPos('G', 'trackbar')
b = cv2.getTrackbarPos('B', 'trackbar')
# 改变背景图片颜色
img[:] = [b, g, r]
cv2.imshow('trackbar', img)
key = cv2.waitKey(10)
if key & 0xff == ord('q'):
break
cv2.destroyAllWindows()
#09.mouse.py
import cv2
import numpy as np
# 鼠标回调函数
def mouse_callback(event, x, y, flags, userdata):
#获取鼠标点击事件及位置坐标
print(event, x, y, flags, userdata)
# mouse_callback(1, 100, 100, 16, "6666")
# 创建窗口
cv2.namedWindow('mouse', cv2.WINDOW_NORMAL)
cv2.resizeWindow('mouse', 640, 360)
# 设置鼠标回调
cv2.setMouseCallback('mouse', mouse_callback, '123')
# 显示窗口和背景
img = np.zeros((360, 640, 3), np.uint8)
while True:
cv2.imshow('mouse', img)
key = cv2.waitKey(1)
if key & 0xFF == ord('q'):
break
cv2.destroyAllWindows()
Hue:色相,即色彩,如红色,蓝色
Saturation:饱和度,颜色的纯度
Value:名度
Hue:色相,即色彩,如红色,蓝色
Saturation:饱和度,颜色的纯度
Ligthness:亮度
#07.sp_merge.py
import cv2
import numpy as np
img = np.zeros((480, 640, 3), np.uint8)
#通道分离
b, g, r = cv2.split(img)
b[10:100, 10:100] = 255
g[10:100, 10:100] = 255
#通道合并
img2 = cv2.merge((b, g, r))
cv2.imshow('img', img)
cv2.imshow('b', b)
cv2.imshow('g', g)
cv2.imshow('img2',img2)
cv2.waitKey(0)
#08.draw.py
import cv2
from cv2 import FONT_HERSHEY_COMPLEX
import numpy as np
# 创建背景图片
img = np.zeros((480, 640, 3), np.uint8)
# 画线,坐标为(x,y))
cv2.line(img, (10, 20), (300, 400), (0, 0, 255), 1)
cv2.line(img, (10, 100), (300, 100), (0, 230, 25), 5)
# 画矩形
cv2.rectangle(img, (320, 200), (500, 100), (0, 0, 255), 5)
# 画圆
cv2.circle(img, (320, 240), 100, (0, 0, 255))
cv2.circle(img, (320, 240), 27, (255, 0, 0), 1)
# 画椭圆
# 度是按顺时针计算的
# 0度是从左侧开始的
cv2.ellipse(img, (320, 240), (100, 50), 0, 0, 360, (0, 124, 123), 3)
cv2.ellipse(img, (320, 240), (200, 100), 90, 0, 360, (0, 124, 123), 3)
# 画多边形
# 三个点
pts = np.array([(300, 10), (150, 100), (450, 100)], np.int32)
cv2.polylines(img, [pts], True, (112, 150, 123))
# 填充多边形
cv2.fillPoly(img, [pts], (255, 255, 0))
#绘制文本
cv2.putText(img, 'hello,world',(40,200),cv2.FONT_HERSHEY_COMPLEX,3,(255,255,0))
cv2.imshow('img', img)
cv2.waitKey(0)
# 基本功能
# 可以通过鼠标进行基本图形的绘制
# 1. 可以画线,当用户按下l键,即选择了画线。此时,滑动鼠标即可画线
# 2. 可以画矩形,当用户按下r键,即选择了画矩形。此时,滑动鼠标即可画矩形
# 3. 可以画线,当用户按下c键,即选择了画圆。此时,滑动鼠标即可画圆
# .....
# curshape: 0-drawline, 1-drawrectangle, 2-drawcircle
import cv2
import numpy as np
curshape = 0
startpos = (0, 0)
# 显示窗口和背景
img = np.zeros((480, 640, 3), np.uint8)
# 鼠标回调函数
def mouse_callback(event, x, y, flags, userdata):
global curshape, startpos
print(event, x, y, flags, userdata)
if event & cv2.EVENT_LBUTTONDOWN == cv2.EVENT_LBUTTONDOWN:
startpos = (x, y)
elif event & cv2.EVENT_LBUTTONUP == cv2.EVENT_LBUTTONUP:
if curshape == 0: # line
cv2.line(img, startpos, (x, y), (0, 0, 255), 3)
elif curshape == 1: # rectangle
cv2.rectangle(img, startpos, (x, y), (255, 0, 0), 3)
elif curshape == 2: # circle
a = (x-startpos[0])
b = (y-startpos[1])
r = int((a**2+b**2)**0.5)
cv2.circle(img, startpos, r, (0, 255, 0))
else:
print('error:no shape')
# 创建窗口
cv2.namedWindow('drawshape', cv2.WINDOW_NORMAL)
# 设置鼠标回调
cv2.setMouseCallback('drawshape', mouse_callback)
while True:
cv2.imshow('drawshape', img)
key = cv2.waitKey(1) & 0xFF
if key == ord('q'):
break
elif key == ord('l'): # line
curshape = 0
elif key == ord('r'): # rectangle
curshape = 1
elif key == ord('c'): # circle
curshape = 2
cv2.destroyAllWindows()
add(a,b)
#11.img_add.py
import cv2
import numpy as np
# 加载图像
tx = cv2.imread('.//src//tx1.jpg')
# 图的加法运算就是矩阵的加法运算
# 两张图尺寸必须相等
# 问题 图片路径不能包含中文??
# 见00.Chinese_Path.py
print(tx.shape)
#生成一张背景图
img = np.ones((640, 640, 3), np.uint8) * 50
#加法运算
result = cv2.add(tx, img)
cv2.imshow('car', tx)
cv2.imshow('img', img)
cv2.imshow('result', result)
cv2.waitKey(0)
subtract(a,b) a减b
#12.img_sub.py
#下图一 在加法的基础上减去背景图,还原
orig_1 = cv2.subtract(tx, img)
#下图二 在原图上减去背景图,更暗
orig_1 = cv2.subtract(tx, img)
cv2.imshow('orig_1', orig_1)
multiply(A,B) 乘法 升的更快
divide(A,B) **除法 ** 降的更快
图像溶合
addWeighted(A,alpha,B,bate,gamma)
alpha 和 beta是权重
gamma 静态权重
# 图像溶合
# addWeighted(A,alpha,B,bate,gamma)
# alpha 和 beta是权重
# gamma 静态权重
# 13.img_fus.py
import cv2
import numpy as np
smile = cv2.imread('.//src//tx2.jpg')
boy = cv2.imread('.//src//tx1.jpg')
# 只有两张图的属性是一样的才可以进行溶合
print(smile.shape)
print(boy.shape)
result = cv2.addWeighted(smile,0.7,boy,0.3,0)
cv2.imshow('add',result)
cv2.waitKey(0)
# 14.img_bitwise.py
import cv2
from cv2 import bitwise_and
import numpy as np
# 创建一张图片
img1 = np.zeros((400, 400), np.uint8)
img2 = np.zeros((400, 400), np.uint8)
img1[50:250, 50:250] = 255
img2[200:350, 200:350] = 255
# new_img = cv2.bitwise_not(img1) #非运算
# new_img = cv2.bitwise_and(img1,img2) #与运算
# new_img = cv2.bitwise_or(img1, img2) # 或运算
new_img = cv2.bitwise_xor(img1, img2) # 异或运算
cv2.imshow('img1', img1)
cv2.imshow('img2', img2)
cv2.imshow('result', new_img)
cv2.waitKey(0)
cv2.bitwise_not(img1)
new_img = cv2.bitwise_and(img1,img2)
cv2.bitwise_or(img1, img2)
cv2.bitwise_xor(img1, img2)
引入一张图片
要有一个logo,可自己创建
计算图片在什么地方,在添加的地方变成黑色
利用add,将logo与图处叠加到一起
# 1. 引入一张图片,
# 2. 要有一个logo,可自己创建
# 3. 计算图片在什么地方,在添加的地方变成黑色
# 4. 利用add,将logo与图处叠加到一起
import cv2
import numpy as np
# 导入图片
tx = cv2.imread('.//src//tx1.jpg')
print(tx.shape)
# 创建logo
logo = np.zeros((200, 200, 3), np.uint8)
mask = np.zeros((200, 200), np.uint8)
# 绘制logo
logo[20:120, 20:120] = [0, 0, 255]
logo[80:180, 80:180] = [0, 255, 0]
mask[20:120, 20:120] = 255
mask[80:180, 80:180] = 255
# 对mask按位求反
m = cv2.bitwise_not(mask)
# 选择添加logo的位置
roi = tx[0:200, 0:200]
# 与m进行与操作 roi是三通道的
tmp = cv2.bitwise_and(roi, roi, mask=m)
# 添加水印 logo
dst = cv2.add(tmp, logo)
tx[0:200, 0:200] = dst
cv2.imshow('logo', logo)
cv2.imshow('mask', mask)
cv2.imshow('m', m)
cv2.imshow('dst', dst)
cv2.imshow('tmp', tmp)
cv2.imshow('tx', tx)
cv2.waitKey(0)
resize(src, dst, dsize,fx,fy,interpolation)
#16.img_scale.py
import cv2
from cv2 import INTER_NEAREST
import numpy as np
tx = cv2.imread('.//src//tx1.jpg')
print(tx.shape)
#图像缩放
new = cv2.resize(tx, (800, 800), interpolation=cv2.INTER_AREA)
# new = cv2.resize(tx,None,fx=0.3,fy=0.3)
cv2.imshow('tx', tx)
cv2.imshow('new', new)
cv2.waitKey(0)
flip(img,flipCode)
# 17.img_flip.py
import cv2
import numpy as np
tx = cv2.imread('.//src//tx1.jpg')
print(tx.shape)
# 图像翻转
# new = cv2.flip(tx, 0) # 上下
# new = cv2.flip(tx,1) #左右
new = cv2.flip(tx, -1) # 上下 左右
cv2.imshow('tx', tx)
cv2.imshow('new', new)
cv2.waitKey(0)
rotate(img, reteateCode)
#18.img_rotate.py
import cv2
import numpy as np
tx = cv2.imread('.//src//tx1.jpg')
print(tx.shape)
# 图像旋转
# new = cv2.rotate(tx, cv2.ROTATE_180) # 180度
# new = cv2.rotate(tx, cv2.ROTATE_90_CLOCKWISE) # 90度
new = cv2.rotate(tx, cv2.ROTATE_90_COUNTERCLOCKWISE) # 270度
cv2.imshow('tx', tx)
cv2.imshow('new', new)
cv2.waitKey(0)
仿射变换是图像旋转、缩放、平移的总称。
矩阵中的每个像素由 (x,y)组成,因此其变换矩阵式2x2的矩阵,平移向量为2x1的向量,所在平移矩阵为2x3矩阵。
# 19.img_affine.py
import cv2
import numpy as np
tx = cv2.imread('.//src//tx1.jpg')
h, w, ch = tx.shape
# 生成变换矩阵
M = np.float32([[1, 0, 100], [0, 1, 0]])
## [1,0,平移量]
## [1,0][0,1] 2x2矩阵
new = cv2.warpAffine(tx, M, (w, h))
cv2.imshow('tx', tx)
cv2.imshow('new', new)
cv2.waitKey(0)
# 在 19.img_affine.py 的基础上将M改为如下
# 对图像进行中心旋转、缩放
# 旋转角度为逆时针
# 中心点是(x,y)
M = cv2.getRotationMatrix2D((w/2, h/2), 45, 0.3)
要达到如图所示的变换,可用如下api
# 在 19.img_affine.py 的基础上将M改为如下
#src变换前三个点
#dst变换对应的三个点
src= np.float32([[400,300],[500,300],[400,600]])
dst = np.float32([[200,400],[300,500],[100,610]])
M = cv2.getAffineTransform(src, dst)
将一种坐标系变成另外一种角度,达到我们想要的结果。通过透视变换,可以进行图片矫正。
warpPerspective(img,M,dsize,…) 参数说明同仿射变换 ★关键在于 M
getPersectiveTransform(src,dst) 需要四个点(图形的四个角) 获取变换矩阵
# 20.img_presective.py
import cv2
import numpy as np
img = cv2.imread('.//src//page.png')
print(img.shape)
# 设置对应点
src = np.float32([[20, 195], [380, 190], [0, 715], [450, 690]])
dst = np.float32([[0, 0], [475, 0], [0, 595], [475, 595]])
# 获取变换矩阵
M = cv2.getPerspectiveTransform(src, dst)
# 进行透视变换
new = cv2.warpPerspective(img, M, (475, 595))
cv2.imshow('img', img)
cv2.imshow('new', new)
cv2.waitKey(0)
滤波的作用:一幅图像通过滤波器得到另一幅图像,其中滤波器又称为卷积核,滤波的过程成为卷积。
卷积核一般为奇数,如3x3, 5x5, 7x7等。一方面是增加padding的原因;另一方面是保证锚点在中间,防止位置发生偏移。
在在深度学习中,卷积核越大,看到的(感受野)越多,提取的特征越好,同时计算量也就越大。
锚点即卷积核的中心
计算公式: N = (W - F + 2P ) / S + 1
卷积cv2.filter2D(src, ddepth, kernel, anchor, delta, borderType)*
# 21.img_filter.py
import cv2
import numpy as np
img = cv2.imread('.//src//ZH-CN7993615424_UHD_4k.jpg')
# 生成一个5*5的矩阵 值为 1/25
kernal = np.ones((5, 5), np.float32) / 25
dst = cv2.filter2D(img, -1, kernal)
cv2.imshow('dst', dst)
cv2.imshow('img', img)
cv2.waitKey(0)
主要作用:去噪;
与均值滤波类似,主要区别为方盒滤波有参数
参数a的作用
当normalize == true 时,方盒滤波=均值滤波
方盒滤波 cv2.boxFilter(src, ddepth, ksize, anchor, normalize, borderType)
ksize:卷积核大小
anchor:锚点 默认(-1,-1) 根据核的尺寸寻找锚点
normalize:
borderType:边界
均值滤波 cv2.blur(src, ksize, anchor, borderType)
# 21.img_blur.py
import cv2
import numpy as np
img = cv2.imread('.//src//ZH-CN7993615424_UHD_4k.jpg')
#均值滤波
dst = cv2.blur(img, (5,5))
cv2.imshow('dst', dst)
cv2.imshow('img', img)
cv2.waitKey(0)
中心点值可能不是最大,但占比权重最大;周围点值可能很大,但占比权重小。如下图所示。
优点:高斯滤波主要解决高斯噪点。
API GanssianBlur(img, kernal, sigmaX, sigmaY, …)
sigma越大,图像越模糊,即平滑程度越大。
当没有sigma时,则以卷积核大小为基准。
# 23.img_GanssBlur.py
import cv2
import numpy as np
img = cv2.imread('.//src//Ganss.png')
# 高斯滤波
dst = cv2.GaussianBlur(img, (9,9), sigmaX=1)
cv2.imshow('dst', dst)
cv2.imshow('img', img)
cv2.waitKey(0)
假设有一个数组[1556789],取其中的中间值作为卷积后的结果值。
优点:对胡椒噪音效果明显
API medianBlur(img, ksize)
# 24.img_medianBlur.py
import cv2
import numpy as np
img = cv2.imread('.//src//Pepper_noise.png')
# 中值滤波
dst = cv2.medianBlur(img, 9)
cv2.imshow('dst', dst)
cv2.imshow('img', img)
cv2.waitKey(0)
优点:可以保留边缘,同时可以对边缘内的区域进行平滑处理。
主要作用是进行美颜
API bilaterFilter(img, d, sigmaColor, sigmaSpace,…)
d:核的大小
sigmaColor:控制图像灰度变化权重。设置的较大的时候,灰度可以变化的范围也就是越大,那么灰度差异越大也能得到比较高的权重值,细小的边缘因此会被影响掉,不能有很好的保边效果。
sigmaSpace:控制空间距离的权重变化。当该值较大时,其有效的空间范围就越大,当较小的时候,有效的空间范围就越小,当窗口一定的情况下,sigmaS越大,那么其边缘点也能得到较大的权重,去噪效果明显。
图像中灰度变化在一定的范围内,两者的灰度差异越大的话,得到的权重应该越小,但是要保证灰度级差异大,也能得到一个有效的权重,就是说,灰度级的差异要落在有效的概率范围内,可以选择3sigma或者2sigma有效范围。因此,sigmaS的设置要根据设定的滤波窗口的大小设置。若Half_size为4,可以选用2sigma原则,设置为2
# 25.img_bilateralfilter.py
import cv2
import numpy as np
img = cv2.imread('.//src//lena.png')
# 中值滤波
dst = cv2.bilateralFilter(img, 7, 20,50)
cv2.imshow('dst', dst)
cv2.imshow('img', img)
cv2.waitKey(0)
作用:检测边缘
常见的高通滤波:
API
Sobel(src, ddepth, dx, dy, ksize = 3, scale = 1(缩放), delta = 0, borderType = BORDER_DEFAUTLT)
# 26.img_Sobel.py
import cv2
import numpy as np
img = cv2.imread('.//src//chess.png')
# Sobel滤波
# 索贝尔算子y方向边缘
d1 = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize = 5)
# 索贝尔算子x方向边缘
d2 = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize = 5)
dst = d1 + d2
#dst = cv2.add(d1, d2)
#cv2.imshow('d1',d1)
#cv2.imshow('d2',d2)
cv2.imshow('dst', dst)
cv2.imshow('img', img)
cv2.waitKey(0)
API Scharr(src, ddepth, dx, dy, scale = 1, delta = 0, borderType = BORDER_DEFAUTLT)
# 在26.img_Sobel.py中修改
dst = cv2.Scharr(img, cv2.CV_64F, 1, 0)
API Laplacian(img, ddepth, ksize=1, scale=1, borderType = BORDER_DEFAULT)
# 在26.img_Sobel.py中修改
dst = cv2.Laplacian(img, cv2.CV_64F, ksize = 5)
如果超过maxVal一定是超过边缘,如果低于maxVal一定不是边缘。
如果在maxVal与minVal之间,看所确定的值是否是连续的。如A是边缘,C在maxVal与minVal之间且与A是连续的(在同一条线上);而B就不被认为是边缘。
API Canny(img, minVal, maxVal,…)
# 27.img_Canny.py
import cv2
import numpy as np
img = cv2.imread('.//src//lena.png')
# Canny边缘检测
dst = cv2.Canny(img, 100, 200) # 图一
# dst = cv2.Canny(img, 50, 150) # 图二
cv2.imshow('dst', dst)
cv2.imshow('img', img)
cv2.waitKey(0)
Kirsch算子由以下8个卷积核组成,图像与每一个卷积核进行卷积,然后取绝对值作为对应方向上的边缘强度的量化。对8个卷积结果取绝对值,然后取最大值作为最后输出的边缘强度。
与Kirsch算子类似,Robinson算子也是由8个卷积核组成,其检测过程和Kirsch是一样的。
Kirsch算子使用了8个方向上的卷积核,所以其检测的边缘比标准的Prewitt算子和Sobel算子检测到的边缘会显得更加丰富。
处理的基本方法:
腐蚀与膨胀
开运算:先做腐蚀再做膨胀
闭运算:先做膨胀再做腐蚀
顶帽
黑帽
将图像的每个像素变成两种值,如0,255
API threshod(img, thresh, maxVal, type)
两个返回值:ret:True或False,代表有没有读到图片;dst: 目标图像;
img:图像,最好是灰度图
thresh:阈值
maxVal:超过阈值,替换成maxVal
type:
# 28.img_binary.py
import cv2
img = cv2.imread('.//src//tx1.jpg')
# 转换为灰度图像
tmp = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 二值化
# 超过阈值使用maxVal代替 如下图一
ret, dst = cv2.threshold(tmp, 180, 255, cv2.THRESH_BINARY)
# 低于阈值使用maxVal代替 如下图二
ret, dst = cv2.threshold(tmp, 180, 255, cv2.THRESH_BINARY_INV)
# 低于阈值保留,高于的使用阈值替代
ret, dst = cv2.threshold(tmp, 180, 255, cv2.THRESH_TRUNC)
# 高于阈值保留,高于的使用0替代
ret, dst = cv2.threshold(tmp, 180, 255, cv2.THRESH_TOZERO)
# 低于阈值保留,高于的使用0替代
ret, dst = cv2.threshold(tmp, 180, 255, cv2.THRESH_TOZERO_INV)
print(dst.shape)
cv2.imshow('img', img)
cv2.imshow('tmp', tmp)
cv2.imshow('dst', dst)
cv2.waitKey(0)
二值化缺点:由于光照不均匀以及阴影的存在,只有一个阈值会使得在阴影处的白色被二值化成黑色/白色。如下图所示。
对于这种图像,则适用于自适应阈值。
API adaptiveThreshold(img, maxVal, adaptiveMethod, type, blockSize, C)
# 29.img_adaptive.py
import cv2
img = cv2.imread('.//src//math.png')
# 转换为灰度图像
tmp = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 二值化
# 超过阈值使用maxVal代替
ret1, dst1 = cv2.threshold(tmp, 180, 255, cv2.THRESH_BINARY)
# 自适应阈值
dst2 = cv2.adaptiveThreshold(
tmp, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 0)
cv2.imshow('img', img)
cv2.imshow('tmp', tmp)
cv2.imshow('dst1', dst1)
cv2.imshow('dst2', dst2)
cv2.waitKey(0)
结果如下:图一为原始图像;图二为对应灰度图像;图三为二值化图像;图四为自适应阈值后的图像。
通过腐蚀,可以使图像体积小很多。
当卷积核全部位于白色区域时,卷积核中心才是白色,否则为黑色。
一般情况下,腐蚀的卷积核为全 1。
API erode(img, kernel, iterations = 1)
# 30.img_erode.py
import cv2
import numpy as np
img = cv2.imread('.//src//j.png')
# 创建一个3x3的卷积核
kernel = np.ones((3, 3), np.uint8)
# 腐蚀
dst = cv2.erode(img, kernel, iterations=5)
cv2.imshow('img', img)
cv2.imshow('dst', dst)
cv2.waitKey(0)
卷积核的类型
API:获取卷积核 getStructuringElement(type, size)
size: (3, 3) (5, 5) ……
type:
# 31.img_getkernel.py
import cv2
import numpy as np
img = cv2.imread('.//src//j.png')
# 获取一个卷积核
kernel1 = cv2.getStructuringElement(cv2.MORPH_RECT, (7, 7)) # 矩形
kernel2 = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (7, 7)) # 圆形
kernel3 = cv2.getStructuringElement(cv2.MORPH_CROSS, (7, 7)) # 交叉(十字架)
print(kernel1, kernel2, kernel3)
# 腐蚀
dst1 = cv2.erode(img, kernel1, iterations=3)
dst2 = cv2.erode(img, kernel2, iterations=3)
dst3 = cv2.erode(img, kernel3, iterations=3)
# cv2.imshow('img', img)
cv2.imshow('dst1', dst1)
cv2.imshow('dst2', dst2)
cv2.imshow('dst3', dst3)
cv2.waitKey(0)
即腐蚀的逆运算。
只要卷积核中心不为 0,其周边全部变为非 0 。
膨胀的快慢与卷积核大小有关。
API dilate(img, kernel, iterations = 1)
# 32.img_dilate.py
import cv2
import numpy as np
img = cv2.imread('.//src//j.png')
# 创建一个卷积核
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5)) # 矩形
# 腐蚀
dst1 = cv2.erode(img, kernel, iterations=1)
# 膨胀
dst2 = cv2.dilate(img, kernel, iterations=1)
cv2.imshow('img', img)
cv2.imshow('dst1', dst1)
cv2.imshow('dst2', dst2)
cv2.waitKey(0)
思考:
开运算 = 腐蚀 + 膨胀
作用:消除字体外部噪点。
实现一:先腐蚀,再膨胀
# 修改32.img_dilate.py
import cv2
img = cv2.imread('.//src//dotJ.png')
# 创建一个卷积核
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (7, 7)) # 矩形
# 腐蚀
dst1 = cv2.erode(img, kernel, iterations=2)
# 膨胀
dst2 = cv2.dilate(dst1, kernel, iterations=2)
cv2.imshow('img', img)
cv2.imshow('dst1', dst1)
cv2.imshow('dst2', dst2)
cv2.waitKey(0)
实现二:调用API
API morphologyEx(img, MORPH_OPEN), kernel)
# 33.img_open.py
import cv2
img = cv2.imread('.//src//dotJ.png')
# 创建一个卷积核
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 9)) # 矩形
# 开运算
dst = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
cv2.imshow('img', img)
cv2.imshow('dst1', dst)
cv2.waitKey(0)
闭运算 = 膨胀 + 腐蚀
作用: 消除图像内部噪点。
实现:调用API
API morphologyEx(img, MORPH_CLOSE), kernel)
# 34.img_close.py
import cv2
img = cv2.imread('.//src//dotinJ.png')
# 创建一个卷积核
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 9)) # 矩形
# 闭运算
dst = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
cv2.imshow('img', img)
cv2.imshow('dst1', dst)
cv2.waitKey(0)
梯度 = 原图 - 腐蚀
作用: 求图像边沿。
API morphologyEx(img, MORPH_GRADIENT), kernel)
# 35.img_gradient.py
import cv2
img = cv2.imread('.//src//j.png')
# 创建一个卷积核
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 9)) # 矩形
# 梯度运算
dst = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel)
cv2.imshow('img', img)
cv2.imshow('dst1', dst)
cv2.waitKey(0)
顶帽 = 原图 - 开运算
作用: 可以得到图像外的噪声。
API morphologyEx(img, MORPH_TOPHAT), kernel)
# 36.img_tophat.py
import cv2
img = cv2.imread('.//src//tophat.png')
# 创建一个卷积核
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (19, 19)) # 矩形
# 顶帽运算
dst = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel)
cv2.imshow('img', img)
cv2.imshow('dst1', dst)
cv2.waitKey(0)
黑帽 = 闭运算 - 原图
作用: 可以得到图像内的噪声。
API morphologyEx(img, MORPH_BLACKHAT), kernel)
# 37.img_blackhat.py
import cv2
img = cv2.imread('.//src//dotinJ.png')
# 创建一个卷积核
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (7, 7)) # 矩形
# 黑帽运算
dst = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel)
cv2.imshow('img', img)
cv2.imshow('dst1', dst)
cv2.waitKey(0)
开运算,先腐蚀在膨胀,去除大图形外的小图形
闭运算,线膨胀在腐蚀,去除大图形内的小图形
梯度,求图形边缘
顶帽,原图减去开运算,得到大图形外的小图形
黑帽,闭运算减去原图,得到大图形内的小图形
具有相同颜色或强度的连续点的曲线。
作用:
注意点:
API findContours(img,mode, ApproximationMode…)
# 38.img_contours.py
import cv2
img = cv2.imread('.//src//contours.png')
print(img.shape)
# 转化为灰度图 ---单通道
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
print(gray.shape)
# 二值化
ret, binary = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY)
# 轮廓查找
contours, hirarchy = cv2.findContours(
binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# contours, hirarchy = cv2.findContours(
# binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# 打印轮廓列表
print(contours)
# cv2.imshow('img', img)
# cv2.imshow('gray', gray)
cv2.imshow('bin', binary)
cv2.waitKey(0)
API drawContours(img, contours, contourIdx, color)
img:目标图像
contours:轮廓点集
contourIdx , -1:表示绘制所有轮廓
color:颜色(0,0,255)
thickness:线宽,-1 是全部填充
# 在38.img_contours.py基础上修改
import cv2
img = cv2.imread('.//src//contours.png')
print(img.shape)
# 转化为灰度图 ---单通道
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
print(gray.shape)
# 二值化
ret, binary = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY)
# 轮廓查找
contours, hirarchy = cv2.findContours(
binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# 轮廓列表
print(contours)
# 绘制轮廓 会在原图上进行修改
cv2.drawContours(img, contours, -1, (0, 0, 255), 1)
cv2.imshow('img', img)
cv2.imshow('bin', binary)
cv2.waitKey(0)
API contourArea(contour)
API arcLength(curve,closed)
# 在38.img_contours.py基础上修改
import cv2
img = cv2.imread('.//src//contours.png')
print(img.shape)
# 转化为灰度图 ---单通道
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
print(gray.shape)
# 二值化
ret, binary = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY)
# 轮廓查找
contours, hirarchy = cv2.findContours(
binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# 绘制轮廓
cv2.drawContours(img, contours, -1, (0, 0, 255), 1)
# 计算面积
area = cv2.contourArea(contours[0])
print("area=%d" % (area))
# 计算周长
len = cv2.arcLength(contours[0], True)
print('len=%d' % (len))
cv2.imshow('img', img)
cv2.waitKey(0)
API approxPolyDP(curve, epsilon, closed)
API convexHull(points, clockwise,…)
# 39.img_approx_hull.py
import cv2
# 连接直线形成闭合
def drawShape(src, points):
i = 0
while i < len(points):
if i == len(points) - 1: # 处理最后一个点,与第一个点连形成闭合
x, y = points[i][0]
x1, y1 = points[0][0]
cv2.line(src, (x, y), (x1, y1), (0, 0, 255), 2)
else:
x, y = points[i][0] # 连接点集
x1, y1 = points[i+1][0]
cv2.line(src, (x, y), (x1, y1), (0, 0, 255), 2)
i += 1
img = cv2.imread('.//src//hand.png')
# 转化为灰度图 ---单通道
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 二值化
ret, binary = cv2.threshold(gray, 100, 255, cv2.THRESH_BINARY)
# 轮廓查找
contours, hirarchy = cv2.findContours(
binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# 绘制轮廓
cv2.drawContours(img, contours, 0, (0, 255, 0), 3)
# 多边形逼近
e = 20
approx = cv2.approxPolyDP(contours[0], e, True)
# 多边形凸包
hull = cv2.convexHull(contours[0])
drawShape(img, approx)
drawShape(img, hull)
cv2.imshow('img', img)
cv2.waitKey(0)
API minAreaRect(points)
API boundingRect(array)
# 40.img_mbRect.py
import cv2
import numpy as np
img = cv2.imread('.//src//hello.png')
print(img.shape)
# 转化为灰度图 ---单通道
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
print(gray.shape)
# 二值化
ret, binary = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY)
# 轮廓查找
contours, hirarchy = cv2.findContours(
binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# 绘制轮廓
cv2.drawContours(img, contours, -1, (0, 0, 255), 1)
# 最小外接矩形
r = cv2.minAreaRect(contours[1])
box = cv2.boxPoints(r)
# box为float类型,需要转换为int型
box = np.int0(box)
cv2.drawContours(img, [box], 0, (0, 0, 255), 3)
# 最大外接矩阵
x, y, w, h = cv2.boundingRect(contours[1])
cv2.rectangle(img, (x, y), (x+w, y+h), (255, 0, 0), 2)
cv2.imshow('img', img)
cv2.waitKey(0)
对于车辆识别来讲,主要分为以下几个步骤:
加载视频,在OpenCV的基本操作中已经做过阐述,详见从摄像头/视频文件读取视频帧
import cv2
import numpy as np
# 加载视频
cap = cv2.VideoCapture('.//src//video.mp4')
while True:
ret, frame = cap.read()
if ret == True:
cv2.imshow('video', frame)
key = cv2.waitKey(1) & 0xFF
if key == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
对视频来讲,大部分场景,背景是相同的不变的,差别在于运动的物体,因此运动的物体可视为前景,而静止的物体则被视为背景,通过去除背景,就可以将运动的物体提取出来。
在视频中某一帧,某一特定区域内,它的像素在整个时间轴的范围内没有发生变化,则可以认为是背景。
API creatBackgroundSubtractorMOG(…)
在最新的OpenCV v4中 API修改为createBackgroundSubtractorMOG2(),BackgroundSubtractorMOG2用于动态目标检测,用到的是基于自适应混合高斯背景建模的背景减除法,相对于BackgroundSubtractorMOG,其具有更好的抗干扰能力,特别是光照变化。
详见:https://docs.opencv.org/3.2.0/d7/d7b/classcv_1_1BackgroundSubtractorMOG2.html
bgsubmog = cv2.createBackgroundSubtractorMOG2()
# 去背景
mask = bgsubmog.apply(dst)
# 灰度化
cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 高斯降噪
blur = cv2.GaussianBlur(frame, (7, 7), 7)
# 中值滤波
dst = cv2.medianBlur(blur, 5)
# 腐蚀
erode = cv2.erode(mask, kernel)
# 膨胀
dilate = cv2.dilate(erode, kernel, 3)
# 闭操作
close = cv2.morphologyEx(dilate, cv2.MORPH_CLOSE, kernel)
close = cv2.morphologyEx(close, cv2.MORPH_CLOSE, kernel)
close = cv2.morphologyEx(close, cv2.MORPH_CLOSE, kernel)
# 获取轮廓
cnts, h = cv2.findContours(
close, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cv2.line(frame, (10, line_high), (1200, line_high), (255, 255, 0), 2)
for (x, y) in cars:
if((y < line_high - offset) and (y > line_high)+offset):
carno += 1
cars.remove((x, y))
print(carno)
cv2.putText(frame, 'Car nums:'+str(carno), (500, 60),
cv2.FONT_HERSHEY_SCRIPT_COMPLEX, 2, (255, 0, 0), 5)
import cv2
from cv2 import createBackgroundSubtractorMOG2
from cv2 import line
import numpy as np
# 加载视频
cap = cv2.VideoCapture('.//src//video.mp4')
bgsubmog = createBackgroundSubtractorMOG2()
# 创建一个卷积核
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (7, 7)) # 矩形
# 设置最小宽高
min_w = 150
min_h = 150
# 检测线的高度
line_high = 500
# 线的偏移量
offset = 4
# 统计车的数量
carno = 0
# 存放有效车辆数组
cars = []
def center(x, y, w, h):
x1 = int(w/2)
y1 = int(h/2)
cx = x + x1
cy = y+y1
return cx, cy
while True:
ret, frame = cap.read()
if ret == True:
# 灰度化
cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 高斯降噪
blur = cv2.GaussianBlur(frame, (7, 7), 7)
# 中值滤波
dst = cv2.medianBlur(blur, 5)
# 去背景
mask = bgsubmog.apply(dst)
# 腐蚀
erode = cv2.erode(mask, kernel)
# 膨胀
dilate = cv2.dilate(erode, kernel, 3)
# 闭操作
close = cv2.morphologyEx(dilate, cv2.MORPH_CLOSE, kernel)
close = cv2.morphologyEx(close, cv2.MORPH_CLOSE, kernel)
close = cv2.morphologyEx(close, cv2.MORPH_CLOSE, kernel)
close = cv2.morphologyEx(close, cv2.MORPH_CLOSE, kernel)
# 获取轮廓
cnts, h = cv2.findContours(
close, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
cv2.line(frame, (10, line_high), (1200, line_high), (255, 255, 0), 2)
# 绘制轮廓
for (i, c) in enumerate(cnts):
(x, y, w, h) = cv2.boundingRect(c)
# 对车辆的宽高进行判断
# 以验证是否是有效的车辆
isValid = (w >= min_w) and (h >= min_h)
if not isValid:
continue
# 绘制矩形
cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 0, 255), 5)
# 对车进行统计
cpoint = center(x, y, w, h)
cars.append(cpoint)
print(cpoint)
# 要有一条线
# 有范围 6
# 从数组中减去
for (x, y) in cars:
if((y < line_high - offset) and (y > line_high)+offset):
carno += 1
cars.remove((x, y))
print(carno)
cv2.putText(frame, 'Car nums:'+str(carno), (500, 60),
cv2.FONT_HERSHEY_SCRIPT_COMPLEX, 2, (255, 0, 0), 5)
cv2.imshow('video', frame)
key = cv2.waitKey(20) & 0xFF
if key == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
由于是基于形态学的轮廓识别,对于视频的拍摄要较高的要求,如道路等背景必须完全静止,拍摄晃动会导致不能正确识别前景、起风等不可控天气因素造成的树木、小草等晃动会被CV“过度”识别,误以为是车辆,或表现为或大或小的噪点增多……为了解决这种问题,使用深度学习、人工智能等实现车辆识别更加准确,以后将慢慢学习更新。
什么是特征
图像特征就是指有意义的图像区域,具有独特性、易于识别性,比如角点、斑点以及高密度区。
Harris点总结
API cornerHarris(img, dst, blockSize, ksize, k)
# 41.img_harris.py
import cv2
import numpy as np
# harris
blockSize = 2
ksize = 3
k = 0.04
img = cv2.imread('.//src//chess.png')
# 灰度化
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Harris角点检测
dst = cv2.cornerHarris(gray, blockSize, ksize, k)
# Harris角点的展示
img[dst > 0.01*dst.max()] = [0, 0, 255]
cv2.imshow('harris', img)
cv2.waitKey(0)
Shi-Tomasi是Harris角点检测的改进,Harris角点检测的稳定性和k有关,而k是个经验值,不好设定最佳值,对于不同的图片需要设定不同的最佳k值。
API goodFeaturesToTracck(img, maxCorners, …)
# 42.img_Shi-Tomasi.py
import cv2
import numpy as np
# Shi-Tomasi参数
maxCorners = 1000
ql = 0.01
minDistance = 10
img = cv2.imread('.//src//chess.png')
# 灰度化
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Shi-Tomasi角点检测
corners = cv2.goodFeaturesToTrack(gray, maxCorners, ql, minDistance)
corners = np.int0(corners)
# Shi-Tomasi绘制角点
for i in corners:
x, y = i.ravel()
cv2.circle(img, (x, y), 3, (255, 0, 0), -1)
cv2.imshow('harris', img)
cv2.waitKey(0)
出现的原因
我们知道,一条线是由许多像素点连续拼接而成的,对于缩放前的角被Harris识别为角点,但放大后,之前识别的角点被许多连续的像素点替代,这时Harris就无法正确的识别为角点,而是用SIFT关键点检测依然可以正确识别到角点。
使用SIFT步骤
# 43.img_sift.py
import cv2
# 读文件
img = cv2.imread('.//src//chess.png')
# 灰度化
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 创建sift对象
sift = cv2.SIFT_create()
# 进行检测
kp, des = sift.detectAndCompute(gray, None)
# 绘制keypoints
cv2.drawKeypoints(gray, kp, img)
cv2.imshow('img', img)
cv2.waitKey(0)
##### SIFT计算描述算子
关键点和描述子
只计算描述子
同时计算关键点和描述子
SURF(Speeded-Up Robust Features)
SURF的优点
SIFT最大的问题是速度慢,如果对于一系列的图片进行特征点检测,SIFT则显得比较吃力,因此才有SURF,SURF主要解决SIFT的速度问题。
使用SURF的步骤
# 44.img_ruft.py
import cv2
import numpy as np
# 读文件
img = cv2.imread('.//src//chess.png')
# 灰度化
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 创建surf对象
surf = cv2.xfeatures2d.SURF_create()
# 使用surf进行检测
kp, des = surf.detectAndCompute(gray, None)
# 绘制keypoints
cv2.drawKeypoints(gray, kp, img)
#cv2.imshow('img', img)
cv2.waitKey(0)
通过编程可能会有的小伙伴遇到如下这个问题:
这是由于在最新版本中,OpenCV已经没有SURF的版权,将opencv版本退到3.4.2即可解决,卸载之前的包,然后重新安装。
pip uninstall opencv-python
pip install opencv-python==3.4.2.16
pip install opencv-contrib-python==3.4.2.16
然后使用即可解决。
surf = cv2.xfeatures2d.SURF_create()
优势
使用ORB的步骤
# 45.img_ord.py
import cv2
#读文件
img = cv2.imread('.//src//chess.png')
#灰度化
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#创建ORB对象
orb = cv2.ORB_create()
#orb进行检测
kp, des = orb.detectAndCompute(gray, None)
#绘制keypoints
cv2.drawKeypoints(gray, kp, img)
cv2.imshow('img', img)
cv2.waitKey(0)
通过ORB算法与其他算法比较发现,ORB算子得到的特征点比较少,这也是ORB速度快的原因:通过减少数据量,降低计算量,达到快速度。
BF(Brute-Force),暴力特征匹配方法
原理
它使用第一组中的每个特征的描述子,与第二组中的所有特征描述子进行匹配。计算它们之间的差距,然后将最接近一个匹配数据返回。
OpenCV特征匹配步骤
API BFMatcher()
API match(des1, des2)
API drawMatches()
#46.img_bfmatch.py
import cv2
# 读文件
img1 = cv2.imread('.//src//opencv_search.png')
img2 = cv2.imread('.//src//opencv_orig.png')
# 灰度化
g1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
g2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
# 创建sift对象
sift = cv2.xfeatures2d.SIFT_create()
# 进行检测
kp1, des1 = sift.detectAndCompute(g1, None)
kp2, des2 = sift.detectAndCompute(g2, None)
# 创建bf暴力匹配对象
bf = cv2.BFMatcher(cv2.NORM_L1)
match = bf.match(des1, des2)
img3 = cv2.drawMatches(img1, kp1, img2, kp2, match, None)
cv2.imshow('img3', img3)
cv2.waitKey(0)
通过观察我们可以看到,大多数的特征点是可以一一对应、相互匹配到的,而对于一些匹配不到的特征点则是按照相似度最高进行匹配。
FLANN优缺点
FLANN特征匹配步骤
创建FLANN匹配器,FlannBasedMatcher(……)
进行特征匹配,flann.match/knnMatch(……)
绘制匹配点,cv2.drawMatches/drawMatchersKnn(……)
API FlannBasedMatcher(index_params, search_params)
index_params 字典: 匹配算法KDTREE、LSH
search_params 字典:指定KDTREE算法中遍历树的次数
KDTREE 设置方法
index_params = dict(algorithm = FLANN_INDEX_KDTREE, tree = 5)
,经验值:5 ,比较快search_params = dict(checks = 50)
API knnMatch(des1, des2, k)
API DMatch()
API drawMatchesKnn()
# 47.img_flann.py
import cv2
# 打开两个文件
img1 = cv2.imread('.//src//opencv_search.png')
img2 = cv2.imread('.//src//opencv_orig.png')
# 灰度化
g1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
g2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
# 他建SIFT特征检测器
sift = cv2.xfeatures2d.SIFT_create()
# 计算描述子与特征点
kp1, des1 = sift.detectAndCompute(g1, None)
kp2, des2 = sift.detectAndCompute(g2, None)
# 创建匹配器
index_params = dict(algorithm=1, trees=5)
search_params = dict(checks=50)
flann = cv2.FlannBasedMatcher(index_params, search_params)
# 对描述子进行匹配计算
matchs = flann.knnMatch(des1, des2, k=2)
good = []
for i, (m, n) in enumerate(matchs):
if m.distance < 0.7 * n.distance:
good.append(m)
ret = cv2.drawMatchesKnn(img1, kp1, img2, kp2, [good], None)
cv2.imshow('result', ret)
cv2.waitKey()
特征匹配 + 单应性矩阵
对于同一个物体,从不同的方向看去,物体的同一点在不同的图像上位置不同,如planar_surface上的 X
点在image1上为 x
,在image2上为 x'
,图像中的 H
则是图像与真实物体之间变换的单应性矩阵。
# 48.img_find.py
import cv2
import numpy as np
# 打开两个文件
img1 = cv2.imread('.//src//opencv_search.png')
img2 = cv2.imread('.//src//opencv_orig.png')
# 灰度化
g1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
g2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
# 他建SIFT特征检测器
sift = cv2.xfeatures2d.SIFT_create()
# 计算描述子与特征点
kp1, des1 = sift.detectAndCompute(g1, None)
kp2, des2 = sift.detectAndCompute(g2, None)
# 创建匹配器
index_params = dict(algorithm=1, trees=5)
search_params = dict(checks=50)
flann = cv2.FlannBasedMatcher(index_params, search_params)
# 对描述子进行匹配计算
matchs = flann.knnMatch(des1, des2, k=2)
good = [] # 存放角点
for i, (m, n) in enumerate(matchs):
if m.distance < 0.7 * n.distance:
good.append(m)
if len(good) >= 4:
srcPts = np.float32([kp1[m.queryIdx].pt for m in good]).reshape(-1, 1, 2)
dstPts = np.float32([kp2[m.trainIdx].pt for m in good]).reshape(-1, 1, 2)
# 查找单应性矩阵
H, _ = cv2.findHomography(
srcPts, dstPts, cv2.RANSAC, 5.0) # cv2.RANSAC 随机抽样一致算法
h, w = img1.shape[:2]
pts = np.float32([[0, 0], [0, h-1], [w-1, h-1], [w-1, 0]]
).reshape(-1, 1, 2)
dst = cv2.perspectiveTransform(pts, H)
# 对找到的图像进行圈框
cv2.polylines(img2, [np.int32(dst)], True, (0, 0, 255))
else:
print('the number of good is less than 4.')
exit()
ret = cv2.drawMatchesKnn(img1, kp1, img2, kp2, [good], None)
cv2.imshow('result', ret)
cv2.waitKey()
左边的OpenCV logo在右侧图像中找到并全框起来。
对于同一景色,从不同方向进行观测/拍照,得到两张图片则会有相关联的景色,如上图山体,那么将
这两张图像按照相同部分进行拼接,就可以得到一份不会重叠而又更加宽阔的景色,如下图。
图像合并的步骤
# 49.img_stitch.py
# 整体思路
# 第一步,读取文件,将图片设置成一样大小640*480
# 第二步,找特征点,描述子,计算单应性矩阵
# 第三步,根据单应性矩阵对图像进行变换,然后平移★
# 第四步,拼接并输出最终结果
import cv2
import numpy as np
# 计算单应性矩阵
def get_homo(img1, img2):
# 1. 创建特征转换对象
# 2. 通过特征转换对象获得特征点和描述子
# 3. 创建特征匹配器
# 4. 进行特征匹配
# 5. 过滤特征,找出有效的特征匹配点
# 创建特征转换对象
sift = cv2.SIFT_create()
# 获取特征点和描述子
k1, d1 = sift.detectAndCompute(img1, None)
k2, d2 = sift.detectAndCompute(img2, None)
# 创建特征匹配器
bf = cv2.BFMatcher()
matches = bf.knnMatch(d1, d2, k=2)
# 过滤特征,找出有效的特征匹配点
# 设置阈值
verify_tatio = 0.8
verify_matches = []
for m1, m2 in matches:
if m1.distance < verify_tatio * m2.distance:
verify_matches.append(m1)
# 设置最小匹配点个数
min_match = 8
if len(verify_matches) > min_match:
img1_pts = []
img2_pts = []
for m in verify_matches:
img1_pts.append(k1[m.queryIdx].pt)
img2_pts.append(k2[m.trainIdx].pt)
# [(x1,y1),(x2,y2),……]
# [[x1,y1],[x2,y2],……]
img1_pts = np.float32(img1_pts).reshape(-1, 1, 2)
img2_pts = np.float32(img2_pts).reshape(-1, 1, 2)
# 获取单应性矩阵
H, mask = cv2.findHomography(img1_pts, img2_pts, cv2.RANSAC, 5.0)
return H
else:
print('err:Not enough matches!')
exit()
# 图像拼接
def stitch_img(img1, img2, H):
# 1. 获得每张图片的四个角点
# 2. 对图片进行变换(单应性矩阵使图进行旋转,平移)
# 3. 创建一张大图,将两张图拼接到一起
# 4. 将结果输出
# 获得原始图的高/宽
h1, w1 = img1.shape[:2]
h2, w2 = img2.shape[:2]
# 获得四个角点
img1_dims = np.float32(
[[0, 0], [0, h1], [w1, h1], [w1, 0]]).reshape(-1, 1, 2)
img2_dims = np.float32(
[[0, 0], [0, h2], [w2, h2], [w2, 0]]).reshape(-1, 1, 2)
img1_transform = cv2.perspectiveTransform(img1_dims, H)
result_dims = np.concatenate((img2_dims, img1_transform), axis=0)
[x_min, y_min] = np.int32(result_dims.min(axis=0).ravel()-0.5)
[x_max, y_max] = np.int32(result_dims.max(axis=0).ravel()+0.5)
# 平移的距离
transform_dist = [-x_min, -y_min]
transform_array = np.array([[1, 0, transform_dist[0]],
[0, 1, transform_dist[1]],
[0, 0, 1]])
# 投影变换
result_img = cv2.warpPerspective(
img1, transform_array.dot(H), (x_max-x_min, y_max-y_min))
# cv2.imshow('1', result_img)
# 图像拼接
result_img[transform_dist[1]:transform_dist[1]+h2,
transform_dist[0]:transform_dist[0]+w2] = img2
return result_img
# 读取图像
img1 = cv2.imread('.//src//map1.png')
img2 = cv2.imread('.//src//map2.png')
# 修改尺寸
img1 = cv2.resize(img1, (640, 480))
img2 = cv2.resize(img2, (640, 480))
inputs = np.hstack((img1, img2))
# 获得单应性矩阵
H = get_homo(img1, img2)
# 进行图像拼接
result_img = stitch_img(img1, img2, H)
cv2.imshow('input', inputs)
cv2.imshow('result', result_img)
cv2.waitKey(0)
· ——学习ing 2022.03.17
待学习...
待学习...
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。