🎐直方图均衡化
什么是直方图?
-
数据统计方法
-
bin:组距/直条 → 横坐标,如梯度、色彩等特征统计量
Sub.1 绘制直方图🎐
横坐标:像素值;纵坐标:个数
Pt.1 API学习 💐
1. cv2.calcHist() - 核心直方图计算
hist = cv2.calcHist(images, channels, mask, histSize, ranges)
- 参数
images
:图像列表(可同时处理多图),需用[img]
包裹(即使单图)channels
:通道索引列表- 灰度图:[0]
- 彩色图:[0](B)、[1](G)、[2](R)
mask
:ROI区域掩膜,None表示全图计算hitszie
:bin数量,[256]表示0-255每个值独立统计ranges
:像素范围,灰度图为[0, 256](Python左闭右开)
- 返回值: 形状为 (256, 1) 的numpy数组,每个元素表示对应像素值的出现次数
- 注意点:
- 彩色图需先拆分通道,分别计算各通道直方图
- 多图处理时,hist结果会累加所有图的统计值
2. cv2.minMaxLoc() - 极值定位
minVal, maxVal, minLoc, maxLoc = cv2.minMaxLoc(hist)
- 参数:
hist
:calcHist() 返回的直方图数组 - 返回值:
minVal/maxVal
:最小/最大出现次数minLoc/maxLoc
:对应像素值的位置(形如 (x, 0) )(在直方图数组中的位置索引)
- 注意点:
- 最小值/最大值:指的是数量最少/多的像素值(maxLoc[0]即出现频率最高的像素值)
- 可用
hist.max()
获取最大值,用于归一化
3. 绘制
cv2.line(img, pt1, pt2, color, thickness)
参数:
img
:绘图画布(需提前创建空白图像)pt1/pt2
:线段起终点坐标(x,y)color
:线条颜色(灰度图用标量值,彩色用(B,G,R)元组)thickness
:线宽(负数表示填充)
注意点:
✅ 直方图绘制需遍历每个bin,用线条高度映射统计值
✅ 需归一化处理:hist_norm = hist / maxVal * height
Pt.2 代码模式 🍀
Study Example 🍀
import cv2 as cv
import numpy as np
"""
绘制直方图
"""
def draw_hist(image):
# 1. 检测是否为灰度图
if len(image.shape) == 3:
gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
else:
gray = image
# 2. 直方图计算
hist = cv.calcHist([gray], [0], None, [256], [0, 256])
# 3. 获取最小值和最大值及其位置索引
min_val, max_val, min_loc, max_loc = cv.minMaxLoc(hist)
# 如果只是为了用最大值,也可以直接使用 max_val = hist.max()
# 4. 使用numpy创建图像(背景) -> 注意: shape 是 (高, 宽, 通道数)
hist_img = np.zeros((300, 256, 3), dtype=np.uint8)
# 5. 归一化:限制直方图的高
hpt = int(0.9 * 300)
hist_norm = hist / max_val * hpt # 广播机制
# 6. 绘制直方图
for i in range(256):
# 绘制直方图的每个竖条
# 注意: 这里的 hist_norm[i] 是一个数组,所以需要取第一个元素
cv.line(hist_img, (i, 300), (i, 300 - int(hist_norm[i][0])), (255, 255, 255))
return hist_img
if __name__ == "__main__":
img = cv.imread("images/CuteCat.jpg")
cat = cv.resize(img, (0,0), fx=0.1, fy=0.1)
gray = cv.cvtColor(cat, cv.COLOR_BGR2GRAY)
hist_img = draw_hist(cat)
cv.imshow("cat", cat)
cv.imshow("gray", gray)
cv.imshow("hist", hist_img)
cv.waitKey(0)
cv.destroyAllWindows()
输出:
Sub.2 直方图均衡化🎐
Pt.1 核心概念⚡
1. 原理
重新排列像素值,暗的更暗,亮的更亮 → "拉伸灰度值范围",增强对比度
2. 作用
- 增强对比度:明暗差异更明显
- 提高图像质量:图像细节更丰富
- 标准化图像:让不同光照条件下的图像有更一致的灰度分布
Pt.2 自适应直方图均衡化 (AHE)
通过调整图像像素值分布,改善图像对比度和亮度
(1)原理
- 遍历统计:每个像素值的个数、比例、累计比例
- 计算:新像素值 = 缩放范围 × 累计比例
- 重新映射:新像素值代替原位置 → 原本集中的灰度值分散开
(2)适用范围
-
适用于灰度分布不均且灰度分布集中在更窄的范围,图像的细节不够清晰且对比度较低的情况。
-
缺点:有噪声 →噪点被放大;高对比度处 → 过曝;
-
原因:未考虑局部特征和全局对比度的差异 → 改用CLAHE(带限制的均衡化)
(3)API 使用
dst = cv2.equalizeHist(imgGray)
注意点:需传入灰度图,返回的是处理后的新图像
Study Example 🍀
import cv2
# 读取图片并转灰度
img = cv2.imread('dark_photo.jpg')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 直方图均衡化 → 记住这个函数!
equ = cv2.equalizeHist(gray)
# 显示对比
cv2.imshow('Before', gray)
cv2.imshow('After', equ)
cv2.waitKey(0)
Pt.3 对比度受限的自适应直方图均衡化 (CLAHE)
- (1)原理
- 在每一个小区域内(默认8×8)进行直方图均衡化
- 对小区域内的对比度进行限制(防止噪点被放大)
- (2)主要步骤
- 图像分块:小块tiles,默认8×8
- 计算子区域直方图 → 子区域直方图均衡化
- 对比度限制:API中的clipLimit参数
- 重采样和领域像素融合:平滑过渡各个小块
- 合成输出图像
(3)API使用
step1:创建CLAHE对象
clahe = cv2.createCLAHE(clipLimit=None, tileGridSize=None)
参数说明:
clipLimit
:限制调整幅度,值越大对比越强(推荐2.0-3.0)tileGridSize
:分块大小,值越小局部效果越明显(常用8x8)
stpe2:应用
equ_clahe = clahe.apply(gray))
# 第一步:创建CLAHE对象 → 相当于买了个智能美颜灯
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
# 第二步:应用 → 把美颜灯对着照片照一下
equ_clahe = clahe.apply(gray)
Study Example 🍀
from Test import draw_hist
import cv2 as cv
"""
直方图均衡化:AHE、CLAHE
"""
# 读图
img = cv.imread("images/zhifang.png")
cat = cv.resize(img, (0,0), fx=0.1, fy=0.1)
gray = cv.cvtColor(cat, cv.COLOR_BGR2GRAY)
# AHE 普通直方图均衡化
equ = cv.equalizeHist(gray)
# CLAHE 对比度受限的直方图均衡化
# 创建CLAHE对象
clahe = cv.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
# 应用CLAHE
equ_clahe = clahe.apply(gray)
# 绘制原图、AHE、CLAHE 的直方图
hist_img = draw_hist(cat)
hist_equ = draw_hist(equ)
hist_clahe = draw_hist(equ_clahe)
# 依次显示
# 原图和其灰度图、直方图
cv.imshow("org", cat)
cv.imshow("gray", gray)
cv.imshow("hist", hist_img)
# AHE后的原图和直方图
cv.imshow("equ_img", equ)
cv.imshow("equ_hist", hist_equ)
# CLAHE后的原图和直方图
cv.imshow("clahe_img", equ_clahe)
cv.imshow("clahe_hist", hist_clahe)
cv.waitKey(0)
cv.destroyAllWindows()
🏖️ 模版匹配
Pt.1 核心概念⚡
Q1:什么是模版匹配?(WHAT)
-
定义:在目标图中找到与模板最相似的小区域
-
特点:
- 过程类似卷积,滑动比较,但是无边缘填充
- 返回结果:相似度矩阵 → 目标图大小 - 模板图大小 + 1
Q2:什么时候要用到模版匹配?(WHEN)
e.g. 场景联想
- 监控视频里找走失的小孩(已知小孩衣服模板)
- 工业检测找零件是否缺失
- 游戏脚本自动识别按钮位置
Q3:为什么要用模版匹配?(WHY)
✅ 优点:简单易用,适合固定图案
❌ 缺点:
- 旋转/缩放后失效(就像侧脸照片找正脸)
- 速度慢(大图找大模板时)
Pt.2 如何用?(HOW)
result = cv2.matchTemplate(image, temple, method)
基本参数:
- image → 灰度图/彩色图,若彩色图像,将在各通道独立匹配
- temple → 模板图像:必须比目标图小,且与目标同通道
- method → 匹配方法(6种3组,普通版+归一化版)
匹配方法 method
:
方法名 | 一句话原理 | 最佳值位置 | 适用场景 |
---|---|---|---|
TM_SQDIFF | 平方差匹配(越小越像) | 最小值点 | 精确匹配 |
TM_SQDIFF_NORMED | 归一化平方差(抗亮度变化) | 最小值点 | 光照变化时 |
TM_CCORR | 相关度匹配(越大越像) | 最大值点 | 少用,对亮度敏感 |
TM_CCORR_NORMED | 归一化相关匹配 | 最大值点 | 常用,综合性能好 |
TM_CCOEFF | 相关系数匹配 | 最大值点 | 考虑均值差异时 |
TM_CCOEFF_NORMED | 归一化相关系数(最常用) | 最大值点 | 绝大多数情况 |
Study Example 🍀
import cv2 as cv
import numpy as np
"""
模版匹配
"""
# 读图及预处理
org = cv.imread("images/game.png")
img = org.copy()
temp = cv.imread("images/temp.png")
img_gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
temp_gray = cv.cvtColor(temp, cv.COLOR_BGR2GRAY)
# 获取模板宽高
h, w = temp.shape[:2]
# 模版匹配,拿到匹配结果,返回匹配程度矩阵
res = cv.matchTemplate(img_gray, temp_gray, cv.TM_CCOEFF_NORMED)
# 设置阈值,使用 np.where 筛选出符合条件的坐标
threshold = 0.8
loc = np.where(res >= threshold)
"""
loc 得到的是一个元组,其中包含两个数组,分别表示匹配到的所有点的 x 和 y 坐标
loc[0] 表示所有匹配到的点的 y 坐标,loc[1] 表示所有匹配到的点的 x 坐标
zip(*loc) 是将 loc 中的元素进行解包,然后使用 zip 函数将它们组合成一个元组的列表
zip(*loc)得到的pt元组是 (y,x),用[::-1]将其反转为 (x,y)
"""
for pt in zip(*loc[::-1]): # pt 为左上角坐标
# 计算右下角坐标
bottom_right = (pt[0] + w, pt[1] + h)
# 绘制矩形框
cv.rectangle(img, pt, bottom_right, (0,0,255), 1, cv.LINE_AA)
# 显示结果
cv.imshow("org", org)
cv.imshow("img", img)
cv.waitKey(0)
cv.destroyAllWindows()
🎍 霍夫变换
Pt.1 核心概念⚡
Q1:什么是霍夫变换?(WHAT)
- 定义:霍夫变换是一种图像处理技术,用于检测图像中的几何形状(如直线、圆等)。
- 核心思想:将图像中的点映射到参数空间,通过统计参数空间中的“投票”结果,找到几何形状的参数。
Q2:霍夫变换有什么用?为什么要用它?(WHY)
- 用途:
- 检测图像中的直线、圆、椭圆等几何形状。
- 适用于噪声较多或形状不完整的场景(如断断续续的车道线)。
- 优点:
- 对噪声鲁棒性强。
- 能检测不完整的形状。
- 缺点:
- 计算量大(尤其是高维形状如圆)。
- 参数调优复杂。
Q3:什么时候用霍夫变换?(WHEN)
- 典型场景:
- 车道线检测(直线)。
- 工业零件定位(圆、矩形)。
- 医学图像分析(血管、细胞轮廓)。
- 前置条件:通常需要先做边缘检测(如Canny)。
- 后续处理:根据检测到的形状进行坐标提取或几何分析。
Q4:有几种霍夫变换?分别什么原理?
1. 霍夫直线变换(标准霍夫变换)
-
原理:
-
- 为什么要引入极坐标系:垂直直线k无穷大,无法表示
- 图像中的每个点 (x,y) → 对应霍夫空间(ρ−θ空间)中的一条曲线
- 当多条曲线在霍夫空间中交于一点 ⇒ 这些曲线对应的图像空间中的点 共线
- 设置一个阈值,统计霍夫空间曲线交点处的曲线数量,超过阈值的点 对应的直线,就是检测到的直线。
2. 统计概率霍夫直线变换
标准霍夫计算量大,且只能得到直线的参数 → 绘制麻烦
- 概率版改进:
- 只处理随机采样的点,减少计算量。
- 直接返回线段的端点坐标。
3. 霍夫圆变换
- 原理:
- 圆需要三个参数 (圆心x, y, 半径r ) ⇒ 映射到三维参数空间
- 图像空间的每个点 ←→ 霍夫空间的一个三维曲面
- 霍夫空间的曲面相交与一点 ⇒ 这些点在图像空间中位于同一个圆
- 实际中,常用霍夫梯度法减少计算量
- 图像的梯度信息 → 确定可能的圆心位置
- 圆心周围的点 → 确定半径
Pt.2 如何使用霍夫变换 (HOW)
import cv2 as cv
import numpy as np
"""
霍夫变换:霍夫直线变换、统计概率霍夫直线变换、霍夫圆变换
"""
# 读图及预处理
_ = cv.imread("images/graph_white.png")
img = cv.resize(_, None, fx=1.5, fy=1.5)
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
edges = cv.Canny(gray, 50, 150) # 必须要做边缘检测!
# 直线变换
lines = cv.HoughLines(edges, 1, np.pi/180, 80)
hough = img.copy()
if lines is not None:
for line in lines:
rho, theta = line[0]
sin_theta, cos_theta = np.sin(theta), np.cos(theta)
x0, x1 = 0, img.shape[1]
# 由 rho = x*cos_theta + y*sin_theta 得到 y = (rho - x*cos_theta) / sin_theta
y0 = np.int_((rho - x0 * cos_theta) / sin_theta)
y1 = np.int_((rho - x1 * cos_theta) / sin_theta)
# 绘制直线
cv.line(hough, (x0, y0), (x1, y1),
(0, 255, 0), 1, cv.LINE_AA)
# 统计概率直线变换
lines_p = cv.HoughLinesP(edges, 1, np.pi/180, 80,
minLineLength=50, maxLineGap=10)
hough_p = img.copy()
if lines_p is not None:
for line in lines_p:
x0, y0, x1, y1 = line[0]
cv.line(hough_p, (x0, y0), (x1, y1),
(0, 255, 0), 1, cv.LINE_AA)
# 霍夫圆变换
# 圆变换对噪声敏感,需要先做高斯滤波
cv.GaussianBlur(edges, (5, 5), 0)
circles = cv.HoughCircles(edges, cv.HOUGH_GRADIENT, 1, 100, param2=30)
circles = np.int_(np.round(circles)) # 转换为整数
hough_c = img.copy()
if circles is not None:
for circle in circles[0,:]:
center = (circle[0], circle[1])
radius = circle[2]
cv.circle(hough_c, center, radius, (0, 255, 0), 2, cv.LINE_AA)
# 显示结果
cv.imshow("org", img)
cv.imshow("hough", hough)
cv.imshow("hough_p", hough_p)
cv.imshow("hough_c", hough_c)
cv.waitKey(0)
cv.destroyAllWindows()
输出:
🎋 图像亮度变换
Pt.1 核心概念 ⚡
- 对比度调整:放大/缩小像素间的差异 → 暗处更暗,亮出更亮(通过拉伸像素值分布范围)
- 亮度调整:整体平移像素值 → 全图变亮/暗(不改变像素间差异)
- OpenCV调整对比度和亮度的公式:
g(i, j) = α·f(i, j) + β
- α 控制对比度(α>1 对比度增强,0<α<1 对比度减弱)
- β 控制亮度(β>0 变亮,β<0 变暗)
- 实际应用:对比度需要αβ共同控制(如β影响人眼对比度感知,或联合优化防止过曝)
Pt.2 调整方法 💐
(1)线性变换 - 颜色加权加法
颜色加权加法 (EP2 颜色与灰度处理)
dst = cv2.addWeighted(src1, alpha, src2, beta, gamma)
- 单图调整:快速写法
# 对比度增强为1.5倍,亮度+30
adjusted = cv2.addWeighted(img, 1.5, img, 0, 30)
- 规范写法:src2设为全零矩阵,表示不混合其他图像
# 正确用法:src2设为0,只调整src1(当前图像)
adjusted = cv2.addWeighted(image, 1.5, np.zeros_like(image), 0, 30)
实时预览? -- 滑动条 TrackBar
Pt.3 滑动条(TrackBar)动态调整
一、基础概念
(1)什么是Trackbar?
- Trackbar(滚动条/滑动条)是OpenCV中用于动态调整参数的交互工具,依附于指定窗口。属于图形用户界面(GUI)组件。
- 作用:实时交互工具,通过拖动滑块改变参数值。
(2)核心函数
cv2.createTrackbar(trackbar_name, window_name, value, max_val, callback)
trackbar_name
:滑动条名称(字符串)window_name
:所属窗口名称(需先创建窗口)value
:初始值max_val
:最大值(最小值固定为0)callback
:回调函数(滑动条变化时触发)
cv2.getTrackbarPos()
- 获取滑动条当前值
- 参数:滑动条名称和窗口名称
import cv2
import numpy as np
"""
通过滑动条控制图像亮度和对比度
"""
# 全局变量存储图像和调整参数
_ = cv2.imread("images/CuteCat.jpg")
image = cv2.resize(_, None, fx=0.1, fy=0.1)
alpha = 1.0 # 对比度 1.0表示原图
beta = 0 # 亮度 0
# 给滑条创建窗口,设置初始值
window_name = "Contrast & Brightness"
cv2.namedWindow(window_name, cv2.WINDOW_AUTOSIZE)
# 滑动条回调函数,改变图像亮度和对比度
def update_image(val):
global image, alpha, beta
# 获取滑动条当前值
alpha = cv2.getTrackbarPos("Contrast", window_name) / 100
beta = cv2.getTrackbarPos("Brightness", window_name) - 100
"""结合创建滑条时设置的初始值,将对比度和亮度调整范围映射到 0.0~3.0 和 -100~100"""
# 应用公式:g(x) = α*f(x) + β
dst = np.clip(image.astype(float) * alpha + beta, 0, 255).astype(np.uint8)
# 并排显示原图和调整结果
combined = np.hstack([image, dst])
cv2.imshow(window_name, combined)
# 创建对比度和亮度滑条
cv2.createTrackbar("Contrast", window_name, 100, 300, update_image)
cv2.createTrackbar("Brightness", window_name, 100, 200, update_image)
# 初始化显示
update_image(0)
# 等待用户退出
cv2.waitKey(0)
cv2.destroyAllWindows()
输出:
🌌 形态学变换
(Morphological Transformations),通常处理二值图像/灰度图,根据像素值进行逻辑运算,实现图像形状的修改(如去噪、边缘检测、连接区域等)
It.1 腐蚀(Erosion)
1. 定义:
- 腐蚀操作会将图像中的前景物体(通常为白色)进行 “收缩”。
- 它通过核在图像上滑动,核内有0置为0,核内全1才为1。
-
效果:腐蚀操作能逐步收缩目标物体边界,消除孤立的噪声像素,细化连续的前景区域。 → 腐蚀后白色像素(非0)变少
2. API
erode = cv2.erode(image, kernel, iterations=1)
iterations
:迭代次数(腐蚀次数)
It.2 膨胀(Dilation)
1. 定义:
- 用核在图像上滑动,核内有白,则中心像素变白(扩大白色区域)。
-
效果:膨胀后白色像素(非0)变多了
3. API
dilated = cv2.dilate(img, kernel, iterations=1)
It.3 开运算(Opening)和闭运算(Closing)
开运算 | 闭运算 | |
原理 | 先腐蚀,后膨胀 → 更"干净" | 先膨胀,后腐蚀 → 更"完整" |
特点 | 去除小噪声,保留主体结构 | 填充物体内部孔洞,闭合边缘 |
# 开运算
opened = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel)
# 闭运算
closed = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
It.4 礼帽运算(Top Hat)和黑帽运算(Black Hat)
礼帽运算 | 黑帽运算 | |
定义 | 原图 - 开运算结果 | 闭运算结果 - 原图 |
作用 | 提取比周围亮的细节 | 提取比周围暗的细节 |
场景 | 背景均匀时的亮斑提取 | 深色缺陷检测,暗斑提取 |
# 礼帽运算
tophat = cv2.morphologyEx(image, cv2.MORPH_TOPHAT, kernel)
# 黑帽运算
blackhat = cv2.morphologyEx(image, cv2.MORPH_BLACKHAT, kernel)
It.5 形态学梯度(Morphological Gradient)
1. 定义
- 膨胀图 - 腐蚀图
- 效果:突出边缘
# 形态学梯度提取边缘
gradient = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel)