OpenCV-Python Tutorial : A Candy from Official Main Page(二)

发布于:2025-07-06 ⋅ 阅读:(14) ⋅ 点赞:(0)

三、image processing

3.8图像金字塔-cv.pyrDown、cv.pyrUp

图像金字塔有两种,一种是高斯金字塔,一种是拉普拉斯金字塔。

3.8.1高斯金字塔

高斯金字塔是一种多分辨率图像表示方法,通过对图像逐层降采样(缩小)生成一系列分辨率递减的图像集合。其核心思想是模拟人眼观察物体时从粗到细的感知过程,广泛应用于图像缩放、特征提取、图像融合等任务。

3.8.2拉普拉斯金字塔

拉普拉斯金字塔是一种基于高斯金字塔的多尺度图像表示方法,通过差分操作提取不同分辨率下的高频细节信息,常用于图像融合、压缩和增强等任务。其核心思想是存储高斯金字塔相邻层之间的丢失信息,从而实现图像的无损重建。

cv.pyrDown()对图像进行高斯平滑(模糊)并降采样(缩小到 1/4 大小,宽高各减半)

dst = cv.pyrDown(src[, dst[, dstsize[, borderType]]])
src:输入图像(单通道或多通道)。
dst(可选):输出图像。
dstsize(可选):目标尺寸,默认自动计算为 (src.cols/2, src.rows/2)。
borderType(可选):边界填充方式(默认 cv.BORDER_DEFAULT)。

cv.pyrUp()对图像进行上采样(放大到 2 倍宽高)并高斯平滑,但不会恢复降采样时丢失的信息

dst = cv.pyrUp(src[, dst[, dstsize[, borderType]]])
参数:
(同 cv.pyrDown,但 dstsize 默认为 (src.cols*2, src.rows*2))

cv.subtract()计算两幅图像的差值(逐像素相减),用于提取拉普拉斯金字塔层。

dst = cv.subtract(src1, src2[, dst[, mask[, dtype]]])
src1, src2:输入图像(需同尺寸、同通道数)。
mask(可选):掩膜,仅处理指定区域。
dtype(可选):输出数据类型。

demo1:对比原图与cv.pyrdown下采样与cv.pyrup上采样的结果

import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt

img = cv.imread('image1.png')
img = cv.cvtColor(img, cv.COLOR_BGR2RGB)
assert img is not None, "file could not be read, check with os.path.exists()"
lower_reso = cv.pyrDown(img)
higher_reso = cv.pyrUp(lower_reso)

titles = ['img', 'lower_reso', 'higher_reso']
images = [img, lower_reso, higher_reso]

for i in range(3):
    plt.subplot(1, 3, i+1)
    plt.imshow(images[i])
    plt.title(titles[i])
    plt.xticks([])
    plt.yticks([])
plt.show()

demo2:可视化图像金字塔

import cv2
import numpy as np
from matplotlib import pyplot as plt

def build_gaussian_pyramid(img, levels):
    g = img.copy()
    # 确保初始尺寸是偶数
    if g.shape[0] % 2 != 0 or g.shape[1] % 2 != 0:
        g = cv2.resize(g, (g.shape[1] // 2 * 2, g.shape[0] // 2 * 2))
    pyramid = [g]
    for _ in range(levels):
        g = cv2.pyrDown(g)
        pyramid.append(g)
    return pyramid

def build_laplacian_pyramid(gaussian_pyramid):
    pyramid = [gaussian_pyramid[-1]]
    for i in range(len(gaussian_pyramid)-1, 0, -1):
        up_img = cv2.pyrUp(gaussian_pyramid[i])
        # 确保尺寸匹配
        h, w = gaussian_pyramid[i-1].shape[:2]
        up_img = cv2.resize(up_img, (w, h))
        err = cv2.subtract(gaussian_pyramid[i-1], up_img)
        pyramid.append(err)
    return pyramid[::-1]

# 读取图像并翻转
img1 = cv2.imread('image3.png')
img1 = img1[:,:1200]
cv2.imshow("",img1)
cv2.waitKey(0)
cv2.destroyAllWindows()
img2 = cv2.flip(img1, 1)

# 构建金字塔
gp1 = build_gaussian_pyramid(img1, 5)  # 注意:6层高斯金字塔实际生成7个图像(含原图)
gp2 = build_gaussian_pyramid(img2, 5)
lp1 = build_laplacian_pyramid(gp1)
lp2 = build_laplacian_pyramid(gp2)

# 绘制子图
plt.figure(figsize=(15, 10))
for i in range(6):
    plt.subplot(4, 6, i+1)
    plt.imshow(cv2.cvtColor(gp1[i], cv2.COLOR_BGR2RGB))
    plt.title(f'GP1 L{i}')
    plt.axis('off')
for i in range(6):
    plt.subplot(4, 6, i+7)
    plt.imshow(cv2.cvtColor(gp2[i], cv2.COLOR_BGR2RGB))
    plt.title(f'GP2 L{i}')
    plt.axis('off')
for i in range(6):
    plt.subplot(4, 6, i+13)
    plt.imshow(cv2.cvtColor(lp1[i], cv2.COLOR_BGR2RGB))
    plt.title(f'LP1 L{i}')
    plt.axis('off')
for i in range(6):
    plt.subplot(4, 6, i+19)
    plt.imshow(cv2.cvtColor(lp2[i], cv2.COLOR_BGR2RGB))
    plt.title(f'LP2 L{i}')
    plt.axis('off')
plt.tight_layout()
plt.show()

如图,img1和img2的高斯金字塔和拉普拉斯金字塔,以img1为例:

img1的高斯金字塔是第一行,拉普拉斯金字塔是第三行,然后高斯金字塔和拉普拉斯金字塔的最顶层L5是一样的,拉普拉斯的L4 = 高斯的L4 - cv.pyrup(高斯的L5)

3.9 轮廓(contours in opencv)

3.9.1基础介绍-cv.findContours()、cv.drawContours() 

轮廓可以简单地解释为连接所有具有相同颜色或强度的连续点(沿边界)的曲线。轮廓是用于形状分析、物体检测与识别的有用工具。

为提高准确性,请使用二值图像。因此在查找轮廓前,应先应用阈值处理或Canny边缘检测
从OpenCV 3.2版本开始,findContours()函数不再修改源图像。
在OpenCV中,查找轮廓就像从黑色背景中寻找白色物体。所以请记住:待检测物体应为白色,背景应为黑色。

cv.findContours() 是 OpenCV 中用于检测图像轮廓的函数

contours, hierarchy = cv.findContours(image, mode, method[, contours[, hierarchy[, offset]]])
参数说明
image – 输入图像(必须为 二值图像,通常需要先进行阈值处理或边缘检测)。
注意:OpenCV 3.2 之后,该函数不会修改原始图像。
目标物体应为 白色(前景),背景应为 黑色。
mode – 轮廓检索模式,决定如何提取轮廓:
cv.RETR_EXTERNAL – 只检测最外层轮廓
cv.RETR_LIST – 检测所有轮廓,不建立层级关系
cv.RETR_TREE – 检测所有轮廓,并建立完整的层级结构(嵌套轮廓)
method – 轮廓近似方法:
cv.CHAIN_APPROX_NONE – 存储所有轮廓点(不压缩)
cv.CHAIN_APPROX_SIMPLE – 压缩冗余点(如直线只保留端点)

返回值
contours – 检测到的轮廓列表,每个轮廓是一个点集(np.array)。
hierarchy – 轮廓的层级信息(用于嵌套轮廓分析)。

绘制轮廓

cv.drawContours() 函数用于绘制轮廓,也可以用来绘制任何已知边界点的形状

cv.drawContours(
    image,          # 要绘制轮廓的目标图像(通常先复制原图)
    contours,       # 轮廓列表(Python list格式)
    contourIdx,     # 要绘制的轮廓索引(-1表示绘制所有轮廓)
    color,          # 轮廓颜色(BGR格式,如 (0, 255, 0) 代表绿色)
    thickness,      # 轮廓线粗细(像素值,-1表示填充轮廓内部)
    [lineType],     # (可选)线型(如 cv.LINE_AA抗锯齿)
    [hierarchy],    # (可选)层级关系(配合复杂轮廓结构使用)
    [maxLevel],     # (可选)最大绘制层级(默认全部绘制)
    [offset]        # (可选)轮廓点坐标偏移量
)
关键参数
image
目标图像:必须是 彩色图像(如果是灰度图需先转BGR,否则无法显示颜色)。
contours
轮廓列表:直接传入 cv.findContours() 返回的 contours 列表。
注意:即使只画一个轮廓,也要以列表形式传入,如 [contour]。
contourIdx
轮廓索引:
-1:绘制所有轮廓。
0:只绘制第一个轮廓。
1:只绘制第二个轮廓,依此类推。
color 和 thickness
颜色:BGR格式,例如 (255, 0, 0) 表示蓝色。
线粗:
2:2像素宽的线条。
-1:填充轮廓内部(如绘制实心形状)。

demo画出图片轮廓

from pickle import NONE
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt

img = cv.imread("image1.png")
assert img is not None, "img is not exists"
# img = cv.cvtColor(img, cv.COLOR_BGR2RGB)
img_gray = cv.cvtColor(img, cv.COLOR_RGB2GRAY)
img_edge = cv.Canny(img_gray,100,200)

contours, hierarchy = cv.findContours(img_edge, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
cv.drawContours(img, contours, -1, (0,125,0), 2)
cv.imshow('All Contours', img)
cv.waitKey(0)

轮廓实际上是一层包含一层的numpy数组

3.9.2Contour Features基础特征提取(轮廓分析基础

  • 常用特征(面积、周长、质心、边界框等)
  • 轮廓近似(多边形拟合)
  • 凸包(Convex Hull)
  • 几何形状检查(矩形度、圆形度等)
1.矩(Moments)-cv.moments(contour)
moments = cv.moments(contour)
cx = int(moments["m10"] / moments["m00"])  # 质心x坐标
cy = int(moments["m01"] / moments["m00"])  # 质心y坐标
area = moments["m00"]                      # 轮廓面积
2.面积与周长-cv.contourArea(contour)、cv.arcLength(contour, closed=True)
area = cv.contourArea(contour)              # 轮廓面积
perimeter = cv.arcLength(contour, closed=True)  # 周长(closed表示轮廓是否闭合)
3.边框cv.boundingRect(contour)、cv.rectangle、cv.minAreaRect(contour)、cv.boxPoints

矩形

x, y, w, h = cv.boundingRect(contour)      # 矩形框坐标和宽高
cv.rectangle(img, (x,y), (x+w,y+h), (0,255,0), 2)

旋转矩形

rect = cv.minAreaRect(contour)             # 返回旋转矩形(中心点、宽高、旋转角度)
box = cv.boxPoints(rect)                   # 获取四个顶点坐标
box = np.int0(box)                         # 转为整数
cv.drawContours(img, [box], 0, (0,0,255), 2)
4.轮廓近似-cv.arcLength、cv.approxPolyDP

用更少的点简化轮廓(减少噪声或压缩数据)

epsilon = 0.01 * cv.arcLength(contour, True)  # 近似精度(1%周长)
approx = cv.approxPolyDP(contour, epsilon, True)  # 多边形近似
应用:检测图像中的多边形(如识别四边形、三角形)。
5.凸包(Convex Hull)-cv.convexHull

计算轮廓的凸包(包裹轮廓的最小凸多边形)

hull = cv.convexHull(contour)              # 计算凸包
cv.drawContours(img, [hull], 0, (255,0,0), 2)
应用:检测凸性缺陷或物体形状分析。
6.几何形状检查-cv.isContourConvex、cv.minEnclosingCircle

(1) 凸性检测

is_convex = cv.isContourConvex(contour)      # 返回True/False

(2) 矩形度与圆形度

矩形度:轮廓面积与最小外接矩形面积的比值。

rect_area = w * h
extent = area / rect_area                   # 接近1则为矩形

圆形度:衡量轮廓接近圆形的程度。

(x,y), radius = cv.minEnclosingCircle(contour)
circle_area = np.pi * radius**2
circularity = area / circle_area            # 接近1则为圆形
7 .关键应用场景
  • 目标检测:通过边界框定位物体。
  • 形状分类:利用面积、周长、Hu矩区分不同形状。
  • 工业检测:检查零件是否符合几何规格(如凸性缺陷)。
  • OCR预处理:用多边形近似提取文字区域。

3.9.3提取高级几何属性

包括:

  • 纵横比(Aspect Ratio)
  • 轮廓面积与凸包面积比(Solidity)
  • 等效直径(Equivalent Diameter)
  • 方向(Orientation)
  • 掩膜与像素点(Mask & Pixel Points)
  • 最大值/最小值及其位置
  • 平均颜色/强度
  • 极点(Extreme Points)
1.纵横比

边界矩形宽高比(width/height

x, y, w, h = cv.boundingRect(contour)
aspect_ratio = float(w) / h
应用:区分细长物体(如笔)与方形物体(如硬币)。
2.轮廓面积与凸包面积比(Solidity)

轮廓面积(contourArea)与凸包面积(convexHull)的比值。

hull = cv.convexHull(contour)
hull_area = cv.contourArea(hull)
solidity = float(area) / hull_area  # area为轮廓面积
应用:检测凹性缺陷(如零件损伤)。
3.等效直径(Equivalent Diameter)

与轮廓面积相同的圆的直径。

equi_diameter = np.sqrt(4 * area / np.pi)
4.方向(Orientation)

轮廓的主轴角度(通过拟合椭圆获取)。

(x, y), (MA, ma), angle = cv.fitEllipse(contour)  # MA:长轴, ma:短轴, angle:角度
5.生成轮廓掩膜(Mask)
mask = np.zeros(gray.shape, np.uint8)
cv.drawContours(mask, [contour], 0, 255, -1)  # -1表示填充内部
pixel_points = np.transpose(np.nonzero(mask))  # 获取轮廓内所有像素坐标
6.最大值/最小值及位置

在ROI内找到最亮/最暗点。

min_val, max_val, min_loc, max_loc = cv.minMaxLoc(gray, mask=mask)
7.平均颜色/强度
mean_val = cv.mean(img, mask=mask)  # 返回BGR通道均值
8.极点(Extreme Points)

轮廓的最上、最下、最左、最右点。

leftmost = tuple(contour[contour[:, :, 0].argmin()][0])
rightmost = tuple(contour[contour[:, :, 0].argmax()][0])
topmost = tuple(contour[contour[:, :, 1].argmin()][0])
bottommost = tuple(contour[contour[:, :, 1].argmax()][0])
应用:物体姿态估计或裁剪感兴趣区域。
9.属性速查表
属性	函数/公式	应用场景
纵横比	w / h	形状分类
Solidity	area / hull_area	凹性检测
等效直径	sqrt(4*area/π)	尺寸筛选
方向	cv.fitEllipse()	物体朝向分析
极值点	argmin() / argmax()	边界定位
平均颜色	cv.mean()	区域色彩分析

3.9.4 凸性缺陷(Convexity Defects)-cv.convexHull、cv.convexityDefects

OpenCV: Contours : More Functions

  • 凸包(Convex Hull):包裹轮廓的最小凸多边形(无凹陷)。

  • 凸性缺陷:轮廓与凸包之间的偏离区域(即轮廓的凹陷部分)。

hull = cv.convexHull(contour, returnPoints=False)  # 返回凸包的索引而非坐标点
defects = cv.convexityDefects(contour, hull)       # 计算凸性缺陷

returnPoints=False:要求 convexHull 返回凸包点在原始轮廓中的索引(而非坐标),这是计算缺陷的必要条件。
defects 返回值:一个形状为 (N,1,4) 的 NumPy 数组,每行包含 4 个值:
[start_index, end_index, farthest_index, distance]
start_point:缺陷起始点(轮廓索引)。
end_point:缺陷结束点(轮廓索引)。
farthest_point:凹陷最深处点(轮廓索引)。
distance:最远点到凸包的近似距离。

示例

import cv2 as cv
import numpy as np

img = cv.imread('star.jpg')
assert img is not None, "file could not be read, check with os.path.exists()"
img_gray = cv.cvtColor(img,cv.COLOR_BGR2GRAY)
ret,thresh = cv.threshold(img_gray, 127, 255,0)
contours,hierarchy = cv.findContours(thresh,2,1)
cnt = contours[0]

hull = cv.convexHull(cnt,returnPoints = False)
defects = cv.convexityDefects(cnt,hull)

for i in range(defects.shape[0]):
    s,e,f,d = defects[i,0]
    start = tuple(cnt[s][0])
    end = tuple(cnt[e][0])
    far = tuple(cnt[f][0])
    cv.line(img,start,end,[0,255,0],2)
    cv.circle(img,far,5,[0,0,255],-1)

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

应用

手势识别

通过手指间的凸性缺陷数量判断伸出的手指数。

工业检测

检测零件表面的凹痕或缺陷(如齿轮齿槽的完整性)。

医学图像分析

识别器官轮廓的异常凹陷(如心脏瓣膜形态)。

3.9.5匹配形状-cv.matchShapes()

OpenCV附带一个函数cv.matchShapes(),它使我们能够比较两个形状或两个轮廓,并返回一个显示相似性的指标。结果越低,匹配度越好。它是根据hu-moment值计算的。

import cv2 as cv
import numpy as np

img1 = cv.imread('star.jpg', cv.IMREAD_GRAYSCALE)
img2 = cv.imread('star2.jpg', cv.IMREAD_GRAYSCALE)
assert img1 is not None, "file could not be read, check with os.path.exists()"
assert img2 is not None, "file could not be read, check with os.path.exists()"

ret, thresh = cv.threshold(img1, 127, 255,0)
ret, thresh2 = cv.threshold(img2, 127, 255,0)
contours,hierarchy = cv.findContours(thresh,2,1)
cnt1 = contours[0]
contours,hierarchy = cv.findContours(thresh2,2,1)
cnt2 = contours[0]

ret = cv.matchShapes(cnt1,cnt2,1,0.0)
print( ret )

3.9.6轮廓层级(Hierarchy)

主要内容包括:

1.轮廓层级(Hierarchy) 的本质与数据结构

2. 4 种轮廓检索模式(RETR_LISTRETR_EXTERNALRETR_CCOMPRETR_TREE)的区别

3.举例如何利用层级关系分析嵌套轮廓(如孔洞检测)

3.10Histograms

3.10.1直方图介绍-cv.calcHist

直方图是什么呢?您可以将直方图视为图形或图,这为您提供了有关图像强度分布的总体概念。这是一个在X轴上包含像素值(范围为0到255,并非总是)和Y轴图像中相应像素数的图。

这只是理解图像的另一种方式。通过查看图像的直方图,您可以直观地了解该图像的对比度、亮度、强度分布等。如今,几乎所有的图像处理工具都提供了直方图上的功能。下面是来自Cambridge in Color网站的图片,我建议您访问该网站了解更多详情。

你可以看到图像和它的直方图。(记住,这个直方图是为灰度图像绘制的,而不是彩色图像)。直方图的左侧区域显示图像中较暗的像素数量,右侧区域显示较亮的像素数量。从直方图中,你可以看到黑暗区域比明亮的区域更多,中间色调的数量(中等范围的像素值,例如127左右)非常少。

hist = cv.calcHist(
    images,    # 输入图像(列表形式)
    channels,  # 需统计的通道索引(列表)
    mask,      # 掩膜(None 表示全图)
    histSize,  # BIN 数量(列表)
    ranges,    # 像素值范围
    [hist[, accumulate]]  # 可选输出及累积标志
)

参数详解

参数名	数据类型	说明
images	List[ndarray]	源图像列表(即使单图像也需用 [img] 包裹)。支持 uint8 或 float32 类型。
channels	List[int]	通道索引:灰度图用 [0],BGR 彩色图按 [0](B)、[1](G)、[2](R) 选择。
mask	ndarray or None	掩膜图像:与源图同尺寸,None 表示统计全图,非零区域限定统计范围。
histSize	List[int]	BIN 数量:如 [256] 表示将 0~255 分为 256 个区间。
ranges	List[float]	像素值范围:通常为 [0, 256](注意上限 256 不包含)。

cv.calcHist() 默认会返回一个 直方图数组(NumPy 数组),即使不显式接收返回值,函数内部仍会计算直方图。但若不保存返回值,计算结果将丢失。

demo:画出直方图

import cv2 as cv
import numpy as numpy
from matplotlib import pyplot as plt

img = cv.imread("image4.png")
assert img is not None, "file could not be read, check with os.path.exists()"
img = cv.cvtColor(img,cv.COLOR_BGR2RGB)
hist = cv.calcHist([img],[1],None,[256],[0,256])

titles = ['img','hist']
imgs = [img,hist]
for i in range(2):
    if i == 0:
        plt.subplot(1,2,i+1)
        plt.title(titles[i])
        plt.imshow(imgs[i])
    if i == 1:
        plt.subplot(1,2,i+1)
        plt.title(titles[i])
        plt.plot(hist)
plt.show()

3.10.2直方图均衡化-

1.普通均衡化

直方图均衡化(Histogram Equalization)的核心概念

当图像的像素值仅局限于某一特定范围时(如过亮图像的所有像素集中在高值区域),图像的对比度会显得较低。直方图均衡化通过将像素值分布拉伸到整个范围(通常是 0~255),使得亮区、暗区和中间调的细节都能得到增强,从而显著提升图像的视觉对比度。

以下是关于 OpenCV 中的直方图均衡化函数 cv.equalizeHist() 的详细说明和使用方法:

equalized_img = cv.equalizeHist(src)
输入参数:
src:8 位单通道灰度图像(dtype=uint8)。
输出:
equalized_img:直方图均衡化后的图像(同尺寸、同类型)。


网站公告

今日签到

点亮在社区的每一天
去签到