目录
一、基础操作
1. openCV界面
# 导入 OpenCV
import cv2
# 创建窗口,
cv2.namedWindow('window',cv2.WINDOW_NORMAL)
# 更改窗口:window_autosize,设置大小无用
cv2.resizeWindow('window',width=800,height=600)
# 展示窗口、图片等
cv2.imshow('window',0)
# 等待按键
# 1. 将窗口堵塞。等带按键、并会返回按键的ASCII码
# 2. 可以设置等待的时间,单位 ms
# 3. 等待时间为 0 ,则为一直等待
key = cv2.waitKey(0)
# ord():获取字符的ASCII码
# key & 0xFF:将 int 类型的低字节给提去出来,因为ASCII码为一字节
if key & 0xFF == ord('q'):
# 销毁窗口
cv2.destroyAllWindows()
2. 图像的基础操作
2.1 图像的输入与输出
import cv2
# 图片显示函数
def showImage(name:str,image):
""" 显示图片的函数 """
cv2.imshow(name,image)
cv2.waitKey(0)
cv2.destroyAllWindows()
image_path = 'D:\\Python_Demo\\test.png' # 替换为你的图片路径
image = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
# 检查图片是否读取成功
if image is None:
print("Error: Could not read the image.")
else:
# 显示图片
showImage('Displayed Image', image)
# 保存图片
save_path = 'D:\\Python_Demo\\output_test.png' # 替换为保存图片的路径
cv2.imwrite(save_path, image)
print(f"Image saved to {save_path}")
其中,cv2.imread(图片路径,flags) 的 flags如下:
cv2.IMREAD_COLOR (默认) 读取彩色图像
cv2.IMREAD_GRAYSCALE 读取灰度图像
cv2.IMREAD_UNCHANGED 读取原始图像
cv2.IMREAD_ANYCOLOR 读取图像并尝试将其转换为 BGR 格式
cv2.IMREAD_ANYDEPTH 读取图像并保持其位深度
cv2.IMREAD_REDUCED_GRAYSCALE_2 读取图像并将其缩小为原始尺寸的 1/2 按灰度模式读取
cv2.IMREAD_REDUCED_COLOR_2 读取图像并将其缩小为原始尺寸的 1/2,按彩色模式读取
cv2.IMREAD_REDUCED_GRAYSCALE_4 读取图像并将其缩小为原始尺寸的 1/4,按灰度模式读取
cv2.IMREAD_REDUCED_COLOR_4 读取图像并将其缩小为原始尺寸的 1/4,按彩色模式读取
cv2.IMREAD_REDUCED_GRAYSCALE_8 读取图像并将其缩小为原始尺寸的 1/8,按灰度模式读取
cv2.IMREAD_REDUCED_COLOR_8 读取图像并将其缩小为原始尺寸的 1/8,按彩色模式读取
2.2 图片的数组的本质
图片数据类型:
读取的图片类型为
<class 'numpy.ndarray'> # 即图片其实是一个数据
数组类型:uint8,一字节的无符号整数
数组维度: 三维,[高度像素,宽度像素,RGB值]
所以可以通过:
blackImage = np.zeros(shape=(380,640,3),dtype=np.uint8)
whiteImage = np.full(shape=(10,10,4),fill_value=255,dtype=np.uint8)
import cv2
import numpy as np
image = cv2.imread("D:\\Python_Demo\\test.png",cv2.IMREAD_COLOR)
# 打印图片的类型和数组类型
print("图片类型:", type(image))
print("数组类型:", image.dtype)
print("数组维度:", image.shape)
# 裁剪图片:将原图片的高度 100 - 200 的像素;宽度 50 - 100 的像素。提取出来
cropped_image = image[ 100:200,50:100 ]
# RGB 通道的拆分:结果为:高度像素 x 宽度像素 的二维数组
b,g,r = cv2.split(image)
b = image[:,:,0]
g = image[:,:,1]
r = image[:,:,2]
# 合并多个被拆分出来的通道:将三个二维数组,组合成三维的数组
merged_img = cv2.merge((b, g, r))
# 单通道图片
b_channel = merged_img[:,:,0]
g_channel = merged_img[:,:,1] * 0
r_channel = merged_img[:,:,2] * 0
imgB = cv2.merge((b_channel, g_channel, r_channel))
# 纯色图片
blackImage = np.zeros(shape=(380,640,3),dtype=np.uint8)
whiteImage = np.full(shape=(10,10,4),fill_value=255,dtype=np.uint8)
# 显示图像
cv2.imshow('Cropped Image', cropped_image)
cv2.imshow('Merged Image', merged_img)
cv2.imshow('Blue Channel Image', imgB)
cv2.imshow('Black Image', blackImage)
cv2.imshow('White Image', whiteImage)
# 等待按键事件并关闭所有窗口
cv2.waitKey(0)
cv2.destroyAllWindows()
2.3 修改像素尺寸
作用: 图像数据本质上就是一个「三维矩阵」,在对多个图像进行数学运算时,就需要同步矩阵维度。
import cv2
# 读取图像
image = cv2.imread("D:\\Python_Demo\\test.png", cv2.IMREAD_COLOR)
# 使用缩放比例调整图像大小,并选择插值方法
# (width, height):直接指定需要的像素值
# fx,fy:当设定(width, height)为(0,0)时,fx与fy分别表示图片两个方向上的缩放比列
# cv2.resize(img, Tuple[width, height], fx, fy)
resized_image = cv2.resize(image, (0, 0), fx=2.5, fy=1.25)
# 显示图像
cv2.imshow('Resized Image with Cubic Interpolation', resized_image)
cv2.waitKey(0)
cv2.destroyAllWindows()
3. 视频的基础操作
3.1 视频的本质
视频本质上就是由一个图片的数组加音频组合而成的文件。视频播放时,就是按照一定时间间隔依次读取图片数组中的图片并显示。这个时间间隔为帧数,即一秒钟中能显示多少张图片。
3.2 视频的输入与输出
3.2.1 视频文件读取
import cv2
# 读取视频
video = cv2.VideoCapture("D:\\Python_Demo\\test.mp4")
# 视频读取
while video.isOpened():
# 读取一帧
flag,frame = video.read()
# 显示
if flag == True:
cv2.imshow('video',frame)
# 控制播放速度:以 60 帧的速度进行图片显示
if cv2.waitKey(1000 // 60) == ord('q'):
break
# 释放
video.release()
cv2.destroyAllWindows()
3.2.2 摄像头捕获
只要修改 cv2.VideoCapture() 的参数就行,其余和读取视频文件一样
import cv2
# 读取视频
video = cv2.VideoCapture(0)
# 视频读取
while video.isOpened():
# 读取一帧
flag,frame = video.read()
# 显示
if flag == True:
cv2.imshow('video',frame)
# 控制播放速度:以 10 帧的速度进行图片显示
# 1000 // 10 表示每帧之间等待 100 毫秒
if cv2.waitKey(1000 // 10) == ord('q'):
break
# 释放
video.release()
cv2.destroyAllWindows()
3.2.3 视频保存
import cv2
# 读取视频
video = cv2.VideoCapture(0)
# 视频保存格式
videoForm = cv2.VideoWriter_fourcc(*'mp4v')
# 保存视频的类,输入参数为:
# 保存路径,保存格式,保存的视频帧数,(宽度像素,高度像素)
videoSave = cv2.VideoWriter('./Camera.mp4',videoForm,10,(640, 480))
# 视频读取
while video.isOpened():
# 读取一帧
flag,frame = video.read()
# 是否读取成功
if flag == True:
# 显示
cv2.imshow('video',frame)
# 保存
videoSave.write(frame)
# 控制播放速度:以 10 帧的速度进行图片显示
# 1000 // 10 表示每帧之间等待 100 毫秒
if cv2.waitKey(1000 // 10) == ord('q'):
break
# 释放
videoSave.release()
video.release()
cv2.destroyAllWindows()
帧数:视频输出的帧率由 cv2.VideoWriter 所指定,而 cv2.waitKey(1000 // 24) 规定的是摄像头拍照的帧数,该值的上限由摄像头所决定,硬件不行,设置多大都没用
摄像头的像素:
import cv2
video = cv2.VideoCapture(0)
# 读取一帧
flag,frame = video.read()
# 查看像素
# 查看读取是否成功
if flag:
# 打印像素数据
print(frame)
# 打印图像的类型
print(f"Image type: {type(frame)}")
# 打印图像的形状 (height, width, channels)
print(f"Image shape: {frame.shape}")
else:
print("Failed to capture image")
这个示例中,Image shape: (480, 640, 3) 表示图像的高度为 480 像素,宽度为 640 像素,每个像素有 3 个通道(BGR)。
这里print(frame)输出的像素值为 (高度,宽度,颜色通道),而cv2.VideoWriter设定的像素值应当是 (宽度,高度)
4. 回调函数
作用: 当某一个「事件」发生,就能马上执行对应处理程序,例如,点击鼠标右键,可以弹出菜单。处理程序靠「回调函数」实现,即当发生某个事件时,就会调用某个函数。
import cv2
import numpy as np
# 定义鼠标回调函数
# event:事件类型 例如鼠标左键按下 释放等
# x,y:鼠标所在像素值
# flags:与事件关联的特定条件(如按下的键)
# userdata:用户传入数据
def mouse_callback(event, x, y, flags, userdata:any):
if event == cv2.EVENT_LBUTTONDOWN:
print(event,x,y,flags,userdata)
# 创建窗口
# 创建一个名为 'Event Test' 的窗口,cv2.WINDOW_NORMAL 允许调整窗口大小
cv2.namedWindow('Event Test',cv2.WINDOW_NORMAL)
# 将窗口大小设置为 640x380 像素
cv2.resizeWindow('Event Test',width=640,height=380)
# 鼠标事件指定回调函数
cv2.setMouseCallback('Event Test',mouse_callback,"userdata")
# 生成一个背景图片
bg = np.zeros((380,640,3),dtype=np.uint8)
bg[:] = [0, 0, 0] # B G R格式
while True:
cv2.imshow('Event Test',bg)
if cv2.waitKey(0) == ord('q'):
break
cv2.destroyAllWindows()
只有在cv2.imshow()中显示了一张「像素图片」后,mouse_callback中的(x,y)才能正确的输出鼠标的位置坐标值。
二、界面控件
1. TrackBar
import cv2
# trackbar 改变时的回调函数
def onTrackbarChange(value):
print(value)
# 创建窗口
# 创建一个名为 'Event Test' 的窗口,cv2.WINDOW_NORMAL 允许调整窗口大小
cv2.namedWindow('trackbar',cv2.WINDOW_NORMAL)
# 将窗口大小设置为 640x380 像素
cv2.resizeWindow('trackbar',width=640,height=380)
# 创建trackbar
# createTrackbar(trackbarName, windowName, defaultValue, maxValue, onChangeCallback) -> None
cv2.createTrackbar('bar','trackbar',0,255,onTrackbarChange)
# 读取trackbar 的值 初值!!
# getTrackbarPos(trackbarname, windowName) -> trackbarValue
value = cv2.getTrackbarPos('bar','trackbar')
print(value)
cv2.waitKey(0)
cv2.destroyAllWindows()
2. 几何图形
2.1 直线 圆形 矩形
# %% 画直线、圆形、矩形
import cv2
import numpy as np
# 创建窗口
cv2.namedWindow('draw_shape',cv2.WINDOW_NORMAL)
cv2.resizeWindow('draw_shape',width=640, height=360)
# 必须先有一张背景图,用来当画布
canvas = np.zeros(shape=(360,640,3),dtype=np.uint8)
canvas[:,:] = [255,255,0] # RGB
# 矩形
# rectangle(canvas:img, pt1:tuple, pt2:tuple, color[, thickness[, lineType[, shift]]]) -> img
imgRect = cv2.rectangle(canvas,(20,40),(100,100),(255,0,0),3)
# 圆形
# circle(canvas:img, center:tuple:, radius, color[, thickness[, lineType[, shift]]]) -> img
imgCircle = cv2.circle(canvas,(200,60),50,(0,0,255),3,16)
# 直线
# img = cv2.line()
# line(canvas:img, pt1:tuple, pt2:tuple, color[, thickness[, lineType[, shift]]]) -> img
imgLine2 = cv2.line(canvas,(20,150),(200,170),(255,0,0),20,2)
imgLine8 = cv2.line(canvas,(20,200),(200,220),(255,0,0),20,8)
imgLine16 = cv2.line(canvas,(20,250),(200,270),(255,0,0),20,16)
cv2.imshow('draw_shape',canvas)
cv2.waitKey(0)
cv2.destroyAllWindows()
2.2 椭圆
# 椭圆
# axes:长轴、短轴
# angle:椭圆倾斜角度,顺时针
# ArcStartAngle, ArcEndAngle:起始弧长和终止弧长,顺时针
# 5ellipse(img, center:tuple, axes:tuple, angle,
# ArcStartAngle, ArcEndAngle,
# color[, thickness[, lineType[, shift]]]) -> img
imgEllipse = cv2.ellipse(canvas, (350, 200), (100, 50), 30, 0, 360, (0, 255, 0), 3)
2.3 多边形
# 多边形
pts1 = np.array([(20, 60), (300, 150), (50, 300)])
pts2 = np.array([(400, 60), (300, 100)])
cv2.polylines(canvas, [pts1, pts2], True, (255, 0, 0))
# 填充的多边形
pts_fill = np.array([[100, 100], [200, 150], [150, 250], [50, 200]])
cv2.fillPoly(canvas, [pts_fill], (0, 255, 255))
3. 文本
3.1 英文
# %% 画直线、圆形、矩形、椭圆、多边形、文本
import cv2
import numpy as np
# 创建窗口
cv2.namedWindow('draw_shape', cv2.WINDOW_NORMAL)
cv2.resizeWindow('draw_shape', width=640, height=360)
# 必须先有一张背景图,用来当画布
canvas = np.zeros(shape=(360, 640, 3), dtype=np.uint8)
canvas[:, :] = [255, 255, 0]
# 文本
font = cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(canvas, 'OpenCV', (50, 330), font, 1, (255, 255, 255), 2, cv2.LINE_AA)
# 显示图像
cv2.imshow('draw_shape', canvas)
cv2.waitKey(0)
cv2.destroyAllWindows()
3.2 中文
import cv2
import numpy as np
from PIL import Image,ImageFont,ImageDraw
# 必须先有一张背景图,用来当画布
canvas = np.zeros(shape=(360,640,3),dtype=np.uint8)
canvas[:,:] = [255,255,0]
# 导入字体
font = ImageFont.truetype('./asset/eva_font.otf',size=35)
# 创建画布
canvasBg = Image.fromarray(canvas)
# 创建画笔
brush = ImageDraw.Draw(canvasBg)
# 写入中文
brush.text(xy=(100,100),text="使徒襲来",font=font,fill=(255,0,0,0))
# 转换图片
canvas = np.array(canvasBg)
三、图像基础理论 待补充
1. 色彩空间

图片放大后,就是由带有不同「颜色」的「像素块」所构成。色彩空间就是描述每个像素块的颜色在数学上应该怎么表示。
1.1 RGB模型

根据光学三原色而来,图像中的一个像素点由一个数组[R,G,B]构成,一般该数组的类型为「一个字节的无符号整型」。而在 OpenCV 中,采用的是反人类的[B,G,R]。
1.2 HSV模型

HSV 模型的像素点也是通过一个「三维向量」进行表示
-
Hue:色相,将所有颜色通过一个数值进行表示 -
Saturation:饱和度,颜色与「白色」的混合程度 -
Value:明度,颜色的明亮程度
RGB 转 HSV 公式:RGB到HSV转换| 颜色转换 (rapidtables.org)

1.3 HSL模型

整个形式和 HSV 类似
-
Hue:色相,与 HSV 一样 -
Saturation:饱和度,颜色的稀释程度 -
Lightness/Brightness:被灯光照射的亮度
| HSV | 所有颜色 | 色相中混入「白色」的量 | 色相中混入「黑色」的量 |
| HSL | 所有颜色 | 色相被稀释的程度 | 拿灯光照射的情况,没光线就黑,强烈光线就白 |
RGB转HSL公式:RGB到HSL转换器| 颜色转换 (rapidtables.org)

1.4 YUV模型
-
作用: 可以对色彩空间进行压缩,说人话就是 缩减了用来表示像素颜色的数据量,这就使得该模型在图像、视频压缩上应用广泛。
-
思想: 由于人眼对颜色的感知能力较弱,反而对光影关系(黑白图)更为敏感。所以,在 YUV 模型中,精确保留了图片的「黑白关系」,而对颜色信息进行了部分删除。
-
通道:
-
Y:该通道储存的是「黑白关系」,即「灰度图」。 -
UV:这两个通道储存了颜色信息。在对图片颜色时,首先就会对这个两个通道的颜色数据进行丢弃。
-
-
色彩空间压缩: 一共有 4 个像素,每个像素都有3个通道值表示颜色,一个通道为一个字节,那么所有数据一共就有
4 x 3 x 1B = 12B。现在通过 YUV 模型对图片进行压缩,丢掉一半的颜色信息,Y 通道全部保留4 x 1B = 4B,UV 通道丢弃一半就是2 x 4 x 1B / 2 = 4B,最后数据大小就为4B + 4B = 8B。
2.图像参数
-
像素: 图片中的一个颜色块
-
颜色通道: 表示一个像素(颜色)的向量的分量,一个分量就是一个颜色通道,例如 RGB模型,像素的组成就为
[R,G,B],这就有三个颜色通道 -
位深: 一个颜色通道值由几位二级制数表示
-
像素块坐标: 图片的左上角为坐标原点,一个坐标对应一个像素
四、图像变换
1. 图像运算
1.1 加减乘除
import cv2
# 图像直接相加
image1_path = 'D:\\Python_Demo\\test.png' # 替换为你的图片路径
image1 = cv2.imread(image1_path, cv2.IMREAD_GRAYSCALE)
image2_path = 'D:\\Python_Demo\\output_test.png' # 替换为你的图片路径
image2 = cv2.imread(image2_path, cv2.IMREAD_GRAYSCALE)
# 确保图像尺寸相同
if image1.shape == image2.shape:
added_image = image1 + image2
cv2.imshow('Direct Addition', added_image)
# 使用cv2.add()函数相加
added_image_cv = cv2.add(image1,image2)
cv2.imshow('cv2.add Addition', added_image_cv)
# 使用cv2.addWeighted()函数相加
alpha = 0.5 # 图像1的权重
beta = 0.5 # 图像2的权重
gamma = 0 # 加上的标量
weighted_image = cv2.addWeighted(image1, alpha, image2, beta, gamma)
cv2.imshow('Weighted Addition', weighted_image)
# 显示图像
cv2.waitKey(0)
cv2.destroyAllWindows()
else:
print("图像尺寸不匹配,无法相加。")

1.2 位运算
import cv2
# 读取图像
image1_path = 'D:\\Python_Demo\\test.png' # 替换为你的图片路径
image1 = cv2.imread(image1_path, cv2.IMREAD_GRAYSCALE)
image2_path = 'D:\\Python_Demo\\output_test.png' # 替换为你的图片路径
image2 = cv2.imread(image2_path, cv2.IMREAD_GRAYSCALE)
# 确保图像尺寸相同
if image1.shape == image2.shape:
# 与运算
bitwise_and_image = cv2.bitwise_and(image1, image2)
cv2.imshow('Bitwise AND', bitwise_and_image)
# 或运算
bitwise_or_image = cv2.bitwise_or(image1, image2)
cv2.imshow('Bitwise OR', bitwise_or_image)
# 异或运算
bitwise_xor_image = cv2.bitwise_xor(image1, image2)
cv2.imshow('Bitwise XOR', bitwise_xor_image)
# 非运算 (仅对一张图像)
bitwise_not_image1 = cv2.bitwise_not(image1)
cv2.imshow('Bitwise NOT Image 1', bitwise_not_image1)
bitwise_not_image2 = cv2.bitwise_not(image2)
cv2.imshow('Bitwise NOT Image 2', bitwise_not_image2)
# 显示图像
cv2.waitKey(0)
cv2.destroyAllWindows()
else:
print("图像尺寸不匹配,无法进行位运算。")

2. 旋转与翻转
2.1 翻转
import cv2
# 读取图像
image1_path = 'D:\\Python_Demo\\test.png' # 替换为你的图片路径
image1 = cv2.imread(image1_path, cv2.IMREAD_COLOR)
# 翻转
# flip(src, flipCode[, dst]) -> dst
# flipCode = 0:垂直翻转
# flipCode < 0:垂直 + 水平翻转
# flipCode > 0:水平翻转
img0 = cv2.flip(image1,0)
imgLow0 = cv2.flip(image1,-1)
imgGreat0 = cv2.flip(image1,1)
cv2.imshow('Bitwise img0', img0)
cv2.imshow('Bitwise imgLow0', imgLow0)
cv2.imshow('Bitwise imgGreat0', imgGreat0)
# 显示图像
cv2.waitKey(0)
cv2.destroyAllWindows()
2.2 旋转
import cv2
# 读取图像
image1_path = 'D:\\Python_Demo\\test.png' # 替换为你的图片路径
image1 = cv2.imread(image1_path, cv2.IMREAD_COLOR)
# 旋转
# rotate(src, rotateCode[, dst]) -> dst
# roteCode:cv2.ROTATE_
imgr = cv2.rotate(image1,cv2.ROTATE_180)
cv2.imshow('Bitwise img0', imgr)
# 显示图像
cv2.waitKey(0)
cv2.destroyAllWindows()
翻转不会改变原来图片的np.ndarray.shape,旋转会修改。
3. 仿射变化
3.1 介绍
仿射变换就是对图片的像素坐标进行位置变换,进而实现对图片的平移、旋转和缩放。对于图片中的像素会定义一个坐标系,该坐标系以横向像素为x轴,高度像素为y轴,坐标原点为图片左上角。

仿射变换中集合中的一些性质保持不变:
-
共线性:若几个点变换前在一条线上,则仿射变换后仍然在一条线上
-
平行性:若两条线变换前平行,则变换后仍然平行
-
共线比例不变性:变换前一条线上的两条线段的比例在变换后比例不变
3.2 变化的数字表达
3.2.1 平移

3.2.2 缩放

3.2.3 旋转

五、图像滤波
1. 卷积操作
卷积核来实现更强的低通滤波效果。下面是一个使用 5x5 平均滤波卷积核的示例
import cv2
import numpy as np
# 读取图像
image1_path = 'D:\\Python_Demo\\test.png'
img = cv2.imread(image1_path)
# 定义5x5的平均滤波卷积核
kernel_blur = np.array([[1/25, 1/25, 1/25, 1/25, 1/25],
[1/25, 1/25, 1/25, 1/25, 1/25],
[1/25, 1/25, 1/25, 1/25, 1/25],
[1/25, 1/25, 1/25, 1/25, 1/25],
[1/25, 1/25, 1/25, 1/25, 1/25]], dtype=np.float32)
# 应用滤波器
blurred_img = cv2.filter2D(img, -1, kernel_blur)
# 显示原始图像和模糊后的图像
cv2.imshow('Original Image', img)
cv2.imshow('More Blurred Image', blurred_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
使用一个锐化卷积核(3x3)来实现锐化效果
import cv2
import numpy as np
# 读取图像
image1_path = 'D:\\Python_Demo\\test.png'
img = cv2.imread(image1_path)
# 定义3x3的锐化卷积核
kernel_sharpen = np.array([[ 0, -1, 0],
[-1, 5, -1],
[ 0, -1, 0]], dtype=np.float32)
# 应用滤波器
sharpened_img = cv2.filter2D(img, -1, kernel_sharpen)
# 显示原始图像和锐化后的图像
cv2.imshow('Original Image', img)
cv2.imshow('Sharpened Image', sharpened_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
2. 低通滤波
-
消除图片中的高斯噪声
-
消除图片中的椒盐噪声
-
图片模糊
2.1 方盒滤波与均值滤波
方盒滤波的主要用途就是:去噪声,方盒滤波可以有效地平滑图像,降低图像细节(平滑处理可以去除图像中的细小细节,使图像显得更加柔和。对一些需要降低细节的任务,如人脸检测前的预处理、背景模糊等,有很好的效果)在一些复杂的图像处理任务(如边缘检测、特征提取)之前,可以通过方盒滤波进行预处理
import cv2
import numpy as np
# 读取图像
image1_path = 'D:\\Python_Demo\\test.png'
img = cv2.imread(image1_path)
# 定义核的大小
kernel_size = (5, 5)
# 方盒滤波,归一化
box_filtered_normalized = cv2.boxFilter(img, -1, kernel_size, normalize=True)
# 方盒滤波,不归一化
box_filtered_non_normalized = cv2.boxFilter(img, -1, kernel_size, normalize=False)
# 显示原始图像和方盒滤波后的图像
cv2.imshow('Original Image', img)
cv2.imshow('Box Filtered (Normalized)', box_filtered_normalized)
cv2.imshow('Box Filtered (Non-Normalized)', box_filtered_non_normalized)
cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.boxFilter 提供了更多的灵活性,而均值滤波的cv2.blur会更简单和直接
import cv2
import numpy as np
# 读取图像
image1_path = 'D:\\Python_Demo\\test.png'
img = cv2.imread(image1_path)
# 定义核的大小
kernel_size = (5, 5)
# 均值滤波
blurred_img = cv2.blur(img, kernel_size)
# 显示原始图像和均值滤波后的图像
cv2.imshow('Original Image', img)
cv2.imshow('Blurred Image', blurred_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
2.2 高斯滤波
高斯模糊与均值滤波的对比
-
高斯模糊:使用高斯分布进行卷积,对边缘保留较好,模糊效果更加自然。
-
均值滤波:使用均值进行卷积,模糊效果较为生硬,可能会导致边缘细节丢失。
一维高斯分布概率密度函数:
import cv2
import numpy as np
# 读取图像
image1_path = 'D:\\Python_Demo\\test.png'
img = cv2.imread(image1_path)
# 定义核的大小
kernel_size = (5, 5)
# 定义标准差
sigmaX = 1.5
# 高斯模糊
gaussian_blurred_img = cv2.GaussianBlur(img, kernel_size, sigmaX)
# 显示原始图像和高斯模糊后的图像
cv2.imshow('Original Image', img)
cv2.imshow('Gaussian Blurred Image', gaussian_blurred_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
2.3 中值滤波
-
椒盐噪声:随机出现的「纯白点」或者「纯黑点」
-
效果:去除「椒盐噪声」效果最好
import cv2
# 读取图像
image1_path = 'D:\\Python_Demo\\test.png'
img = cv2.imread(image1_path)
# 定义核的大小(必须为奇数)
kernel_size = 5
# 中值滤波
median_blurred_img = cv2.medianBlur(img, kernel_size)
# 显示原始图像和中值滤波后的图像
cv2.imshow('Original Image', img)
cv2.imshow('Median Blurred Image', median_blurred_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
2.4 双边滤波
保留边缘细节:
-
与其他滤波方法不同,双边滤波在平滑图像时能够很好地保留边缘细节,适用于需要在保持边缘锐利的情况下进行去噪的场景。
图像增强:
-
双边滤波可以用于图像增强,使图像的细节更加明显。
import cv2
# 读取图像
image1_path = 'D:\\Python_Demo\\test.png'
img = cv2.imread(image1_path)
# 定义参数
d = 9 # 邻域直径
sigmaColor = 75 # 颜色空间的标准差
sigmaSpace = 75 # 坐标空间的标准差
# 双边滤波
bilateral_filtered_img = cv2.bilateralFilter(img, d, sigmaColor, sigmaSpace)
# 显示原始图像和双边滤波后的图像
cv2.imshow('Original Image', img)
cv2.imshow('Bilateral Filtered Image', bilateral_filtered_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
方盒滤波:简单的平均滤波,计算简单,但效果生硬。
均值滤波:平滑图像,去噪效果一般,容易模糊边缘。
高斯滤波:自然平滑,边缘保留较好。
中值滤波:对椒盐噪声特别有效,边缘保留好。
双边滤波:在去噪的同时很好地保留边缘细节。
3. 高通滤波
-
边缘监测
-
图像边缘:图像的灰度图中,相邻像素灰度值差距较大的位置

3.1 sobel算子
import cv2
import numpy as np
img = cv2.imread('D:\\Python_Demo\\test.png',cv2.IMREAD_GRAYSCALE)
img = cv2.resize(img,(0,0),fx=0.6,fy=0.6)
# Sobel(src, ddepth, dx, dy[, dst[, ksize:int[, scale[, delta[, borderType]]]]]) -> dst
# 竖着的边界
imgv = cv2.Sobel(img,cv2.CV_16S,dx=1,dy=0,ksize=3)
imgv = cv2.convertScaleAbs(imgv)
# 横着的边界
imgh = cv2.Sobel(img,cv2.CV_16S,dx=0,dy=1,ksize=3)
imgh = cv2.convertScaleAbs(imgh)
# 边界叠加
imga = cv2.add(imgh,imgv)
cv2.imshow('sobel',np.hstack((img,imgv,imgh,imga)))
cv2.waitKey(0)
cv2.destroyAllWindows()
3.2 schar算子
-
介绍: 对 Sobel 算子的改进。
import cv2
import numpy as np
img = cv2.imread('D:\\Python_Demo\\test.png',cv2.IMREAD_GRAYSCALE)
img = cv2.resize(img,(0,0),fx=0.6,fy=0.6)
# cv2.Scharr(src, ddepth, dx, dy[, dst[, scale[, delta[, borderType]]]]) -> dst
# 竖着的边界
imgv = cv2.Scharr(img,cv2.CV_16S,dx=1,dy=0)
imgv = cv2.convertScaleAbs(imgv)
# 横着的边界
imgh = cv2.Scharr(img,cv2.CV_16S,dx=0,dy=1)
imgh = cv2.convertScaleAbs(imgh)
# 边界叠加
imga = cv2.add(imgh,imgv)
cv2.imshow('sobel',np.hstack((img,imgv,imgh,imga)))
cv2.waitKey(0)
cv2.destroyAllWindows()
3.3 拉普拉斯算子
import cv2
# 读取图像
image_path = 'D:\\Python_Demo\\test.png'
img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
# 计算拉普拉斯算子
laplacian = cv2.Laplacian(img, cv2.CV_64F)
# 转换为 8 位无符号整数格式
abs_laplacian = cv2.convertScaleAbs(laplacian)
# 显示结果
cv2.imshow('Original Image', img)
cv2.imshow('Laplacian', abs_laplacian)
cv2.waitKey(0)
cv2.destroyAllWindows()
3.4 Canny边缘检测
import cv2
# 读取图像
image_path = 'D:\\Python_Demo\\test.png'
img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
# 执行 Canny 边缘检测
edges = cv2.Canny(img, 100, 200) # 100 是低阈值,200 是高阈值
# 显示结果
cv2.imshow('Original Image', img)
cv2.imshow('Canny Edges', edges)
cv2.waitKey(0)
cv2.destroyAllWindows()
Sobel 算子和 Scharr 算子适合于普通的边缘检测任务,能够检测水平和垂直方向的边缘。
拉普拉斯算子用于图像的边缘增强,能够使边缘更加锐利。
Canny 边缘检测则是一种全面的边缘检测方法,结合了多个步骤来确保高质量的边缘检测结果,适用于需要较高精度的任务和应用场景。
六、图像形态学
形态学操作,主要是对二值图像进行一系列处理。
1. 阈值控制
import cv2
# 读取图像,转为灰度图
image_path = 'D:\\Python_Demo\\test.png'
img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
# 全局阈值处理
ret, binary_img = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
# 自动计算阈值(OTSU 方法)
ret, otsu_img = cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
# 自适应阈值处理
adaptive_img = cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 11, 2)
# 显示结果图像
cv2.imshow('Original Image', img)
cv2.imshow('Binary Threshold', binary_img)
cv2.imshow('OTSU Threshold', otsu_img)
cv2.imshow('Adaptive Threshold', adaptive_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
2. 腐蚀和膨胀
2.1 腐蚀
import cv2
# 读取图像,转为灰度图
import numpy as np
image_path = 'D:\\Python_Demo\\test.png'
img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
# 创建一个 5x5 的结构元素(矩形)
kernel = np.ones((5, 5), np.uint8)
# 进行腐蚀操作
eroded_img = cv2.erode(img, kernel, iterations=1)
# 显示结果图像
cv2.imshow('Original Image', img)
cv2.imshow('Eroded Image', eroded_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
2.2 膨胀
import cv2
# 读取图像,转为灰度图
import numpy as np
image_path = 'D:\\Python_Demo\\test.png'
img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
# 创建一个 5x5 的结构元素(矩形)
kernel = np.ones((5, 5), np.uint8)
# 进行膨胀操作
dilated_img = cv2.dilate(img, kernel, iterations=1)
# 显示结果图像
cv2.imshow('Original Image', img)
cv2.imshow('Dilated Image', dilated_img)
cv2.waitKey(0)
cv2.destroyAllWindows()
3. 形态学操作
import cv2
# 读取图像,转为灰度图
import numpy as np
image_path = 'D:\\Python_Demo\\test.png'
img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
# 创建一个 5x5 的结构元素(矩形)
kernel = np.ones((5, 5), np.uint8)
# op:cv2.MORPH_ 形态学的操作类型
# cv2.morphologyEx(src, op, kernel:np.ndarray[, dst[, anchor[, iterations[, borderType[, borderValue]]]]]) -> dst
dilated_img1 = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
dilated_img2 = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
dilated_img3 = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel)
dilated_img4 = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel)
dilated_img5 = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel)
# 显示结果图像
cv2.imshow('sobel',np.hstack((img,dilated_img1,dilated_img2,dilated_img3)))
cv2.imshow('sobel1',np.hstack((dilated_img4,dilated_img5)))
cv2.waitKey(0)
cv2.destroyAllWindows()
| 名称 | 操作 | OpenCV | 应用 |
|---|---|---|---|
| 开运算 | 先腐蚀、再膨胀 | cv2.MORPH_OPEN | 去除边界上的毛刺、去二值图的噪点 (去掉较小的形状) |
| 闭运算 | 先膨胀、再腐蚀 | cv2.MORPH_CLOSE | 中空形状或者邻近的形状形成一整块 |
| 梯度运算 | 膨胀 - 腐蚀 | cv2.MORPH_GRADIENT | 二值图的边缘 |
| 礼帽 | 原始 - 开运算 | cv2.MORPH_TOPHAT | 把由「开运算」去除的像素,从原图中截取出来。(去掉较大的形状) |
| 黑帽 | 闭运算 - 原始 | cv2.MORPH_BLACKHAT | 将「闭运算」填充的像素,从原图中截取出来 |
七、图像处理
1. 图像金字塔
-
作用: 将图像根据分辨率大小分为多个档次。
-
图像金字塔是一种多尺度图像表示方法,主要用途包括尺度空间分析、图像融合、特征提取与匹配、图像压缩和图像分割。它提供了在不同尺度下分析和处理图像的能力,支持多种复杂的图像处理和计算机视觉任务
1.1 高斯金字塔
-
向下采样(缩小图片):
-
首先进行高斯滤波
-
去除偶数的行、列
-
-
向上采用(放大图片):
用「零」填充偶数行、列

对放大的图片进行高斯卷积,将「零」值进行填充
import cv2
# 读取图像
image_path = 'D:\\Python_Demo\\test.png'
image = cv2.imread(image_path)
# 确认图像读取成功
if image is None:
print("Error: Could not read the image.")
else:
# 显示原始图像
cv2.imshow('Original Image', image)
# 向上采样
upsampled_image = cv2.pyrUp(image)
cv2.imshow('Upsampled Image', upsampled_image)
cv2.imwrite('D:\\Python_Demo\\upsampled_test.png', upsampled_image)
# 向下采样
downsampled_image = cv2.pyrDown(image)
cv2.imshow('Downsampled Image', downsampled_image)
cv2.imwrite('D:\\Python_Demo\\downsampled_test.png', downsampled_image)
# 等待按键
cv2.waitKey(0)
cv2.destroyAllWindows()
1.2 拉普拉斯金字塔

import cv2
import matplotlib.pyplot as plt
def build_laplacian_pyramid(image, levels):
# 构建高斯金字塔
gaussian_pyramid = [image]
for i in range(levels):
image = cv2.pyrDown(image)
gaussian_pyramid.append(image)
# 构建拉普拉斯金字塔
laplacian_pyramid = []
for i in range(levels, 0, -1):
gaussian_expanded = cv2.pyrUp(gaussian_pyramid[i])
if gaussian_expanded.shape != gaussian_pyramid[i-1].shape:
gaussian_expanded = cv2.resize(gaussian_expanded, (gaussian_pyramid[i-1].shape[1], gaussian_pyramid[i-1].shape[0]))
laplacian = cv2.addWeighted(gaussian_pyramid[i-1], 1, gaussian_expanded, -1, 0)
laplacian_pyramid.append(laplacian)
return laplacian_pyramid
# 读取图像
image_path = 'D:\\Python_Demo\\test.png'
image = cv2.imread(image_path, cv2.IMREAD_COLOR)
# 检查图像是否成功加载
if image is None:
print("Error: Could not read image.")
else:
# 构建拉普拉斯金字塔
levels = 3
laplacian_pyramid = build_laplacian_pyramid(image, levels)
# 显示拉普拉斯金字塔的每一层
plt.figure(figsize=(15, 5))
for i in range(levels):
plt.subplot(1, levels, i+1)
plt.title(f"Laplacian Level {i+1}")
plt.imshow(cv2.cvtColor(laplacian_pyramid[i], cv2.COLOR_BGR2RGB))
plt.axis('off')
plt.show()
2. 图像轮廓
轮廓定义:构成任何一个形状的边界或外形线,是指将「边缘」连接起来形成的一个整体。
2.1 轮廓提取
-
mode: 轮廓检索模式
-
RETR_EXTERNAL:只检索最外面的轮廓;
-
RETR_LIST:检索所有的轮廓,并将其保存到一条链表当中;
-
RETR_CCOMP:检索所有的轮廓,并将他们组织为两层:顶层是各部分的外部边界,第二层是空洞的边界:
-
RETR_TREE: 检索所有的轮廓,并重构嵌套轮廓的整个层次; 最常用。
-
-
method: 重新绘制轮廓的算法
-
CHAIN_APPROX_NONE:以Freeman链码的方式输出轮廓,轮廓信息完整保留
-
CHAIN_APPROX_SIMPLE:压缩水平的、垂直的和斜的部分,只保留顶点。
-

import cv2
import matplotlib.pyplot as plt
# 读取图像
image_path = 'D:\\Python_Demo\\test.png'
image = cv2.imread(image_path)
# 灰度化
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 二值化
ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
# contours:从图像中查找出来的轮廓数组
# hierarchy:轮廓层级
# imageSrc:传入的图像,又返回了一份。不明白。。。。
# cv2.findContours(image, mode, method[, contours[, hierarchy[, offset]]]) ->imageSrc, contours, hierarchy
# 查找轮廓
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# 绘制轮廓
contour_image = image.copy()
cv2.drawContours(contour_image, contours, -1, (0, 255, 0), 2)
# 显示原始图像和带轮廓的图像
plt.figure(figsize=(10, 5))
plt.subplot(1, 2, 1)
plt.title('Original Image')
plt.imshow(cv2.cvtColor(image, cv2.COLOR_BGR2RGB))
plt.axis('off')
plt.subplot(1, 2, 2)
plt.title('Image with Contours')
plt.imshow(cv2.cvtColor(contour_image, cv2.COLOR_BGR2RGB))
plt.axis('off')
plt.show()
用于轮廓检测的图像,首先得进行二值处理(阈值操作)或 Canny 边缘检测。
2.2 轮廓绘制
import cv2
import numpy as np
# 读取图像
image_path = 'D:\\Python_Demo\\test.png'
image = cv2.imread(image_path)
# 灰度图
imgGray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 二值化
retval, imgBinary = cv2.threshold(imgGray, 127, 255, cv2.THRESH_BINARY)
# 提取轮廓
contours, hierarchy = cv2.findContours(imgBinary, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
# canvas:轮廓要绘制在哪张背景图上,直接覆盖原图
# contours:findContours 找到的轮廓信息
# contourIdx:轮廓数组contours的索引值,-1 为全部
# color:轮廓颜色
# thickness:轮廓厚度
# cv2.drawContours(canva:image, contours, contourIdx, color[, thickness[, lineType[, hierarchy[, maxLevel[, offset]]]]]) -> image
# 绘制轮廓
canva = image.copy()
imgRes = cv2.drawContours(canva, contours, -1, (0, 0, 255), 1)
# 显示图像
cv2.imshow('contours', imgRes)
cv2.waitKey(0)
cv2.destroyAllWindows()
2.3 轮廓特征
import cv2
import numpy as np
# 读取图像
image_path = 'D:\\Python_Demo\\test.png'
image = cv2.imread(image_path)
# 灰度化
imgGray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 二值化
retval, imgBinary = cv2.threshold(imgGray, 127, 255, cv2.THRESH_BINARY)
# 提取轮廓
contours, hierarchy = cv2.findContours(imgBinary, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
# 绘制轮廓
canva = image.copy()
cv2.drawContours(canva, contours, -1, (0, 0, 255), 2)
# 轮廓索引
# cnt = contours[0]
# 计算面积
# area = cv2.contourArea(cnt)
# 计算周长
# arcLength(curve, closed) -> retval
# arc = cv2.arcLength(cnt,True)
# 计算并打印第一个轮廓的面积和周长
if len(contours) > 0:
cnt = contours[0] # 获取第一个轮廓
# 计算轮廓面积
area = cv2.contourArea(cnt)
print("Contour Area:", area)
# 计算轮廓周长
arc = cv2.arcLength(cnt, True) # True 表示轮廓是闭合的
print("Contour Arc Length:", arc)
else:
print("No contours found.")
# 显示图像和带轮廓的图像
cv2.imshow('Original Image', image)
cv2.imshow('Image with Contours', canva)
cv2.waitKey(0)
cv2.destroyAllWindows()
2.4 轮廓近似
import cv2
import numpy as np
# 读取图像并转换为灰度图像
image_path = 'D:\\Python_Demo\\test.png'
image = cv2.imread(image_path)
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 二值化处理
retval, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
# 查找轮廓
contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# 对第一个轮廓进行多边形逼近
epsilon = 0.01 * cv2.arcLength(contours[0], True)
approx = cv2.approxPolyDP(contours[0], epsilon, True)
# 绘制原始轮廓和逼近多边形
canva = image.copy()
cv2.drawContours(canva, [contours[0]], -1, (0, 255, 0), 2) # 绘制原始轮廓
cv2.drawContours(canva, [approx], -1, (255, 0, 0), 2) # 绘制逼近多边形
# 显示图像和处理结果
cv2.imshow('Original Image', image)
cv2.imshow('Approximated Contour', canva)
cv2.waitKey(0)
cv2.destroyAllWindows()
2.5 轮廓标记
作用: 用一个形状(矩形、圆圈等)将轮廓标记出来。
import cv2
import numpy as np
# 创建一个空白画布
canvas = np.zeros((400, 800, 3), dtype=np.uint8)
# 绘制几何图形
cv2.rectangle(canvas, (50, 50), (150, 150), (255, 0, 0), -1) # 蓝色实心矩形
cv2.circle(canvas, (300, 100), 50, (0, 255, 0), -1) # 绿色实心圆
cv2.ellipse(canvas, (600, 200), (100, 50), 0, 0, 270, (0, 0, 255), -1) # 红色实心椭圆
# 转换为灰度图像
gray = cv2.cvtColor(canvas, cv2.COLOR_BGR2GRAY)
# 二值化处理
retval, binary = cv2.threshold(gray, 50, 255, cv2.THRESH_BINARY)
# 查找轮廓
contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# 绘制轮廓
canvabg = canvas.copy()
cv2.drawContours(canvabg, contours, -1, (255, 255, 255), 2)
# 合并原始图像和带有轮廓的图像
combined_image = np.hstack((canvas, canvabg))
# 显示合并后的图像
cv2.imshow('Original and Contours', combined_image)
cv2.waitKey(0)
cv2.destroyAllWindows()
3. 模板匹配
-
思路: 将模板图片当作卷积核与被匹配的图片进行卷积操作,然后根据具体 匹配算法 计算出每一步卷积操作的置信度,根据置信度来确定模板图像在被匹配图像中的位置。
# templ:模板图片
# method:匹配算法
cv2.matchTemplate(image, templ, method[, result[, mask]]) -> result
-
method:
-
TM_SQDIFF:计算平方不同,计算出来的值越小,越相关
-
TM_CCORR:计算相关性,计算出来的值越大,越相关
-
TM_CCOEFF:计算相关系数,计算出来的值越大,越相关
-
TM_SQDIFF NORMED:计算归一化平方不同,计算出来的值越接近0,越相关
-
TM_CCORR NORMED:计算归一化相关性,计算出来的值越接近1,越相关
-
TM_CCOEFF NORMED:计算归一化相关系数,计算出来的值越接近1,越相关
-
-
result:每一步卷积操作记录一次结果,其数组大小就为(与卷积运算结果维度计算一样)

result数组的索引值,对应的是模板图片在原始图片重合的左上角像素的坐标。
import cv2
# 导入图片
img = cv2.imread('D:\\Python_Demo\\test.png')
imgTemp = img[80:250, 250:440] # 选择模板区域
# 模板匹配
result = cv2.matchTemplate(img, imgTemp, cv2.TM_SQDIFF_NORMED)
# 统计出数组的中最大值、最小值以及对应的索引
minVal, maxVal, minLoc, maxLoc = cv2.minMaxLoc(result)
top_left = minLoc
bottom_right = (top_left[0] + imgTemp.shape[1], top_left[1] + imgTemp.shape[0])
# 绘制矩形框
cv2.rectangle(img, top_left, bottom_right, (255, 0, 0), 2)
# 显示结果
cv2.imshow('Matching Result', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
4. 直方图
4.1 对比度
-
定义: 一幅图像中明暗区域最亮的白和最暗的黑之间不同亮度层级的测量,即指一幅图像灰度反差的大小。差异范围越大代表对比越大,差异范围越小代表对比越小。 说人话,应该就是图片灰度图的明暗分布明显,数学上就是灰度值的差异大。
图像对比度:

4.2 绘制直方图

# OpenCV 方法
# images:图像,输入 [ image ]
# channels:选择通道,输入 [ channel ]
# mask:遮罩
# hisSize:有几根柱子,输入 [ hisSize ]
# range:取值范围
cv2.calcHist(images: List[Mat], channels: List[int],
mask: Mat | None, histSize: List[int], ranges: List[int]) -> hist
# matplotlib 方法
# data :要绘制直方图的一维数据
# hisSize:柱子的个数
plt.hist(data,hisSize)
推荐使用 matplotlib 方式,OpenCV 方式最后还得用 matplotlib 进行绘图。
4.3 均衡化

import cv2
import numpy as np
import matplotlib.pyplot as plt
img = cv2.imread('D:\\Python_Demo\\test.png')
# 转换颜色空间:主要为了对 灰度 进行均值化
yuv =cv2.cvtColor(img,cv2.COLOR_BGR2YUV)
# 均衡化
yEqul = cv2.equalizeHist(yuv[:,:,0])
# 替换原来的灰度
yuv[:,:,0] = yEqul
# 还原颜色空间
imgEual = cv2.cvtColor(yuv,cv2.COLOR_YUV2BGR)
cv2.imshow('match',np.hstack((img,imgEual)))
cv2.waitKey(0)
cv2.destroyAllWindows()
4.4 CLAHE
-
直方图均衡化问题:
-
为全局效果,这就导致图像中原来暗部和亮部的细节丢失,例如上图猫的帽子和左脚处。
-
可能导致噪点的放大。
-
-
思路: 将图片拆分为多个部分,然后每个部分分别进行均衡化处理,且对每个部分的直方图概率分布做限制(防止某个灰度值的概率分布过大,进而导致均衡化后的灰度值过大)。
算法实现:
图像分块

找每个块的中心点(黄色标记)

分别计算每个块的灰度直方图,并进行「阈值限制」

绘制好直方图后,柱子的分布值与设定「阈值」进行比较,超过阈值的部分则进行裁剪,并均匀分配给所有的柱子。分配后,直方图又要柱子超出时(绿色部分),继续重复上述操作,直至直方图柱子都在「阈值」下方。 现在只是对「直方图分布」进行修改,并没有修改原始图像的任何内容。
得到每个块的直方图分布后,根据直方图均衡化算法对每个块的中心点(黄色标记)进行均衡化处理。 只对中心点进行均衡化是为了加快计算速度,对每一个像素都进行处理会浪费很多时间。
根据中心点均衡化后的灰度值,利用插值算法计算图像块剩余像素的灰度值。插值算法计算效果和直接均衡化效果差不多,但是差值计算速度更快。
# 生成自适应均衡化算法
# clipLimit :阈值,1 表示不做限制。值越大,对比度越大
# tileGridSize:如何拆分图像
clahe = cv2.createCLAHE([, clipLimit[, tileGridSize]]) -> retval
# 对像素通道进行自适应均值化处理
dst = clahe.apply(src)
import cv2
# 读取输入图像
src = cv2.imread('D:\\Python_Demo\\test.png', cv2.IMREAD_GRAYSCALE)
# 创建 CLAHE 对象
# clipLimit: 阈值,1 表示不做限制。值越大,对比度越大
# tileGridSize: 如何拆分图像
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
# 对图像进行自适应均衡化处理
dst = clahe.apply(src)
# 显示原始图像和处理后的图像
cv2.imshow('Original Image', src)
cv2.imshow('CLAHE Image', dst)
# 等待按键输入,然后关闭窗口
cv2.waitKey(0)
cv2.destroyAllWindows()
5. 图像傅里叶变换
5.1 二维傅里叶变换
-
思想: 二维傅里叶变换中,认为二维数据是由无数个「正弦平面波」所构成。

离散傅里叶变换公式:

将二维数据进行傅里叶变换后得到的值 𝐹(𝑢,𝑢)F(u,u) 则代表了相应的「正弦平面波」
5.2 正弦平面波

直观定义: 将一维正弦曲线朝着纵向的一个方向上将其拉伸得到一个三维的波形,然后将波形的幅值变化用二维平面进行表示,再将二维平面波绘制成灰度图,即波峰为白色、波谷为黑色。
数学参数:
![]()
5.3 二维傅里叶变换结果

5.4 傅里叶变换实现
import cv2
import numpy as np
# 读取输入图像
img = cv2.imread('D:\\Python_Demo\\test.png')
# 转换为 YUV 颜色空间
yuv = cv2.cvtColor(img, cv2.COLOR_BGR2YUV)
# 提取 Y 通道(灰度)
y_channel = yuv[:, :, 0]
# 将灰度值转换为浮点类型
yfloat = np.float32(y_channel)
# 对灰度图像进行傅里叶变换
dft = cv2.dft(yfloat, flags=cv2.DFT_COMPLEX_OUTPUT)
# 将频谱从左上角移动到中心
dft_shift = np.fft.fftshift(dft)
# 计算幅值
magnitude = cv2.magnitude(dft_shift[:, :, 0], dft_shift[:, :, 1])
# 对幅值进行对数变换
magnitude_log = np.log1p(magnitude)
# 幅值太大了,重新映射到 (0 - 255),方便显示
magnitude_log = np.uint8(255 * magnitude_log / np.max(magnitude_log))
# 显示原始灰度图像和傅里叶变换后的幅值图像
cv2.imshow('Gray Image', y_channel)
cv2.imshow('DFT Magnitude', magnitude_log)
# 等待按键输入,然后关闭窗口
cv2.waitKey(0)
cv2.destroyAllWindows()
由于离散傅里叶变换具有「共轭对称性」,上面的输出结果其实是被重复了3次。具体结果只需看「左上角矩形」就行,其余的都是重复。

# 频谱中心化
shiftA = np.fft.fftshift(A)
5.5 傅里叶滤波
-
对图像灰度进行傅里叶变换,得到频域结果
-
将要删除的频率所对应的傅里叶变换结果全部置为 0+𝑖00+i0
-
对修改后的傅里叶变换结果进行傅里叶反变换
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 读取输入图像
image_path = 'D:\\Python_Demo\\test.png'
img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
# 获取图像尺寸
rows, cols = img.shape
crow, ccol = rows // 2 , cols // 2
# 进行傅里叶变换
dft = cv2.dft(np.float32(img), flags=cv2.DFT_COMPLEX_OUTPUT)
dft_shift = np.fft.fftshift(dft)
# 创建低通滤波器
low_pass_filter = np.zeros((rows, cols, 2), np.uint8)
low_pass_filter[crow-30:crow+30, ccol-30:ccol+30] = 1
# 应用低通滤波器
fshift_low = dft_shift * low_pass_filter
fshift_low = np.fft.ifftshift(fshift_low)
img_low_pass = cv2.idft(fshift_low)
img_low_pass = cv2.magnitude(img_low_pass[:,:,0], img_low_pass[:,:,1])
# 创建高通滤波器
high_pass_filter = np.ones((rows, cols, 2), np.uint8)
high_pass_filter[crow-30:crow+30, ccol-30:ccol+30] = 0
# 应用高通滤波器
fshift_high = dft_shift * high_pass_filter
fshift_high = np.fft.ifftshift(fshift_high)
img_high_pass = cv2.idft(fshift_high)
img_high_pass = cv2.magnitude(img_high_pass[:,:,0], img_high_pass[:,:,1])
# 显示原始图像和滤波后的图像
plt.figure(figsize=(12, 6))
plt.subplot(131), plt.imshow(img, cmap='gray')
plt.title('Original Image'), plt.xticks([]), plt.yticks([])
plt.subplot(132), plt.imshow(img_low_pass, cmap='gray')
plt.title('Low Pass Filtered'), plt.xticks([]), plt.yticks([])
plt.subplot(133), plt.imshow(img_high_pass, cmap='gray')
plt.title('High Pass Filtered'), plt.xticks([]), plt.yticks([])
plt.show()

-
高通滤波:增强边缘
-
低通滤波:模糊图片
八、图像特征
1. 角点检测
角点定义 两条边的交点,或者说角点的局部邻域应该具有两个不同区域的不同方向的边界

-
基本思想: 角点周围的灰度值变化肯定较大
-
检测原理:使用一个滑动窗口在灰度图上进行任意方向上的滑动,比较滑动前与滑动后两个位置的灰度值:
-
几乎没有变化:滑动窗口处于颜色填充区域,例如左图所示
-
在一个方向上有变化:滑动窗口处于图片边缘,例如中间图所示
-
各个方向变化剧烈:滑动窗口极有可能处于角点位置,例如右图所示
-
import cv2
import numpy as np
# 读取输入图像
image_path = 'D:\\Python_Demo\\test.png'
img = cv2.imread(image_path)
# 将图像转换为灰度图
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 转换为浮点类型
gray = np.float32(gray)
# 进行 Harris 角点检测
dst = cv2.cornerHarris(gray, blockSize=2, ksize=3, k=0.04)
# 结果进行膨胀,以便更好地标示角点
dst = cv2.dilate(dst, None)
# 设置阈值,显示角点
img[dst > 0.01 * dst.max()] = [0, 0, 255]
# 显示结果
cv2.imshow('Original Image', img)
cv2.waitKey(0)
cv2.destroyAllWindows()
2. SIFT算法
-
人眼视觉:
-
近大远小:同一物体,近处看时感觉比较大,远处看时感觉比较小;
-
模糊:更准确说应该是「粗细」,我们看近处,可以看到物体的细节(人会觉得比较清楚)。
-
-
尺度空间作用: 对输入图片模拟出图片信息近大远小以及模糊的效果,从而使得机器对同一张图片的感知与人眼近似。
SIFT尺度空间:不同大小的高斯核函数对图像进行卷积滤波同时在层级变换之间进行下采样来构建金字塔模式下的尺度空间。

近大远小:不同的Octave具有不同的分辨率。
模糊:同一Octave下的图片,进行不同程度的「高斯滤波」

具体后续详细说明。
3. BF特征匹配
-
思路: 暴力匹配,遍历两张图片关键点的描述符,然后比较两个描述符之间的差异,例如计算两个描述符之间的距离。
-
交叉检测(crossCheck): 蓝色图的关键点A与紫色图的关键点B匹配时,A 描述符与紫图中所有描述符最接近的点是 B,同时 B 描述符与蓝图中所有描述符最接近的点也要是 A,这样才认为 A 点与 B 点匹配。

4. HOG特征描述
方向梯度直方图 HOG (histogram of oriented gradients):可以用来表示图像的物体特征,因此能够用于物体检测。方向梯度直方图?没错,实现思路就与 SIFT 算法 中的关键点方向直方图近似。
HOG特征计算如下
图片预处理,从图片中截取出目标

利用 Soble 算子,计算目标图片在横向与纵向上的梯度图

根据横向方向的梯度 𝑔𝑥gx 与 纵向方向上的梯度 𝑔𝑦gy 计算总梯度和梯度方向

将原来的 64 x 128 的图像划分为一个个 8x8 的单元(图中的一块绿色方格)。然后对每一个8x8 的单元格分别进行直方图统计,直方图的横坐标为梯度方向,纵坐标可以为梯度的累加值、该方向上梯度出现次数等。
对于梯度方向的取值范围为 0∘∼180∘0∘∼180∘ 并非 0∘∼360∘0∘∼360∘,因为 𝛼α 与 𝛼+180∘α+180∘ 其实位于同一直线上,只是方向不同,所以就将 𝛼α 与 𝛼+180∘α+180∘ 同一看作是 𝛼α 方向。 假设梯度方向被划分为了9个部分,那么 8x8的像素块经过一次方向梯度直方图统计就被压缩为 9x1 的向量。这样对 64x128 图片的 8x16 个绿色格子全部进行直方图统计,就能得到128 个 9x1 的向量。至此,就将原图 64x128x3 = 24576 的像素值压缩成了 128x9x1 = 1152的向量。
从上面的操作可以看出, 方向梯度直方图实现了对图片特征的压缩提取。

上述 8x8 单元格的直方图统计后,得到的结果对「光线变化」敏感(光线影响体现在像素的数值变化,当像素的数值发生变化,就会导致梯度的幅值和方向发生改变)。特征描述符对光线敏感,这对物体特征描述是不利的(图片上的光影一变化,特征描述符号就变了,这肯定不行的)。因此,还需要对上述的方向梯度直方图统计结果进行「归一化」处理,排除光线的干扰。
将四个绿色格子8x8组成一个蓝色的滑动窗口16x16,这样在一个滑动窗口中,就存在 4x9x1 = 36x1 的向量

利用蓝色的滑动窗口16x16,对图片中的所有绿色单元格8x8进行归一化,最终得到 「HOG特征描述符」。 对于64x128的图片,蓝色滑动窗口在横向要滑动 7 次;在纵向要滑动 15 次。滑动窗口进行一次归一化,可以得到 36x1 的向量,当滑动窗扫描完整张图片,可以得到 7x15x36x1=3782x1的向量。最终得到的这个3780x1的向量,就是当前64x128目标图片的「HOG描述符」。
import cv2
import numpy as np
# 读取输入图像
image_path = 'D:\\Python_Demo\\test.png'
img = cv2.imread(image_path)
# 将图像转换为灰度图
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 初始化 HOG 描述子
hog = cv2.HOGDescriptor()
# 计算 HOG 特征
hog_features = hog.compute(gray)
# 重新调整 HOG 特征的形状,以便进行显示
hog_features = hog_features.reshape(-1, 36) # 36 是每个块的描述子大小
# 显示 HOG 特征图像
hog_image = np.zeros((gray.shape[0], gray.shape[1]), dtype=np.uint8)
cell_size = (8, 8) # 每个单元格的大小
block_size = (2, 2) # 每个块包含的单元格数
stride = (8, 8) # 块的步幅
nbins = 9 # 梯度方向的数量
cell_rows = gray.shape[0] // cell_size[1]
cell_cols = gray.shape[1] // cell_size[0]
# 画出每个单元格的 HOG 特征
for i in range(cell_rows - 1):
for j in range(cell_cols - 1):
cell_feature = hog_features[i * (cell_cols - 1) + j]
cell_angle = np.arange(0, 360, 360 // nbins)
center = (j * cell_size[0] + cell_size[0] // 2, i * cell_size[1] + cell_size[1] // 2)
for k in range(nbins):
angle = cell_angle[k]
magnitude = cell_feature[k]
x1 = int(center[0] + magnitude * np.cos(angle))
y1 = int(center[1] - magnitude * np.sin(angle))
x2 = int(center[0] - magnitude * np.cos(angle))
y2 = int(center[1] + magnitude * np.sin(angle))
cv2.line(hog_image, (x1, y1), (x2, y2), 255, 1)
cv2.imshow('HOG Features', hog_image)
cv2.waitKey(0)
cv2.destroyAllWindows()
九、运动检测
1.背景建模
帧差法
由于场景中的目标在运动,目标的影像在不同图像帧中的位置不同。该类算法对时间上连续的两帧图像进行差分运算,不同帧对应的像素点相减,判断灰度差的绝对值,当绝对值超过一定阈值时,即可判断为运动目标,从而实现目标的检测功能。
bgimg = cv2.imread('./asset/diffFrame_background.jpg')
fgimg = cv2.imread('./asset/diffFrame_people.jpg')
bgimgGray = cv2.cvtColor(bgimg,cv2.COLOR_BGR2GRAY)
fgimgGray = cv2.cvtColor(fgimg,cv2.COLOR_BGR2GRAY)
# 灰度差值运算
diff = np.abs(cv2.subtract(bgimgGray,fgimgGray))
# 标记灰度变化较大的部分
0mask = np.zeros_like(diff)
1mask[diff > 12] = 255
混合高斯模型
# 高斯混合模型
GaussModel = cv2.createBackgroundSubtractorMOG2()
# 高斯混合
mask = GaussModel.apply(img)
2.光流估计
-
光流:空间运动物体在观测成像平面上的像素运动的「瞬时速度」,根据各个像素点的速度矢量特征,可以对图像进行动态分析,例如目标跟踪。
-
分类
-
稀疏光流:选择运动物体的特征点进行光流估计
-
稠密光流:选择整个运动物体进行光流估计
-
OpenCV 自带的算法太旧,精度也不行,所以直接忽略,推荐学习比较新的
-
YOLO
-
Faster-Rcnn
-
Mask-Rcnn
十、目标识别
openCV的方法就不说了,直接上dlib,OpenCV 实现太繁琐,方法太旧
1. dlib实现
1.1 dlib人脸检测
人脸检测的内部实现靠的就是 HOG 描述符、SVM 等算法实现。
# 获取默认的检测
detector = dlib.get_frontal_face_detector()
# upsample_num_times :对图片进行上采样(放大)多少次
# return:rectangles
# 对图片进行检测
faces = detector(image: array, upsample_num_times: int=0L)
# rectangle 类
y1 = rectangle.bottom() # detect box bottom y value
y2 = rectangle.top() # top y value
x1 = rectangle.left() # left x value
x2 = rectangle.right() # right x value
cap:
import cv2
import dlib
# 获取默认的检测器
detector = dlib.get_frontal_face_detector()
# 打开摄像头
cap = cv2.VideoCapture(0)
while True:
# 读取一帧图像
ret, frame = cap.read()
if not ret:
break
# 转换为灰度图像
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 检测人脸
faces = detector(gray, upsample_num_times=1)
# 遍历检测到的每个脸
for face in faces:
# 获取人脸矩形框的坐标
x1 = face.left()
y1 = face.top()
x2 = face.right()
y2 = face.bottom()
# 在原图上绘制矩形框
cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
# 显示结果图像
cv2.imshow('Detected Faces', frame)
# 按下 'q' 键退出循环
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# 释放摄像头并关闭窗口
cap.release()
cv2.destroyAllWindows()
image:
import cv2
import dlib
# 获取默认的检测器
detector = dlib.get_frontal_face_detector()
# 读取图像
image_path = 'D:\\Python_Demo\\yyy.jpg'
image = cv2.imread(image_path)
if image is None:
print(f"Could not read the image from {image_path}")
exit()
# 转换为灰度图像
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 检测人脸
faces = detector(gray, upsample_num_times=1)
# 遍历检测到的每个脸
for face in faces:
# 获取人脸矩形框的坐标
x1 = face.left()
y1 = face.top()
x2 = face.right()
y2 = face.bottom()
# 在原图上绘制矩形框
cv2.rectangle(image, (x1, y1), (x2, y2), (0, 255, 0), 2)
# 显示结果图像
cv2.imshow('Detected Faces', image)
cv2.waitKey(0)
# 关闭窗口
cv2.destroyAllWindows()
1.2 人脸追踪
上述的人脸检测步骤其实只适用于「单张图片」的人脸检测,如果对视频的每一帧都使用同样的方法一帧图片一帧图片检测,在 dlib 中可能会很慢。为了加快视频中的人脸检测,可以采用追踪的方式。
import cv2
import dlib
# 获取默认的人脸检测器和追踪器
detector = dlib.get_frontal_face_detector()
tracker = dlib.correlation_tracker()
# 打开摄像头
cap = cv2.VideoCapture(0)
while True:
# 读取一帧图像
ret, frame = cap.read()
if not ret:
break
# 转换为灰度图像(dlib 人脸检测需要灰度图像)
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 第一帧时检测人脸并启动追踪
if tracker.get_position() is None:
# 检测第一个人脸
faces = detector(gray, 0)
if len(faces) > 0:
face = faces[0]
# 启动追踪器
tracker.start_track(frame, face)
# 更新追踪器
tracker.update(frame)
# 获取追踪结果
tracked_position = tracker.get_position()
# 转换为整数坐标(用于绘制)
x1 = int(tracked_position.left())
y1 = int(tracked_position.top())
x2 = int(tracked_position.right())
y2 = int(tracked_position.bottom())
# 在图像上绘制矩形框
cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
# 显示结果图像
cv2.imshow('Tracked Face', frame)
# 按下 'q' 键退出循环
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# 释放摄像头并关闭窗口
cap.release()
cv2.destroyAllWindows()
-
追踪器其实只要初始化时,给定一个
dlib.rectangle位置,之后就会追踪这个区域,因此,只要初始化时,给定一个目标位置,追踪器就能够对目标进行追踪,不一定非得是人脸。 -
当被追踪的目标跑出画面后,然后又跑回来,追踪器就可能追踪不了了。
import cv2
import dlib
# 获取默认的人脸检测器
detector = dlib.get_frontal_face_detector()
# 创建相关追踪器
tracker = dlib.correlation_tracker()
# 打开摄像头
cap = cv2.VideoCapture(0)
# 是否已经启动追踪
tracking_started = False
# 读取并显示视频流
while True:
ret, frame = cap.read()
if not ret:
break
# 转换为灰度图像以便检测人脸
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 检测人脸
faces = detector(gray, 0)
# 如果检测到人脸,并且尚未启动追踪,则开始追踪第一个检测到的人脸
if len(faces) > 0 and not tracking_started:
face = faces[0] # 获取第一个检测到的人脸
# 启动追踪器
tracker.start_track(frame, face)
tracking_started = True # 设置追踪状态为已启动
# 如果已经启动了追踪器,则更新追踪器并获取追踪结果
if tracking_started:
tracker.update(frame)
tracked_position = tracker.get_position()
# 将浮点数位置转换为整数位置
x1 = int(tracked_position.left())
y1 = int(tracked_position.top())
x2 = int(tracked_position.right())
y2 = int(tracked_position.bottom())
# 在视频帧上绘制矩形框来标记追踪到的人脸
cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
# 显示结果图像
cv2.imshow('Face Tracking', frame)
# 按下 'q' 键退出循环
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# 释放摄像头资源并关闭窗口
cap.release()
cv2.destroyAllWindows()
8万+

被折叠的 条评论
为什么被折叠?



