零基础玩转OpenCV|Day 05:图像处理与计算机视觉基础:从直方图均衡到形态学变换

目录

一、直方图均衡化

1、什么是直方图均衡化

2、绘制直方图

3、直方图均衡化

二、模板匹配

1、什么是模板匹配

2、绘制轮廓

三、霍夫变换

1、霍夫直线变换(Hough Line Transform)

2、霍夫圆变换

四、图像亮度调整

1、亮度变换

2、线性变换

3、直接像素值修改

五、形态学变换

1、核

2、腐蚀

3、膨胀

4、其他运算

六、总结


一、直方图均衡化

1、什么是直方图均衡化

(1)定义        

        直方图均衡化是一种用于增强图像对比度的图像处理技术。它通过重新分布图像的像素强度值(灰度值),使得像素值尽可能地均匀分布在 0~255 范围内,从而提升图像的视觉效果,尤其是当图像整体偏暗或偏亮时。

(2)原理

1、目标:

让图像的灰度级分布更“均匀”,增强对比度。

2、核心思想:
  1. 统计原始图像的灰度直方图(每个灰度值出现的频率)。

  2. 计算累积分布函数(CDF, Cumulative Distribution Function)

  3. 利用 CDF 对原始像素值进行映射 → 得到新的像素值。

  4. 新图像的直方图趋于平坦(即各个灰度级使用更均衡)。

3、数学过程简述:

设图像大小为 M×N ,总像素数 S=M×N

  • 直方图:h(k)= 灰度值为 k 的像素个数(k=0,1,...,255 )

  • 概率分布:p(k)=Sh(k)​

  • 累积分布:cdf(k)=∑j=0k​p(j)

  • 映射函数:T(k)=round(255×cdf(k))

然后将原图中所有灰度值为 k 的像素替换为 T(k) 。

(3)直方图均衡化的作用

场景效果

图像过暗

提亮细节

图像过亮

增强阴影部分

对比度低

拉开灰度差距,使纹理更清晰

医疗影像、监控画面

常用于预处理以增强可读性

2、绘制直方图

代码示例:

import cv2 as cv
import numpy as np
# 读图
bg = cv.imread('../images/bg.png')
# 创建黑图,绘制直方图
black = np.zeros((256, 256, 3),np.uint8)
# 统计像素 cv.calcHist(图像,通道,掩膜, bins, ranges)
hist = cv.calcHist([bg], [1], None, [256], [0, 256])
print(hist)

# 获取直方图的最小值 ,以及最大值和最小值对应的索引 [列,行 ] (x,y)
minVal, maxVal, minLoc, maxLoc = cv.minMaxLoc(hist)

# 定义直方图的高
h_hist = np.int32(256)

# 循环拿像素的个数
for i in range(256):
    # 获取像素的个数
    l = int(hist[i].item() * h_hist/maxVal)
    point1 = (i, 256 - l)
    point2 = (i, 256)  
    cv.line(black , point1, point2, (255, 0, 0), 1)

cv.imshow('dst', black)
cv.waitKey(0)
cv.destroyAllWindows()

直方图展示:

可以看出这张图的 像素值分布非常不均衡,所以图像的对比度会很差,观察到的不清晰

3、直方图均衡化

(1)自适应直方图均衡化

OpenCV 提供了简单高效的函数来实现直方图均衡化。

dst = cv.equalizeHist(src)

  • src:输入图像,必须是 单通道灰度图(Grayscale),类型 uint8

  • dst:输出图像,经过均衡化后的灰度图

代码示例:

import cv2 as cv
# 读图
img = cv.imread('../images/num.png', cv.IMREAD_GRAYSCALE)
# 直方图均衡化
dst = cv.equalizeHist(img)
cv.imshow("img", img)
cv.imshow("dst", dst)
cv.waitKey(0)
cv.destroyAllWindows()

结果展示:

可以看出经过直方图均衡化的图像更亮了,对比度更高了,但是我们能看到,人头像的脸部明显曝光严重,已经看不清轮廓了,这就是普通的自适应直方图均衡化的缺点,由此我们引入了对比度受限的自适应直方图均衡化。

(2)对比度受限的自适应直方图均衡化

        普通直方图均衡化对整个图像进行处理,可能导致噪声放大局部过增强。为此,OpenCV 提供了 CLAHE(Contrast Limited Adaptive Histogram Equalization)。

CLAHE 原理

  • 将图像分成小块(称为 tiles

  • 对每个小块进行直方图均衡化

  • 使用对比度限制(clip limit)防止噪声放大

  • 最后通过双线性插值拼接块,避免块边界明显

API使用:

# 创建 CLAHE 对象
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))

# 对灰度图应用 CLAHE
img_clahe = clahe.apply(img)  # img 是灰度图
  • clipLimit:对比度限制阈值,防止过增强(默认 2.0)

  • tileGridSize:分块大小,如 (8,8) 表示 8x8 个小块

代码示例:

import cv2 as cv
img = cv.imread('../images/zhifang.png', cv.IMREAD_GRAYSCALE)
# 创建CLAHE对象
clahe = cv.createCLAHE(clipLimit = 2.0, tileGridSize = (8, 8))
# 使用CLAHE调用apply()方法
cl1 = clahe.apply(img)

cv.imshow('img', img)
cv.imshow('cl1', cl1)
cv.waitKey(0)
cv.destroyAllWindows()

结果展示:

可以看出我们的CLAHE既能提升对比度,但也不会造成脸部曝光,效果更好。

注意事项:

  1. 输入图像必须是 uint8 类型

  2. 彩色图像建议先转到 YUV/HSV 空间再处理亮度通道

  3. CLAHE 的 tileGridSize 不宜太小,否则计算量大;不宜太大,否则失去局部性

  4. clipLimit 过大会导致噪声增强,一般设为 2~4


二、模板匹配

1、什么是模板匹配

(1)定义

模板匹配(Template Matching) 是一种在大图像(源图像)中寻找与小图像(模板图像)最相似区域的图像处理技术。

  • 模板图像(Template):你想找的小图像(比如一个图标、一个数字、一个按钮)。

  • 源图像(Source):你要在其中搜索的大图像(比如整个屏幕截图)。

  • 目标:找出模板在源图像中出现的位置(通常是左上角坐标)。

(2)基本思想

  • 将模板图像在源图像上滑动(从左到右,从上到下)。

  • 在每个位置,计算模板与当前区域的相似度

  • 相似度最高的位置,就是最可能匹配的位置。

(3)匹配方法

OpenCV 提供了多种匹配方法,通过 cv2.matchTemplate()method 参数指定。常见的有:

方法说明

cv2.TM_SQDIFF

平方差匹配,值越小越匹配(最佳匹配处为0)

cv2.TM_SQDIFF_NORMED

归一化平方差,值越接近0越匹配

cv2.TM_CCORR

相关匹配,值越大越匹配

cv2.TM_CCORR_NORMED

归一化相关匹配,值在 [0,1],越接近1越匹配 ✅(推荐)

cv2.TM_CCOEFF

相关系数匹配,值越大越匹配

cv2.TM_CCOEFF_NORMED

归一化相关系数匹配,值在 [-1,1],越接近1越匹配 ✅(最常用)

✅ 推荐使用 TM_CCOEFF_NORMEDTM_CCORR_NORMED,因为它们对光照变化更鲁棒。

API使用:

result = cv2.matchTemplate (image, template, method, result=None, mask=None)

参数说明

参数

说明

image

源图像(大图),灰度图或彩色图

template

模板图像(小图),必须比源图像小

method

匹配方法,如cv2.TM_CCOEFF_NORMED

result

输出匹配结果的矩阵(可选)

mask

掩码(仅用于TM_SQDIFFTM_CCORR_NORMED

返回值

  • 一个二维数组(热力图),每个位置的值表示该位置的匹配程度。

  • 值越高(或越低,取决于方法)表示越匹配。

2、绘制轮廓

示例代码:

import cv2 as cv
import numpy as np
# 读图
img = cv.imread('../images/game.png')
gray_img = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# print(gray_img.shape)

temp = cv.imread('../images/temp.png')
gray_temp = cv.cvtColor(temp, cv.COLOR_BGR2GRAY)
h, w = temp.shape[:2]
 
# print(gray_temp.shape)
# 模板匹配 拿到匹配结果矩阵
res = cv.matchTemplate(gray_img, gray_temp, cv.TM_CCOEFF_NORMED)
# print(res.shape)
# 设置阈值
thresh = 0.8
# 获取匹配上的结果的索引
loc = np.where(res >= thresh)
# print(len(loc))
# 解包,拿到成对的 x y 索引
for i in zip(*loc):
    x, y = i[1], i[0]
    cv.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 1)

cv.imshow('img', img)
cv.waitKey(0)
cv.destroyAllWindows()

展示结果:

匹配相应形状,绘出轮廓:


三、霍夫变换

        霍夫变换(Hough Transform) 是一种用于检测图像中具有特定形状(如直线、圆等)的特征提取技术。它通过将图像空间中的点映射到参数空间进行投票,从而检测出这些几何形状。

为什么需要霍夫变换?

  • 边缘检测(如 Canny)只能得到离散的边缘点。

  • 霍夫变换可以将这些点 “连接” 起来,识别出直线、圆等几何结构。

  • 对噪声和缺失部分具有较强的鲁棒性。

1、霍夫直线变换(Hough Line Transform)

(1)原理

在笛卡尔坐标系中,一条直线表示为:
                                y=mx+b
但当直线垂直时,斜率 m 会趋于无穷大,不方便处理。

因此,霍夫变换使用极坐标表示
                                xcosθ+ysinθ=ρ
其中:

  • ρ :原点到直线的垂直距离

  • θ :该垂线与 x 轴的夹角

(2)变换过程

  1. 对图像中的每一个边缘点 (x,y)

  2. 在参数空间 (ρ,θ) 中,绘制所有可能经过该点的直线(即曲线)

  3. 多个点共线时,这些曲线会在 (ρ,θ) 空间相交于一点

  4. 通过“投票”机制,统计交点的得票数,得票高的点即为检测到的直线

(3)API:cv2.HoughLines()cv2.HoughLinesP()

标准霍夫变换:cv2.HoughLines()

lines = cv2.HoughLines(edges, rho, theta, threshold)

  • edges:二值边缘图像(如 Canny 检测结果)

  • rho:ρ 的精度(像素单位,通常为 1)

  • theta:θ 的精度(弧度单位,通常为 np.pi/180

  • threshold:投票阈值,只有得票数超过该值才认为是直线

返回值lines 是一个数组,每个元素是 (rho, theta)

⚠️ 缺点:返回的是无限长的直线,且计算量大。

示例代码:

import cv2 as cv
import numpy as np
# 读图
img = cv.imread("../images/huofu.png")
# 灰度化
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# 二值化
_, binary = cv.threshold(gray, 127, 255, cv.THRESH_BINARY)
# 边缘检测
edges = cv.Canny(binary, 30, 70)
# 霍夫变换 返回的是[r, theta]
lines = cv.HoughLines(edges, 0.8, np.pi/180, 90)

for line in lines:
    r, theta = line[0]
    sin_theta = np.sin(theta)
    cos_theta = np.cos(theta)
    x1, x2 = 0, img.shape[1]
    y1 = int((r - x1 * cos_theta)/sin_theta)
    y2 = int((r - x2 * cos_theta)/sin_theta)
    cv.line(img, (x1, y1), (x2, y2), (0, 255, 0), 1)

cv.imshow('img', img)
cv.waitKey(0)
cv.destroyAllWindows()

结果展示:

概率霍夫变换:cv2.HoughLinesP()(推荐使用)

lines = cv2.HoughLinesP(edges, rho, theta, threshold, minLineLength=None, maxLineGap=None)

  • minLineLength:最小线段长度(小于该值的线段被丢弃)

  • maxLineGap:允许将两条线段视为一条线的最大间隔

返回值:每条线段表示为 [x1, y1, x2, y2]

✅ 优点:直接返回线段端点,适合实际应用。

示例代码:

import cv2 as cv
import numpy as np
# 读图
img = cv.imread("../images/huofu.png")
# 灰度化
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# 二值化
_, binary = cv.threshold(gray, 127, 255, cv.THRESH_BINARY)
# 边缘检测
edges = cv.Canny(binary, 30, 70)

lines = cv.HoughLinesP(binary, 0.8, np.pi/180, 90, minLineLength = 30, maxLineGap = 10)

for line in lines:
    x1, y1, x2, y2 = line[0]
    cv.line(img, (x1, y1), (x2, y2), (0, 0, 255), 1)
cv.imshow('img', img)
cv.waitKey(0)
cv.destroyAllWindows()

结果展示:

2、霍夫圆变换

(1)原理

圆的方程为:

参数空间为三维:圆心 (a,b) 和半径 r

霍夫圆变换对每个边缘点在 (a,b,r) 空间进行投票,找到峰值点。

由于计算量大,OpenCV 使用 霍夫梯度法(Hough Gradient Method),先利用边缘方向信息缩小搜索范围。

(2)API

circles = cv2.HoughCircles(image, method, dp, minDist,
                           param1=None, param2=None,
                           minRadius=0, maxRadius=0)

  • image:灰度图像(必须是单通道)

  • method:目前只支持 cv2.HOUGH_GRADIENT

  • dp:累加器分辨率与图像分辨率的反比(如 dp=1 表示相同分辨率,dp=2 表示一半)

  • minDist:检测到的圆心之间的最小距离

  • param1:Canny 边缘检测的高阈值(低阈值自动为一半)

  • param2:累加器阈值,越小检测到的圆越多(通常 10~50)

  • minRadius, maxRadius:圆的最小和最大半径

示例代码:

import cv2 as cv
import numpy as np
# 读图
img = cv.imread("../images/huofu.png")
# 灰度化
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# 二值化
_, binary = cv.threshold(gray, 127, 255, cv.THRESH_BINARY)
# 边缘检测
edges = cv.Canny(binary, 30, 70)
# 霍夫圆变换 cv.HoughCircles(edges, method, dp, minDist, param1, param2, minRadius, maxRadius)
circles = cv.HoughCircles(edges, cv.HOUGH_GRADIENT, 1, 20, param2=20)

for circle in circles:
    x, y, r = circle[0]
    
    x, y, r = np.int_(np.round(x)), np.int_(np.round(y)), np.int_(np.round(r))
    
    cv.circle(img, (x, y), r, (0, 255, 0), 2)

cv.imshow('img', img)
cv.waitKey(0)
cv.destroyAllWindows()

结果展示:


四、图像亮度调整

1、亮度变换

(1)什么是亮度?

        亮度(Brightness)是指图像中像素的灰度值或颜色强度。对于灰度图像,亮度就是像素值(0~255);对于彩色图像,通常是对每个颜色通道(如 R、G、B)分别处理。

2、线性变换

代码示例:

import cv2 as cv
# 读图
cat = cv.imread('../images/cat1.png')
# 线性变换
dst = cv.addWeighted(cat, 1, cat, 0, -50)
cv.imshow('cat', cat)
cv.imshow('dst', dst)

cv.waitKey(0)
cv.destroyAllWindows()

结果展示:

3、直接像素值修改

        这里我们采用一个有趣的滑动条来实现我们直观的像素值修改。

示例代码:

import cv2 as cv
import numpy as np
# 读图
img = cv.imread("../images/cat1.png")
# 创建窗口 用于实现滑条
window_name = "slide"
cv.namedWindow(window_name)

def change(p):
    x = p/256*511-255
    dst = np.uint8(np.clip(img + x, 0, 255))
    cv.imshow("dst", dst)
    print(x)
# 创建滑条
initial_value = 100
change(initial_value)
cv.createTrackbar("add_p", window_name, initial_value, 255, change)
cv.waitKey(0)
cv.destroyAllWindows()

结果展示:


五、形态学变换

        形态学变换主要作用于二值图像(黑白图像),但也适用于灰度图像。其核心思想是使用一个称为结构元素(Structuring Element) 的小矩阵(也叫核)在图像上滑动,根据核的形状和位置对图像像素进行变换。

1、核

        核(kernel)其实就是一个小区域,通常为3*3、5*5、7*7大小,有着其自己的结构,比如矩形结构、椭圆结构、十字形结构,通过不同的结构可以对不同特征的图像进行形态学操作的处理。

import cv2 as cv
import numpy as np
car = cv.imread("../images/cat1.png", cv.IMREAD_GRAYSCALE)
# 定义核
kernel = np.ones((5, 5), np.uint8)

2、腐蚀

  • 原理:将图像中的前景物体“变小”,消除细小的白色噪点,分离相连的物体。

  • 规则:只有当结构元素完全覆盖在前景区域(白色)时,中心像素才保留为白色,否则变为黑色。

  • 效果:边界被“腐蚀”掉一层。

适用于去除小的亮斑噪声。

# 腐蚀
erosion = cv.erode(car, kernel, iterations = 1)
cv.imshow("car", car)
cv.imshow("erosion", erosion)

3、膨胀

  • 原理:将图像中的前景物体“变大”,填充小的空洞,连接邻近物体。

  • 规则:只要结构元素与前景区域有重叠,中心像素就变为白色。

  • 效果:边界向外“膨胀”。

常用于连接断裂的边缘。

# 膨胀
dilation = cv.dilate(car, kernel, iterations = 1)
cv.imshow("dilation", dilation)

4、其他运算

(1)开运算(Opening)

  • 定义:先腐蚀,再膨胀。

  • 公式opening = dilation(erode(image))

  • 作用:去除小的噪点,同时保持大物体的形状基本不变。

适合去除图像中的小亮点噪声。

# 开运算
opening = cv.morphologyEx(car, cv.MORPH_OPEN, kernel)
cv.imshow("opening", opening)

(2)闭运算(Closing)

  • 定义:先膨胀,再腐蚀。

  • 公式closing = erode(dilate(image))

  • 作用:填充物体内部的小孔或裂缝,连接邻近的物体。

适合去除小的暗点(空洞)。

# 闭运算
closing = cv.morphologyEx(car, cv.MORPH_CLOSE, kernel)
cv.imshow("closing", closing)

(3)形态学梯度(Morphological Gradient)

  • 定义:膨胀图减去腐蚀图。

  • 公式gradient = dilate(image) - erode(image)

  • 作用:得到物体的轮廓(边缘)。

# 形态学梯度 膨胀与腐蚀的差
gradient = cv.morphologyEx(car, cv.MORPH_GRADIENT, kernel)
cv.imshow("gradient", gradient)

(4)顶帽(Top Hat)

  • 定义:原图减去开运算结果。

  • 公式tophat = image - opening

  • 作用:突出比结构元素小的亮区域(如细小文字、亮点)。

# 顶帽运算 原图与开运算的差
tophat = cv.morphologyEx(car, cv.MORPH_TOPHAT, kernel)
cv.imshow("tophat", tophat)

(5)黑帽(Black Hat)

  • 定义:闭运算结果减去原图。

  • 公式blackhat = closing - image

  • 作用:突出比结构元素小的暗区域(如小孔、暗斑)。

# 黑帽运算 闭运算与原图之间的差
blackhat = cv.morphologyEx(car, cv.MORPH_BLACKHAT, kernel)
cv.imshow("blackhat", blackhat)
cv.imshow("car", car)


六、总结

        撒花!!以上内容就是我们opencv剩下的全部内容了,掌握了这些我们就已经可以实现大部分简单的图像处理操作了!有任何问题都可以在评论区留下评论或者私信作者哦!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值