一、引言:从基础到进阶
在前几篇博客中,我们已经学习了OpenCV的众多基础操作——图像读写、阈值分割、形态学处理、轮廓检测、模板匹配等。今天,我们将更进一步,探索四个非常实用的进阶技术:图像金字塔、直方图分析、透视变换和图像旋转。
这些技术在实际项目中应用广泛:图像金字塔常用于多尺度特征检测;直方图是图像增强和颜色分析的核心工具;透视变换可以校正倾斜的文档或拍摄的照片;图像旋转则是图像预处理的基本操作。本篇博客将结合四个完整的代码示例,带领大家逐一掌握这些技术。
二、图像金字塔(Image Pyramid)
图像金字塔是计算机视觉中一种多尺度表示方法,它将图像分解成一系列不同分辨率(尺度)的图像,形成一个“金字塔”形状。OpenCV提供了两种金字塔:高斯金字塔和拉普拉斯金字塔。
2.1 高斯金字塔——向下采样与向上采样
高斯金字塔通过连续的下采样(缩小)和上采样(放大)来创建不同尺度的图像。
-
向下采样(pyrDown):图像尺寸缩小为原来的1/4(宽度和高度各减半)。它先对图像进行高斯卷积,再删除偶数行和偶数列。
-
向上采样(pyrUp):图像尺寸放大为原来的4倍(宽度和高度各加倍)。它先插入零行零列,再进行高斯卷积。
import cv2
import numpy as np
view = cv2.imread('view.png', cv2.IMREAD_GRAYSCALE)
view = cv2.resize(view, (500, 500))
cv2.imshow('view', view)
# 向下采样(缩小)
view_down1 = cv2.pyrDown(view) # 尺寸变为250×250
view_down2 = cv2.pyrDown(view_down1) # 尺寸变为125×125
cv2.imshow('view_down1', view_down1)
cv2.imshow('view_down2', view_down2)
# 向上采样(放大)
view_up1 = cv2.pyrUp(view) # 尺寸变为1000×1000
view_up2 = cv2.pyrUp(view_down1) # 从250×250放大到500×500
cv2.imshow('view_up1', view_up1)
cv2.imshow('view_up2', view_up2)
注意:向下采样会丢失信息,即使对下采样后的图像再上采样,也无法恢复原图,图像会变得模糊。这就像把一张高清图片缩小后再放大,细节永久丢失了。
2.2 拉普拉斯金字塔——残差图像
拉普拉斯金字塔并不是通过直接操作图像得到的,而是通过原图与上采样后图像的差值来构建。它记录了每一层下采样丢失的细节信息,常被用于图像压缩和重建。
# 对下采样后的图像进行上采样
view_down1_up = cv2.pyrUp(view_down1)
view_down2_up = cv2.pyrUp(view_down2)
# 计算拉普拉斯残差
L0 = view – view_down1_up # 原图与第一次上采样后的差值
L1 = view_down1 – view_down2_up
# 可以从残差中恢复原图
fuyuan = view_down1_up + L0
cv2.imshow('L0', L0)
cv2.imshow('L1', L1)
cv2.imshow('fuyuan', fuyuan)
拉普拉斯金字塔的每一层都是一张“残差图”,它包含了该尺度下丢失的边缘和细节信息。利用这些残差,我们可以从低分辨率图像逐步重建高分辨率图像。
三、直方图——图像数据的统计之美
直方图是图像处理中最基础、最重要的工具之一。它统计了图像中每个像素值出现的次数,直观地反映出图像的亮度分布、对比度和饱和度等信息。
3.1 直方图的计算与绘制
OpenCV提供了cv2.calcHist()函数来计算直方图。结合Matplotlib,我们可以轻松绘制出直方图。
import cv2
import numpy as np
import matplotlib.pyplot as plt
phone = cv2.imread('phone.png', 0)
# 方法一:使用numpy的ravel() + matplotlib的hist()
a = phone.ravel()
plt.hist(a, bins=256)
plt.show()
# 方法二:使用calcHist()
phone_hist = cv2.calcHist([phone], [0], None, [256], [0, 256])
plt.plot(phone_hist)
plt.show()
calcHist参数详解:
[phone]:输入图像,用方括号括起来
[0]:通道索引,灰度图为0,彩色图可指定0、1、2(BGR)
None:掩膜(后面会讲)
[256]:BINS数量,即灰度级的划分区间数
[0, 256]:像素值范围
对于彩色图像,可以分别绘制三个通道的直方图:
img = cv2.imread('phone.png')
color = ('b', 'g', 'r')
for i, col in enumerate(color):
histr = cv2.calcHist([img], [i], None, [256], [0, 256])
plt.plot(histr, color=col)
plt.show()
3.2 掩膜(Mask)——只统计感兴趣区域
有时我们只想统计图像中某个区域的直方图,这时可以使用掩膜。掩膜是一个与图像大小相同的二值图像,白色区域(像素值为255)表示感兴趣的区域,黑色区域(0)表示忽略。
phone = cv2.imread('phone.png', 0)
mask = np.zeros(phone.shape[:2], np.uint8)
mask[50:350, 100:470] = 255 # 定义一个矩形区域
# 使用掩膜
Phone_msk = cv2.bitwise_and(phone, phone, mask=mask)
phone_hist_mask = cv2.calcHist([phone], [0], mask, [256], [0, 256])
plt.plot(phone_hist_mask)
plt.show()
cv2.bitwise_and()将原图与掩膜进行按位与运算,掩膜为0的区域被置黑,为255的区域保留原值。
3.3 直方图均衡化——增强对比度
直方图均衡化是一种常用的图像增强技术,它将原始图像的灰度直方图从集中分布拉伸为均匀分布,从而增加对比度,使图像更清晰。
beauty = cv2.imread('beauty.jpg', 0)
beauty_equalize = cv2.equalizeHist(beauty)
# 对比原图与均衡化后的效果
res = np.hstack((beauty, beauty_equalize))
cv2.imshow('beauty', res)
均衡化后的图像直方图变得更加平坦,像素值分布在整个灰度范围内,图像细节更突出。这对于欠曝或过曝的照片特别有用。
四、透视变换——矫正倾斜的图片
在扫描文档或拍摄照片时,经常会出现视角倾斜的问题。透视变换可以将倾斜的平面“拉正”,就像从正上方拍摄一样。
4.1 获取四个顶点
透视变换的核心是找到原图中矩形的四个顶点,然后映射到目标矩形的四个顶点。首先需要检测图像中的最大轮廓(假设它是我们要矫正的目标)。
import cv2
import numpy as np
def resize(image, width=None, height=None):
# 省略实现…
image = cv2.imread("shopping.jpg")
ratio = image.shape[0] / 500.0
orig = image.copy()
image = resize(orig, height=500)
# 灰度化 + 二值化
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
edged = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
# 检测轮廓
cnts = cv2.findContours(edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)[-2]
# 按面积排序,取最大轮廓
screenCnt = sorted(cnts, key=cv2.contourArea, reverse=True)[0]
# 计算轮廓周长并进行多边形近似(得到顶点)
peri = cv2.arcLength(screenCnt, True)
screenCnt = cv2.approxPolyDP(screenCnt, 0.05 * peri, True)
cv2.approxPolyDP():对轮廓进行多边形逼近,0.05 * peri是逼近精度。这里得到4个顶点(如果是矩形的话)。
4.2 四点变换函数
我们自定义four_point_transform函数,将原图中的四个点映射到矩形区域。
def order_points(pts):
# 将四个点按左上、右上、右下、左下顺序排列
rect = np.zeros((4, 2), dtype="float32")
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(img, pts):
rect = order_points(pts)
(tl, tr, br, bl) = rect
# 计算目标矩形的宽度和高度
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)
warped = cv2.warpPerspective(img, M, (maxWidth, maxHeight))
return warped
order_points:根据点的坐标特征(求和与求差)将四个点排序。
cv2.getPerspectiveTransform:计算透视变换矩阵。
cv2.warpPerspective:应用透视变换。
4.3 应用变换并处理
# 应用透视变换(注意:原图坐标要乘以缩放比例ratio)
warped = four_point_transform(orig, screenCnt.reshape(4, 2) * ratio)
# 转灰度并二值化,再腐蚀去掉细小噪点
warped = cv2.cvtColor(warped, cv2.COLOR_BGR2GRAY)
ref = cv2.threshold(warped, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]
kernel = np.ones((2, 2), np.uint8)
ref_new = cv2.erode(ref, kernel, iterations=1)
# 旋转90度(若需要)
rotated = cv2.rotate(ref_new, cv2.ROTATE_90_COUNTERCLOCKWISE)
cv2.imshow('result', rotated)
通过透视变换,我们成功地将一张倾斜拍摄的购物小票“拉直”,变成了一张正面视图。
五、图像旋转——两种常用方法
图像旋转是图像预处理中非常常见的操作。OpenCV提供了多种旋转方式。
5.1 使用numpy的rot90
np.rot90可以对图像进行90度的整数倍旋转,简单高效。
img = cv2.imread("kele.png")
rotate_image1 = np.rot90(img, k=-1) # k=-1 顺时针90度
rotate_image2 = np.rot90(img, k=1) # k=1 逆时针90度
5.2 使用cv2.rotate
OpenCV自带的cv2.rotate函数更加直观,支持三种旋转方式:
rotate_image3 = cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE) # 顺时针90度
rotate_image4 = cv2.rotate(img, cv2.ROTATE_90_COUNTERCLOCKWISE) # 逆时针90度
rotate_image5 = cv2.rotate(img, cv2.ROTATE_180) # 180度
这两种方法都只支持90度的整数倍旋转。如果需要任意角度旋转,可以使用cv2.warpAffine配合旋转矩阵,但那属于仿射变换的范畴。
六、综合总结
| 高斯金字塔 | cv2.pyrDown(), cv2.pyrUp() | 多尺度特征检测、图像缩放 |
| 拉普拉斯金字塔 | 原图 – 上采样图 | 图像压缩、融合、重建 |
| 直方图计算 | cv2.calcHist(), plt.hist() | 图像统计、颜色分析 |
| 直方图均衡化 | cv2.equalizeHist() | 增强对比度、改善光照 |
| 掩膜 | cv2.bitwise_and() | 只处理感兴趣区域 |
| 透视变换 | cv2.getPerspectiveTransform(), cv2.warpPerspective() | 文档矫正、视角变换 |
| 图像旋转 | np.rot90(), cv2.rotate() | 图像方向调整 |




