数字图像处理(第三版)第11章 表示和描述

表示

边界追踪

边界追踪是从二值图像中提取目标边界的过程。常用的算法包括基于连通性的方法和基于边缘检测的方法。一种经典的边界追踪算法是基于 Moore-Neighbor Tracing (MNT) 算法,它从图像中找到起始点并依据邻域像素进行追踪,直到回到起始点为止。
给定一个二值区域R或其边界,追踪R的边界或给定边界的算法步骤如下:
1.令起始点 b 0 b_{0} b0为图像中左上角标记为1的点。使用 c 0 c_{0} c0表示 b 0 b_{0} b0西侧的邻点。很明显, c 0 c_{0} c0总是背景点。从 c 0 c_{0} c0开始按顺时针方向考察 b 0 b_{0} b0的8个邻点。令 b 1 b_{1} b1表示所遇到的值为1的第一个邻点,并直接令 c 1 c_{1} c1(背景)是序列中 b 1 b_{1} b1之前的点。存储 b 0 b_{0} b0 b 1 b_{1} b1的位置,以便在步骤5中使用。
2.令 b b b= b 1 b_{1} b1 c c c= c 1 c_{1} c1
3.从c开始按顺时针方向行进,令b的8个邻点为 n 1 n_{1} n1 n 2 n_{2} n2,… , n 8 n_{8} n8 。找到标为1的第一个 n k n_{k} nk.
4.令b= n k n_{k} nk,和c = n k − 1 n_{k-1} nk1 .
重复步骤3和步骤4,直到b= b 0 b_{0} b0。且找到的下一个边界点为 b 1 b_{1} b1
在这里插入图片描述

链码

链码是一种编码方法,用于描述边界的连续性和方向。链码沿着边界的像素依次记录像素的位置和连接顺序。常见的链码表示有4连通链码和8连通链码,分别使用4个和8个方向编码。以这种方向性数字序列表示的编码称为佛雷曼链码
实验

import cv2
import numpy as np
def draw_chain_code(image, chain):
    for i in range(len(chain)):
        x, y = chain[i]
        cv2.circle(image, (x, y), 1, (255, 0, 0), -1)
        if i > 0:
            cv2.line(image, (chain[i - 1][0], chain[i - 1][1]), (x, y), (255, 0, 0), 1)
def get_contour_chain(contour):
    chain = []
    for point in contour:
        x, y = point[0]
        chain.append((x, y))
    return chain

# 加载图像并转为灰度
image_path = 'D:\Pycharm Projects\pythonStudy\pythonProject1\iankong.jpg'  # 更改为你的图像路径
img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)
# 二值化图像
_, binary_img = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
# 寻找轮廓
contours, _ = cv2.findContours(binary_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# 创建空白图像用于绘制轮廓
output_img = np.zeros_like(binary_img)
# 对每个轮廓进行处理
for contour in contours:
    # 获取链码
    chain = get_contour_chain(contour)
    # 在输出图像上绘制链码
    draw_chain_code(output_img, chain)
# 显示图像
cv2.imshow('Binary Image', binary_img)
cv2.imshow('Chain Code Image', output_img)
cv2.waitKey(0)
cv2.destroyAllWindows()

实验结果
在这里插入图片描述
心得
1.图像处理的基础很重要:这个实验让我意识到图像处理的基础知识非常重要。比如,从彩色图像到灰度图像的转换、二值化等操作,都是后续处理的基础。
2.轮廓检测的实用性:使用OpenCV的轮廓检测功能非常直观和实用,它可以帮助我们提取图像中的关键信息。通过简单的几行代码就能找到物体的边缘,这在很多应用场景中都非常有用。
3.链码的应用:链码是一种表示图像轮廓的方法,通过记录轮廓点之间的相对位置,可以简洁地描述轮廓的形状。这个实验让我了解到链码可以用来简化轮廓的存储,并且方便后续的分析和处理。

使用最小周长多边形的多边形近似

这种方法旨在通过优化多边形的顶点数和周长来逼近原始边界。可以使用动态规划或凸包方法来实现,例如 Graham 扫描法。
计算产生MPP(最小周长多边形),使用下图(b)封入图(a)中的一条边界。最终收缩,形成图(c)的效果。
在这里插入图片描述
MPP算法
MPP 算法的一般步骤如下:
1.初始化
确定一组点集合 P,这些点构成了我们想要包围的区域。
2.构建凸包
使用凸包算法(如Graham扫描算法、Jarvis步进算法等)来找出这些点的凸包。凸包是一个包含所有点的最小凸多边形。
3.优化周长
由于凸包可能不是具有最小周长的多边形,因此需要进一步优化。
可以尝试移除凸包中的某些顶点或添加新的顶点来减小周长。
这一步骤可能涉及到迭代的过程,直到找到一个满足条件的多边形。
4.验证结果
确保最终的多边形包含了所有的点并且确实是最小周长的多边形。
5.输出结果
输出最终的多边形的顶点坐标。

示例
假设我们有一组点集合 P = { ( 1 , 1 ) , ( 2 , 2 ) , ( 3 , 1 ) , ( 4 , 2 ) , ( 5 , 1 ) } P = \{ \left( {1,1}\right) ,\left( {2,2}\right) ,\left( {3,1}\right) ,\left( {4,2}\right) ,\left( {5,1}\right) \} P={(1,1),(2,2),(3,1),(4,2),(5,1)}
初始化:
我们从这些点开始。
1.构建凸包
使用Graham扫描算法或其他凸包算法找到这些点的凸包。
对于这组点,凸包可能是由点 ( 1 , 1 ) (1,1) (1,1), ( 5 , 1 ) (5,1) (5,1), ( 4 , 2 ) (4,2) (4,2) ( 2 , 2 ) (2,2) (2,2)形成的多边形。
2.优化周长
在这个例子中,凸包本身就是最小周长多边形,因为没有其他多边形能够同时包含所有的点且周长更短。
因此,凸包就是最终的答案。
3.验证结果
确认所有点都被包含在多边形内,并且多边形的周长确实是所有可能的多边形中最短的。
4.输出结果
输出多边形的顶点: ( 1 , 1 ) (1,1) (1,1), ( 5 , 1 ) (5,1) (5,1), ( 4 , 2 ) (4,2) (4,2) ( 2 , 2 ) (2,2) (2,2)

其他多边形近似方法

聚合技术
聚合技术的主要目标是合并相邻的边或点,以减少多边形的复杂度,同时尽量保持原始形状的关键特征。这种方法通常用于减少轮廓点的数量,从而简化形状的表示。

聚合技术的步骤:
初始化:选取一个多边形轮廓,该轮廓通常是由一系列点组成的序列。
边合并:对于每一对相邻的边,检查是否可以合并。通常使用某种标准来决定是否合并,比如合并后的边与原边的偏差小于某个阈值。
重复合并:重复这个过程,直到没有更多的边可以合并为止。
输出结果:输出简化后的多边形轮廓。

分裂技术
分裂技术则采取相反的方法,即通过增加点来细化多边形的表示。这种方法通常用于需要更高精度的情况,比如当原始轮廓的细节很重要时。

分裂技术的步骤:
初始化:同样从一个多边形轮廓开始。
边细分:对于每条边,检查是否需要进一步细分。通常也是基于某种标准来决定,比如边的长度超过某个阈值。
插入新点:在需要细分的边上插入新的点,以提高细节。
重复细分:重复这个过程,直到所有边都达到足够的精度。
输出结果:输出细化后的多边形轮廓。

标记图

标记图是边界的一维函数表示,它可以使用各种方式生成。最简单的是以角度的函数形式画出质心到边界的距离。如下图所示。不管如何生成标记图,基本概念都是将边界表示简化为描述起来更简单的函数。
在这里插入图片描述
标记图是将相邻像素标记为同一对象的过程,通常用于分割和识别多个物体或区域。

边界描绘子

一些简单的描绘子

概念:简单描绘子通常包括边界上的点数、边界长度等基本信息。这类描绘子虽然简单,但在某些情况下已经足够用于基本的形状分类。
实现步骤
1.计算轮廓点数:轮廓上的点数直接给出了边界的基本信息。
2.计算边界长度:通过计算相邻轮廓点之间的距离并累加得到边界总长度。
示例
假设有一个轮廓由以下点组成: P = [ ( 1 , 1 ) , ( 2 , 2 ) , ( 3 , 3 ) , ( 4 , 2 ) , ( 5 , 1 ) ] 。 P = \left\lbrack {\left( {1,1}\right) ,\left( {2,2}\right) ,\left( {3,3}\right) ,\left( {4,2}\right) ,\left( {5,1}\right) }\right\rbrack \text{。} P=[(1,1),(2,2),(3,3),(4,2),(5,1)]
轮廓点数: ∣ P ∣ = 5 \left | P \right |=5 P=5
边界长度: ∑ i = 0 4 ( x i + 1 − x i ) 2 + ( y i + 1 − y i ) 2 \mathop{\sum }\limits_{{i = 0}}^{4}\sqrt{{\left( {x}_{i + 1} - {x}_{i}\right) }^{2} + {\left( {y}_{i + 1} - {y}_{i}\right) }^{2}} i=04(xi+1xi)2+(yi+1yi)2

形状数

形状数是一种用来描述边界复杂度的描绘子,它反映了边界凹凸的程度。最常用的形状数是弗雷德霍姆指数,它通过计算轮廓上特定点的曲率来衡量。
实现步骤
计算每个点的曲率:对于边界上的每个点,计算它与前一点和后一点构成的角度。
计算总的曲率变化:对所有点的曲率变化求和。
示例
假设有一个简单的三角形轮廓: P = [ ( 1 , 1 ) , ( 5 , 1 ) , ( 3 , 4 ) ] P = [(1,1),(5,1),(3,4)] P=[(1,1),(5,1),(3,4)]
对于每个点,我们可以计算其与前后点构成的角度,然后求和得到总的曲率变化。

傅里叶描绘子

概念:傅里叶描绘子是通过对边界轮廓进行傅里叶变换获得的。它能够提取出形状的频谱特征,非常适合于形状的识别和匹配。
实现步骤
将边界轮廓转换为复数序列:每个轮廓点 ( x i , y i (x_{i},y_{i} xi,yi可以表示为复数 z i = x i + j y i z_{i}=x_{i}+jy_{i} zi=xi+jyi
对复数序列进行离散傅里叶变换(DFT)。
提取低频分量作为描绘子。
示例
假设有一个轮廓由以下点组成: P = [ ( 1 , 1 ) , ( 2 , 2 ) , ( 3 , 3 ) , ( 4 , 2 ) , ( 5 , 1 ) ] 。 P = \left\lbrack {\left( {1,1}\right) ,\left( {2,2}\right) ,\left( {3,3}\right) ,\left( {4,2}\right) ,\left( {5,1}\right) }\right\rbrack \text{。} P=[(1,1),(2,2),(3,3),(4,2),(5,1)]将这些点转换为复数序列。使用numpy库进行DFT计算。

import numpy as np

# 轮廓点
points = np.array([(1, 1), (2, 2), (3, 3), (4, 2), (5, 1)])

# 转换为复数
complex_points = points[:, 0] + 1j * points[:, 1]

# 进行DFT
fourier_coeffs = np.fft.fft(complex_points)

# 提取低频分量作为描绘子
descriptor = fourier_coeffs[:3]
print(descriptor)

实验结果
在这里插入图片描述

统计矩

概念:统计矩是基于轮廓点的位置计算的统计量,包括中心矩、归一化中心矩等。这些矩能够提供关于形状的大小、位置和方向的信息。
实现步骤
计算轮廓的质心。
计算轮廓点相对于质心的中心矩。
归一化中心矩,消除尺度影响。
示例
假设有一个轮廓由以下点组成: P = [ ( 1 , 1 ) , ( 2 , 2 ) , ( 3 , 3 ) , ( 4 , 2 ) , ( 5 , 1 ) ] 。 P = \left\lbrack {\left( {1,1}\right) ,\left( {2,2}\right) ,\left( {3,3}\right) ,\left( {4,2}\right) ,\left( {5,1}\right) }\right\rbrack \text{。} P=[(1,1),(2,2),(3,3),(4,2),(5,1)]
实验

import numpy as np

# 轮廓点
points = np.array([(1, 1), (2, 2), (3, 3), (4, 2), (5, 1)])

# 计算质心
centroid = np.mean(points, axis=0)

# 计算中心矩
centered_points = points - centroid
mu_20 = np.sum(centered_points[:, 0]**2)
mu_02 = np.sum(centered_points[:, 1]**2)
mu_11 = np.sum(centered_points[:, 0] * centered_points[:, 1])

# 归一化中心矩
mu_20_normalized = mu_20 / (mu_20 + mu_02)**(1.5)
mu_02_normalized = mu_02 / (mu_20 + mu_02)**(1.5)
mu_11_normalized = mu_11 / (mu_20 + mu_02)**(1.5)

print(f"Normalized Centered Moments: mu_20={mu_20_normalized:.2f}, mu_02={mu_02_normalized:.2f}, mu_11={mu_11_normalized:.2f}")

实验结果
在这里插入图片描述
这些描绘子各有优缺点,选择哪种描绘子取决于具体的应用需求。例如,如果需要识别旋转不变性,则归一化中心矩会是一个好的选择;如果需要快速识别形状,则傅里叶描绘子可能是更好的选择。

区域描绘子

一些简单的描绘子

简单描绘子通常包括区域面积、周长等基本信息。这类描绘子虽然简单,但在某些情况下已经足够用于基本的形状分类。
实现步骤
计算区域面积:对于二值图像,可以通过统计白色像素的数量来得到区域面积。
计算边界长度:通过计算边界上的像素数量或使用距离度量来得到边界长度。
示例
假设有一个二值图像,其中白色像素表示感兴趣区域,黑色像素表示背景。该区域由以下点组成: P = [ ( 1 , 1 ) , ( 2 , 2 ) , ( 3 , 3 ) , ( 4 , 2 ) , ( 5 , 1 ) ] 。 P = \left\lbrack {\left( {1,1}\right) ,\left( {2,2}\right) ,\left( {3,3}\right) ,\left( {4,2}\right) ,\left( {5,1}\right) }\right\rbrack \text{。} P=[(1,1),(2,2),(3,3),(4,2),(5,1)]
区域面积: ∣ P ∣ = 5 \left | P \right |=5 P=5
边界长度: ∑ i = 0 4 ( x i + 1 − x i ) 2 + ( y i + 1 − y i ) 2 \mathop{\sum }\limits_{{i = 0}}^{4}\sqrt{{\left( {x}_{i + 1} - {x}_{i}\right) }^{2} + {\left( {y}_{i + 1} - {y}_{i}\right) }^{2}} i=04(xi+1xi)2+(yi+1yi)2

拓扑描绘子

概念:拓扑描绘子关注的是区域内部的结构,例如连通组件的数量、孔洞的数量等。最常用的拓扑描绘子是欧拉数。
实现步骤
使用连通组件标记算法找到所有连通的区域。
计算连通组件的数量 C C C和孔洞的数量 H H H
欧拉数: E = C − H E=C-H E=CH

纹理

图像处理中用于描述区域纹理的三种主要方法是统计方法、结构方法和频谱方法。统计方法生成诸如平滑、粗糙、粒状等纹理特征。结构技术处理图像像元的排列。频谱技术基于傅里叶频谱的特征,主要用于检测图像中的全局周期性,方法是识别频谱中的高能量的窄波峰。
在这里插入图片描述

使用主分量进行描绘

概念:主分量描绘子利用主成分分析的方法来提取图像区域的主要特征方向。这种方法能够找出数据的最大方差方向,即数据分布的主轴。

实现步骤
1.数据准备:从图像中提取感兴趣的区域并将其转换为向量形式。
2.均值中心化:计算所有向量的平均值,并从每个向量中减去这个平均值。
3.协方差矩阵计算:基于中心化的数据计算协方差矩阵。
4.特征值和特征向量计算:求解协方差矩阵的特征值和特征向量。
5.选取主分量:根据特征值的大小排序特征向量,并选取前几个作为主分量。

关系描绘子

示例
假设我们有一个二值图像区域,我们想提取它的主分量。使用如下代码:

import numpy as np
import matplotlib.pyplot as plt

# 假设有两个物体的位置
object1 = np.array([20, 20])  # 物体1的中心位置
object2 = np.array([50, 50])  # 物体2的中心位置

# 计算物体间的距离
distance = np.linalg.norm(object1 - object2)

# 计算物体间的角度
angle = np.arctan2(object2[1] - object1[1], object2[0] - object1[0])

# 描述物体间的关系
relationship = np.array([distance, angle])

# 显示结果
print("Distance between objects:", distance)
print("Angle between objects:", np.degrees(angle))
print("Relationship vector:", relationship)

运行结果
在这里插入图片描述
总结
从图像中分割出来的物体区域的表示和描述是大多数前期步骤。这些描述构成了后续学习的目标识别方法的输入。具体选择其中的哪一种方法,取决于考虑的问题,目的是为了捕获物体或物体类之间本质差异的描绘子,同时尽可能保留位置、大小和方向的独立性。

  • 7
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一只小小程序猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值