[OpenCV] EP7_直方图与形态学操作

🎐直方图均衡化

什么是直方图?

  • 数据统计方法

  • bin:组距/直条 → 横坐标,如梯度、色彩等特征统计量

 

Sub.1 绘制直方图🎐

横坐标:像素值;纵坐标:个数

Pt.1 API学习 💐

1. cv2.calcHist() - 核心直方图计算

hist = cv2.calcHist(images, channels, mask, histSize, ranges)

 

  1. 参数
    • images :图像列表(可同时处理多图),需用[img]包裹(即使单图)
    • channels :通道索引列表
      • 灰度图:[0]
      • 彩色图:[0](B)、[1](G)、[2](R)
    • mask :ROI区域掩膜,None表示全图计算
    • hitszie :bin数量,[256]表示0-255每个值独立统计
    • ranges :像素范围,灰度图为[0, 256](Python左闭右开)
  2. 返回值: 形状为 (256, 1) 的numpy数组,每个元素表示对应像素值的出现次数
  3. 注意点
    • 彩色图需先拆分通道,分别计算各通道直方图
    • 多图处理时,hist结果会累加所有图的统计值

2. cv2.minMaxLoc() - 极值定位

minVal, maxVal, minLoc, maxLoc = cv2.minMaxLoc(hist)

  1. 参数hist:calcHist() 返回的直方图数组
  2. 返回值
    • minVal/maxVal:最小/最大出现次数
    • minLoc/maxLoc:对应像素值的位置(形如 (x, 0) )(在直方图数组中的位置索引)
  3. 注意点:
    • 最小值/最大值:指的是数量最少/多的像素值(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)原理

  1. 遍历统计:每个像素值的个数、比例、累计比例
  2. 计算:新像素值 = 缩放范围 × 累计比例
  3. 重新映射:新像素值代替原位置 → 原本集中的灰度值分散开

(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)主要步骤
    1. 图像分块:小块tiles,默认8×8
    2. 计算子区域直方图 → 子区域直方图均衡化
    3. 对比度限制:API中的clipLimit参数
    4. 重采样和领域像素融合:平滑过渡各个小块
    5. 合成输出图像

(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. 场景联想

  1. 监控视频里找走失的小孩(已知小孩衣服模板)
  2. 工业检测找零件是否缺失
  3. 游戏脚本自动识别按钮位置

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. 霍夫直线变换(标准霍夫变换)
  • 原理:

    1. 为什么要引入极坐标系:垂直直线k无穷大,无法表示
    2. 图像中的每个点 (x,y) → 对应霍夫空间(ρ−θ空间)中的一条曲线
    3. 当多条曲线在霍夫空间中交于一点 ⇒ 这些曲线对应的图像空间中的点 共线
    4. 设置一个阈值,统计霍夫空间曲线交点处的曲线数量,超过阈值的点 对应的直线,就是检测到的直线。
2. 统计概率霍夫直线变换

标准霍夫计算量大,且只能得到直线的参数 → 绘制麻烦

  • 概率版改进:
    • 只处理随机采样的点,减少计算量。
    • 直接返回线段的端点坐标。
3. 霍夫圆变换
  • 原理:
    1. 圆需要三个参数 (圆心x, y, 半径r ) ⇒ 映射到三维参数空间
    2. 图像空间的每个点 ←→ 霍夫空间的一个三维曲面
    3. 霍夫空间的曲面相交与一点 ⇒ 这些点在图像空间中位于同一个圆
    4. 实际中,常用霍夫梯度法减少计算量
      • 图像的梯度信息 → 确定可能的圆心位置
      • 圆心周围的点 → 确定半径

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)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值