博主精品专栏导航
- 🍕 专栏:Pytorch项目实战
- 🌭 Opencv 图像处理(全)
- 🍱 Opencv C++图像处理(全)
- 🍳 Pillow 图像处理(PIL.Image)
- 🍝 Pytorch基础(全)
- 🥙 Python常用内置函数(全)
- 🍰 卷积神经网络CNN的经典模型
- 🍟 卷积神经网络CNN的实战知识
- 🥘 三万字硬核详解:yolov1、yolov2、yolov3、yolov4、yolov5、yolov7
备注:以下源码均可运行,不同项目涉及的函数均有详细分析说明。
11、图像项目实战
(一)银行卡号识别 —— sort_contours()、resize()
【信用卡检测流程详解】
11、提取模板的每个数字
1111、读取模板图像、转换成灰度图、转换成二值图
1122、轮廓检测、绘制轮廓、对得到的所有轮廓进行排序(编号)
1133、提取模板的所有轮廓 - 每一个数字
22、提取信用卡的所有轮廓
2211、读取待检测图像、转换为灰度图、顶帽操作、sobel算子操作、闭操作、二值化、二次膨胀+腐蚀
2222、轮廓检测、绘制轮廓
33、提取银行卡《四个数字一组》轮廓,然后每个轮廓与模板的每一个数字进行匹配,得到最大匹配结果
3311、在所有轮廓中,识别出《四个数字一组》的轮廓(共有四个),并进行阈值化、轮廓检测和轮廓排序
3322、在《四个数字一组》中,提取每个数字的轮廓以及坐标,并进行模板匹配得到最大匹配结果
44、在原图像上,用矩形画出《四个数字一组》,并在原图上显示所有的匹配结果
import cv2 # opencv读取的格式是BGR
import matplotlib.pyplot as plt # Matplotlib是RGB
import numpy as np
def sort_contours(cnt_s, method="left-to-right"):
reverse = False
ii_myutils = 0
if method == "right-to-left" or method == "bottom-to-top":
reverse = True
if method == "top-to-bottom" or method == "bottom-to-top":
ii_myutils = 1
bounding_boxes = [cv2.boundingRect(cc_myutils) for cc_myutils in cnt_s] # 用一个最小的矩形,把找到的形状包起来x,y,h,w
(cnt_s, bounding_boxes) = zip(*sorted(zip(cnt_s, bounding_boxes), key=lambda b: b[1][ii_myutils], reverse=reverse))
return cnt_s, bounding_boxes
def resize(image, width=None, height=None, inter=cv2.INTER_AREA):
dim_myutils = None
(h_myutils, w_myutils) = image.shape[:2]
if width is None and height is None:
return image
if width is None:
r_myutils = height / float(h_myutils)
dim_myutils = (int(w_myutils * r_myutils), height)
else:
r_myutils = width / float(w_myutils)
dim_myutils = (width, int(h_myutils * r_myutils))
resized = cv2.resize(image, dim_myutils, interpolation=inter)
return resized
######################################################################
# 11、提取模板的每个数字
######################################################################
# 读取模板图像(银行卡对应0~9的数字模板)
img = cv2.imread(r'ocr_a_reference.png')
ref_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 转换成灰度图
ref_BINARY = cv2.threshold(ref_gray, 10, 255, cv2.THRESH_BINARY_INV)[1] # 转换成二值图像
"""#######################################
# 轮廓检测:contours, hierarchy = cv2.findContours(img, mode, method)
# 输入参数 mode: 轮廓检索模式
# (1)RETR_EXTERNAL: 只检索最外面的轮廓;
# (2)RETR_LIST: 检索所有的轮廓,但检测的轮廓不建立等级关系,将其保存到一条链表当中,
# (3)RETR_CCOMP: 检索所有的轮廓,并建立两个等级的轮廓。顶层是各部分的外部边界,内层是的边界信息;
# (4)RETR_TREE: 检索所有的轮廓,并建立一个等级树结构的轮廓;(最常用)
# method: 轮廓逼近方法
# (1)CHAIN_APPROX_NONE: 存储所有的轮廓点,相邻的两个点的像素位置差不超过1。 例如:矩阵的四条边。(最常用)
# (2)CHAIN_APPROX_SIMPLE: 压缩水平方向,垂直方向,对角线方向的元素,只保留该方向的终点坐标。 例如:矩形的4个轮廓点。
# 输出参数 contours:所有的轮廓
# hierarchy:每条轮廓对应的属性
# 备注0:轮廓就是将连续的点(连着边界)连在一起的曲线,具有相同的颜色或者灰度。轮廓在形状分析和物体的检测和识别中很有用。
# 备注1:函数输入图像是二值图,即黑白的(不是灰度图)。所以读取的图像要先转成灰度的,再转成二值图。
# 备注2:函数在opencv2只返回两个值:contours, hierarchy。
# 备注3:函数在opencv3会返回三个值:img, countours, hierarchy
#######################################"""
refCnts, hierarchy = cv2.findContours(ref_BINARY.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
"""#######################################
# 绘制轮廓:v2.drawContours(image, contours, contourIdx, color, thickness) ———— (在图像上)画出图像的轮廓
# 输入参数 image: 需要绘制轮廓的目标图像,注意会改变原图
# contours: 轮廓点,上述函数cv2.findContours()的第一个返回值
# contourIdx: 轮廓的索引,表示绘制第几个轮廓。-1表示绘制所有的轮廓
# color: 绘制轮廓的颜色(RGB)
# thickness: (可选参数)轮廓线的宽度,-1表示填充
# 备注:图像需要先复制一份copy(), 否则(赋值操作的图像)与原图会随之一起改变。
#######################################"""
img_Contours = img.copy()
cv2.drawContours(img_Contours, refCnts, -1, (0, 0, 255), 3)
# print(np.array(refCnts).shape)
# 画图(图像处理并得到轮廓的图形化显示)
plt.subplot(221), plt.imshow(img, 'gray'), plt.title('(0)ref')
plt.subplot(222), plt.imshow(ref_gray, 'gray'), plt.title('(1)ref_gray')
plt.subplot(223), plt.imshow(ref_BINARY, 'gray'), plt.title('(2)ref_BINARY')
plt.subplot(224), plt.imshow(img_Contours, 'gray'), plt.title('(3)img_Contours')
plt.show()
#######################################
# 对得到的所有轮廓进行排序(编号):从左到右,从上到下
refCnts = sort_contours(refCnts, method="left-to-right")[0]
#######################################
# 提取(模板的)所有轮廓 - 数字
digits = {} # 保存每个模板的数字 - 元组初始化
for (i, c) in enumerate(refCnts):
(x, y, w, h) = cv2.boundingRect(c) # 得到轮廓(数字)的外接矩形的左上角的(x, y)坐标、宽度和长度
roi = ref_BINARY[y:y + h, x:x + w] # 获得外接矩形的坐标
roi = cv2.resize(roi, (57, 88)) # 将感兴趣区域的图像(数字)resize相同的大小
digits[i] = roi # 保存每个模板(数字)
######################################################################
# 22、提取信用卡的所有轮廓
"""######################################################################
# 初始化卷积核:getStructuringElement(shape, ksize)
# 输入参数: shape:形状
# (1) MORPH_RECT 矩形
# (2) MORPH_CROSS 十字型
# (3) MORPH_ELLIPSE 椭圆形
# ksize:卷积核大小。例如:(3, 3)表示3*3的卷积核
######################################"""
rect_Kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 3))
square_Kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
######################################
# 读取输入图像(待检测信用卡图像),并进行预处理
image_card = cv2.imread(r'images\credit_card_01.png')
image_resize = resize(image_card, width=300)
image_gray = cv2.cvtColor(image_resize, cv2.COLOR_BGR2GRAY)
"""#######################################
# 形态学变化函数:cv2.morphologyEx(src, op, kernel)
# 参数说明:src传入的图片,op进行变化的方式, kernel表示方框的大小
# op变化的方式有五种:
# 开运算(open): cv2.MORPH_OPEN 先腐蚀,再膨胀。 开运算可以用来消除小黑点。
# 闭运算(close): cv2.MORPH_CLOSE 先膨胀,再腐蚀。 闭运算可以用来突出边缘特征。
# 形态学梯度(morph-grad): cv2.MORPH_GRADIENT 膨胀后图像(减去)腐蚀图像。 可以突出团块(blob)的边缘,保留物体的边缘轮廓。
# 顶帽(top-hat): cv2.MORPH_TOPHAT 原始输入(减去)开运算结果。 将突出比原轮廓亮的部分。
# 黑帽(black-hat): cv2.MORPH_BLACKHAT 闭运算结果(减去)原始输入 将突出比原轮廓暗的部分。
#######################################"""
image_tophat = cv2.morphologyEx(image_gray, cv2.MORPH_TOPHAT, rect_Kernel) # 礼帽操作,突出更明亮的区域
"""#######################################
# Sobel算子是一种常用的边缘检测算子。对噪声具有平滑作用,提供较为精确的边缘方向信息,但是边缘定位精度不够高。
# 边缘就是像素对应的灰度值快速变化的地方。如:黑到白的边界
# 图像是二维的。Sobel算子在x,y两个方向求导,故有不同的两个卷积核(Gx, Gy),且Gx的转置等于Gy。分别反映了每一点像素在水平方向和在垂直方向上的亮度变换情况.
########################################
# dst = cv2.Sobel(src, ddepth, dx, dy, ksize)
# 输入参数 src 输入图像
# ddepth 图像的深度,-1表示采用的是与原图像相同的深度。目标图像的深度必须大于等于原图像的深度;
# dx和dy 表示的是求导的阶数,0表示这个方向上没有求导,一般为0、1、2。
# ksize 卷积核大小,一般为3、5。
# 同时对x和y进行求导,会导致部分信息丢失。(不建议)- 分别计算x和y,再求和(效果好)
########################################
# (1)cv2.CV_16S的说明
# (1)Sobel函数求完导数后会有负值,还有会大于255的值。
# (2)而原图像是uint8,即8位无符号数。所以Sobel建立图像的位数不够,会有截断。
# (3)因此要使用16位有符号的数据类型,即cv2.CV_16S。
# (2)cv2.convertScaleAbs(): 给图像的所有像素加一个绝对值
# 通过该函数将其转回原来的uint8形式。否则将无法显示图像,而只是一副灰色的窗口。
########################################"""
# 进行sobel算子操作 ksize=-1相当于用3*3的卷积核进行筛选ll(内置的卷积核)
image_gradx = cv2.Sobel(image_tophat, ddepth=cv2.CV_32F, dx=1, dy=0, ksize=-1) # 检索图像的边界,gradx是经过Sobel算子处理后的图像的像素点矩阵
image_gradx = np.absolute(image_gradx) # 对数组中每一个元素求绝对值
(minVal, maxVal) = (np.min(image_gradx), np.max(image_gradx)) # 找到最大边界差值和最小边界插值
image_gradx = (255 * ((image_gradx - minVal) / (maxVal - minVal))) # 归一化公式,将图像像素数据限制在0-1之间,便于后续的操作
image_gradx = image_gradx.astype("uint8") # 将gradx的矩阵元素改为数据类型uint8,一般图像的像素点类型都是uint8
"""
sobel_Gx1 = cv2.Sobel(image_tophat, ddepth=cv2.CV_32F, dx=1, dy=0, ksize=3) # 3*3卷积核
sobel_Gx_Abs1 = cv2.convertScaleAbs(sobel_Gx1) # (1)左边减右边(2)白到黑是正数,黑到白就是负数,且所有的负数会被截断成0,所以要取绝对值。
sobel_Gy1 = cv2.Sobel(image_tophat, cv2.CV_64F, 0, 1, ksize=3)
sobel_Gy_Abs1 = cv2.convertScaleAbs(sobel_Gy1)
sobel_Gx_Gy_Abs1 = cv2.addWeighted(sobel_Gx_Abs1, 0.5, sobel_Gy_Abs1, 0.5, 0) # 权重值x + 权重值y +偏置b
"""
########################################
# 闭操作(先膨胀,再腐蚀)将银行卡分成四个部分,每个部分的四个数字连在一起
image_CLOSE = cv2.morphologyEx(image_gradx, cv2.MORPH_CLOSE, square_Kernel)
"""########################################
# 图像阈值 ret, dst = cv2.threshold(src, thresh, max_val, type)
# 输入参数 dst: 输出图
# src: 输入图,只能输入单通道图像,通常来说为灰度图
# thresh: 阈值
# max_val: 当像素值超过了阈值(或者小于阈值,根据type来决定),所赋予的值
# type: 二值化操作的类型,包含以下5种类型:
# (1) cv2.THRESH_BINARY 超过阈值部分取max_val(最大值),否则取0
# (2) cv2.THRESH_BINARY_INV THRESH_BINARY的反转
# (3) cv2.THRESH_TRUNC 大于阈值部分设为阈值,否则不变
# (4) cv2.THRESH_TOZERO 大于阈值部分不改变,否则设为0
# (5) cv2.THRESH_TOZERO_INV THRESH_TOZERO的反转
########################################"""
# THRESH_OTSU 会自动寻找合适的阈值,适合双峰,需把阈值参数设置为0。
image_thresh = cv2.threshold(image_CLOSE, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
# (二次)闭操作,将四个连在一起的数字进行填充形成一个整体。
image_2_dilate = cv2.dilate(image_thresh, square_Kernel, iterations=2) # 膨胀(迭代次数2次)
image_1_erode = cv2.erode(image_2_dilate, square_Kernel, iterations=1) # 腐蚀(迭代次数1次)
image_2_CLOSE = image_1_erode
# 计算轮廓
threshCnts, hierarchy = cv2.findContours(image_2_CLOSE.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# cnts = threshCnts # 代表图像轮廓的点集
image_Contours = image_resize.copy()
cv2.drawContours(image_Contours, threshCnts, -1, (0, 0, 255), 3)
plt.subplot(241), plt.imshow(image_card, 'gray'), plt.title('(0)image_card')
plt.subplot(242), plt.imshow(image_gray, 'gray'), plt.title('(1)image_gray')
plt.subplot(243), plt.imshow(image_tophat, 'gray'), plt.title('(2)image_tophat')
plt.subplot(244), plt.imshow(image_gradx, 'gray'), plt.title('(3)image_gradx')
plt.subplot(245), plt.imshow(image_CLOSE, 'gray'), plt.title('(4)image_CLOSE')
plt.subplot(246), plt.imshow(image_thresh, 'gray'), plt.title('(5)image_thresh')
plt.subplot(247), plt.imshow(image_2_CLOSE, 'gray'), plt.title('(6)image_2_CLOSE')
plt.subplot(248), plt.imshow(image_Contours, 'gray'), plt.title('(7)image_Contours')
plt.show()
######################################################################
# 33、提取银行卡" 四个数字一组 "轮廓,然后每个轮廓与模板的每一个数字进行匹配,得到最大匹配结果
######################################################################
# 3311、识别出四个数字一组的所有轮廓(理论上是四个)
########################################
locs = [] # 保存四个数字一组的轮廓坐标 - 列表初始化
for (i, c) in enumerate(threshCnts): # 遍历轮廓
(x, y, w, h) = cv2.boundingRect(c) # 计算矩形
ar = w / float(h) # (四个数字一组)的长宽比
# 匹配(四个数字为一组)轮廓的大小 —— 以实际图像大小进行调整
if (2.0 < ar and ar < 4.0):
if (35 < w < 60) and (10 < h < 20):
locs.append((x, y, w, h)) # 符合的留下来
# 将符合的轮廓从左到右排序
locs = sorted(locs, key=lambda x:x[0])
########################################
# 3322、在四个数字一组中,提取每个数字的轮廓坐标并进行模板匹配
########################################
output = [] # 保存银行卡中每个数字的轮廓坐标 - 列表初始化
# 遍历银行卡的每一个数字
for (ii, (gX, gY, gW, gH)) in enumerate(locs): # ii 应为四组
groupOutput = [] # 信用卡每个数字的最后匹配结果存储
group_digit = image_gray[gY - 5:gY + gH + 5, gX - 5:gX + gW + 5] # 根据坐标提取每一个组(将每个轮廓的结果放大一些,避免信息丢失)
group_digit_th = cv2.threshold(group_digit, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1] # 二值化
digitCnts, hierarchy = cv2.findContours(group_digit_th.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 获取轮廓
digitCnts = sort_contours(digitCnts, method="left-to-right")[0] # 对获得的轮廓进行编号
# 计算每一组中的每一个数值
for jj in digitCnts: # jj 应为四个数字
(x, y, w, h) = cv2.boundingRect(jj) # 获取当前数值的轮廓
roi = group_digit[y:y + h, x:x + w] # 获取当前数值的坐标
roi = cv2.resize(roi, (57, 88)) # 修改尺寸大小(该大小应与模板数字的大小相同)
cv2.imshow("Image", roi)
cv2.waitKey(200) # 延迟200ms
"""########################################
# 模板匹配:cv2.matchTemplate(image, template, method)
# 输入参数 image 待检测图像
# template 模板图像
# method 模板匹配方法:
# (1)cv2.TM_SQDIFF: 计算平方差。 计算出来的值越接近0,越相关
# (2)cv2.TM_CCORR: 计算相关性。 计算出来的值越大,越相关
# (3)cv2.TM_CCOEFF: 计算相关系数。 计算出来的值越大,越相关
# (4)cv2.TM_SQDIFF_NORMED: 计算(归一化)平方差。 计算出来的值越接近0,越相关
# (5)cv2.TM_CCORR_NORMED: 计算(归一化)相关性。 计算出来的值越接近1,越相关
# (6)cv2.TM_CCOEFF_NORMED: 计算(归一化)相关系数。 计算出来的值越接近1,越相关
# (最好选择有归一化操作,效果好)
########################################
# 获取匹配结果函数:min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(ret)
# 其中: ret是cv2.matchTemplate函数返回的矩阵;
# min_val, max_val, min_loc, max_loc分别表示最小值,最大值,最小值与最大值在图像中的位置
# 如果模板方法是平方差或者归一化平方差,要用min_loc; 其余用max_loc
########################################"""
scores = [] # 计算【轮廓中的数字: roi】和【模板中的数字: digitROI】的匹配分数
for (kk, digitROI) in digits.items(): # kk 应为10数字(对应模板的十个数)
result = cv2.matchTemplate(roi, digitROI, cv2.TM_CCOEFF)
(_, max_score, _, _) = cv2.minMaxLoc(result) # max_score表示最大值
scores.append(max_score) # 将对象max_score添加到列表scores后面
groupOutput.append(str(np.argmax(scores))) # 将最大匹配分数对应的数字保存下来
"""########################################
# 添加文字及修改格式函数:cv2.putText(img, str(i), (123, 456)), font, 2, (0, 255, 0), 3)
# 输入参数依次是:图片,添加的文字,左上角坐标,字体,字体大小,颜色,字体粗细
########################################"""
# 在原图像上,用矩形将" 四个数字一组 "画出来(应共有四个矩形,对应四个组)
cv2.rectangle(image_resize, (gX - 5, gY - 5), (gX + gW + 5, gY + gH + 5), (0, 0, 255), 1)
cv2.putText(image_resize, "".join(groupOutput), (gX, gY - 15), cv2.FONT_HERSHEY_SIMPLEX, 0.65, (0, 0, 255), 2)
# 将银行卡每一个数字对应的实际匹配值保存下来
output.extend(groupOutput) # 将groupOutput列表添加到output列表后面
# 打印结果(在原图上显示所有的匹配结果)
cv2.imshow("Image", image_resize)
cv2.waitKey(0)
深究 Pycharm shadows name ‘xxxx’ from outer scope 警告
(二)文档扫描OCR识别 —— cv2.getPerspectiveTransform() + cv2.warpPerspective()、np.argmin()、np.argmax()、np.diff()
计算轮廓的长度:cv2.arcLength(curve, closed)
找出轮廓的多边形拟合曲线:approxPolyDP(contourMat, 10, true)
求最小值对应的索引:np.argmin()
求最大值对应的索引:np.argmax()
求(同一行)列与列之间的差值:np.diff()
import numpy as np
import cv2
import matplotlib.pyplot as plt # Matplotlib是RGB
"""######################################################################
# 计算齐次变换矩阵:cv2.getPerspectiveTransform(rect, dst)
# 输入参数 rect输入图像的四个点(四个角)
# dst输出图像的四个点(方方正正的图像对应的四个角)
######################################################################
# 仿射变换:cv2.warpPerspective(src, M, dsize, dst=None, flags=None, borderMode=None, borderValue=None)
# 透视变换:cv2.warpAffine(src, M, dsize, dst=None, flags=None, borderMode=None, borderValue=None)
# src:输入图像 dst:输出图像
# M:2×3的变换矩阵
# dsize:变换后输出图像尺寸
# flag:插值方法
# borderMode:边界像素外扩方式
# borderValue:边界像素插值,默认用0填充
#
# (Affine Transformation)可实现旋转,平移,缩放,变换后的平行线依旧平行。
# (Perspective Transformation)即以不同视角的同一物体,在像素坐标系中的变换,可保持直线不变形,但是平行线可能不再平行。
#
# 备注:cv2.warpAffine需要与cv2.getPerspectiveTransform搭配使用。
######################################################################"""
def order_points(pts):
rect = np.zeros((4, 2), dtype="float32") # 一共4个坐标点
# 按顺序找到对应坐标0123分别是 左上,右上,右下,左下
# 计算左上,右下
s = pts.sum(axis=1)
rect[0] = pts[np.argmin(s)] # np.argmin() 求最小值对应的索引
rect[2] = pts[np.argmax(s)] # np.argmax() 求最大值对应的索引
# 计算右上和左下
diff = np.diff(pts, axis=1) # np.diff 求(同一行)列与列之间的差值
rect[1] = pts[np.argmin(diff)]
rect[3] = pts[np.argmax(diff)]
return rect
def four_point_transform(image, pts):
rect = order_points(pts) # 获取输入坐标点
(tl, tr, br, bl) = rect # 获取四边形的四个点,每个点有两个值,对应(x, y)坐标
# 计算输入的w和h值
widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))
widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2))
maxWidth = max(int(widthA), int(widthB)) # 取四边形上下两边中,最大的宽度
heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))
heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))
maxHeight = max(int(heightA), int(heightB)) # 取四边形左右两边中,最大的高度
# 变换后对应坐标位置
dst = np.array([[0, 0], [maxWidth - 1, 0], [maxWidth - 1, maxHeight - 1], [0, maxHeight - 1]], dtype="float32")
"""###############################################################################
# 计算齐次变换矩阵:cv2.getPerspectiveTransform(rect, dst)
###############################################################################"""
M = cv2.getPerspectiveTransform(rect, dst)
"""###############################################################################
# 透视变换(将输入矩形乘以(齐次变换矩阵),得到输出矩阵)
###############################################################################"""
warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight))
return warped
def resize(image, width=None, height=None, inter=cv2.INTER_AREA):
dim = None
(h, w) = image.shape[:2]
if width is None and height is None:
return image
if width is None:
r = height / float(h)
dim = (int(w * r), height)
else:
r = width / float(w)
dim = (width, int(h * r))
resized = cv2.resize(image, dim, interpolation=inter)
return resized
##############################################
image = cv2.imread(r'images\receipt.jpg')
ratio = image.shape[0] / 500.0 # resize之后坐标也会相同变化,故记录图像的比率
orig = image.copy()
image = resize(orig, height=500)
##############################################
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 转换为灰度图
gray = cv2.GaussianBlur(gray, (5, 5), 0) # 高斯滤波操作
edged = cv2.Canny(gray, 75, 200) # Canny算法(边缘检测)
##############################################
print("STEP 1: 边缘检测")
cv2.imshow("Image", image)
cv2.imshow("Edged", edged)
cv2.waitKey(0)
cv2.destroyAllWindows()
##############################################
# 轮廓检测
cnts, hierarchy = cv2.findContours(edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE) # 轮廓检测
cnts = sorted(cnts, key=cv2.contourArea, reverse=True)[:5] # 选定所有轮廓中前五个轮廓,并进行排序
for c in cnts:
peri = cv2.arcLength(c, True) # 计算轮廓近似
approx = cv2.approxPolyDP(c, 0.02 * peri, True) # 找出轮廓的多边形拟合曲线
if len(approx) == 4: # 如果当前轮廓是四个点(矩形),表示当前轮廓是所需求目标
screenCnt = approx
break
##############################################
print("STEP 2: 获取轮廓")
cv2.drawContours(image, [screenCnt], -1, (0, 255, 0), 2) # 在原图上画出检测得到的轮廓
cv2.imshow("Outline", image)
cv2.waitKey(0)
cv2.destroyAllWindows()
##############################################
# 透视变换
warped = four_point_transform(orig, screenCnt.reshape(4, 2) * ratio) # 得到的轮廓要乘以图像的缩放尺寸
warped = cv2.cvtColor(warped, cv2.COLOR_BGR2GRAY) # 转换为灰度图
ref = cv2.threshold(warped, 100, 255, cv2.THRESH_BINARY)[1] # 二值化处理
ref = resize(ref, height=500)
##############################################
print("STEP 3: 齐次变换")
cv2.imshow("Scanned", ref)
cv2.waitKey(0)
cv2.destroyAllWindows()
##############################################
# 轮廓点绘制的颜色通道是BGR; 但是Matplotlib是RGB; 故在绘图时,(0, 0, 255)会由BGR转换为RGB(红 - 蓝)
orig = cv2.cvtColor(orig, cv2.COLOR_BGR2RGB) # BGR转换为RGB格式
edged = cv2.cvtColor(edged, cv2.COLOR_BGR2RGB)
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
ref = cv2.cvtColor(ref, cv2.COLOR_BGR2RGB)
plt.subplot(2, 2, 1), plt.imshow(orig), plt.title('orig')
plt.subplot(2, 2, 2), plt.imshow(edged), plt.title('edged')
plt.subplot(2, 2, 3), plt.imshow(image), plt.title('contour')
plt.subplot(2, 2, 4), plt.imshow(ref), plt.title('rectangle')
plt.show()
"""######################################################################
# 计算轮廓的长度:retval = cv2.arcLength(curve, closed)
# 输入参数: curve 轮廓(曲线)。
# closed 若为true,表示轮廓是封闭的;若为false,则表示打开的。(布尔类型)
# 输出参数: retval 轮廓的长度(周长)。
######################################################################
# 找出轮廓的多边形拟合曲线:approxCurve = approxPolyDP(contourMat, 10, true)
# 输入参数: contourMat: 轮廓点矩阵(集合)
# epsilon: (double类型)指定的精度, 即原始曲线与近似曲线之间的最大距离。
# closed: (bool类型)若为true, 则说明近似曲线是闭合的; 反之, 若为false, 则断开。
# 输出参数: approxCurve: 轮廓点矩阵(集合);当前点集是能最小包容指定点集的。画出来即是一个多边形;
######################################################################"""
(三)全景拼接 —— detectAndDescribe()、matchKeypoints()、cv2.findHomography()、cv2.warpPerspective()、drawMatches()
函数功能:利用sift算法,实现全景拼接算法,将给定的两幅图片拼接为一幅.
11:从输入的两张图片里检测关键点、提取(sift)局部不变特征。
22:匹配的两幅图像之间的特征(Lowe’s算法:比较最近邻距离与次近邻距离)
33:使用RANSAC算法(随机抽样一致算法),利用匹配特征向量估计单映射变换矩阵(homography:单应性)。
44:利用33得到的单映矩阵应用透视变换。
import cv2
import numpy as np
"""#########################################################
# 预定义框架说明
# 定义一个Stitcher类:stitch()、detectAndDescribe()、matchKeypoints()、drawMatches()
# stitch() 拼接函数
# detectAndDescribe() 检测图像的SIFT关键特征点,并计算特征描述子
# matchKeypoints() 匹配两张图片的所有特征点
# cv2.findHomography() 计算单映射变换矩阵
# cv2.warpPerspective() 透视变换(作用:缝合图像)
# drawMatches() 建立直线关键点的匹配可视化
#
# 备注:cv2.warpPerspective()需要与cv2.findHomography()搭配使用。
#########################################################"""
class Stitcher:
##################################################################################
def stitch(self, images, ratio=0.75, reprojThresh=4.0, showMatches=False):
(imageB, imageA) = images # 获取输入图片
(kpsA, featuresA) = self.detectAndDescribe(imageA) # 检测A、B图片的SIFT关键特征点,并计算特征描述子
(kpsB, featuresB) = self.detectAndDescribe(imageB)
M = self.matchKeypoints(kpsA, kpsB, featuresA, featuresB, ratio, reprojThresh) # 匹配两张图片的所有特征点,返回匹配结果。
if M is None: # 如果返回结果为空,没有匹配成功的特征点,退出算法
return None
# 否则,提取匹配结果 #
(matches, H, status) = M # H是3x3视角变换矩阵
result = cv2.warpPerspective(imageA, H, (imageA.shape[1] + imageB.shape[1], imageA.shape[0])) # 将图片A进行视角变换,result是变换后图片
result[0:imageB.shape[0], 0:imageB.shape[1]] = imageB # 将图片B传入result图片最左端
if showMatches: # 检测是否需要显示图片匹配
vis = self.drawMatches(imageA, imageB, kpsA, kpsB, matches, status) # 生成匹配图片
return (result, vis)
return result
##################################################################################
def detectAndDescribe(self, image):
# gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 将彩色图片转换成灰度图
descriptor = cv2.xfeatures2d.SIFT_create() # 建立SIFT生成器
"""#####################################################
# 如果是OpenCV3.X,则用cv2.xfeatures2d.SIFT_create方法来实现DoG关键点检测和SIFT特征提取。
# 如果是OpenCV2.4,则用cv2.FeatureDetector_create方法来实现关键点的检测(DoG)。
#####################################################"""
(kps, features) = descriptor.detectAndCompute(image, None) # 检测SIFT特征点,并计算描述子
kps = np.float32([kp.pt for kp in kps]) # 将结果转换成NumPy数组
return (kps, features) # 返回特征点集,及对应的描述特征
##################################################################################
def matchKeypoints(self, kpsA, kpsB, featuresA, featuresB, ratio, reprojThresh):
matcher = cv2.BFMatcher() # 建立暴力匹配器
rawMatches = matcher.knnMatch(featuresA, featuresB, 2) # 使用KNN检测来自A、B图的SIFT特征匹配对,K=2
matches = []
for m in rawMatches:
if len(m) == 2 and m[0].distance < m[1].distance * ratio: # 当最近距离跟次近距离的比值小于ratio值时,保留此匹配对
matches.append((m[0].trainIdx, m[0].queryIdx)) # 存储两个点在featuresA, featuresB中的索引值
if len(matches) > 4: # 当筛选后的匹配对大于4时,计算视角变换矩阵
# 投影变换矩阵:3*3。有八个参数对应八个方程,其中一个为1用于归一化。对应四对,每对(x, y)
ptsA = np.float32([kpsA[i] for (_, i) in matches]) # 获取匹配对的点坐标
ptsB = np.float32([kpsB[i] for (i, _) in matches])
(H, status) = cv2.findHomography(ptsA, ptsB, cv2.RANSAC, reprojThresh) # 使用RANSAC算法利用匹配特征向量估计单映矩阵(homography:单应性)
return (matches, H, status)
return None # 如果匹配对小于4时,返回None
##################################################################################
def drawMatches(self, imageA, imageB, kpsA, kpsB, matches, status):
(hA, wA) = imageA.shape[:2]
(hB, wB) = imageB.shape[:2]
vis = np.zeros((max(hA, hB), wA + wB, 3), dtype="uint8")
vis[0:hA, 0:wA] = imageA # 将A、B图左右连接到一起
vis[0:hB, wA:] = imageB
for ((trainIdx, queryIdx), s) in zip(matches, status):
if s == 1: # 当点对匹配成功时,画到可视化图上
ptA = (int(kpsA[queryIdx][0]), int(kpsA[queryIdx][1]))
ptB = (int(kpsB[trainIdx][0]) + wA, int(kpsB[trainIdx][1]))
cv2.line(vis, ptA, ptB, (0, 255, 0), 1)
return vis # 返回可视化结果
##################################################################################
if __name__ == '__main__':
# 读取拼接图片
imageA = cv2.imread("left_01.png")
imageB = cv2.imread("right_01.png")
# 把图片拼接成全景图
stitcher = Stitcher() # 调用拼接函数
(result, vis) = stitcher.stitch([imageA, imageB], showMatches=True)
# 显示所有图片
cv2.imshow("Image A", imageA)
cv2.imshow("Image B", imageB)
cv2.imshow("Keypoint Matches", vis)
cv2.imshow("Result", result)
cv2.waitKey(0)
cv2.destroyAllWindows()
基于OpenCV全景拼接(Python)SIFT/SURF
(四)停车场车位检测(基于Keras的CNN分类) —— pickle.dump()、pickle.load()、cv2.fillPoly()、cv2.bitwise_and()、cv2.circle()、cv2.HoughLinesP()、cv2.line()
该项目共分为三个py文件:Parking.py(定义所有的功能函数)、train.py(训练神经网络)、park_test.py(开始检测停车位状态)
(1)Parking.py
#####################################################
# Parking.py
#####################################################
import matplotlib.pyplot as plt
import cv2
import os
import glob
import numpy as np
class Parking:
def show_images(self, images, cmap=None):
cols = 2
rows = (len(images)+1)//cols
plt.figure(figsize=(15, 12))
for i, image in enumerate(images):
plt.subplot(rows, cols, i+1)
cmap = 'gray' if len(image.shape) == 2 else cmap
plt.imshow(image, cmap=cmap)
plt.xticks([])
plt.yticks([])
plt.tight_layout(pad=0, h_pad=0, w_pad=0)
plt.show()
def cv_show(self, name, img):
cv2.imshow(name, img)
cv2.waitKey(0)
cv2.destroyAllWindows()
def select_rgb_white_yellow(self, image):
# 图像背景信息过滤(即截取图像中,指定范围的颜色)
lower = np.uint8([120, 120, 120])
upper = np.uint8([255, 255, 255])
# (1)lower_red和高于upper_red的部分分别变成0
# (2)lower_red~upper_red之间的值变成255
white_mask = cv2.inRange(image, lower, upper)
self.cv_show('white_mask', white_mask)
masked = cv2.bitwise_and(image, image, mask=white_mask)
self.cv_show('masked', masked)
return masked
def convert_gray_scale(self, image):
return cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
def detect_edges(self, image, low_threshold=50, high_threshold=200):
return cv2.Canny(image, low_threshold, high_threshold)
def filter_region(self, image, vertices):
# 功能:剔除掉不需要的地方,提取出多边形(所有车位的位置),纯白显示
#####################################################
# zeros_like(array, dtype=float, order='C')
# 作用:返回一个给定形状和类型的数据,填充全为0。
# 输入参数 (1)array:输入数据
# (2)dtype:返回数组的数据类型(可选参数,默认float)
# (3)order:C代表行优先;F代表列优先(可选参数)
#####################################################
mask = np.zeros_like(image) # 新建模板(0:纯黑色)
if len(mask.shape) == 2:
#####################################################
# 填充任意多边形:cv2.fillPoly(img, ppt, Scalar);
# 输入参数 (1)img 在该图像上绘图。
# (2)ppt 多边形的顶点集
# (5)Scarlar 填充多边形的颜色(255, 255, 255),即RGB的值为白色
#####################################################
cv2.fillPoly(mask, vertices, 255) # 填充多边形(255:纯白色)
self.cv_show('mask', mask)
#####################################################
# cv2.bitwise_and() # 按位与
# cv2.bitwise_or() # 或
# cv2.bitwise_not() # 非
# cv2.bitwise_xor() # 异或
###################################
# dst = cv2.bitwise_and(src1, src2, mask=mask)
# src1/src2是相同类型和大小的图像
# mask=mask表示要提取的区域(可选参数)
# (1&1=1,1&0=0,0&1=0,0&0=0)
#####################################################
return cv2.bitwise_and(image, mask) # 按位与操作
def select_region(self, image):
# 功能:(手动)选择区域 —— 在原图中,筛选出所有车位的位置,并框成一个多边形。
# 多边形的关键点位置由自定义设置,详细如下:
rows, cols = image.shape[:2] # 获得图像的长和宽
pt_1 = [cols*0.05, rows*0.90] # 位置点1
pt_2 = [cols*0.05, rows*0.70] # 位置点2
pt_3 = [cols*0.30, rows*0.55] # 位置点3
pt_4 = [cols*0.6, rows*0.15] # 位置点4
pt_5 = [cols*0.90, rows*0.15] # 位置点5
pt_6 = [cols*0.90, rows*0.90] # 位置点6
vertices = np.array([[pt_1, pt_2, pt_3, pt_4, pt_5, pt_6]], dtype=np.int32) # 将数据转换为numpy数组
point_img = image.copy()
point_img = cv2.cvtColor(point_img, cv2.COLOR_GRAY2RGB) # 转换为灰度图
for point in vertices[0]: # 遍历所有的位置点
"""#####################################################
# 绘制圆型:cv2.circle(图像, 圆心, 半径, 颜色, 厚度)
# 输入参数 (1)图像:在该图像上绘图。
# (2)圆心:圆的中心坐标。坐标表示为两个值的元组, 即(X坐标值, Y坐标值)。
# (3)半径:圆的半径。
# (4)颜色:圆的边界线颜色。对于BGR, 我们传递一个元组。例如:(255, 0, 0)为蓝色。
# (5)厚度:正数表示线的粗细。其中:-1表示实心圆。
#####################################################"""
cv2.circle(point_img, (point[0], point[1]), 10, (0, 0, 255), 4) # 在每一个位置点绘制一个空心圆
self.cv_show('point_img', point_img)
return self.filter_region(image, vertices)
def hough_lines(self, image):
"""#####################################################
# 检测图像中所有的线:cv2.HoughLinesP(image, rho=0.1, theta=np.pi / 10, threshold=15, minLineLength=9, maxLineGap=4)
# image 输入的图像需要是边缘检测后的结果
# minLineLengh (线的最短长度,比这个短的都被忽略)
# MaxLineCap (两条直线之间的最大间隔,小于此值,认为是一条直线)
# rho 距离精度
# theta 角度精度
# threshod 超过设定阈值才被检测出线段
#####################################################"""
return cv2.HoughLinesP(image, rho=0.1, theta=np.pi/10, threshold=15, minLineLength=9, maxLineGap=4)
def draw_lines(self, image, lines, color=[255, 0, 0], thickness=2, make_copy=True):
# 功能:在原图上画出满足条件的所有线
if make_copy:
image = np.copy(image)
cleaned = []
for line in lines:
for x1, y1, x2, y2 in line: # 一条线由两个点组成,每个点的坐标为(x, y)
# abs(y2-y1) <= 1 ———— 图像中都是直线,斜率趋近于0
# abs(x2-x1) >= 25 and abs(x2-x1) <= 55 ———— 线段自定义筛选(依据实际情况设置)
if abs(y2-y1) <= 1 and abs(x2-x1) >= 25 and abs(x2-x1) <= 55:
cleaned.append((x1, y1, x2, y2)) # 保存满足条件的所有线
cv2.line(image, (x1, y1), (x2, y2), color, thickness) # 在原图上,画出满足条件的所有线
print(" No lines detected: ", len(cleaned))
return image
def identify_blocks(self, image, lines, make_copy=True):
#####################################################
# 功能:识别出所有的车位
# Step 1: 过滤部分直线,提取有效的线(即对应于车位的线)
# Step 2: 对直线进行排序
# Step 3: 找到多个列,每一列对应一排车
# Step 4: 得到每一列矩形的坐标
# Step 5: 把列矩形画出来
#####################################################
if make_copy:
new_image = np.copy(image)
# Step 1: 过滤部分直线,提取有效的线(即对应于车位的线)
cleaned = []
for line in lines:
for x1, y1, x2, y2 in line:
if abs(y2-y1) <=1 and abs(x2-x1) >=25 and abs(x2-x1) <= 55:
cleaned.append((x1, y1, x2, y2)) # 保存满足条件的所有线
# Step 2: 对直线进行排序
import operator
list1 = sorted(cleaned, key=operator.itemgetter(0, 1)) # 给所有的线标记顺序(排序:从上到下,从左到右)
# Step 3: 找到多个列,每一列对应一排车
clusters = {} # 找到同一列的所有线
dIndex = 0
clus_dist = 10 # 列与最近一列的距离(依据实际情况设置)
for i in range(len(list1) - 1):
distance = abs(list1[i+1][0] - list1[i][0])
if distance <= clus_dist:
if not dIndex in clusters.keys(): clusters[dIndex] = []
clusters[dIndex].append(list1[i])
clusters[dIndex].append(list1[i + 1])
else:
dIndex += 1 # 同一列的线就汇总,否则跳过
# Step 4: 得到每一列矩形的坐标
rects = {}
i = 0
for key in clusters: # 12列
all_list = clusters[key]
cleaned = list(set(all_list))
if len(cleaned) > 5: # 如果个数大于5,则定义为一列
cleaned = sorted(cleaned, key=lambda tup: tup[1])
avg_y1 = cleaned[0][1] # 提取每一列第一条线。 [0]表示第一条线
avg_y2 = cleaned[-1][1] # 提取每一列最后一条线 [-1]表示最后一条线
avg_x1 = 0 # 由于不同线的 x 层次不齐,故取均值
avg_x2 = 0
for tup in cleaned:
avg_x1 += tup[0]
avg_x2 += tup[2]
avg_x1 = avg_x1/len(cleaned) # x1是矩形的起始点
avg_x2 = avg_x2/len(cleaned) # x2是矩形的终止点
rects[i] = (avg_x1, avg_y1, avg_x2, avg_y2) # 得到每一列矩形的四点坐标
i += 1
print("Num Parking Lanes: ", len(rects)) # 共有12个矩形
# Step 5: 把列矩形画出来
buff = 7
for key in rects: # key表示第几列
tup_topLeft = (int(rects[key][0] - buff), int(rects[key][1]))
tup_botRight = (int(rects[key][2] + buff), int(rects[key][3]))
cv2.rectangle(new_image, tup_topLeft, tup_botRight, (0, 255, 0), 3)
return new_image, rects
def draw_parking(self, image, rects, make_copy=True, color=[255, 0, 0], thickness=2, save=True):
if make_copy:
new_image = np.copy(image)
gap = 15.5 # 固定每两个停车位之间的距离间隔(y轴)
spot_dict = {} # 字典:一个车位对应一个位置
tot_spots = 0
# 微调 ——— 由于检测得到的矩形具有一定的误差,所以进行人为操作,达到精度化
adj_y1 = {0: 20, 1: -10, 2: 0, 3: -11, 4: 28, 5: 5, 6: -15, 7: -15, 8: -10, 9: -30, 10: 9, 11: -32}
adj_y2 = {0: 30, 1: 50, 2: 15, 3: 10, 4: -15, 5: 15, 6: 15, 7: -20, 8: 15, 9: 15, 10: 0, 11: 30}
adj_x1 = {0: -8, 1: -15, 2: -15, 3: -15, 4: -15, 5: -15, 6: -15, 7: -15, 8: -10, 9: -10, 10: -10, 11: 0}
adj_x2 = {0: 0, 1: 15, 2: 15, 3: 15, 4: 15, 5: 15, 6: 15, 7: 15, 8: 10, 9: 10, 10: 10, 11: 0}
for key in rects: # key表示第几列
tup = rects[key]
x1 = int(tup[0] + adj_x1[key])
x2 = int(tup[2] + adj_x2[key])
y1 = int(tup[1] + adj_y1[key])
y2 = int(tup[3] + adj_y2[key])
cv2.rectangle(new_image, (x1, y1), (x2, y2), (0, 255, 0), 2) # 在图像上画出微调后的矩形
num_splits = int(abs(y2-y1)//gap) # 计算每一列可以平均停放多少辆车(由于误差不能识别出精确的停车位,故进行平均计算估计)
for i in range(0, num_splits+1): # 切六刀,则有七个停车位
y = int(y1 + i*gap)
#####################################################
# 画直线段:cv2.line(img, pt1, pt2, color, thickness)
# 输入参数 img 要划的线所在的图像;
# pt1 直线起点
# pt2 直线终点
# color 直线的颜色
# thickness=1 线条粗细
#####################################################
# 画出所有停车位的横线
cv2.line(new_image, (x1, y), (x2, y), color, thickness)
if 0 < key < len(rects) - 1: # 第一列与最后一列都是单排停车位,其余都是双排停车位(依据实际情况设置)
# 画出双排停车位之间的竖直线
x = int((x1 + x2)/2) # 两个位置点的中点就是对应的竖直线坐标
cv2.line(new_image, (x, y1), (x, y2), color, thickness)
# 计算数量
if key == 0 or key == (len(rects) - 1): # 如果是单排停车位,直接+1
tot_spots += num_splits + 1
else: # 如果是双排停车位,就要+1,再乘以2
tot_spots += 2*(num_splits + 1)
# 用字典将每个停车位进行键值对一一对应
if key == 0 or key == (len(rects) - 1): # 第一列或最后一列(单排停车位)
for i in range(0, num_splits+1):
cur_len = len(spot_dict)
y = int(y1 + i*gap)
spot_dict[(x1, y, x2, y+gap)] = cur_len + 1 # 第一列和最后一列停车位的坐标
else: # 双排停车位
for i in range(0, num_splits+1):
cur_len = len(spot_dict)
y = int(y1 + i*gap)
x = int((x1 + x2)/2) # 双排停车位对应的中点位置
spot_dict[(x1, y, x, y+gap)] = cur_len + 1 # 双排停车位的左边一排坐标
spot_dict[(x, y, x2, y+gap)] = cur_len + 2 # 双排停车位的右边一排坐标
print("total parking spaces: ", tot_spots, cur_len)
if save:
filename = 'with_parking.jpg'
cv2.imwrite(filename, new_image)
return new_image, spot_dict
def assign_spots_map(self, image, spot_dict, make_copy=True, color=[255, 0, 0], thickness=2):
if make_copy:
new_image = np.copy(image)
for spot in spot_dict.keys():
(x1, y1, x2, y2) = spot
cv2.rectangle(new_image, (int(x1), int(y1)), (int(x2), int(y2)), color, thickness)
return new_image
def save_images_for_cnn(self, image, spot_dict, folder_name='cnn_data'):
# 功能:裁剪停车位得到所有停车位对应的图像,并保存到指定的文件夹路径下。(提供数据给CNN训练。训练前需人工筛选图像,分为两类:车位是否被占用。)
for spot in spot_dict.keys(): # 遍历所有停车位 —— 字典的键进行索引
(x1, y1, x2, y2) = spot
(x1, y1, x2, y2) = (int(x1), int(y1), int(x2), int(y2)) # 坐标值取整
spot_img = image[y1:y2, x1:x2] # 裁剪停车位
spot_img = cv2.resize(spot_img, (0, 0), fx=2.0, fy=2.0) # 图像裁剪(原图像是在太mini)
spot_id = spot_dict[spot] # 停车位坐标 —— 键对应的值
filename = 'spot' + str(spot_id) + '.jpg' # 每个停车位用键来命名(以此可以后续索引需求)
print(spot_img.shape, filename, (x1, x2, y1, y2))
cv2.imwrite(os.path.join(folder_name, filename), spot_img) # 保存停车位图像到指定的文件夹(cnn_data)路径下
def make_prediction(self, image, model, class_dictionary):
# 预处理
img = image/255.
# 转换成4D tensor
image = np.expand_dims(img, axis=0)
# 用训练好的模型进行训练
class_predicted = model.predict(image)
inID = np.argmax(class_predicted[0])
label = class_dictionary[inID]
return label
def predict_on_image(self,image, spot_dict , model,class_dictionary,make_copy=True, color=[0, 255, 0], alpha=0.5):
if make_copy:
new_image = np.copy(image)
overlay = np.copy(image)
self.cv_show('new_image', new_image)
cnt_empty = 0
all_spots = 0
for spot in spot_dict.keys():
all_spots += 1
(x1, y1, x2, y2) = spot
(x1, y1, x2, y2) = (int(x1), int(y1), int(x2), int(y2))
spot_img = image[y1:y2, x1:x2]
spot_img = cv2.resize(spot_img, (48, 48))
label = self.make_prediction(spot_img,model,class_dictionary) # 预测图像(停车位)是否被占用
if label == 'empty':
cv2.rectangle(overlay, (int(x1), int(y1)), (int(x2), int(y2)), color, -1)
cnt_empty += 1
cv2.addWeighted(overlay, alpha, new_image, 1 - alpha, 0, new_image) # 图像融合
cv2.putText(new_image, "Available: %d spots" % cnt_empty, (30, 95), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
cv2.putText(new_image, "Total: %d spots" % all_spots, (30, 125), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
save = False
if save:
filename = 'with_marking.jpg'
cv2.imwrite(filename, new_image)
self.cv_show('new_image', new_image)
return new_image
def predict_on_video(self, video_name, final_spot_dict, model, class_dictionary, ret=True):
cap = cv2.VideoCapture(video_name)
count = 0
while ret:
ret, image = cap.read()
count += 1
if count == 5: # 每四帧图像判断一次
count = 0
new_image = np.copy(image)
overlay = np.copy(image)
cnt_empty = 0
all_spots = 0
color = [0, 255, 0]
alpha = 0.5
for spot in final_spot_dict.keys():
all_spots += 1
(x1, y1, x2, y2) = spot
(x1, y1, x2, y2) = (int(x1), int(y1), int(x2), int(y2))
spot_img = image[y1:y2, x1:x2]
spot_img = cv2.resize(spot_img, (48,48))
label = self.make_prediction(spot_img, model, class_dictionary) # 预测帧图像(停车位)是否被占用
if label == 'empty':
cv2.rectangle(overlay, (int(x1), int(y1)), (int(x2), int(y2)), color, -1)
cnt_empty += 1
cv2.addWeighted(overlay, alpha, new_image, 1 - alpha, 0, new_image)
cv2.putText(new_image, "Available: %d spots" % cnt_empty, (30, 95), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
cv2.putText(new_image, "Total: %d spots" % all_spots, (30, 125), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
cv2.imshow('frame', new_image)
if cv2.waitKey(10) & 0xFF == ord('q'):
break
cv2.destroyAllWindows()
cap.release()
(2)train.py
#####################################################
# train.py
#####################################################
import numpy
import os
from keras import applications
from keras.preprocessing.image import ImageDataGenerator
from keras import optimizers
from keras.models import Sequential, Model
from keras.layers import Dropout, Flatten, Dense, GlobalAveragePooling2D
from keras import backend as k
from keras.callbacks import ModelCheckpoint, LearningRateScheduler, TensorBoard, EarlyStopping
from keras.models import Sequential
from keras.layers.normalization import BatchNormalization
from keras.layers.convolutional import Conv2D
from keras.layers.convolutional import MaxPooling2D
from keras.initializers import TruncatedNormal
from keras.layers.core import Activation
from keras.layers.core import Flatten
from keras.layers.core import Dropout
from keras.layers.core import Dense
files_train = 0
files_validation = 0
########################################
cwd = os.getcwd() # 获取当前工作路径
folder = 'train_data/train' # 训练数据
for sub_folder in os.listdir(folder):
path, dirs, files = next(os.walk(os.path.join(folder, sub_folder))) # 读取数据
files_train += len(files)
########################################
folder = 'train_data/test' # 测试数据
for sub_folder in os.listdir(folder):
path, dirs, files = next(os.walk(os.path.join(folder, sub_folder))) # 读取数据
files_validation += len(files)
########################################
print(files_train, files_validation)
########################################
# CNN训练参数指定
img_width, img_height = 48, 48
train_data_dir = "train_data/train"
validation_data_dir = "train_data/test"
nb_train_samples = files_train
nb_validation_samples = files_validation
batch_size = 32
epochs = 15
num_classes = 2
# 调用kera框架中的applications中的VGG16网络。里面封装了好几个网络模型(VGG16、VGG19、Resnet50、MobileNet等等)
# weights='imagenet' 表示直接调用imagenet训练好的权重(当前数据太少)
model = applications.VGG16(weights='imagenet', include_top=False, input_shape=(img_width, img_height, 3))
########################################
# 将前10层网络" 冻起来 "
for layer in model.layers[:10]:
layer.trainable = False
x = model.output
x = Flatten()(x)
predictions = Dense(num_classes, activation="softmax")(x)
model_final = Model(input=model.input, output=predictions)
model_final.compile(loss="categorical_crossentropy", optimizer = optimizers.SGD(lr=0.0001, momentum=0.9), metrics=["accuracy"])
########################################
# 数据增强
train_datagen = ImageDataGenerator(rescale=1./255, horizontal_flip=True, fill_mode="nearest",
zoom_range=0.1, width_shift_range=0.1, height_shift_range=0.1, rotation_range=5)
test_datagen = ImageDataGenerator(rescale=1./255, horizontal_flip=True, fill_mode="nearest",
zoom_range=0.1, width_shift_range=0.1, height_shift_range=0.1, rotation_range=5)
train_generator = train_datagen.flow_from_directory(train_data_dir, target_size=(img_height, img_width), batch_size=batch_size, class_mode="categorical")
validation_generator = test_datagen.flow_from_directory(validation_data_dir, target_size=(img_height, img_width), class_mode="categorical")
########################################
checkpoint = ModelCheckpoint("car1.h5", monitor='val_acc', verbose=1, save_best_only=True, save_weights_only=False, mode='auto', period=1)
early = EarlyStopping(monitor='val_acc', min_delta=0, patience=10, verbose=1, mode='auto')
history_object = model_final.fit_generator(train_generator, samples_per_epoch=nb_train_samples, epochs=epochs,
validation_data=validation_generator, nb_val_samples=nb_validation_samples, callbacks=[checkpoint, early])
# 训练完之后会自动生成文件:car1.h5
# 备注:最终的训练结果只有百分之九十。(1)数据预处理还有进步空间(2)神经网络模型可优化
(3)park_test.py
#####################################################
# park_test.py
#####################################################
from __future__ import division
import matplotlib.pyplot as plt
import cv2
import os
import glob
import numpy as np
from keras.applications.imagenet_utils import preprocess_input
from keras.models import load_model
from keras.preprocessing import image
from PIL import Image
# Pillow(PIL)是Python中较为基础的图像处理库,主要用于图像的基本处理,比如裁剪图像、调整图像大小和图像颜色处理等。
# 与Pillow相比,OpenCV和Scikit-image的功能更为丰富,所以使用起来也更为复杂,主要应用于机器视觉、图像分析等领域,比如众所周知的“人脸识别”应用 。
import pickle
from Parking import Parking # 导入自定义库
cwd = os.getcwd()
def img_process(test_images, park): # park:实例化的类
white_yellow_images = list(map(park.select_rgb_white_yellow, test_images)) # select_rgb_white_yellow():图像背景信息过滤
park.show_images(white_yellow_images)
########################################################################################################
gray_images = list(map(park.convert_gray_scale, white_yellow_images)) # convert_gray_scale():转换为灰度图
park.show_images(gray_images)
########################################################################################################
edge_images = list(map(lambda image: park.detect_edges(image), gray_images)) # detect_edges():使用cv2.Canny算法进行边缘检测
park.show_images(edge_images)
########################################################################################################
roi_images = list(map(park.select_region, edge_images)) # select_region():筛选出多边形(停车场的位置),去除图像中冗余的区域
park.show_images(roi_images)
########################################################################################################
list_of_lines = list(map(park.hough_lines, roi_images)) # hough_lines():检测图像中所有的线
########################################################################################################
line_images = []
for image, lines in zip(test_images, list_of_lines):
line_images.append(park.draw_lines(image, lines)) # draw_lines():画出图像中所有满足条件的线(即停车线)
park.show_images(line_images)
########################################################################################################
rect_images = [] # new_image 绘制有所有矩形的图像
rect_coords = [] # rects 每个矩形对应的四点坐标
for image, lines in zip(test_images, list_of_lines):
new_image, rects = park.identify_blocks(image, lines) # identify_blocks(): 画出每一列的矩形
rect_images.append(new_image)
rect_coords.append(rects)
park.show_images(rect_images) # 绘制(矩形)图像
########################################################################################################
delineated = [] # new_image 绘制有微调后的所有矩形、以及所有停车位的图像
spot_pos = [] # spot_dict 每个停车位的坐标(字典 - 数据结构)
for image, rects in zip(test_images, rect_coords):
new_image, spot_dict = park.draw_parking(image, rects) # draw_parking:画出停车位
delineated.append(new_image)
spot_pos.append(spot_dict)
park.show_images(delineated) # 绘制(矩形+停车位)图像
final_spot_dict = spot_pos[1] # 取出字典(键值对)的所有值(每个值对应一个停车位的坐标位置)。[1]:值
print(len(final_spot_dict)) # 打印所有的停车位数
########################################################################################################
with open('spot_dict.pickle', 'wb') as handle: # 打开文件(open),且自动关闭(with)
"""#####################################################
# Python中的pickle模块实现了基本的数据序列与反序列化。序列化对象可以在磁盘上保存对象,并在需要的时候读取出来。任何对象都可以执行序列化操作。
#####################################################
# (1)序列化-存档:pickle.dump(obj, file, protocol)
# 输入参数 对象:就是你要存的东西,类型可以是list、string以及其他任何类型
# 文件:就是要将对象存储的目标文件
# 使用协议:有3种,索引0为ASCII(默认值),1是旧式2进制,2是新式2进制协议
# fw = open("pickleFileName.txt", "wb")
# pickle.dump("try", fw)
#####################################################
# (2)反序列化-读档:pickle.load(file)
# fr = open("pickleFileName.txt", "rb")
# result = pickle.load(fr)
#####################################################"""
pickle.dump(final_spot_dict, handle, protocol=pickle.HIGHEST_PROTOCOL) # 将当前预处理的结果保存下来,以后直接调用即可。
########################################################################################################
park.save_images_for_cnn(test_images[0], final_spot_dict) # save_images_for_cnn(): 调用CNN神经网络对车位是否被占用进行识别
return final_spot_dict
########################################################################################################
def keras_model(weights_path):
# from keras.models import load_model
model = load_model(weights_path) # load_model():可实现直接读取文件: car1.h5(神经网络训练后生成的模型)
return model
def img_test(test_images, final_spot_dict, model, class_dictionary):
for i in range(len(test_images)):
predicted_images = park.predict_on_image(test_images[i], final_spot_dict, model, class_dictionary)
# predict_on_image():
def video_test(video_name, final_spot_dict, model, class_dictionary):
name = video_name
cap = cv2.VideoCapture(name)
park.predict_on_video(name, final_spot_dict, model, class_dictionary, ret=True)
if __name__ == '__main__':
# 文件夹名:test_images
test_images = [plt.imread(path) for path in glob.glob('test_images/*.jpg')]
weights_path = 'car1.h5'
video_name = 'parking_video.mp4'
# class_dictionary = {0: 'empty', 1: 'occupied'} # 车位占用状态
class_dictionary = {} # 车位占用状态
class_dictionary[0] = 'empty' # 车位为空
class_dictionary[1] = 'occupied' # 车位已占用
park = Parking() # 类的实例化
park.show_images(test_images)
final_spot_dict = img_process(test_images, park) # 图像预处理
model = keras_model(weights_path) # 加载神经网络训练好的模型
img_test(test_images, final_spot_dict, model, class_dictionary)
video_test(video_name, final_spot_dict, model, class_dictionary)
8种主流深度学习框架介绍 Python 中的 PIL 库
(五)答题卡识别与判卷 —— cv2.putText()、cv2.countNonZero()
import cv2 # opencv读取的格式是BGR
import numpy as np
import matplotlib.pyplot as plt # Matplotlib是RGB
def order_points(pts):
# 一共4个坐标点
rect = np.zeros((4, 2), dtype="float32")
# 按顺序找到对应坐标0123分别是 左上,右上,右下,左下
# 计算左上,右下
s = pts.sum(axis=1)
rect[0] = pts[np.argmin(s)]
rect[2] = pts[np.argmax(s)]
# 计算右上和左下
diff = np.diff(pts, axis=1)
rect[1] = pts[np.argmin(diff)]
rect[3] = pts[np.argmax(diff)]
return rect
def four_point_transform(image, pts):
# 获取输入坐标点
rect = order_points(pts)
(tl, tr, br, bl) = rect
# 计算输入的w和h值
widthA = np.sqrt(((br[0] - bl[0]) ** 2) + ((br[1] - bl[1]) ** 2))
widthB = np.sqrt(((tr[0] - tl[0]) ** 2) + ((tr[1] - tl[1]) ** 2))
maxWidth = max(int(widthA), int(widthB))
heightA = np.sqrt(((tr[0] - br[0]) ** 2) + ((tr[1] - br[1]) ** 2))
heightB = np.sqrt(((tl[0] - bl[0]) ** 2) + ((tl[1] - bl[1]) ** 2))
maxHeight = max(int(heightA), int(heightB))
# 变换后对应坐标位置
dst = np.array([[0, 0], [maxWidth - 1, 0], [maxWidth - 1, maxHeight - 1], [0, maxHeight - 1]], dtype="float32")
# 计算变换矩阵
M = cv2.getPerspectiveTransform(rect, dst) # 计算齐次变换矩阵:cv2.getPerspectiveTransform(rect, dst)
warped = cv2.warpPerspective(image, M, (maxWidth, maxHeight)) # 透视变换:(将输入矩形乘以(齐次变换矩阵),得到输出矩阵)
return warped
def sort_contours(cnts, method="left-to-right"):
reverse = False
i = 0
if method == "right-to-left" or method == "bottom-to-top":
reverse = True
if method == "top-to-bottom" or method == "bottom-to-top":
i = 1
boundingBoxes = [cv2.boundingRect(c) for c in cnts]
(cnts, boundingBoxes) = zip(*sorted(zip(cnts, boundingBoxes), key=lambda b: b[1][i], reverse=reverse))
return cnts, boundingBoxes
"""#############################################################
# if __name__ == '__main__':
# (1)“__name__”是Python的内置变量,用于指代当前模块。
# (2)当哪个模块被直接执行时,该模块“__name__”的值就是“__main__”。
# (3)当被导入另一模块时,“__name__”的值就是模块的真实名称。
#############################################################"""
# 需给定每张图像对应选项的正确答案(字典:键对应行,值对应每行的答案)
ANSWER_KEY = {0: 1, 1: 4, 2: 0, 3: 3, 4: 1}
# 图像预处理
image = cv2.imread(r"images/test_01.png")
contours_img = image.copy()
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) # 转换为灰度图
blurred = cv2.GaussianBlur(gray, (5, 5), 0) # 高斯滤波-去除噪音
edged = cv2.Canny(blurred, 75, 200) # Canny算子边缘检测
cnts, hierarchy = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 轮廓检测
cv2.drawContours(contours_img, cnts, -1, (0, 0, 255), 3) # 画出轮廓(答题卡)
###################################################################
# 提取答题卡并进行透视变化
docCnt = None
if len(cnts) > 0:
cnts = sorted(cnts, key=cv2.contourArea, reverse=True) # 根据轮廓大小进行排序
for c in cnts: # 遍历每一个轮廓
peri = cv2.arcLength(c, True) # 计算轮廓的长度
approx = cv2.approxPolyDP(c, 0.02*peri, True) # 找出轮廓的多边形拟合曲线
if len(approx) == 4: # 找到的轮廓是四边形(对应四个顶点)
docCnt = approx
break
warped = four_point_transform(gray, docCnt.reshape(4, 2)) # 透视变换(齐次变换矩阵)
warped1 = warped.copy()
thresh = cv2.threshold(warped, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1] # 0:表示系统自动判断;THRESH_OTSU:自适应阈值设置
###############################
thresh_Contours = thresh.copy()
cnts, hierarchy = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 找到每一个圆圈轮廓
cv2.drawContours(thresh_Contours, cnts, -1, (0, 0, 255), 3) # 画出所有轮廓
###################################################################
# 提取答题卡中所有的有效选项(圆圈)
questionCnts = [] # 提取每个选项的轮廓
for c in cnts:
(x, y, w, h) = cv2.boundingRect(c) # 获取轮廓的尺寸
ar = w / float(h) # 计算比例
if w >= 20 and h >= 20 and 0.9 <= ar <= 1.1: # 自定义设置大小(根据实际情况)
questionCnts.append(c)
questionCnts = sort_contours(questionCnts, method="top-to-bottom")[0] # 按照从上到下对所有的选项进行排序
###############################
correct = 0
for (q, i) in enumerate(np.arange(0, len(questionCnts), 5)): # 每排有5个选项
cnts = sort_contours(questionCnts[i:i + 5])[0] # 对每一排进行排序
bubbled = None
for (j, c) in enumerate(cnts): # 遍历每一排对应的五个结果
mask = np.zeros(thresh.shape, dtype="uint8") # 使用mask来判断结果(全黑:0)表示涂写答案正确
cv2.drawContours(mask, [c], -1, 255, -1) # -1表示填充
# cv_show('mask', mask) # 展示每个选项
mask = cv2.bitwise_and(thresh, thresh, mask=mask) # mask=mask表示要提取的区域(可选参数)
total = cv2.countNonZero(mask) # 通过计算非零点数量来算是否选择这个答案
if bubbled is None or total > bubbled[0]: # 记录最大数
bubbled = (total, j)
color = (0, 0, 255) # 对比正确答案
k = ANSWER_KEY[q]
# 判断正确
if k == bubbled[1]:
color = (0, 255, 0)
correct += 1
cv2.drawContours(warped, [cnts[k]], -1, color, 3) # 画出轮廓
###################################################################
# 展示结果
score = (correct / 5.0) * 100 # 计算总得分
print("[INFO] score: {:.2f}%".format(score))
"""###################################################################
# 在图像上添加文本内容: cv2.putText(img, str(i), (123,456), cv2.FONT_HERSHEY_PLAIN, 2, (0,255,0), 3)
# 各参数依次是:图片,添加的文字,左上角坐标,字体类型,字体大小,颜色,字体粗细
# 添加的字体:"{:.2f}%".format(score) ———— 表示添加score字符串。并且保留全部的整数位,小数点位保留两位。
###################################################################"""
cv2.putText(warped, "{:.1f}%".format(score), (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 0, 255), 2)
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # opencv读取的格式是BGR,Matplotlib是RGB
contours_img = cv2.cvtColor(contours_img, cv2.COLOR_BGR2RGB)
plt.subplot(241), plt.imshow(image, cmap='gray'), plt.axis('off'), plt.title('image')
plt.subplot(242), plt.imshow(blurred, cmap='gray'), plt.axis('off'), plt.title('cv2.GaussianBlur')
plt.subplot(243), plt.imshow(edged, cmap='gray'), plt.axis('off'), plt.title('cv2.Canny')
plt.subplot(244), plt.imshow(contours_img, cmap='gray'), plt.axis('off'), plt.title('cv2.findContours')
plt.subplot(245), plt.imshow(warped1, cmap='gray'), plt.axis('off'), plt.title('cv2.warpPerspective')
plt.subplot(246), plt.imshow(thresh_Contours, cmap='gray'), plt.axis('off'), plt.title('cv2.findContours')
plt.subplot(247), plt.imshow(warped, cmap='gray'), plt.axis('off'), plt.title('cv2.warpPerspective')
plt.show()
(六)背景建模(动态目标识别) —— cv2.getStructuringElement()、cv2.createBackgroundSubtractorMOG2()
"""########################################################
# 背景建模(检测动态目标)
# 方法一:帧差法
# 介绍:由于场景中的目标在运动,目标的影像在不同图像帧中的位置不同。
# (1)该类算法对时间上连续的两帧图像进行差分运算,不同帧对应的像素点相减,判断灰度差的绝对值。
# (2)当绝对值超过一定阈值时,即可判断为运动目标,从而实现目标的检测功能。
# 优缺点:帧差法非常简单,但是会引入噪音和空洞问题
# 方法二:混合高斯模型
# 介绍:(1)背景训练,对图像中每个背景采用一个【混合高斯模型】进行模拟,每个背景的混合高斯的个数可以自适应。
# (2)测试阶段,对新来的像素进行GMM匹配,如果该像素值能够匹配其中一个高斯,则认为是背景,否则认为是前景。
# 特点1:由于整个过程GMM模型在不断更新学习中,所以对动态背景有一定的鲁棒性。
# 特点2:在视频中对于像素点的变化情况应当是符合高斯分布,背景的实际分布应当是多个高斯分布混合在一起,每个高斯模型也可以带有权重。
# 混合高斯模型学习方法
# 1.首先初始化每个高斯模型矩阵参数。
# 2.取视频中T帧图像数据用来训练高斯混合模型,并将第一个像素当做第一个高斯分布。
# 3.其后的像素值与前一个高斯分布的均值进行比较,如果两者差值在3倍方差以内,则属于同一个高斯分布,并对其进行参数更新。否则用此像素创建一个新的高斯分布。
# 混合高斯模型测试方法
# 在测试阶段,对新来像素点的值与混合高斯模型中的每一个均值进行比较,如果其差值在2倍的方差之间的话,则认为是背景,否则认为是前景(动态目标)。
# 将前景赋值为255,背景赋值为0。这样就形成了一副前景二值图。
########################################################"""
import cv2
cap = cv2.VideoCapture('test.avi') # 捕获摄像头
"""########################################################
# 构造卷积核:cv2.getStructuringElement(shape, ksize, anchor=None)
# 输入参数 shape:(1)Enumerator
# (2)MORPH_RECT 矩形
# (3)MORPH_CROSS 十字型
# (4)MORPH_ELLIPSE 椭圆形
# ksize: 卷积核大小(元组类型) 例如:(3, 4)
# anchor: 元素内的描点位置,默认为 (-1, -1)表示形状中心
# 前提:背景是黑色,值为0,物体是白色,值为1
########################################################"""
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(10) & 0xff
if k == 27: # 退出键
break
cap.release()
cv2.destroyAllWindows()
mog2算法 opencv 8 --背景减除 – BackgroundSubtractorMOG2 OpenCV 中 getStructuringElement() 与 morphologyEx() 函数用法
(七)光流估计(轨迹点跟踪)—— cv2.goodFeaturesToTrack()、cv2.calcOpticalFlowPyrLK()
"""##########################################################################
# 光流是空间运动物体在观测成像平面上的像素运动的“瞬时速度”,是利用图像序列中像素在时间域上的变化以及相邻帧之间的相关性来找到上一帧跟当前帧之间存在的相应关系,从而实现目标跟踪。
# 三要素(必要条件) (1)亮度恒定:同一点随着时间的变化(在连续帧之间),其亮度(像素强度)不会发生改变。
# (2)小运动:相邻像素具有相似的运动。
# 因为只有小运动情况下,才能用前后帧之间单位位置变化引起的灰度变化去近似灰度对位置的偏导数。
# (3)空间一致:一个场景上邻近的点投影到图像上也是邻近点,且邻近点速度一致。
# 因为光流法基本方程约束只有一个,而要求x,y方向的速度,有两个未知变量。所以需要连立n多个方程求解。
# - cv2.goodFeaturesToTrack() 确定要追踪的特征点
# - cv2.calcOpticalFlowPyrLK() 追踪视频中的特征点
##########################################################################"""
# 如果跟踪图像中的目标丢失或被遮掩,则后续图像将始终不再现实轨迹角点(待优化)。
import numpy as np
import cv2
cap = cv2.VideoCapture('test.avi')
feature_params = dict(maxCorners=150, qualityLevel=0.3, minDistance=12) # ShiTomasi角点检测的参数
lk_params = dict(winSize=(15, 15), maxLevel=2) # Lucas Kanada光流检测的参数
color = np.random.randint(0, 255, (100, 3)) # 构建随机颜色
#################################
count = 0
while True:
ret, old_frame = cap.read() # 获取帧图像
count = count + 1
if count == 235: # 挑选视频中指定的第N帧为第一帧图像
break
old_gray = cv2.cvtColor(old_frame, cv2.COLOR_BGR2GRAY) # 转化为灰度图
"""#################################################################
# 确定要追踪的特征点:cv2.goodFeaturesToTrack( image, maxCorners, qualityLevel, minDistance, mask=noArray(),
# blockSize=3, bool useHarrisDetector=false, double k=0.04 );
# 输入参数 image: 输入图像,是八位的或者32位浮点型,单通道图像,所以有时候用灰度图
# maxCorners: 返回最大的角点数,是最有可能的角点数,如果这个参数不大于0,那么表示没有角点数的限制。
# qualityLevel: 图像角点的最小可接受参数,质量测量值乘以这个参数就是最小特征值,小于这个数的会被抛弃。
# minDistance: 返回的角点之间最小的欧式距离。
# mask: 检测区域。如果图像不是空的(它需要具有CV_8UC1类型和与图像相同的大小),它指定检测角的区域。
# blockSize: 用于计算每个像素邻域上的导数协变矩阵的平均块的大小。
# useHarrisDetector: 选择是否采用Harris角点检测,默认是false.
# k: Harris检测的自由参数。
# 输出参数 corners: 输出为角点。
# 备注:角点最大数量(数量越多,效率慢),品质因子(品质因子越大,角点越少,但越大越好)、角点距离(在角点距离范围内,取N个角点中最好的一个角点)
#################################################################"""
p0 = cv2.goodFeaturesToTrack(old_gray, mask=None, **feature_params) # 传入字典类型时需要两个**
mask = np.zeros_like(old_frame) # 为绘制光流追踪图,构建一个Mask
while True:
ret, frame = cap.read() # 循环获取帧图像
if not ret:
break
frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
"""#################################################################
# 追踪视频中的特征点:p1, status, err = cv2.calcOpticalFlowPyrLK(old_gray, frame_gray, p0, None, winSize=(15, 15), maxLevel=2)
# 输入参数 old_gray 前一帧图像
# frame_gray 当前帧图像
# p0 待跟踪的特征点向量
# nextPts None
# winSize 搜索窗口的大小
# maxLevel 最大的金字塔层数
# 输出参数 p1 跟踪特征点向量
# status 特征点是否找到,找到的状态为1,未找到的状态为0
#################################################################"""
p1, st, err = cv2.calcOpticalFlowPyrLK(old_gray, frame_gray, p0, None, **lk_params)
# 选择轨迹点
good_new = p1[st == 1] # 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()
a = int(a); b = int(b); c = int(c); d = int(d)
mask = cv2.line(mask, (a, b), (c, d), color[i].tolist(), 2)
"""#####################################################
# 画直线段:cv2.line(img, pt1, pt2, color, thickness)
# 输入参数 img 要划的线所在的图像;
# pt1 直线起点
# pt2 直线终点
# color 直线的颜色
# thickness=1 线条粗细
#####################################################"""
frame = cv2.circle(frame, (a, b), 5, color[i].tolist(), -1)
"""#####################################################
# 绘制圆型:cv2.circle(图像, 圆心, 半径, 颜色, 厚度)
# 输入参数 (1)图像:在该图像上绘图。
# (2)圆心:圆的中心坐标。坐标表示为两个值的元组, 即(X坐标值, Y坐标值)。
# (3)半径:圆的半径。
# (4)颜色:圆的边界线颜色。对于BGR, 我们传递一个元组。例如:(255, 0, 0)为蓝色。
# (5)厚度:正数表示线的粗细。其中:-1表示实心圆。
#####################################################"""
img = cv2.add(frame, mask) # 将轨迹线与帧图像叠加
cv2.imshow('frame', img)
k = cv2.waitKey(50) & 0xff
if k == 27: # 退出键
break
old_gray = frame_gray.copy() # 实时更新前一帧
p0 = good_new.reshape(-1, 1, 2)
cv2.destroyAllWindows()
cap.release()
图文详解 OpenCV中光流以及视频特征点追踪(稀疏光流追踪+ 优化版稀疏光流追踪+密集光流追踪)
(八)DNN模块的分类 —— cv2.dnn.blobFromImage()
import utils_paths
import numpy as np
import cv2
##################################################################
# 提取标签文件中每一行的内容
# (1)训练模型标签文件:"synset_words.txt"
# (2)使用open().read() :打开并读取txt文件中所有的字符串
# (3)strip() :删除字符串两端的空格
# (4)split('\n') :提取每一行的内容
rows = open("synset_words.txt").read().strip().split("\n")
##################################################################
# 提取出每行第一个空格后的字符串
# (1)对每一行的内容进行遍历,遍历之后找每一行的空格(r.find(' '))。
# (2)找到位置后让位置+1,然后r提取到+1的位置一直到最后
# (3)以所有逗号为分隔符然后删除他们,取分割的第一个值
classes = [r[r.find(" ") + 1:].split(",")[0] for r in rows]
##################################################################
# 加载Caffe所需文件
# (1)配置文件:"bvlc_googlenet.prototxt"
# (2)训练好的权重参数:"bvlc_googlenet.caffemodel"
net = cv2.dnn.readNetFromCaffe("bvlc_googlenet.prototxt", "bvlc_googlenet.caffemodel")
##################################################################
# 读取图像路径
# (1)utils_paths.py中的list_images()是提取images文件夹中所有图片的绝对路径,
# (2)将所有绝对路径作为元素组成迭代器
# (3)使用sorted()进行排序。 这个是字符串之间的排序:先比首字母,再比第二个字母,都相同时比长度。
imagePaths = sorted(list(utils_paths.list_images("images/")))
##################################################################
# (单个)图像预测
image = cv2.imread(imagePaths[0]) # 先读取第0张图片
resized = cv2.resize(image, (224, 224)) # 保持训练模型与测试模型数据大小相同
blob = cv2.dnn.blobFromImage(resized, 1, (224, 224), (104, 117, 123))
print("First Blob: {}".format(blob.shape))
net.setInput(blob) # 输入数据
preds = net.forward() # 前向传播得到结果(向量形式)
# 排序,取分类可能性最大的 ———— 该Imagenet是一个千分类模型,它会有1000个值对应1000个分类的概率
# np.argsort()是从小到大排序,故逆序[::-1],然后取第一个值(最大值的索引)
idx = np.argsort(preds[0])[::-1][0]
# 获取要写的内容:(1)该索引对应的标签值(2)pred[0]的值,乘以100,然后保留其两位小数,最后在后面加个百分号
text = "Label: {}, {:.2f}%".format(classes[idx], preds[0][idx] * 100)
# 将要写的内容写在未经处理的图片上
cv2.putText(image, text, (5, 25), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
# 显示预测结果
cv2.imshow("Image", image)
cv2.waitKey(0)
##################################################################
##################################################################
# 预测(其余的所有图) ———— 方法与上述一样,但数据是一个batch。
images = [] # 定义一个空列表存图
# 处理除第0张外的所有图片
for p in imagePaths[1:]:
image = cv2.imread(p)
image = cv2.resize(image, (224, 224))
images.append(image)
blob = cv2.dnn.blobFromImages(images, 1, (224, 224), (104, 117, 123))
print("Second Blob: {}".format(blob.shape))
net.setInput(blob) # 输入数据
preds = net.forward() # 前向传播得到结果(向量形式)
# 首先读进来图,之后找到对应预测结果中最大的,然后写上标签与概率
for (i, p) in enumerate(imagePaths[1:]): # i是序号,p是图片路径
image = cv2.imread(p)
idx = np.argsort(preds[i])[::-1][0]
text = "Label: {}, {:.2f}%".format(classes[idx], preds[i][idx] * 100)
cv2.putText(image, text, (5, 25), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
cv2.imshow("Image", image)
cv2.waitKey(0)
"""##################################################################
# blob = cv2.dnn.blobFromImage() ———— 改变图像的大小,然后令R,G,B三个通道分别减去均值(去除光照影响)。
# 输出参数: Blob 例如:(1,3,224,224) 分别表示:图片数量、图片通道数、图像的宽、图像的高
# 输入参数 resized 要改变的图像
# 缩放系数 我们当前用的时1,所以不变
# (224,224) 图像大小
# (104,117,123) 图像三通道均值。这三个均值是Imagenet提供的。
# 预测一个结果:cv2.dnn.blobFromImage() 处理单张图像
# 预测多个结果:cv2.dnn.blobFromImages() 处理多张图像
##################################################################"""
opencv的DNN模块(详细过程) DNN常用模块简介
(九)矩形涂鸦画板 —— cv.namedWindow()、cv.setMouseCallback()
"""#########################################################################
# 编写一个矩形涂鸦画板
# 功能:鼠标左键按下拖动绘制矩形,鼠标左键弹起时完成绘制
# (1)按' c '键清空画板
# (2)按' ESC '键退出
#########################################################################"""
import numpy as np
import cv2
from random import randint
class Painter:
def __init__(self) -> None:
self.mouse_is_pressed = False
self.last_pos = (-1, -1)
self.width = 300
self.height = 512
self.img = np.zeros((self.width, self.height, 3), np.uint8)
self.window_name = 'painter'
self.color = None
def run(self):
print('画板,拖动鼠标绘制矩形框,按ESC退出,按c键清空画板')
cv2.namedWindow(self.window_name)
cv2.setMouseCallback(self.window_name, lambda event, x, y, flags, param: self.on_draw(event, x, y, flags, param))
while True:
cv2.imshow(self.window_name, self.img)
k = cv2.waitKey(1) & 0xFF
if k == ord('c'): # 按' c '键清空画板
self.clean() # (调用自定义函数):清除画板
elif k == 27: # 按' ESC '键退出
break
cv2.destroyAllWindows()
def on_draw(self, event, x, y, flags, param):
# TODO(You): 请正确实现画板事件响应,完成功能
# 触发左键按下 -> 触发鼠标移动 -> 开始画矩形 -> 触发左键抬起 -> 终止画矩形
pos = (x, y) # 鼠标按下的位置坐标
if event == cv2.EVENT_LBUTTONDOWN: # 触发左键按下
self.mouse_is_pressed = True
self.last_pos = pos
elif event == cv2.EVENT_MOUSEMOVE: # 触发鼠标移动
if self.mouse_is_pressed == True: # 判断鼠标是否按下
self.begin_draw_rectangle(self.last_pos, pos) # (调用自定义函数):开始画矩形
elif event == cv2.EVENT_LBUTTONUP: # 触发左键抬起
self.end_draw_rectangle(self.last_pos, pos) # (调用自定义函数):终止画矩形
self.mouse_is_pressed = False
def clean(self):
cv2.rectangle(self.img, (0, 0), (self.height, self.width), (0, 0, 0), -1)
def begin_draw_rectangle(self, pos1, pos2):
if self.color is None: # 设置颜色(每个矩形的颜色都随机)
self.color = (randint(0, 256), randint(0, 256), randint(0, 256)) # 随机生成三通道颜色
cv2.rectangle(self.img, pos1, pos2, self.color, -1)
def end_draw_rectangle(self, pos1, pos2):
self.color = None
if __name__ == '__main__':
p = Painter() # 类的实例化
p.run() # 调用类函数
"""#########################################################################
# 11、创建鼠标回调函数:cv2.setMouseCallback(windowName, MouseCallback, param=None)
# 输入参数 windowName: 窗口名称
# MouseCallback: 鼠标响应回调函数
# param: 响应函数传递的的参数
#########################################################################
# 22、MouseCallback(int event, int x, int y, int flags, * userdata)
# 输入参数 x: 鼠标的x坐标
# y: 鼠标的y坐标
# userdata: 可选参数
# event: 一个MouseEventTypes常量
# (1)cv.EVENT_FLAG_LBUTTON= 1, 左键拖拽
# (2)cv.EVENT_FLAG_RBUTTON= 2, 右键拖拽
# (3)cv.EVENT_FLAG_MBUTTON= 4, 中键不放
# (4)cv.EVENT_FLAG_CTRLKEY= 8, 按住ctrl不放
# (5)cv.EVENT_FLAG_SHIFTKEY= 16, 按住shift不放
# (6)cv.EVENT_FLAG_ALTKEY= 32, 按住alt不放
# flags: 一个MouseEventFlags常量
# (1)cv.EVENT_MOUSEMOVE= 0, 鼠标移动
# (2)cv.EVENT_LBUTTONDOWN= 1, 左键按下
# (3)cv.EVENT_RBUTTONDOWN= 2, 右键按下
# (4)cv.EVENT_MBUTTONDOWN= 3, 中键按下
# (5)cv.EVENT_LBUTTONUP= 4, 左键释放
# (6)cv.EVENT_RBUTTONUP= 5, 右键释放
# (7)cv.EVENT_MBUTTONUP= 6, 中键释放
# (8)cv.EVENT_LBUTTONDBLCLK= 7, 左键双击
# (9)cv.EVENT_RBUTTONDBLCLK= 8, 右键双击
# (10)cv.EVENT_MBUTTONDBLCLK= 9, 中健双击
# (11)cv.EVENT_MOUSEWHEEL= 10, 滚轮滑动
# (12)cv.EVENT_MOUSEHWHEEL= 11 横向滚轮滑动
#########################################################################"""
(十)创建轨迹条 —— createTrackbar()、cv2.getTrackbarPos()
10.1、创建一个轨迹条,用于阈值化图像
import cv2
img = cv2.imread('1.png') # 加载图像
cv2.imshow('Image', img) # 显示图像
cv2.createTrackbar('Threshold', 'Image', 0, 255, lambda x: None) # 创建阈值滑动条
while True:
threshold_value = cv2.getTrackbarPos('Threshold', 'Image') # 获取滑动条的阈值
threshold_img = cv2.threshold(img, threshold_value, 255, cv2.THRESH_BINARY)[1] # 阈值化图像
cv2.imshow('Threshold Image', threshold_img) # 显示图像
if cv2.waitKey(1) == 27: # Esc退出
break
cv2.destroyAllWindows() # 关闭所有窗口
10.2、创建一个轨迹条,用于画板调色
Python OpenCV 使用滑动条(调色)
python -opencv 使用滑动条(调色画板)
OpenCV学习——实现滑动条式调色板
python -opencv 使用滑动条(图像膨胀)
共有三个功能:(1)创建轨迹条(2)打开控制开关,根据RGB设置背景色(3)打开控制开关,根据RGB设置画笔颜色,在画板上画画。
"""#########################################################################
# (1)滑动条控制R、G、B的值
# (2)开关按钮switch,用于确认是否使用自定义RGB改变原图。
# 0:不改变原图 1:调色 2:调色画板
#########################################################################"""
import cv2
import numpy as np
# 定义回调函数,此程序无需回调,所以Pass即可
def nothing(x):
pass
def Mouseback(event, x, y, flags, param):
if flags == cv2.EVENT_FLAG_LBUTTON and event == cv2.EVENT_MOUSEMOVE:
cv2.circle(img, (x, y), 1, [b, g, r], 1)
img = np.zeros((300, 512, 3), np.uint8)
cv2.namedWindow('image', cv2.WINDOW_NORMAL)
cv2.createTrackbar('R', 'image', 0, 255, nothing)
cv2.createTrackbar('G', 'image', 0, 255, nothing)
cv2.createTrackbar('B', 'image', 0, 255, nothing)
switch = 'OFF ON'
cv2.createTrackbar(switch, 'image', 0, 2, nothing)
while (1):
cv2.imshow('image', img)
k = cv2.waitKey(1)
if k == ord('q'):
break
r = cv2.getTrackbarPos('R', 'image')
g = cv2.getTrackbarPos('G', 'image')
b = cv2.getTrackbarPos('B', 'image')
s = cv2.getTrackbarPos(switch, 'image')
if s == 0: # 不改变原图
img[:] = 0
elif s == 1: # 调色
img[:] = [b, g, r]
elif s == 2: # 调色画板
cv2.setMouseCallback('image', Mouseback)
cv2.destroyAllWindows()
"""#########################################################################
# 11、创建一个滑动条: cv2.createTrackbar(Track_name, img, min, max, TrackbarCallback)
# 输入参数:
# Track_name: 滑动条的名字。
# img: 滑动条所在画布。
# min: 滑动条的最小值。
# max: 滑动条的最大值。
# TrackbarCallback: 滑动条的回调函数。
# 22、获取滑动条的值: value = cv2.getTrackbarPos(Track_name, img)
# 输入参数:
# Track_name: 滑动条的名字。
# img: 滑动条所在画布。
# 输出参数: 滑动条当前所在的位置(值)。
#########################################################################"""
(十一)基于二值化实现人像抠图与背景替换 —— np.where()、np.uint8()
图像处理实战–Opencv实现人像迁移
# 检测原理:
# (1)将前景图像通过二值化进行抠图,去除白色背景,得到人像位置。
# (2)将人像在背景图像中的位置置为0。
# (3)将两张图像进行像素值累加。
# 只适用于前景图像的物体背景为白色的图像,背景图像随意。
# 因此采用二值化抠图,故只能指定唯一像素,而白色背景为常用背景。
import cv2
import numpy as np
import matplotlib.pyplot as plt
q_img = cv2.imread('black.png') # (1)前景图像
b_img = cv2.imread('starry_night.jpg') # (2)背景图像
# 将风景图片尺寸调整为与人像图片一致, 需要进行图像像素累加计算。
q_img = cv2.resize(q_img, (b_img.shape[1], b_img.shape[0]))
print(q_img.shape)
print(b_img.shape)
# (3)二值化抠图
img = q_img.copy()
for i in range(img.shape[0]): # 高
for j in range(img.shape[1]): # 宽
# 颜色接近背景颜色的像素点置为1,其余部分置为0
if 255 == img[i][j][0] and 255 == img[i][j][1] and 255 == img[i][j][2]:
img[i][j] = 255
else:
img[i][j] = 0
# (4)人像迁移
# 黑白反转(前景图像:人像位置置为1,其余位置置为0)
img_t = np.where(img == 0, 1, 0)
img3 = np.uint8(q_img * img_t)
# 白黑反转(背景图像:人像位置置为0,其余位置置为1)
img_t = np.where(img_t == 1, 2, img_t)
img_t = np.where(img_t == 0, 1, img_t)
img_t = np.where(img_t == 2, 0, img_t)
img4 = np.uint8(b_img * img_t)
# 图像像素值累加(尺寸必须相同)
img5 = img4 + img3
# (5)绘图
titles = ['q_img', 'b_img', 'img', 'img3', 'img4', 'img5']
images = [q_img, b_img, img, img3, img4, img5]
for ii in range(6):
images[ii] = cv2.cvtColor(images[ii], cv2.COLOR_BGR2RGB)
plt.subplot(2, 3, ii+1)
plt.imshow(images[ii], 'gray')
plt.axis('off')
plt.xticks([]), plt.yticks([])
plt.show()
22、图像基本操作
(一)图像的读取、保存和显示 —— cv2.imread(),cv2.imwrite(),cv2.imshow()
(1)多张图(在同一个窗口)同时显示 plt.subplot()
(2)cv2.imshow()和plt.imshow()的区别
"""#####################################################################
# cv2是opencv在python中的缩写;
# Matplotlib 是一个 Python 库,可以通过 python 脚本创建二维图形和图表。
# Matplotlib 中的 pyplot 模块,可以控制线条样式,字体属性,格式化轴等功能。且支持各种各样的图形绘制,如直方图,条形图,功率谱,误差图等。
#####################################################################"""
import cv2 # opencv读取图像的格式BGR(图像的格式RGB)
import matplotlib.pyplot as plt # plt默认RGB通道
"""#####################################################################
# 11、读取图像:cv2.imread(img_path, flag)
# 输入参数
# img_path: 图像的路径(若路径错误,则返回None。但不会报错)
# flag: cv2.IMREAD_COLOR(也可以传入1) (默认)加载彩色图像RGB
# cv2.IMREAD_GRAYSCALE(也可以传入0) 将图像转换为灰度图,
# cv2.IMREAD_UNCHANGED(也可以传入-1) 加载原图
# 备注1:OpenCV 支持JPG、PNG、TIFF等常见格式图像文件加载(默认读取的格式是BGR)(图像的格式是RGB)
# 备注2:转义字符\可以转义很多字符,比如:'\n'表示换行,'\t'表示制表符,'\\'表示\。当然如果不需要转义,可以使用(r'cat.hpg');
###################################
# 路径说明(路径中不能出现中文,否则系统异常提示。)
cat_path = r'C:\Users\my\Desktop\py_test\cat.jpg' # 绝对路径
cat_path = r'cat.jpg' # 相对路径(即当前同级目录):不指定路径,则默认当前.py文件的路径下。
cat_path = r'./cat.jpg' # 相对路径(即当前同级目录):与上同效。
cat_path = r'../py_test/cat.jpg' # 相对路径(上级目录):py_test表示存放当前.py文件的文件夹
#####################################################################"""
cat_path = r'C:\Users\my\Desktop\py_test\cat.jpg'
img0 = cv2.imread(cat_path)
img1 = cv2.imread(cat_path, cv2.IMREAD_COLOR)
img2 = cv2.imread(cat_path, cv2.IMREAD_GRAYSCALE)
img3 = cv2.imread(cat_path, cv2.IMREAD_UNCHANGED)
"""#####################################################################
# 22、保存图像:cv2.imwrite(img_path_name, img)
# 输入参数 img_path_name: 自定义待保存图像的路径+名字
# img: 待保存图像
#####################################################################"""
cv2.imwrite('gray_cat.png', img2)
"""#####################################################################
# 33、显示图像:cv2.imshow(window_title, img)
# 输入参数 window_title: 自定义窗口的名字
# img: 待显示图像
# 备注1:窗口会自适应图像大小
# 备注2:指定多个窗口名称,可以显示多幅图像
# 备注3:显示多幅图像的时候,若cv2.imshow()指定相同的窗口名,这样后面显示的图像会覆盖前面的图像,从而只产生一个(连续)窗口。如:视频
#####################################################################"""
cv2.imshow('raw_img', img0)
cv2.imshow('cv2.IMREAD_COLOR', img1)
cv2.imshow('cv2.IMREAD_GRAYSCALE', img2)
cv2.imshow('cv2.IMREAD_UNCHANGED', img3)
cv2.waitKey(1000) # 延迟一秒后自动关闭图像
cv2.destroyAllWindows() # (同时)关闭所有图窗
"""#####################################################################
# 键盘绑定函数:cv2.waitKey()
# (1)cv2.waitKey(0): 表示无限期的等待键盘输入,按任意键继续。如:空格键
# (2)cv2.waitKey(delay): 当 delay>0 (单位:ms)时使用,表示等待一定时间。1秒(s) = 1000毫秒
#####################################################################"""
#####################################################################
# 44、(在同一个窗口)同时显示多张图
# plt.subplot(231)或者plt.subplot(2,3,1) # 该图指定了(row)2*3(col)的子图区域,并且同一个坐标轴1,2,3,4,5,6内分别画图。
# plt.plot() # 直接在一张大的画布中画图,相当于获取当前活跃的axes然后在上面作图。
# 备注1:在jupyter notebook上,单是plt.imshow()就可显示图片,同时也显示其格式。
# 备注2:在pycharm上,单是plt.imshow()不显示图像,需搭配plt.show()。而加上plt.show()后,结果:仅显示图片,不显示格式。
##################################
plt.subplot(141), plt.imshow(img0, 'gray'), plt.title('raw_img')
plt.subplot(142), plt.imshow(img1, 'gray'), plt.title('cv2.IMREAD_COLOR')
plt.subplot(143), plt.imshow(img2, 'gray'), plt.title('cv2.IMREAD_GRAYSCALE')
plt.subplot(144), plt.imshow(img3, 'gray'), plt.title('cv2.IMREAD_UNCHANGED')
plt.show()
#####################################################################
# 55、cv2.imshow()和plt.imshow()的区别
# cv2.imshow():常用于对读入图像进行一系列图像处理后的绘图;
# plt.imshow():常用语绘制热图; 热图即通过色差、亮度来展示数据的差异。
# 备注:两者都可以,但要注意图片的格式是RGB格式,而opencv是BGR格式,plt是RGB格式;
##################################
plt.imshow(img0)
plt.colorbar()
plt.show()
(1.1)图窗设置:cv2.namedWindow()、cv2.resizeWindow()、cv2.moveWindow()、cv2.setWindowProperty()。
import cv2
window_name = 'projector' # 图窗标题
cv2.namedWindow(window_name, cv2.WINDOW_KEEPRATIO) # 创建命名窗口
cv2.resizeWindow(window_name, 10, 20) # 自定义窗口大小
cv2.moveWindow(window_name, 100, 200) # 设置窗口的位置
cv2.setWindowProperty(window_name, cv2.WND_PROP_TOPMOST, 1) # 设置窗口显示在最前面
im = cv2.imread('test01.png') # 读取图像
cv2.imshow(window_name, im) # 显示图像
cv2.waitKey(0) # 等待输入任意键
cv2.destroyAllWindows() # 摧毁所有图窗
参数 | cv2.namedWindow(winname, flags) | 创建命名窗口 | |
---|---|---|---|
1 | winname | 窗口名称,用作窗口的标识符。 | |
2 | flags | 窗口属性设置标志。 | |
flags=cv2.WINDOW_NORMAL | 用户可以手动改变窗口大小 | ||
flags=cv2.WINDOW_AUTOSIZE | 窗口大小自动适应图片大小,并且不可手动更改。 | ||
flags=cv2.WINDOW_FREERATIO | 自适应比例 | ||
flags=cv2.WINDOW_KEEPRATIO | 保持比例 | ||
flags=cv2.WINDOW_OPENGL | 窗口创建的时候会支持OpenGL | ||
flags=cv2.WINDOW_GUI_EXPANEDE | 创建的窗口允许添加工具栏和状态栏。 | ||
flags=cv2.WINDOW_GUI_NORMAL | 创建没有状态栏和工具栏窗口。 | ||
flags=cv2.WINDOW_AUTOSIZE | 窗口大小自动适应图片大小,并且不可手动更改。 |
参数 | cv2.resizeWindow(winname, width, height) | 改变窗口大小 |
---|---|---|
1 | winname | 窗口名 |
2 | width | 窗口宽度 |
3 | height | 窗口高度 |
参数 | cv2.moveWindow(winname, x, y) | 设置窗口位置 |
---|---|---|
1 | winname | 窗口名 |
2 | x | 窗口x轴位置 |
3 | y | 窗口y轴位置 |
参数 | cv2.setWindowProperty(winname, prop_id, prop_value) | 设置窗口属性 |
---|---|---|
1 | winname | 窗口名 |
2 | prop_id | 要编辑的窗口属性。如cv2.WINDOW_NORMAL、cv2.WINDOW_KEEPRATIO、cv2.WINDOW_FULLSCREEN等。 |
3 | prop_value | 窗口属性的新值。如cv2.WND_PROP_FULLSCREEN, cv2.WND_PROP_AUTOSIZE, cv2.WND_PROP_ASPECT_RATIO等。 |
(1.2)图窗关闭:cv2.waitKey()、cv2.destroyAllWindows()
参数 | cv2.waitKey() | 键盘绑定函数 |
---|---|---|
1 | cv2.waitKey(0) | 表示无限期的等待键盘输入,按任意键继续。如:空格键 |
2 | cv2.waitKey(delay) | 当 delay>0 (单位:ms)时使用,表示等待一定时间。1秒(s) = 1000毫秒 |
参数 | cv2.destroyAllwindows() | 销毁窗口 |
---|---|---|
1 | cv2.destroyAllwindows() | 摧毁所有窗口 |
2 | cv2.destroyWindow(winname) | 摧毁指定的窗口 |
(二)视频读取与处理 —— cv2.VideoCapture()
OpenCV 保存视频
读取视频 + 检查视频是否可以打开 + 循环读取视频的每一帧(图像)并显示
import cv2 # cv2是opencv在python中的缩写; opencv读取图像的格式BGR(图像的格式RGB)
# (1)视频读取与处理 —— 读取视频
vc = cv2.VideoCapture(r'picture\test.mp4')
# (2)视频读取与处理 —— 检查视频是否可以打开
if vc.isOpened():
open, frame = vc.read()
else:
open = False
# (3)视频读取与处理 —— 循环读取视频的每一帧(图像)
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)
# 使用 waitKey 可以控制视频的播放速度,数值越小,播放速度越快
if cv2.waitKey(10) & 0xFF == 27: # 27 表示退出键(Esc)
break
vc.release()
cv2.destroyAllWindows()
"""###################################################################
# 11、cv2.VideoCapture可以捕获摄像头,用数字来控制不同的设备。
# 0:表示调用电脑自带摄像头。
# 1:表示调用外接USB摄像头。
# 22、vc.isOpened(): 检查视频是否可以打开(返回值:True/False)
# 33、vc.read(): 读取视频的每一帧(图像)(返回值:True/False,图像彩色图)
# 44、cv2.imshow('frame',frame)将每一帧(图像)显示在一个叫frame的窗口上。
# 为什么会产生视频的效果:通过while循环,将图像固定显示在'frame'图窗上,每一帧会覆盖上一帧,就产生了视频的效果。
###################################################################"""
(三)图像的三色图 —— cv2.split() + cv.merge()
(1)图像分割得到三色图(BGR)
(2)将分割的三色图还原为彩色图
(3)保留R通道 + 保留G通道 + 保留B通道
import cv2 # cv2是opencv在python中的缩写; opencv读取图像的格式BGR(图像的格式RGB)
import matplotlib.pyplot as plt # 绘图展示
# 截取部分图像
cat_address = r'C:\Users\my\Desktop\pythonProject\picture\cat.jpg'
img = cv2.imread(cat_address)
cat = img[0:50, 0:200]
"""#############################################
# 图像分割得到三色图: b, g, r = cv2.split(img)
# 功能:将多通道的图像分离成若干个单通道的图像,分割后的单通道图像尺寸大小相同。
# 注意:分割后任意单通道都属于灰度图,而不是对应的颜色通道图;
#############################################"""
b, g, r = cv2.split(img)
"""#############################################
# 将分割的三色图还原为彩色图: img = cv2.merge((b, g, r))
# 功能:将多幅图像合并成一幅多通道图像,合并后的通道数是所有输入图像通道数的总和。
# 注意:所有输入图像的通道数可以不相同,但是所有图像需要具有相同的尺寸和数据类型
#############################################"""
img = cv2.merge((b, g, r))
# img.copy():复制原图进行图像处理,可以避免改变原图
# 只保留红色(R)单通道(G and B通道全部置0即可)
cur_img_R = img.copy()
cur_img_R[:, :, 0] = 0
cur_img_R[:, :, 1] = 0
# 只保留绿色(G)单通道(R and B通道全部置0即可)
cur_img_G = img.copy()
cur_img_G[:, :, 0] = 0
cur_img_G[:, :, 2] = 0
# 只保留蓝色(B)单通道(R and G通道全部置0即可)
cur_img_B = img.copy()
cur_img_B[:, :, 1] = 0
cur_img_B[:, :, 2] = 0
plt.subplot(131), plt.imshow(cur_img_B), plt.title('Red')
plt.subplot(132), plt.imshow(cur_img_G), plt.title('Green')
plt.subplot(133), plt.imshow(cur_img_R), plt.title('Blue')
plt.show()
(四)图像的边缘填充 —— cv2.copyMakeBorder()
import cv2 # cv2是opencv在python中的缩写;opencv读取图像的格式是BGR(图像的格式RGB)
import matplotlib.pyplot as plt # 绘图展示
"""########################################################
# cv2.copyMakeBorder():用于在像相框一样的图像周围创建边框。
# cv2.copyMakeBorder(src, top, bottom, left, right, borderType, value)
# 输入参数: src 原图像。
# top 顶部方向的边框宽度, 以像素数为单位。
# bottom 底部方向的边框宽度(以像素数为单位)。
# left 沿左方向的像素数量的边框宽度。
# right 沿右方向的像素数的边框宽度。
# borderType 描述要添加哪种边框。
# (1)BORDER_REPLICATE :复制法,即用复制最边缘像素。
# (2)BORDER_REFLECT :反射法,对感兴趣的图像中的像素在两边进行复制。例如:fedcba | abcdefgh | hgfedcb
# (3)BORDER_REFLECT_101 :反射法,即用以最边缘像素为轴,对称。gfedcb | abcdefgh | gfedcba
# (4)BORDER_WRAP :外包装法。abcdefgh | abcdefgh | abcdefgh
# (5)BORDER_CONSTANT :常量法,即用常数值填充。
# value: 可选参数, 如果边界类型为cv2.BORDER_CONSTANT, 则描述边界的颜色。
########################################################"""
cat_address = r'C:\Users\my\Desktop\pythonProject\picture\cat.jpg'
img = cv2.imread(cat_address)
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)
plt.subplot(231), plt.imshow(img, 'gray'), plt.title('ORIGINAL')
plt.subplot(232), plt.imshow(replicate, 'gray'), plt.title('BORDER_REPLICATE')
plt.subplot(233), plt.imshow(reflect, 'gray'), plt.title('BORDER_REFLECT')
plt.subplot(234), plt.imshow(reflect101, 'gray'), plt.title('BORDER_REFLECT_101')
plt.subplot(235), plt.imshow(wrap, 'gray'), plt.title('BORDER_WRAP')
plt.subplot(236), plt.imshow(constant, 'gray'), plt.title('BORDER_CONSTANT')
plt.show()
(五)图像融合 —— cv2.addWeighted()
(1)图像截取
(2)图像加/减常数
(3)图像加/减另一个图像
(4)图像相加 cv2.add()
import cv2 # cv2是opencv在python中的缩写;opencv读取图像的格式是BGR(图像的格式RGB)
import matplotlib.pyplot as plt # 绘图展示
img_cat = cv2.imread(r'picture\cat.jpg')
img_dog = cv2.imread(r'picture\dog.jpg')
print(img_cat.shape)
print(img_dog.shape)
########################################
# 图像截取
cat_piece = img_cat[100:300, 0:200]
########################################
# 图像加/减一个常数 ———— 即图像的每个像素都加常数值
img_plus = img_cat - 50
########################################
# 图像相加/相减 ———— 两个图像的尺寸要一样。
# 方式1:在numpy中,如果两个图像相加超过255,将自动减去255,然后得到剩下的值。相当于取余【% 256】
# 方式2:cv2.add():如果两个图像相加超过255,则等于255,否则当前值保留不变;
img_cat = cv2.resize(img_cat, (400, 480))
img_dog = cv2.resize(img_dog, (400, 480))
print(img_cat.shape)
print(img_dog.shape)
res_add1 = img_cat + img_dog
res_add2 = cv2.add(img_cat, img_dog)
########################################
plt.subplot(231), plt.imshow(img_cat, 'gray'), plt.title('img_cat_resize')
plt.subplot(232), plt.imshow(img_dog, 'gray'), plt.title('img_dog_resize')
plt.subplot(233), plt.imshow(cat_piece, 'gray'), plt.title('cat_piece')
plt.subplot(234), plt.imshow(img_plus, 'gray'), plt.title('fig1 - 50')
plt.subplot(235), plt.imshow(res_add1, 'gray'), plt.title('fig1 + fig2')
plt.subplot(236), plt.imshow(res_add2, 'gray'), plt.title('cv2.add')
plt.show()
"""#####################################################################
# 图像融合:cv2.addWeighted(src1, alpha, src2, beta, gamma)
# 功能:将两张相同shape的图像按权重进行融合
# 输入参数 src1/src2 图像1与图像2
# alpha/beta 图像1与图像2对应的权重(融合后的图像偏向于权重高的一边)
# gamma 相当于(y=a*x+b)中的截距。用于调节亮度
# 权重融合公式:dst = src1 * alpha + src2 * beta + gamma
#####################################################################"""
img_cat = cv2.resize(img_cat, (500, 414)) # 将图像裁剪到指定尺寸
img_dog = cv2.resize(img_dog, (500, 414)) # 将图像裁剪到指定尺寸
res = cv2.addWeighted(img_cat, 0.35, img_dog, 0.65, 2)
plt.imshow(res)
plt.show()
(六)颜色空间转换 —— cv2.cvtColor()
import cv2 # opencv读取的格式是BGR
import matplotlib.pyplot as plt # Matplotlib是RGB
"""#####################################################################
# opencv中颜色空间转换函数:cv2.cvtColor()
# opencv中有多种色彩空间,包括 RGB、HSI、HSL、HSV、HSB、YCrCb、CIE XYZ、CIE Lab8种。
# 在opencv中默认的颜色空间是BGR。
#####################################################################"""
img_BGR = cv2.imread(r'picture/cat.jpg') # BGR
plt.subplot(3, 3, 1), plt.imshow(img_BGR), plt.axis('off'), plt.title('BGR')
img_RGB = cv2.cvtColor(img_BGR, cv2.COLOR_BGR2RGB)
plt.subplot(3, 3, 2), plt.imshow(img_RGB), plt.axis('off'), plt.title('RGB')
img_GRAY = cv2.cvtColor(img_BGR, cv2.COLOR_BGR2GRAY)
plt.subplot(3, 3, 3), plt.imshow(img_GRAY), plt.axis('off'), plt.title('GRAY')
img_HSV = cv2.cvtColor(img_BGR, cv2.COLOR_BGR2HSV)
plt.subplot(3, 3, 4), plt.imshow(img_HSV), plt.axis('off'), plt.title('HSV')
img_YcrCb = cv2.cvtColor(img_BGR, cv2.COLOR_BGR2YCrCb)
plt.subplot(3, 3, 5), plt.imshow(img_YcrCb), plt.axis('off'), plt.title('YcrCb')
img_HLS = cv2.cvtColor(img_BGR, cv2.COLOR_BGR2HLS)
plt.subplot(3, 3, 6), plt.imshow(img_HLS), plt.axis('off'), plt.title('HLS')
img_XYZ = cv2.cvtColor(img_BGR, cv2.COLOR_BGR2XYZ)
plt.subplot(3, 3, 7), plt.imshow(img_XYZ), plt.axis('off'), plt.title('XYZ')
img_LAB = cv2.cvtColor(img_BGR, cv2.COLOR_BGR2LAB)
plt.subplot(3, 3, 8), plt.imshow(img_LAB), plt.axis('off'), plt.title('LAB')
img_YUV = cv2.cvtColor(img_BGR, cv2.COLOR_BGR2YUV)
plt.subplot(3, 3, 9), plt.imshow(img_YUV), plt.axis('off'), plt.title('YUV')
plt.show()
(七)阈值处理 —— cv2.threshold() + cv2.adaptiveThreshold()
(1)
全局阈值分割:cv2.threshold()
(2)自适应阈值分割:cv2.adaptiveThreshold()
import cv2 # opencv读取的格式是BGR
import matplotlib.pyplot as plt # Matplotlib是RGB
img_BGR = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE)
_, thresh1 = cv2.threshold(img_BGR, 127, 255, cv2.THRESH_BINARY)
_, thresh2 = cv2.threshold(img_BGR, 127, 255, cv2.THRESH_BINARY_INV)
_, thresh3 = cv2.threshold(img_BGR, 127, 255, cv2.THRESH_TRUNC)
_, thresh4 = cv2.threshold(img_BGR, 127, 255, cv2.THRESH_TOZERO)
_, thresh5 = cv2.threshold(img_BGR, 127, 255, cv2.THRESH_TOZERO_INV)
titles = ['Original Image', 'BINARY', 'BINARY_INV', 'TRUNC', 'TOZERO', 'TOZERO_INV']
images = [img_BGR, thresh1, thresh2, thresh3, thresh4, thresh5]
for ii in range(6):
plt.subplot(2, 3, ii + 1), plt.imshow(images[ii], 'gray')
plt.title(titles[ii])
plt.xticks([]), plt.yticks([])
plt.show()
"""#############################################################
# 函数介绍:全局阈值分割 ———— 根据阈值将图像的像素分为两个类别:高于阈值的像素和低于阈值的像素。
# 函数说明:ret, dst = cv2.threshold(src, thresh, max_val, type)
# 输入参数:
# src: 输入灰度图像
# thresh: 阈值
# max_val: 阈值上限。通常为255(8-bit)。
# type: 二值化操作的类型,包含以下5种类型:
# (1) cv2.THRESH_BINARY 超过阈值部分取max_val(最大值),否则取0
# (2) cv2.THRESH_BINARY_INV THRESH_BINARY的反转
# (3) cv2.THRESH_TRUNC 大于阈值部分设为阈值,否则不变
# (4) cv2.THRESH_TOZERO 大于阈值部分不改变,否则设为0
# (5) cv2.THRESH_TOZERO_INV THRESH_TOZERO的反转
# 输出参数:
# ret 浮点数,表示最终使用的阈值。
# dst 经过阈值分割操作后的二值图像
#############################################################"""
image = cv2.imread('image.jpg', cv2.IMREAD_GRAYSCALE)
thresh1 = cv2.adaptiveThreshold(image, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 11, 2)
thresh2 = cv2.adaptiveThreshold(image, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY_INV, 11, 2)
thresh3 = cv2.adaptiveThreshold(image, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2)
thresh4 = cv2.adaptiveThreshold(image, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 2)
titles = ['gray Image', 'mean + binary', 'mean + binary_inv', 'gray Image', 'gaussian + binary', 'gaussian + binary_inv']
images = [image, thresh1, thresh2, image, thresh3, thresh4]
for ii in range(6):
plt.subplot(2, 3, ii + 1), plt.imshow(images[ii], 'gray')
plt.title(titles[ii])
plt.xticks([]), plt.yticks([])
plt.show()
"""##########################################################################
# 函数介绍:自适应阈值分割 ———— 根据图像不同区域的像素值来自动确定阈值,从而实现更好的图像分割效果。
# 函数说明:adaptive_threshold = cv2.adaptiveThreshold(src, maxValue, adaptiveMethod, thresholdType, blockSize, C)
# 输入参数:
# src: 输入灰度图像。
# maxValue: 阈值上限。通常为255(8-bit)。
# adaptiveMethod: 自适应方法,包含以下2种:
# (1)cv2.ADAPTIVE_THRESH_MEAN_C(局部均值)
# (2)cv2.ADAPTIVE_THRESH_GAUSSIAN_C(局部高斯加权均值)
# thresholdType: 阈值类型。
# (1)cv2.THRESH_BINARY 超过阈值部分取max_val(最大值),否则取0
# (2)cv2.THRESH_BINARY_INV THRESH_BINARY的反转
# blockSize: 区域大小,用于计算局部阈值。通常是一个奇数,例如 3、5、7 等。
# C: 从均值中减去的常数,用于调整阈值的灵敏度。
# 输出参数:
# adaptive_threshold 自适应阈值
##########################################################################"""
(八)均值/高斯/方框/中值滤波 —— cv2.blur() + cv2.boxFilter() + cv2.GaussianBlur() + cv2.medianBlur()
import cv2 # opencv读取的格式是BGR
import matplotlib.pyplot as plt # Matplotlib是RGB
import numpy as np
img = cv2.imread(r'picture/cat.jpg')
"""######################################
# 均值滤波:cv2.blur(img, ksize) ———— 取卷积核所有元素的均值
# 输入参数 ksize 表示卷积核大小。 例如:(3, 3)
# 作用:对于椒盐噪声的滤除效果比较好。
######################################"""
blur = cv2.blur(img, (3, 3))
"""######################################
# 方框滤波:cv2.boxFilter(img, -1, (3, 3), normalize=True)
# 输入参数 normalize=True 选择归一化 即取所有元素之和除以卷积核大小(与均值滤波等同)
# normalize=False 不选择归一化 【容易越界】且当“元素之和>255”,则等于255;
######################################"""
box_T = cv2.boxFilter(img, -1, (3, 3), normalize=True)
box_F = cv2.boxFilter(img, -1, (3, 3), normalize=False)
"""######################################
# 高斯滤波:cv2.GaussianBlur()
# 特点:高斯模糊的卷积核里的数值是满足高斯分布,相当于更重视中间的
######################################"""
aussian = cv2.GaussianBlur(img, (5, 5), 1)
"""######################################
# 中值滤波:cv2.medianBlur() ———— 取卷积核所有元素(从小到大排序)的中间值
# 作用:中值滤波对消除椒盐噪声非常有效,能够克服线性滤波器带来的图像细节模糊等弊端,能够有效保护图像边缘信息;
######################################"""
median = cv2.medianBlur(img, 5)
plt.subplot(2, 3, 1), plt.imshow(img), plt.title('raw')
plt.subplot(2, 3, 2), plt.imshow(blur), plt.title('blur')
plt.subplot(2, 3, 3), plt.imshow(box_T), plt.title('box_T')
plt.subplot(2, 3, 4), plt.imshow(box_F), plt.title('box_F')
plt.subplot(2, 3, 5), plt.imshow(aussian), plt.title('aussian')
plt.subplot(2, 3, 6), plt.imshow(median), plt.title('median')
plt.show()
"""######################################
# np.hstack(img1, img2, img3) 在水平方向上平铺
# np.vstack(img1, img2, img3) 在竖直方向上堆叠
######################################"""
res = np.hstack((blur, aussian, median))
cv2.imshow('median - Gaussian - average', res)
cv2.waitKey(0)
cv2.destroyAllWindows()
res = np.vstack((blur, aussian, median))
cv2.imshow('median - Gaussian - average', res)
cv2.waitKey(0)
cv2.destroyAllWindows()
(九)腐蚀与膨胀 —— cv2.erode() 与 cv2.dilate() + np.zeros() 与 np.ones()
import cv2 # opencv读取的格式是BGR
import numpy as np
import matplotlib.pyplot as plt # Matplotlib是RGB
"""###########################################################
# 腐蚀操作:cv2.erode(src, kernel, iteration)
# 膨胀操作:cv2.dilate(src, kernel, iteration)
# 两者参数说明相同: src表示输入的图片, kernel表示方框的大小, iteration表示迭代的次数
###########################################################"""
img = cv2.imread(r'picture/dige.png')
kernel = np.ones((3, 3), np.uint8) # 初始化卷积核大小
dilate_1 = cv2.dilate(img, kernel, iterations=1) # 膨胀(迭代次数1次)
dilate_2 = cv2.dilate(img, kernel, iterations=2) # 膨胀
dilate_3 = cv2.dilate(img, kernel, iterations=3) # 膨胀
erosion_1 = cv2.erode(img, kernel, iterations=1) # 腐蚀(迭代次数1次)
erosion_2 = cv2.erode(img, kernel, iterations=2) # 腐蚀
erosion_3 = cv2.erode(img, kernel, iterations=3) # 腐蚀
plt.subplot(2, 3, 1), plt.imshow(dilate_1), plt.title('erode-1')
plt.subplot(2, 3, 2), plt.imshow(dilate_2), plt.title('erode-2')
plt.subplot(2, 3, 3), plt.imshow(dilate_3), plt.title('erode-3')
plt.subplot(2, 3, 4), plt.imshow(erosion_1), plt.title('dilate-1')
plt.subplot(2, 3, 5), plt.imshow(erosion_2), plt.title('dilate-2')
plt.subplot(2, 3, 6), plt.imshow(erosion_3), plt.title('dilate-3')
plt.show()
"""###########################################################
# np.zeros()与np.ones():分别创建全0与全1的数组 —— 需导入numpy模块
# 两者输入参数相同,创建数组的方式也相同。
# 下面以np.zeros()为例
# np.zeros(shape, dtype=float, order='C')
# 输入参数 (1)shape:生成numpy数组
# (2)dtype:指定生成的数据类型数据类型,可选参数
# (3)order:表示在内存中是以行为主存储还是以列为主存储,可选参数,c代表行优先(默认);F代表列优先
# 创建一维数组: np.zeros(5)
# 创建多维数组: np.zeros((5,2))
# 创建int类型的数组: np.zeros((5,2),dtype=int)
# 创建x为int类型,y为float类型的数组:np.zeros((5,2),dtype=[('x','int'),('y','float')]
###########################################################"""
python 中的np.zeros()和np.ones()函数
(十)形态学变化 —— cv2.morphologyEx()
主要内容:开运算 + 闭运算 + 梯度计算 + 顶帽 + 黑帽
import cv2 # opencv读取的格式是BGR
import numpy as np
import matplotlib.pyplot as plt # Matplotlib是RGB
"""#######################################
# morphology
# n. (生物)形态学;(语言学中的)词法,形态学;结构,形态
#######################################
# morph
# n. 形素,语素;形态;图像变换
# v. (使)图像变形;将(图像)进行合成处理;改变,变化,变形
#######################################
# 形态学变化函数:cv2.morphologyEx(src, op, kernel)
# 参数说明:src传入的图片,op进行变化的方式, kernel表示方框的大小
# op变化的方式有五种:
# 开运算(open): cv2.MORPH_OPEN 先腐蚀,再膨胀。 开运算可以用来消除小黑点。
# 闭运算(close): cv2.MORPH_CLOSE 先膨胀,再腐蚀。 闭运算可以用来突出边缘特征。
# 形态学梯度(morph-grad): cv2.MORPH_GRADIENT 膨胀后图像(减去)腐蚀图像。 可以突出团块(blob)的边缘,保留物体的边缘轮廓。
# 顶帽(top-hat): cv2.MORPH_TOPHAT 原始输入(减去)开运算结果。 将突出比原轮廓亮的部分。
# 黑帽(black-hat): cv2.MORPH_BLACKHAT 闭运算结果(减去)原始输入 将突出比原轮廓暗的部分。
#######################################"""
img = cv2.imread(r'picture/dige.png')
kernel = np.ones((5, 5), np.uint8) # 卷积核初始化
img_open = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel) # 开运算
img_close = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel) # 闭运算
img_grad = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel) # 梯度计算
img_top = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel) # 顶帽
img_black = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel) # 黑帽
plt.subplot(2, 3, 1), plt.imshow(img), plt.title('RAW')
plt.subplot(2, 3, 2), plt.imshow(img_open), plt.title('MORPH_OPEN')
plt.subplot(2, 3, 3), plt.imshow(img_close), plt.title('MORPH_CLOSE')
plt.subplot(2, 3, 4), plt.imshow(img_grad), plt.title('MORPH_GRADIENT')
plt.subplot(2, 3, 5), plt.imshow(img_top), plt.title('MORPH_TOPHAT')
plt.subplot(2, 3, 6), plt.imshow(img_black), plt.title('MORPH_BLACKHAT')
plt.show()
(十一)边缘检测算子 —— cv2.sobel()、cv2.Scharr()、cv2.Laplacian()、cv2.Canny()
(1) 不同算子的差异:Sobel算子、Scharr算子、Laplacian算子
(2) Canny 不同阈值的区别
>
import cv2
import matplotlib.pyplot as plt
import numpy as np
img = cv2.imread(r'picture\lena.jpg')
############################################################################################
# (1)左边减右边(2)白到黑是正数,黑到白就是负数,且所有的负数会被截断成0,所以要取绝对值。
sobel_Gx = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3)
sobel_Gx_Abs = cv2.convertScaleAbs(sobel_Gx)
########################################
sobel_Gy = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=3)
sobel_Gy_Abs = cv2.convertScaleAbs(sobel_Gy)
# 分别计算x和y,再求和
sobel_Gx_Gy_Abs = cv2.addWeighted(sobel_Gx_Abs, 0.5, sobel_Gy_Abs, 0.5, 0) # 权重值x + 权重值y +偏置b
# 同时对x和y进行求导,会导致部分信息丢失。(不建议)
# sobel_Gx = cv2.Sobel(img, cv2.CV_64F, 1, 1, ksize=3)
########################################
plt.subplot(2, 3, 1), plt.imshow(img), plt.title('RAW')
plt.subplot(2, 3, 2), plt.imshow(sobel_Gx), plt.title('sobel_Gx')
plt.subplot(2, 3, 3), plt.imshow(sobel_Gx_Abs), plt.title('sobel_Gx_Abs')
plt.subplot(2, 3, 4), plt.imshow(sobel_Gy), plt.title('sobel_Gy')
plt.subplot(2, 3, 5), plt.imshow(sobel_Gy_Abs), plt.title('sobel_Gy_Abs')
plt.subplot(2, 3, 6), plt.imshow(sobel_Gx_Gy_Abs), plt.title('sobel_Gx_Gy_Abs')
plt.show()
"""########################################
# Sobel算子: 是一种常用的边缘检测算子。对噪声具有平滑作用,提供较为精确的边缘方向信息,但是边缘定位精度不够高。
# Sobel算子: 是离散微分算子(discrete differentiation operator),它结合了高斯平滑和微分求导,用来计算图像灰度的近似梯度,梯度越大越有可能是边缘。
# 边缘就是像素对应的灰度值快速变化的地方。如:黑到白的边界
# 图像是二维的。Sobel算子在x,y两个方向求导,故有不同的两个卷积核(Gx, Gy),且Gx的转置等于Gy。分别反映了每一点像素在水平方向和在垂直方向上的亮度变换情况.
########################################
# dst = cv2.Sobel(src, ddepth, dx, dy, ksize)
# 输入参数 src 输入图像
# ddepth 图像的深度,-1表示采用的是与原图像相同的深度。目标图像的深度必须大于等于原图像的深度;
# dx和dy 表示的是求导的阶数,0表示这个方向上没有求导,一般为0、1、2。
# ksize 卷积核大小,一般为3、5。
# 同时对x和y进行求导,会导致部分信息丢失。(不建议)- 分别计算x和y,再求和(效果好)
########################################
# (1)cv2.CV_16S的说明
# (1)Sobel函数求完导数后会有负值,还有会大于255的值。
# (2)而原图像是uint8,即8位无符号数。所以Sobel建立图像的位数不够,会有截断。
# (3)因此要使用16位有符号的数据类型,即cv2.CV_16S。
# (2)cv2.convertScaleAbs(): 给图像的所有像素加一个绝对值
# 通过该函数将其转回原来的uint8形式。否则将无法显示图像,而只是一副灰色的窗口。
############################################################################################"""
# 不同算子的差异:Sobel算子、Scharr算子、laplacian算子
img = cv2.imread(r'picture\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('Sobel, Scharr, Laplacian', res)
cv2.waitKey(0)
cv2.destroyAllWindows()
"""############################################################################################
# 边缘检测Canny算子
# (1)使用高斯滤波器,以平滑图像,滤除噪声。
# (2)计算图像中每个像素点的梯度强度和方向。
# (3)应用非极大值(Non-Maximum Suppression)抑制,以消除边缘检测带来的杂散响应。保留大值,去除小值。
# (4)应用双阈值(Double-Threshold)检测来确定真实的和潜在的边缘。
# (5)通过抑制孤立的弱边缘最终完成边缘检测。
############################################################################################"""
img = cv2.imread(r'picture\lena.jpg', cv2.IMREAD_GRAYSCALE)
v1 = cv2.Canny(img, 80, 150) # minVal越小,检测出来的特征越多(可能是假边界值);maxVal越大,检测出来的特征越少
v2 = cv2.Canny(img, 50, 100)
res = np.hstack((v1, v2))
cv2.imshow('Canny', res)
cv2.waitKey(0)
cv2.destroyAllWindows()
opencv边缘检测sobel算子 opencv边缘检测Canny
(十二)图像金字塔 —— cv2.pyrUp()、cv2.pyrDown()
import cv2
import matplotlib.pyplot as plt
img = cv2.imread(r'picture\AM.png')
img_up = cv2.pyrUp(img) # 高斯金字塔:图像上采样(放大一倍)
img_up2 = cv2.pyrUp(img_up) # 高斯金字塔:二次图像上采样(放大两倍)
############################
img_down = cv2.pyrDown(img) # 高斯金字塔:图像下采样(缩小一倍)
img_down2 = cv2.pyrDown(img_down) # 高斯金字塔:二次图像下采样(缩小两倍)
############################
img_up_down = cv2.pyrDown(img_up) # 高斯金字塔:先上采样,然后下采样
img_down_up = cv2.pyrUp(img_down) # 高斯金字塔:先下采样,然后上采样
############################
img_laplacian = img - img_down_up # 拉普拉斯金字塔:(1)源图像先缩小后再放大(2)源图像减去(1)操作后的图像。
plt.subplot(2, 4, 1), plt.imshow(img), plt.title('RAW')
plt.subplot(2, 4, 2), plt.imshow(img_up), plt.title('img_up')
plt.subplot(2, 4, 3), plt.imshow(img_up2), plt.title('img_up2')
plt.subplot(2, 4, 4), plt.imshow(img_down), plt.title('img_down')
plt.subplot(2, 4, 5), plt.imshow(img_down2), plt.title('img_down2')
plt.subplot(2, 4, 6), plt.imshow(img_up_down), plt.title('img_up_down')
plt.subplot(2, 4, 7), plt.imshow(img_down_up), plt.title('img_down_up')
plt.subplot(2, 4, 8), plt.imshow(img_laplacian), plt.title('img_laplacian')
plt.show()
"""############################################################################################
# 高斯金字塔: cv2.pyrUp 与 cv2.pyrDown
# cv2.pyrDown: 向下采样(缩小一倍)。 (1)对图像进行高斯内核卷积;(2)将所有偶数行和列去除;
# cv2.pyrUp: 向上采样(放大一倍),分辨率降低。(1)将图像每隔“一行与一列”,全部填充0;(2)使用先前同样的高斯内核(乘以4)与放大后的图像卷积,获得近似值;
# 形成过程大致: (1)对原图像进行低通滤波和降采样得到一个粗尺度的近似图像,即分解得到的低通近似图像,
# (2)对近似图像经过插值(即上采样)和低通滤波
# (3)计算它和原图像的差值,得到分解的带通分量。
# ——— 第一步:源图像先缩小后再放大; 第二步:源图像减去第一步操作后得到新图像。
# 图像缩放: (1)图像金字塔(2)resize()函数; 后者效果更好,不会降低分辨率。
############################################################################################"""
OpenCV计算机视觉学习 —— 图像金字塔(高斯金字塔,拉普拉斯金字塔,图像缩放resize函数)
(十三)图像轮廓检测 —— cv2.findContours()、cv2.drawContours()、cv2.arcLength()、cv2.approxPolyDP()、cv2.rectangle()
(1) 轮廓的多边形拟合曲线:cv2.approxPolyDP()
(2) 用矩形画出轮廓的边界:cv2.boundingRect()、cv2.rectangle()
(3) 用外接圆画出轮廓的边界:cv2.minEnclosingCircle()、cv2.circle()
import cv2
import matplotlib.pyplot as plt # Matplotlib是RGB
###################################
# 图像二值化 ———— 图像轮廓检测的输入图像是二值图,即黑白的(不是灰度图)
img = cv2.imread(r'picture\contours2.png')
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 灰度图
ret, thresh = cv2.threshold(img_gray, 127, 255, cv2.THRESH_BINARY) # 二值化
###################################
# (1)图像轮廓检测
draw_img1 = img.copy()
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
# 画出图像的轮廓(在图像上) —— 注意:图像需要copy(), 否则原图会随之一起改变。
res1 = cv2.drawContours(draw_img1, contours, -1, (0, 0, 255), 2)
###################################
# (2)轮廓的多边形拟合曲线
draw_img2 = img.copy()
contours1 = contours[0]
epsilon = 0.21*cv2.arcLength(contours1, True) # 系数k:越小越接近于真实轮廓,越大拟合越粗糙
approx = cv2.approxPolyDP(contours1, epsilon, True)
# 画出图像的轮廓(在图像上) —— 注意:图像需要copy(), 否则原图会随之一起改变。
res2 = cv2.drawContours(draw_img2, [approx], -1, (0, 0, 255), 2)
###################################
# (3)用矩形画出轮廓的边界
draw_img3 = img.copy()
x, y, w, h = cv2.boundingRect(contours1)
img_rectangle = cv2.rectangle(draw_img3, (x, y), (x+w, y+h), (0, 255, 0), 2)
###################################
# (4)用外接圆画出轮廓的边界
draw_img4 = img.copy()
(x, y), radius = cv2.minEnclosingCircle(contours1)
center = (int(x), int(y))
radius = int(radius)
img_circle = cv2.circle(draw_img4, center, radius, (0, 255, 0), 2)
###################################
plt.subplot(2, 3, 1), plt.imshow(img), plt.title('RAW') # 轮廓点绘制的颜色通道是BGR; 但是Matplotlib是RGB;
plt.subplot(2, 3, 2), plt.imshow(res1), plt.title('findContours') # 故在绘图时,(0, 0, 255)会由BGR转换为RGB(红 - 蓝)
plt.subplot(2, 3, 3), plt.imshow(res2), plt.title('approxPolyDP')
plt.subplot(2, 3, 4), plt.imshow(draw_img3), plt.title('rectangle')
plt.subplot(2, 3, 5), plt.imshow(draw_img4), plt.title('circle')
plt.show()
"""######################################################################
# (1)轮廓检测:contours, hierarchy = cv2.findContours(img, mode, method)
# 输入参数 mode: 轮廓检索模式
# (1)RETR_EXTERNAL: 只检索最外面的轮廓;
# (2)RETR_LIST: 检索所有的轮廓,但检测的轮廓不建立等级关系,将其保存到一条链表当中,
# (3)RETR_CCOMP: 检索所有的轮廓,并建立两个等级的轮廓。顶层是各部分的外部边界,内层是的边界信息;
# (4)RETR_TREE: 检索所有的轮廓,并建立一个等级树结构的轮廓;(最常用)
# method: 轮廓逼近方法
# (1)CHAIN_APPROX_NONE: 存储所有的轮廓点,相邻的两个点的像素位置差不超过1。 例如:矩阵的四条边。(最常用)
# (2)CHAIN_APPROX_SIMPLE: 压缩水平方向,垂直方向,对角线方向的元素,只保留该方向的终点坐标。 例如:矩形的4个轮廓点。
# 输出参数 contours:所有的轮廓
# hierarchy:每条轮廓对应的属性
# 备注0:轮廓就是将连续的点(连着边界)连在一起的曲线,具有相同的颜色或者灰度。轮廓在形状分析和物体的检测和识别中很有用。
# 备注1:函数输入图像是二值图,即黑白的(不是灰度图)。所以读取的图像要先转成灰度的,再转成二值图。
# 备注2:函数在opencv2只返回两个值:contours, hierarchy。
# 备注3:函数在opencv3会返回三个值:img, countours, hierarchy
######################################################################
# (2)绘制轮廓:v2.drawContours(image, contours, contourIdx, color, thickness) ———— (在图像上)画出图像的轮廓
# 输入参数 image: 需要绘制轮廓的目标图像,注意会改变原图
# contours: 轮廓点,上述函数cv2.findContours()的第一个返回值
# contourIdx: 轮廓的索引,表示绘制第几个轮廓。-1表示绘制所有的轮廓
# color: 绘制轮廓的颜色(RGB)
# thickness: (可选参数)轮廓线的宽度,-1表示填充
# 备注:图像需要先复制一份copy(), 否则(赋值操作的图像)与原图会随之一起改变。
######################################################################
# (3)计算轮廓的长度:retval = cv2.arcLength(curve, closed)
# 输入参数: curve 轮廓(曲线)。
# closed 若为true,表示轮廓是封闭的;若为false,则表示打开的。(布尔类型)
#
# 输出参数: retval 轮廓的长度(周长)。
######################################################################
# (4)找出轮廓的多边形拟合曲线:approxCurve = approxPolyDP(contourMat, epsilon, closed);
# 输入参数: contourMat: 轮廓点矩阵(集合)
# epsilon: (double类型)指定的精度, 即原始曲线与近似曲线之间的最大距离。
# closed: (bool类型)若为true, 则说明近似曲线是闭合的; 反之, 若为false, 则断开。
#
# 输出参数: approxCurve: 轮廓点矩阵(集合);当前点集是能最小包容指定点集的。画出来即是一个多边形;
######################################################################
# (5)绘制矩形边框:cv2.rectangle(img, (x, y), (x+w, y+h), (0, 255, 0), 2)
# (x, y): 矩形定点
# (x+w, y+h): 矩形的宽高
# (0,0,225): 矩形的边框颜色;
# 2: 矩形边框宽度
######################################################################"""
(十四)模板匹配 —— cv2.matchTemplate()、cv2.minMaxLoc()
############################################################################################
# 模板匹配和卷积原理很相似:模板在原图像上从原点开始滑动,并计算模板与(图像被模板覆盖的地方)的相似程度;
# 【假如】原图形大小: AxB; 而模板大小: axb; 则输出结果的矩阵大小: (A-a+1)x(B-b+1)
############################################################################################
import cv2
import matplotlib.pyplot as plt # Matplotlib是RGB
img = cv2.imread(r'picture/lena.jpg', 0) # 读取目标图片(0/1/2,表示RGB单通道)
template = cv2.imread(r'picture/face.jpg', 0) # 读取模板图片
h, w = template.shape[::1] # 获得模板图片的高宽尺寸
##############################
# 如果模板方法是平方差或者归一化平方差,要用min_loc; 其余用max_loc
##############################
res1 = cv2.matchTemplate(img, template, cv2.TM_SQDIFF) # 执行模板匹配,采用的匹配方式cv2.TM_SQDIFF_NORMED
min_val1, max_val1, min_loc1, max_loc1 = cv2.minMaxLoc(res1) # 寻找矩阵(一维数组当做向量,用Mat定义)中的最大值和最小值的匹配结果及其位置
top_left1 = min_loc1
bottom_right1 = (top_left1[0] + w, top_left1[1] + h)
##############################
res2 = cv2.matchTemplate(img, template, cv2.TM_CCORR)
min_val2, max_val2, min_loc2, max_loc2 = cv2.minMaxLoc(res2)
top_left2 = max_loc2
bottom_right2 = (top_left2[0] + w, top_left2[1] + h)
##############################
res3 = cv2.matchTemplate(img, template, cv2.TM_CCOEFF)
min_val3, max_val3, min_loc3, max_loc3 = cv2.minMaxLoc(res3)
top_left3 = max_loc3
bottom_right3 = (top_left3[0] + w, top_left3[1] + h)
##############################
res4 = cv2.matchTemplate(img, template, cv2.TM_SQDIFF_NORMED)
min_val4, max_val4, min_loc4, max_loc4 = cv2.minMaxLoc(res4)
top_left4 = min_loc4
bottom_right4 = (top_left4[0] + w, top_left4[1] + h)
##############################
res5 = cv2.matchTemplate(img, template, cv2.TM_CCORR_NORMED)
min_val5, max_val5, min_loc5, max_loc5 = cv2.minMaxLoc(res5)
top_left5 = max_loc5
bottom_right5 = (top_left5[0] + w, top_left5[1] + h)
##############################
res6 = cv2.matchTemplate(img, template, cv2.TM_CCOEFF_NORMED)
min_val6, max_val6, min_loc6, max_loc6 = cv2.minMaxLoc(res6)
top_left6 = max_loc6
bottom_right6 = (top_left6[0] + w, top_left6[1] + h)
# 画矩形
img1 = img.copy(); img2 = img.copy(); img3 = img.copy()
img4 = img.copy(); img5 = img.copy(); img6 = img.copy()
cv2.rectangle(img1, top_left1, bottom_right1, 255, 2)
cv2.rectangle(img2, top_left2, bottom_right2, 255, 2)
cv2.rectangle(img3, top_left3, bottom_right3, 255, 2)
cv2.rectangle(img4, top_left4, bottom_right4, 255, 2)
cv2.rectangle(img5, top_left5, bottom_right5, 255, 2)
cv2.rectangle(img6, top_left6, bottom_right6, 255, 2)
##############################################
# plt.imshow(img1) 彩图显示
# plt.imshow(img1, cmap='gray') 灰色图
plt.subplot(231), plt.imshow(img1, cmap='gray'), plt.axis('off'), plt.title('cv2.TM_SQDIFF')
plt.subplot(232), plt.imshow(img2, cmap='gray'), plt.axis('off'), plt.title('cv2.TM_CCORR')
plt.subplot(233), plt.imshow(img3, cmap='gray'), plt.axis('off'), plt.title('cv2.TM_CCOEFF')
plt.subplot(234), plt.imshow(img4, cmap='gray'), plt.axis('off'), plt.title('cv2.TM_SQDIFF_NORMED')
plt.subplot(235), plt.imshow(img5, cmap='gray'), plt.axis('off'), plt.title('cv2.TM_CCORR_NORMED')
plt.subplot(236), plt.imshow(img6, cmap='gray'), plt.axis('off'), plt.title('cv2.TM_CCOEFF_NORMED')
plt.show()
"""##############################################
# 模板匹配:cv2.matchTemplate(image, template, method)
# 输入图像:检测对象的图像
# 模板图像:待检测的对象特征
# 模板匹配方法:
# (1)cv2.TM_SQDIFF: 计算平方差。 计算出来的值越接近0,越相关
# (2)cv2.TM_CCORR: 计算相关性。 计算出来的值越大,越相关
# (3)cv2.TM_CCOEFF: 计算相关系数。 计算出来的值越大,越相关
# (4)cv2.TM_SQDIFF_NORMED: 计算(归一化)平方差。 计算出来的值越接近0,越相关
# (5)cv2.TM_CCORR_NORMED: 计算(归一化)相关性。 计算出来的值越接近1,越相关
# (6)cv2.TM_CCOEFF_NORMED: 计算(归一化)相关系数。 计算出来的值越接近1,越相关
# (最好选择有归一化操作,效果好)
##############################################
# 获取匹配结果函数:min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(ret)
# 其中: ret是cv2.matchTemplate函数返回的矩阵;
# min_val, max_val, min_loc, max_loc分别表示最小值,最大值,最小值与最大值在图像中的位置
# 如果模板方法是平方差或者归一化平方差,要用min_loc; 其余用max_loc
##############################################"""
opencv 实现模板匹配、特征点匹配
(十五)直方图(均衡化) —— cv2.calcHist()、img.ravel()、cv2.bitwise_and()、cv2.equalizeHist()、cv2.createCLAHE()
import cv2 # opencv读取的格式是BGR
import matplotlib.pyplot as plt # Matplotlib是RGB
import numpy as np
##############################################
img = cv2.imread(r'picture\cat.jpg', 0) # 0表示灰度图
hist = cv2.calcHist([img], [0], None, [256], [0, 256])
plt.hist(img.ravel(), 256) # img.ravel() 将图片转化成一维数组; 256 是BIN的数目
plt.show()
# hist(): 用于绘制直方图。
##############################################
# 不同颜色通道的直方图
img1 = cv2.imread(r'picture\cat.jpg', 1) # 0表示灰度图
color = ('blue', 'green', 'red')
for i, col in enumerate(color):
hist1 = cv2.calcHist([img1], [i], None, [256], [0, 256]) # img必须是单通道图像,否则报错:error: (-215)
plt.plot(hist1, color=col)
plt.show()
"""##############################################
# 统计每个像素的众数(备注:输入参数需用方括号[]表示)
# 直方图: cv2.calcHist(images,channels,mask,histSize,ranges)
# 输入参数: images: 原图像图像格式必须为 uint8 或 float32。
# channels: (1)灰度图:[0]; (2)彩色图像:[0] [1] [2],分别对应着BGR。
# mask: 掩模图像。(1)统计整幅图像的直方图,则设置为 None。(2)统计图像某一区域的直方图,则制作一个掩模图像。
# histSize: BIN的数目。可以理解为迭代范围。如:[1]表示0,1,2...256;如[10]表示0~10,11~20...;如[256]表示0~256。
# ranges: 统计像素值范围。通常为[0~256]。
##############################################"""
# 创建掩膜:mask
mask = np.zeros(img.shape[:2], np.uint8) # np.uint8:八位无符号整型:0~255
mask[100:300, 100:400] = 255 # 掩膜区域显示255
masked_img = cv2.bitwise_and(img, img, mask=mask) # 与操作
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.title('Raw')
plt.subplot(222), plt.imshow(mask, 'gray'), plt.title('mask')
plt.subplot(223), plt.imshow(masked_img, 'gray'), plt.title('cv2.bitwise_and')
plt.subplot(224), plt.plot(hist_full), plt.plot(hist_mask), plt.title('hist_full + hist_mask')
plt.subplot(224), plt.plot(hist_full), plt.plot(hist_mask), plt.title('hist_full + hist_mask')
plt.xlim([0, 256])
plt.show()
##############################################
# 直方图均衡化
img = cv2.imread(r'picture\cat.jpg', 0) # 0表示灰度图
equ = cv2.equalizeHist(img) # 将0~255两端的像素进行整幅图像的均匀化。即去掉:极黑/极白像素
# 自适应直方图均衡化
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8)) # 将图像切分成8*8份,分别进行直方图均衡化(有效果降低过度处理)
res_clahe = clahe.apply(img)
plt.subplot(131), plt.imshow(img, 'gray'), plt.title('Raw')
plt.subplot(132), plt.imshow(equ, 'gray'), plt.title('cv2.equalizeHist')
plt.subplot(133), plt.imshow(res_clahe, 'gray'), plt.title('cv2.createCLAHE')
plt.show()
##############################################
matplotlib.pyplot.hist()函数 像素点直方图统计、掩膜图像 错误提示:cv::binary_op 错误提示:cv::histPrepareImages
(十六)傅里叶变换 + 低通/高通滤波 —— cv2.dft()、cv2.idft()、np.fft.fftshift()、np.fft.ifftshift()、cv2.magnitud()
傅里叶变换
以时间作为参照来观察动态世界的方法我们称其为时域分析。
世间万物都在随着时间不停的改变,并且永远不会静止下来。但在频域中,你会发现世界是静止的、永恒不变的。
傅里叶告诉我们:任何周期函数,都可以看作是不同振幅,不同相位正弦波的叠加。
举例:利用对不同琴键不同力度,不同时间点的敲击,可以组合出任何一首乐曲。
傅里叶分析可分为傅里叶级数(Fourier Serie)和傅里叶变换(Fourier Transformation)。
傅里叶变换的作用
(1)高频:变化剧烈的灰度分量,例如:边界/图像的轮廓
(2)低频:变化缓慢的灰度分量,例如:一片大海
滤波器
(1)低通滤波器:只保留低频,会使得图像模糊
(2)高通滤波器:只保留高频,会使得图像细节增强
"""##############################################
# 傅里叶变换:cv2.dft(np.float32, cv2.DFT_COMPLEX_OUTPUT)
# 输入参数: (1)输入图像需要先转换成np.float32 格式。
# (2)转换标识 - cv2.DFT_COMPLEX_OUTPUT - 用来输出一个复数阵列
# 逆傅里叶变换:cv2.idft(dft_shift)
# 输入参数: (1)傅里叶变换后并位置转换后的频谱图像。
# 在OpenCV中,我们通过cv2.dft()来实现傅里叶变换,使用cv2.idft()来实现逆傅里叶变换。
# 注意1:变换后得到原始图像的频谱信息。其中:频率为0的部分(零分量)会在左上角,需要使用numpy.fft.fftshift()函数,将其移动到中间位置。
# 注意2:变换后的频谱图像是双通道的(实部,虚部)。需要使用cv2.magnitude函数,将幅度映射到灰度空间[0,255]内,使其以灰度图像显示出来。
# cv2.magnitude(x-实部,y-虚部)
# 输入参数: (1)浮点型x坐标值(实部)
# (2)浮点型y坐标值(虚部)
# 备注:两个参数的必须具有相同的大小(size)
##############################################"""
# 频谱图像设计
img = cv2.imread(r'picture\lena.jpg', 0) # 0表示灰度图
dft = cv2.dft(np.float32(img), flags=cv2.DFT_COMPLEX_OUTPUT) # 傅里叶变换(np.float32 格式)
dft_shift = np.fft.fftshift(dft) # 移动到中心位置
magnitude_spectrum = 20*np.log(cv2.magnitude(dft_shift[:, :, 0],dft_shift[:, :, 1])) # 理解为固定公式即可
# 频谱:最中心的频率最小,像圆一样向外扩散,越来越大。# 备注:20*np.log(cv2.magnitude())
##############################################
# 低通滤波设计
rows, cols = img.shape
crow, ccol = int(rows/2), int(cols/2) # 获取图像中心位置
mask_low = np.zeros((rows, cols, 2), np.uint8)
mask_low[crow-30:crow+30, ccol-30:ccol+30] = 1
fshift_low = dft_shift * mask_low
f_ishift_low = np.fft.ifftshift(fshift_low)
img_low = cv2.idft(f_ishift_low) # 逆傅里叶变换
img_low = cv2.magnitude(img_low[:, :, 0], img_low[:, :, 1]) # 频谱图像转灰度图像
##############################################
# 高通滤波设计
mask_high = np.ones((rows, cols, 2), np.uint8)
mask_high[crow-30:crow+30, ccol-30:ccol+30] = 0
fshift_high = dft_shift * mask_high
f_ishift_high = np.fft.ifftshift(fshift_high)
img_high = cv2.idft(f_ishift_high) # 逆傅里叶变换
img_high = cv2.magnitude(img_high[:, :, 0], img_high[:, :, 1]) # 频谱图像转灰度图像
##############################################
plt.subplot(141), plt.imshow(img, cmap='gray'), plt.title('Input Image'), plt.xticks([]), plt.yticks([])
plt.subplot(142), plt.imshow(magnitude_spectrum, cmap='gray'), plt.title('Magnitude Spectrum'), plt.xticks([]), plt.yticks([])
plt.subplot(143), plt.imshow(img_low, cmap='gray'), plt.title('Low pass filter'), plt.xticks([]), plt.yticks([])
plt.subplot(144), plt.imshow(img_high, cmap='gray'), plt.title('High pass filter'), plt.xticks([]), plt.yticks([])
plt.show()
傅里叶分析之详细剖析(完整版-强烈推荐)
(十七)Harris角点检测 —— cv2.cornerHarris()、np.float32()
import cv2
import numpy as np
img = cv2.imread(r'picture\Black_and_white_chess.jpg')
print('img.shape:', img.shape)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # OpenCV读取图像的颜色是BGR
gray = np.float32(gray)
"""#################################################
# Harris角点检测:res = cv2.cornerHarris(img_gray, blockSize, k_size, k)
# 输入参数 img_gray 数据类型为 float32 的输入图像。
# blockSize 角点检测中要考虑的领域大小 (一般等于2)
# k_size Sobel求导中使用的窗口大小 (一般等于3)
# k 方程中检测器的自由参数, 取值参数为 [0,04,0.06].
# 角点定义: 角点是一个无论框框往哪边移动 框框内像素值都会变化很大的情况而定下来的点。
##################################################
# float16 半精度浮点数,包括:1 个符号位,5 个指数位,10 个尾数位
# float32 单精度浮点数,包括:1 个符号位,8 个指数位,23 个尾数位
# float64 双精度浮点数,包括:1 个符号位,11 个指数位,52 个尾数位
##################################################"""
Harris_dst = cv2.cornerHarris(gray, 2, 3, 0.04)
print('dst.shape:', Harris_dst.shape)
# 最佳阈值因图而异(如果角点>角点最大值*0.01,就判断为角点)
img[Harris_dst > 0.01 * Harris_dst.max()] = [0, 0, 255] # [0, 0, 255] 表示角点用红色表示
cv2.imshow('Harris_dst', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
OpenCV特征提取与检测之Harris角点检测
(十八)SIFT尺度不变特征检测 —— cv2.xfeatures2d.SIFT_create()、sift.detectAndCompute()、sift.detect()、sift.compute()、cv2.drawKeypoints
"""#########################################################
# SIFT 图像特征检测算法
# (Scale-invariant feature transform,SIFT)尺度不变特征变换。
# 特点:具有尺度不变性。即对图片进行放缩、变形、模糊、明暗变化、光照变化、添加噪声,甚至是使用不同的相机拍摄不同角度的照片的情况下,SIFT都能检测到稳定的特征点,并建立对应关系。
# 缺点:计算量比较大,很难实时
# 对比:Harris角点检测算法最大的缺陷是不具有尺度不变性。当图片放大后,原来能检测到的角点就变成了边线,就检测不到了
#########################################################
# 在Opencv中,SIFT函数从3.4.3版本以上已经涉及到专利保护。顾Opencv需要降版本使用。
# 卸载旧版本:pip uninstall opencv-python
# pip uninstall opencv-contrib-python
# 安装新版本:pip install opencv-python==3.4.1.15
# pip install opencv-contrib-python==3.4.1.15
#########################################################"""
import cv2 # OpenCV读取图像的颜色是BGR
img = cv2.imread('test_1.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 转换为灰度图
sift = cv2.xfeatures2d.SIFT_create() # 实例化一个sift()函数
"""#########################################################
# 检测关键点并计算描述子:key_points, des = sift.detectAndCompute(img, None)
# 输出参数:key_points为图像关键点;dst为sift特征向量,通常是128维。
#
# 其中,sift.detectAndCompute()可以拆分为以下两个函数:sift.detect()和sift.compute()。
# key_points = sift.detect(gray, None) # 找出图像中的关键点
# kp, dst = sift.compute(key_points) # 计算关键点对应的sift特征向量
#########################################################"""
key_points, des = sift.detectAndCompute(gray, None) # 找出图像中的关键点
"""#########################################################
# 在图中画出关键点:ret = cv2.drawKeypoints(gray, key_points, img)
# 输入参数:gray表示输入图片;kp表示关键点;img表示输出的图片
#########################################################"""
img = cv2.drawKeypoints(gray, key_points, img) # 在图中画出关键点
cv2.imshow('key_points', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
(十九)暴力特征匹配 —— cv2.BFMatcher_create()、bf.match()、bf_knn.knnMatch()、cv2.drawMatches()
"""########################################################
# 暴力特征检测主要流程
# (1)先在查询描述符中取一个关键点的描述符,将其与训练描述符中的所有关键点描述符进行比较。
# (2)每次比较后会计算出一个距离值,距离最小的值对应最佳匹配结果。
# (3)所有描述符比较完后,匹配器返回匹配结果列表。
########################################################
# 在Opencv中,SIFT函数从3.4.3版本以上已经涉及到专利保护。顾Opencv需要降版本使用。
# 卸载旧版本:pip uninstall opencv-python
# pip uninstall opencv-contrib-python
# 安装新版本:pip install opencv-python==3.4.1.15
# pip install opencv-contrib-python==3.4.1.15
########################################################"""
import cv2 # opencv读取的格式是BGR
import matplotlib.pyplot as plt # Matplotlib是RGB
img1 = cv2.imread(r'picture\box.png')
img2 = cv2.imread(r'picture\box_in_scene.png')
#####################################
sift = cv2.xfeatures2d.SIFT_create() # 创建SIFT特征检测器
##########################
kp1, des1 = sift.detectAndCompute(img1, None) # 获取第一张图关键点kp1与特征向量des1
kp2, des2 = sift.detectAndCompute(img2, None) # 获取第二张图关键点kp2与特征向量des2
#####################################
# 方法1:match()匹配最佳结果
bf = cv2.BFMatcher_create(cv2.NORM_L1, crossCheck=False)
matches = bf.match(des1, des2)
matches = sorted(matches, key=lambda x: x.distance)
result = cv2.drawMatches(img1, kp1, img2, kp2, matches[:15], None)
#####################################
# 方法二:knnMatch()匹配指定数量的最佳结果
bf_knn = cv2.BFMatcher_create(cv2.NORM_HAMMING, crossCheck=False)
ms_knn = bf_knn.knnMatch(des1, des2, k=2)
# 应用比例测试选择要使用的匹配结果
good = []
for m, n in ms_knn:
if m.distance < 0.75 * n.distance:
good.append(m)
img_DEFAUL = cv2.drawMatches(img1, kp1, img2, kp2, good[:20], None, flags=cv2.DrawMatchesFlags_DEFAUL)
img_NO_POINTS = cv2.drawMatches(img1, kp1, img2, kp2, good[:20], None, flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
img_KEYPOINTS = cv2.drawMatches(img1, kp1, img2, kp2, good[:20], None, flags=cv2.DrawMatchesFlags_DRAW_RICH_KEYPOINTS)
#####################################
plt.subplot(231), plt.imshow(img1), plt.title('raw_img1')
plt.subplot(232), plt.imshow(img2), plt.title('raw_img2')
plt.subplot(233), plt.imshow(result), plt.title('match_result')
plt.subplot(234), plt.imshow(img_DEFAUL), plt.title('knnMatch_DEFAUL')
plt.subplot(235), plt.imshow(img_NO_POINTS), plt.title('knnMatch_NO_POINTS')
plt.subplot(236), plt.imshow(img_KEYPOINTS), plt.title('knnMatch_KEYPOINTS')
plt.show()
"""########################################################
# 暴力匹配器:bf = cv2.BFMatcher_create(normType, crossCheck)
# 输出参数 bf 返回的暴力匹配器对象
# 输入参数 crossCheck 默认为False, 表示匹配器为每个查询描述符找到k个距离最近的匹配描述符。若为True, 则只返回满足交叉验证条件的匹配结果。
# normType 距离测量类型
# 方法(1)【SIFT】描述符使用cv2.NORM_L1或cv2.NORM_L2,默认为cv2.NORM_L2。
# 方法(2)【ORB】描述符使用cv2.NORM_HAMMING
###########################
# (1)匹配最佳结果:ms = bf.match(des1, des2)
# 输出参数 ms 为每个关键点的最佳匹配结果(距离值越小匹配度越高)
# 输入参数 des1 为查询描述符
# des2 为训练描述符
###########################
# (2)匹配指定数量的最佳结果:ms = knnMatch(des1, des2, k=n)
# 输出参数 ms 为返回的匹配结果, 每个列表元素是一个子列表, 它包含了由参数k指定个数的DMatch对象
# 输入参数 des1 为查询描述符
# des2 为训练描述符
# k 为返回的最佳匹配个数
########################################################
# 绘制最佳匹配:outImg = cv2.drawMatches(img1, keypoints1, img2, keypoints2, matches1to2[, matchColor[, singlePointColor[, matchesMask[, flags]]]])
# 输出参数 outImg为返回的绘制结果图像, 图像中查询图像与训练图像中匹配的关键点个两点之间的连线为彩色
# 输入参数 (1)img1为查询图像 (2)keypoints1为img1的关键点
# (3)img2为训练图像 (4)keypoints2为img2的关键点
# (5)matches1to2 img1与img2的匹配结果
# (6)matchColor 关键点和链接线的颜色, 默认使用随机颜色
# (7)singlePointColor 单个关键点的颜色, 默认使用随机颜色
# (8)matchesMask 掩膜, 用于决定绘制哪些匹配结果, 默认为空, 表示绘制所有匹配结果
# flags为标志, 可设置为下列参数值:
# (1)cv2.DrawMatchesFlags_DEFAUL (默认方式)绘制两个源图像、匹配项和单个关键点, 没有围绕关键点的圆以及关键点的大小和方向
# (2)cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS 不会绘制单个关键点
# (3)cv2.DrawMatchesFlags_DRAW_RICH_KEYPOINTS 在关键点周围绘制具有关键点大小和方向的圆圈
########################################################"""
OpenCV特征匹配 SIFT demo(图解) OpenCV特征检测之SIFT算法(原理详解) OpenCV特征检测之特征匹配方式详解 文章来源:https://www.toymoban.com/news/detail-439486.html
(二十)图像缩放+镜像+平移+旋转+仿射变换+透视变换 —— cv2.resize()、cv2.getRotationMatrix2D()、cv2.getAffineTransform()、cv2.getPerspectiveTransform()、cv2.warpPerspective()、cv2.warpAffine()
文章来源地址https://www.toymoban.com/news/detail-439486.html
import cv2
import numpy as np
import matplotlib.pyplot as plt # 绘图展示
################################################
img = cv2.imread(r"picture/1.jpg", 1)
imgInfo = img.shape
height = imgInfo[0]; width = imgInfo[1]; deep = imgInfo[2]
################################################################################################
# 图像缩放
# (1)直接进行缩放操作
dstHeight = int(height/2)
dstWidth = int(width/2)
dst_resize_dir = cv2.resize(img, (dstWidth, dstHeight))
# (2)使用邻域插值法进行缩放
dst_resize_linear = np.zeros([dstHeight, dstWidth, 3], np.uint8)
for i in range(dstHeight):
for j in range(dstWidth):
iNew = i * (height * 1.0 / dstHeight)
jNew = j * (width * 1.0 / dstWidth)
dst_resize_linear[i, j] = img[int(iNew), int(jNew)]
################################################################################################
# 镜像
dst_mirror = np.zeros([height * 2, width, deep], np.uint8)
for i in range(height):
for j in range(width):
dst_mirror[i, j] = img[i, j]
dst_mirror[height * 2 - i - 1, j] = img[i, j]
for i in range(width):
dst_mirror[height, i] = (0, 0, 255)
################################################################################################
# 图像平移
dst_trans = np.zeros(imgInfo, np.uint8)
for i in range(height):
for j in range(width - 200): # 平移大小:100
dst_trans[i, j+100] = img[i, j] # 新图像截取原图像部分数据
################################################################################################
# 图像旋转
matRotate = cv2.getRotationMatrix2D((height*0.5, width*0.5), 45, 0.7) # 获取旋转矩阵
dst_rotate = cv2.warpAffine(img, matRotate, (height, width)) # 仿射变换
################################################################################################
# 仿射变换
matSrc = np.float32([[0, 0], [0, height-1], [width-1, 0]]) # 原图的三个点坐标
matDst = np.float32([[50, 50], [100, height-50], [width-200, 100]]) # 仿射的三个点坐标
matAffine = cv2.getAffineTransform(matSrc, matDst) # 获取仿射变换矩阵
dst_affine = cv2.warpAffine(img, matAffine, (height, width)) # 仿射变换
################################################################################################
# 透视变换
matDst1 = np.float32([[50, 50], [100, height-50], [width-200, 100], [width-200, height-50]]) # 仿射的三个点坐标
matSrc1 = np.float32([[0, 0], [0, height-1], [width-1, 0], [width-1, height-1]]) # 原图的三个点坐标
matwarp = cv2.getPerspectiveTransform(matDst1, matSrc1) # 参数1:输入图像的四个点坐标 参数2:输出图像的四个点坐标(方方正正的图像)
dst_Perspective = cv2.warpPerspective(img, matwarp, (height, width))
dst_Perspective = np.hstack((dst_affine, dst_Perspective))
################################################################################################
plt.subplot(241), plt.imshow(img, 'gray'), plt.title('img')
plt.subplot(242), plt.imshow(dst_resize_dir, 'gray'), plt.title('dst_resize_dir (coordinates)')
plt.subplot(243), plt.imshow(dst_resize_linear, 'gray'), plt.title('dst_resize_linear (coordinates)')
plt.subplot(244), plt.imshow(dst_mirror, 'gray'), plt.title('dst_mirror')
plt.subplot(245), plt.imshow(dst_trans, 'gray'), plt.title('dst_trans')
plt.subplot(246), plt.imshow(dst_rotate, 'gray'), plt.title('dst_rotate')
plt.subplot(247), plt.imshow(dst_affine, 'gray'), plt.title('dst_affine')
plt.subplot(248), plt.imshow(dst_Perspective, 'gray'), plt.title('dst_Perspective')
plt.show()
"""################################################
# 图像缩小或放大:cv2.resize(src, dsize, fx=0, fy=0, interpolation=INTER_LINEAR)
# 输入参数: src 输入图片
# dsize (1)矩阵参数缩放到指定大小(width,height); 例如:cv2.resize(img_dog, (500, 414))
# (2)矩阵参数为(0,0),原图像缩放倍数通过fx, fy来控制; 例如:cv2.resize(img_dog, (0, 0), fx=4, fy=4)
# fx, fy 沿x轴,y轴的缩放系数
# interpolation 插值方法,有以下五种(可选参数)
# (1)INTER_NEAREST 最近邻插值 (2)INTER_LINEAR 双线性插值(默认设置)
# (3)INTER_AREA 使用像素区域关系进行重采样。 (4)INTER_CUBIC 4x4像素邻域的双三次插值
# (5)INTER_LANCZOS4 8x8像素邻域的Lanczos插值
################################################
# 获取旋转矩阵:rot_mat = cv2.getRotationMatrix2D(center, angle, scale)
# 输入参数 center 旋转的中心点。一般是图像的中心,取图像长宽的一半
# angle 旋转的角度。正值是顺时针旋转,负值是逆时针旋转
# scale 缩放比例。
################################################
# 获取仿射变换矩阵:matAffine = cv2.getAffineTransform(matSrc, matDst)
# 输入参数 matSrc 原图的三个点坐标
# matDst 仿射的三个点坐标
# 输出参数 matAffine 仿射变换矩阵
################################################
# 计算齐次变换矩阵:cv2.getPerspectiveTransform(rect, dst)
# 输入参数 rect输入图像的四个点(四个角)
# dst输出图像的四个点(方方正正的图像对应的四个角)
################################################
# 仿射变换:cv2.warpPerspective(src, M, dsize, dst=None, flags=None, borderMode=None, borderValue=None)
# 透视变换:cv2.warpAffine(src, M, dsize, dst=None, flags=None, borderMode=None, borderValue=None)
# src:输入图像 dst:输出图像
# M:2×3的变换矩阵
# dsize:变换后输出图像尺寸
# flag:插值方法
# borderMode:边界像素外扩方式
# borderValue:边界像素插值,默认用0填充
#
# (Affine Transformation)可实现旋转,平移,缩放,变换后的平行线依旧平行。
# (Perspective Transformation)即以不同视角的同一物体,在像素坐标系中的变换,可保持直线不变形,但是平行线可能不再平行。
#
# 备注:cv2.warpAffine需要与cv2.getRotationMatrix2D/cv2.getAffineTransform/cv2.getPerspectiveTransform搭配使用。
################################################"""
到了这里,关于Opencv图像处理(全)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!