8.4 形状特征
形状特征是用于描述图像或物体形状的特征,它们可以用于图像分析、目标检测、图像识别和计算机视觉等领域。形状特征提取的目标是从图像中提取出能够描述物体形状的信息,以便对物体进行识别、分类或测量。常用的形状特征提取方法有:边界描述子、预处理后的轮廓特征、模型拟合方法、形状上的变换。
8.4.1 边界描述子
边界描述子是一种常用的形状特征提取方法,它通过对物体的边界进行分析和描述,从中提取出能够描述形状的特征。下面我将详细介绍边界描述子的原理,并给出两个实用且稍微复杂的例子。边界描述子的实现过程如下所示:
- 获取物体的边界:首先需要获取物体的边界,可以通过边缘检测算法(如Canny边缘检测)或轮廓检测算法(如OpenCV中的findContours函数)来获得物体的边界。
- 归一化边界:对于获取的边界点集,将其进行归一化处理,使得边界的起点为坐标原点,同时进行平移和缩放操作,使得边界点分布在一个固定的区域内。
- 提取边界描述子:对归一化后的边界点集进行特征提取。常见的边界描述子包括:
- 傅里叶描述子(Fourier Descriptors):将归一化的边界点集进行傅里叶变换,提取频域特征。傅里叶描述子可以用于对边界形状的旋转、缩放和平移具有不变性。
- 形状上下文(Shape Context):通过计算边界点与其他点之间的相对位置关系,构建形状上下文描述子。形状上下文描述子可以用于对边界形状的旋转和尺度具有不变性。
- 应用边界描述子:提取的边界描述子可以用于形状匹配、物体识别和分类等任务。
在现实应用中,可以通过如下两种方法实现边界描述子在形状特征提取中的应用。
(1)使用傅里叶描述子进行形状匹配
假设我们有一组图像中的物体边界,我们想要在新的图像中识别相似形状的物体,那么:
- 对于每个图像的物体边界,应用边缘检测算法获取边界点集。
- 对边界点集进行归一化处理。
- 对归一化后的边界点集计算傅里叶描述子。
- 在新的图像中,提取物体边界并进行归一化处理。
- 对新图像的归一化边界点集计算傅里叶描述子。
- 对比新图像的傅里叶描述子与之前图像的傅里叶描述子,使用相似度度量方法(如欧氏距离)进行形状匹配。
例如下面是一个使用Python语言实现傅里叶描述子形状匹配的简单例子。
实例8-8:实现傅里叶描述子形状匹配
源码路径:daima\8\foliye.py
import cv2
import numpy as np
def calculate_fourier_descriptor(image):
# 提取轮廓
contours, _ = cv2.findContours(image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
contour = contours[0] # 假设只有一个轮廓
# 计算傅里叶描述子
contour_complex = np.empty(contour.shape[:-1], dtype=complex)
contour_complex.real = contour[:, 0, 0]
contour_complex.imag = contour[:, 0, 1]
fourier_descriptor = np.fft.fft(contour_complex)
return fourier_descriptor
# 读取数据库图像和查询图像
database_image = cv2.imread('database.jpg', cv2.IMREAD_GRAYSCALE)
query_image = cv2.imread('query.jpg', cv2.IMREAD_GRAYSCALE)
# 预处理图像(二值化等)
_, database_image = cv2.threshold(database_image, 127, 255, cv2.THRESH_BINARY)
_, query_image = cv2.threshold(query_image, 127, 255, cv2.THRESH_BINARY)
# 计算数据库图像和查询图像的傅里叶描述子
database_descriptor = calculate_fourier_descriptor(database_image)
query_descriptor = calculate_fourier_descriptor(query_image)
# 计算傅里叶描述子之间的距离
distance = np.linalg.norm(database_descriptor - query_descriptor)
print("Distance:", distance)
在上述代码中,请确保已准备好两幅图像作为数据库图像和查询图像,并将其命名为 database.jpg 和 query.jpg。此代码将计算数据库图像和查询图像的傅里叶描述子,并计算描述子之间的欧氏距离作为形状匹配的度量。请注意,此示例仍然假设图像中只有一个轮廓,您可能需要根据实际情况进行调整。
(2)使用形状上下文描述子进行手势识别
假设我们有一组手势的图像,我们想要对新的手势图像进行识别,那么:
- 对于每个手势图像,应用边缘检测算法获取边界点集。
- 对边界点集进行归一化处理。
- 对归一化后的边界点集计算形状上下文描述子。
- 在新的手势图像中,提取边界并进行归一化处理。
- 对新图像的归一化边界点集计算形状上下文描述子。
- 对比新图像的形状上下文描述子与之前手势图像的描述子,使用相似度度量方法(如相关系数)进行手势识别。
例如下面是一个使用HOG特征和支持向量机(Support Vector Machine, SVM)实现手势识别的例子。
实例8-9:使用HOG特征和支持向量机实现手势识别
源码路径:daima\8\xing.py
import cv2
import numpy as np
from sklearn.svm import SVC
from skimage.feature import hog
from skimage import data, exposure
# 加载手势图像数据集
gesture_images = []
gesture_labels = []
for i in range(1, 6):
image = cv2.imread(f'gesture_{i}.jpg', cv2.IMREAD_GRAYSCALE)
gesture_images.append(image)
gesture_labels.append(i)
# 提取手势图像的HOG特征
gesture_hogs = []
for image in gesture_images:
# 计算HOG特征
hog_features, hog_image = hog(image, orientations=9, pixels_per_cell=(8, 8),
cells_per_block=(2, 2), visualize=True)
# 对HOG图像进行直方图均衡化,增强可视化效果
hog_image = exposure.rescale_intensity(hog_image, in_range=(0, 10))
gesture_hogs.append(hog_features)
# 创建SVM分类器
svm = SVC()
# 使用HOG特征训练分类器
svm.fit(gesture_hogs, gesture_labels)
# 加载待识别手势图像
test_image = cv2.imread('test_gesture.jpg', cv2.IMREAD_GRAYSCALE)
# 提取待识别手势图像的HOG特征
test_hog = hog(test_image, orientations=9, pixels_per_cell=(8, 8),
cells_per_block=(2, 2))
# 使用SVM分类器进行手势识别
predicted_label = svm.predict([test_hog])
print("Predicted Label:", predicted_label[0])
在上述代码中使用库skimage的hog函数来提取手势图像的HOG特征。然后,使用这些特征和对应的标签训练了一个支持向量机(SVM)分类器。接下来,加载待识别的手势图像,提取其HOG特征,并使用SVM分类器进行手势识别,得到预测标签。请确保您准备了手势图像数据集,并将手势图像命名为 gesture_1.jpg、gesture_2.jpg 等,并将待识别的手势图像命名为 test_gesture.jpg。此代码将根据HOG特征进行手势识别,并输出预测的手势标签。
通过边界描述子的提取和匹配,我们可以实现对具有相似形状的物体或手势进行识别和分类。这些例子展示了边界描述子在形状特征提取中的应用,具有实用性、有趣性和一定的复杂性。
8.4.2 预处理后的轮廓特征
预处理后的轮廓特征是一种常用的图像特征提取方法,它通过对图像进行预处理和轮廓提取,然后分析轮廓的形状、大小、方向等特征来描述图像的形状和结构。预处理后的轮廓特征基于对物体边界进行预处理,以减少噪声和不相关信息。常用的方法有:
(1)链码(Chain Code):是一种用于形状描述和特征提取的方法,它将轮廓视为一系列相邻的像素点的有序序列,通过记录像素点之间的连接顺序来表示轮廓的形状。链码方法具有简洁、紧凑的表示形式,适用于描述闭合轮廓的形状特征。将边界转换为链码,描述连续的边界点之间的连接关系。例如下面是一个使用轮廓近似方法(cv2.approxPolyDP)实现链码特征提取的例子。
实例8-10:使用轮廓近似方法(cv2.approxPolyDP)实现链码特征提取
源码路径:daima\8\lian.py
import cv2
import math
# 读取图像并转为灰度图像
image = cv2.imread('888.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
# 二值化处理
_, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
# 查找轮廓
contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
# 获取最长轮廓
longest_contour = max(contours, key=len)
# 使用链码获取形状特征
epsilon = 0.02 * cv2.arcLength(longest_contour, True)
chain_code = cv2.approxPolyDP(longest_contour, epsilon, True)
# 计算距离直方图
max_distance = 0
distance_histogram = [0] * 16
for i in range(len(chain_code) - 1):
dx = chain_code[i + 1][0][0] - chain_code[i][0][0]
dy = chain_code[i + 1][0][1] - chain_code[i][0][1]
distance = int(math.sqrt(dx ** 2 + dy ** 2) * 15 / max_distance) if max_distance != 0 else 0
distance_histogram[distance] += 1
# 打印距离直方图
for i, count in enumerate(distance_histogram):
print(f'Distance {i}: {count}')
# 显示轮廓和特征提取结果
cv2.drawContours(image, [longest_contour], 0, (0, 0, 255), 2)
cv2.imshow('Contour', image)
cv2.waitKey(0)
cv2.destroyAllWindows()
在上述代码中,首先读取图像并将其转换为灰度图像,对灰度图像进行二值化处理,通过阈值将图像转换为黑白两色。然后使用cv2.findContours函数找到图像中的轮廓,根据轮廓的面积排序,选择最大的轮廓作为感兴趣的轮廓。接下来,使用cv2.approxPolyDP函数对感兴趣的轮廓进行多边形逼近,得到轮廓的链码表示。最后,计算链码中每个相邻点的距离,并统计距离的分布,并绘制距离分布直方图,展示轮廓的形状特征。执行效果如图8-3所示。
图8-3 执行效果
(2)形状上下文(Shape Context):是一种常用的形状特征提取方法,它通过描述物体轮廓上的点与其他点之间的关系来表示形状信息。形状上下文方法基于以下两个关键思想:
- 形状点的位置不足以完整描述形状,需要考虑其与其他点的相对位置关系。
- 形状上下文特征用来描述形状点与其他点之间的相对距离和角度。
形状上下文的提取步骤如下所示:
- 选择一组形状点(例如轮廓上的点)作为参考点集。
- 计算每个参考点与其他点之间的相对距离和角度。
- 将这些距离和角度信息组成一个向量,形成形状上下文特征。
在Python中可以使用库Mahotas库实现形状特征提取功能,它提供了一个名为mahotas.features.zernike_moments的函数,用于计算Zernike矩特征。例如下面的代码演示了这一用法。
实例8-11:使用库Mahotas库实现形状特征提取
源码路径:daima\8\shangxia.py
import cv2
import numpy as np
import mahotas.features
# 读取图像
image = cv2.imread('shape.jpg', cv2.IMREAD_GRAYSCALE)
# 二值化图像
_, threshold = cv2.threshold(image, 127, 255, cv2.THRESH_BINARY)
# 寻找轮廓
contours, _ = cv2.findContours(threshold, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
# 提取第一个轮廓
contour = contours[0]
# 计算Zernike矩特征
zernike_moments = mahotas.features.zernike_moments(contour, radius=21)
# 打印特征向量
print(zernike_moments)
在上述代码中,使用OpenCV库读取图像,然后进行二值化处理。接下来,使用OpenCV的findContours函数找到图像中的轮廓,并选择第一个轮廓。最后,使用Mahotas库的zernike_moments函数计算轮廓的Zernike矩特征。
8.4.3 模型拟合方法
模型拟合方法假设物体的形状可以由特定的数学模型来表示,然后通过对模型参数进行拟合来提取形状特征。常用的方法有:
(1)椭圆拟合(Ellipse Fitting):将物体边界拟合为椭圆,并提取椭圆参数作为形状特征。当使用椭圆拟合进行图像特征提取时,通常的步骤如下:
- 读取图像并进行预处理,例如灰度化、二值化等操作。
- 检测图像中的轮廓,可以使用图像处理库(如OpenCV)中的函数进行轮廓检测。
- 对每个轮廓应用椭圆拟合算法,以获得拟合的椭圆参数。
- 根据椭圆参数提取特征,例如椭圆的中心坐标、长轴长度、短轴长度、旋转角度等。
例如下面是一个使用库OpenCV进行椭圆拟合的例子。
实例8-13:使用库OpenCV进行椭圆拟合
源码路径:daima\8\tuo.py
import cv2
import numpy as np
# 读取图像并进行预处理
image = cv2.imread('image.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
_, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
# 轮廓检测
contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 对每个轮廓应用椭圆拟合
ellipses = []
for contour in contours:
if len(contour) >= 5:
ellipse = cv2.fitEllipse(contour)
ellipses.append(ellipse)
# 提取特征
for ellipse in ellipses:
center, axes, angle = ellipse
x, y = map(int, center)
major_axis, minor_axis = map(int, axes)
rotation_angle = int(angle)
# 在图像上绘制椭圆
cv2.ellipse(image, ellipse, (0, 255, 0), 2)
# 打印特征信息
print("椭圆中心坐标:", (x, y))
print("长轴长度:", major_axis)
print("短轴长度:", minor_axis)
print("旋转角度:", rotation_angle)
# 显示结果图像
cv2.imshow("Ellipse Fitting", image)
cv2.waitKey(0)
cv2.destroyAllWindows()
在上述代码中,首先读取图像并进行预处理,然后使用cv2.findContours函数检测图像中的轮廓。接下来,对每个轮廓应用cv2.fitEllipse函数进行椭圆拟合,获取拟合的椭圆参数。最后,根据椭圆参数提取特征并在图像上绘制椭圆。执行效果如图8-4所示。
图8-4 执行效果
请注意,该例子仅演示了基本的椭圆拟合和特征提取过程。根据实际需求,您可能需要根据拟合结果进行更复杂的特征提取和分析。
(2)直线拟合(Line Fitting):将物体边界拟合为直线段,并提取直线参数作为形状特征。当使用直线拟合进行图像特征提取时,基本实现步骤如下:
- 读取图像并进行预处理,例如灰度化、二值化等操作。
- 检测图像中的边缘,可以使用边缘检测算法(如Canny边缘检测)。
- 根据边缘图像,检测图像中的直线段。
- 对检测到的直线段应用直线拟合算法,以获得拟合的直线参数。
- 根据直线参数提取特征,例如直线的斜率、截距等。
例如下面是一个使用OpenCV库实现直线拟合的例子。
实例8-14:使用OpenCV库实现直线拟合
源码路径:daima\8\zhi.py
import cv2
import numpy as np
# 读取图像并进行预处理
image = cv2.imread('image.jpg')
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray, 50, 150)
# 检测直线段
lines = cv2.HoughLinesP(edges, 1, np.pi / 180, threshold=100, minLineLength=100, maxLineGap=10)
# 绘制检测到的直线
for line in lines:
x1, y1, x2, y2 = line[0]
cv2.line(image, (x1, y1), (x2, y2), (0, 255, 0), 2)
# 提取特征
for line in lines:
x1, y1, x2, y2 = line[0]
# 计算直线斜率和截距
slope = (y2 - y1) / (x2 - x1)
intercept = y1 - slope * x1
# 打印特征信息
print("直线斜率:", slope)
print("直线截距:", intercept)
# 显示结果图像
cv2.imshow("Line Fitting", image)
cv2.waitKey(0)
cv2.destroyAllWindows()
在上述代码中,首先读取图像并进行预处理,然后使用Canny边缘检测算法获取图像的边缘图像。接下来,使用cv2.HoughLinesP函数检测图像中的直线段。然后,根据检测到的直线段在图像上绘制直线。最后,根据直线参数提取特征,例如直线的斜率和截距。执行效果如图8-5所示。
图8-5 执行效果
8.4.4 形状上的变换
形状上的变换方法通过将形状进行变换,如缩放、旋转和平移等,来提取形状特征。常用的方法有:
(1)尺度不变特征变换(Scale-Invariant Feature Transform,SIFT):通过对形状进行尺度空间的变换,提取尺度不变的形状特征。尺度不变特征变换是一种用于图像特征提取的算法,它可以在不同尺度、旋转和光照条件下检测和描述图像中的关键点。SIFT算法具有良好的尺度不变性和旋转不变性,因此在目标识别、图像匹配和三维重建等领域得到广泛应用。SIFT算法的主要步骤如下:
- 尺度空间构建:通过使用高斯差分函数对图像进行多次平滑和差分操作,构建尺度空间。
- 关键点检测:在尺度空间中寻找极值点,作为关键点候选。
- 关键点定位:通过在尺度空间的极值点周围进行精确定位,排除低对比度和边缘响应不明显的关键点。
- 方向分配:为每个关键点分配主方向,用于后续的旋转不变性。
- 关键点描述:基于关键点周围的图像区域计算特征向量,形成关键点的描述子。
- 特征匹配:通过计算描述子之间的相似性,实现关键点的匹配。
例如下面是使用库OpenCV实现SIFT算法的例子。
实例8-15:使用库OpenCV实现SIFT算法
源码路径:daima\8\chidu.py
import cv2
# 读取图像
image = cv2.imread('image.jpg')
# 创建SIFT对象
sift = cv2.SIFT_create()
# 检测关键点和计算描述子
keypoints, descriptors = sift.detectAndCompute(image, None)
# 绘制关键点
image_with_keypoints = cv2.drawKeypoints(image, keypoints, None)
# 显示结果图像
cv2.imshow("SIFT Features", image_with_keypoints)
cv2.waitKey(0)
cv2.destroyAllWindows()
本实例展示了如何使用SIFT算法提取图像的关键点和描述子,并将关键点绘制在图像上。SIFT算法具有较强的尺度不变性和旋转不变性,因此提取的特征可以在不同尺度和旋转条件下进行匹配和识别。在上述代码中,首先读取图像,并使用cv2.SIFT_create()创建SIFT对象。然后,使用sift.detectAndCompute()方法检测关键点并计算描述子。接下来,使用cv2.drawKeypoints()函数将关键点绘制在图像上。最后,显示结果图像。执行效果如图8-6所示。
图8-6 执行效果
注意:SIFT算法是一种经典的特征提取方法,但由于其涉及到专利问题,OpenCV的最新版本中可能没有SIFT算法。您可以使用之前的版本,或者考虑其他开源实现的SIFT算法,如VLFeat库等。
(2)主成分分析(Principal Component Analysis,PCA):通过对形状进行主成分分析,提取主要的形状特征。主成分分析是一种常用的降维技术,也可以用于图像形状特征提取。PCA可以通过线性变换将原始数据转换为新的坐标系,使得数据在新坐标系下具有最大的方差。在图像形状特征提取中,PCA可以帮助我们找到最具代表性的形状特征,从而实现形状的描述和分类。使用PCA进行形状特征提取的基本步骤如下:
- 数据准备:将形状数据表示为一组特征向量或特征点集合的形式。每个特征向量或特征点表示形状的一部分。
- 特征标准化:对特征进行标准化处理,使其具有相同的尺度和范围。可以使用均值移除和缩放等方法来实现标准化。
- 协方差矩阵计算:计算特征的协方差矩阵,表示不同特征之间的相关性。
- 特征值分解:对协方差矩阵进行特征值分解,得到特征值和特征向量。
- 特征选择:选择具有最大特征值的特征向量,这些特征向量对应的特征是数据中最具代表性的形状特征。
- 特征投影:将原始数据投影到选定的特征向量上,得到新的特征表示,即形状特征。
例如下面是一个使用库Scikit-learn实现PCA形状特征提取的例子。
实例8-16:使用库Scikit-learn实现PCA形状特征提取
源码路径:daima\8\zhu.py
import numpy as np
from sklearn.decomposition import PCA
# 假设有一个形状数据集,表示为特征向量的集合
shape_data = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
[10, 11, 12]])
# 创建PCA对象
pca = PCA(n_components=2)
# 执行PCA降维
shape_features = pca.fit_transform(shape_data)
# 输出降维后的形状特征
print(shape_features)
在上述代码中,首先准备了一个形状数据集,表示为一个特征向量的集合。然后,创建了一个PCA对象,并指定要保留的主成分数量为2。接下来,使用fit_transform()方法执行PCA降维操作,并将原始形状数据集转换为降维后的形状特征表示。最后,打印输出降维后的形状特征。执行后会输出:
[[ 7.79422863 0. ]
[ 2.59807621 0. ]
[-2.59807621 0. ]
[-7.79422863 -0. ]]
本实例展示了如何使用PCA进行形状特征提取。通过选择适当的主成分数量,我们可以获得最具代表性的形状特征,从而实现形状的描述和分类。
注意:PCA是一种常见的降维技术,也可以用于形状特征提取。除了上述示例中的简单数据集,PCA还可以应用于更复杂的形状数据,例如图像的轮廓或特征点集合。在实际应用中,您可以根据具体的问题和数据类型选择适当的形状表示方法和PCA参数设置。
注意:本节介绍的以上方法只是一些常用的形状特征提取方法,在实际应用中还可以根据具体任务和数据特点选择适合的方法。形状特征的选择和提取需要结合具体的应用场景和需求,并考虑图像的噪声、变形、光照等因素。