数字图像处理中的表示与描述:从像素到可识别对象的完整指南

发布时间:2026/10/15 21:23:33
数字图像处理中的表示与描述:从像素到可识别对象的完整指南 1. 从“像素堆”到“可理解对象”表示与描述到底在解决什么问题很多人学数字图像处理前面几章做滤波、边缘检测、阈值分割跑出来的结果是一张二值图或者一堆边缘点看起来挺像那么回事。但一旦要回答“图里有几个目标”“每个目标是什么形状”“这两个目标是不是同一个东西”就卡住了。原因很简单分割只是把像素分成了“前景”和“背景”但计算机并不知道前景里那一坨像素到底代表什么。表示和描述这一章干的就是把“一坨像素”变成“一个可以被算法比较、匹配、识别的对象”这件事。我习惯把这一章理解成图像处理流水线里的“翻译层”。前面几章是“把图变干净”这一章是“把干净的东西说清楚”。说清楚分两步走第一步叫表示解决“用什么数据结构来存这个目标”第二步叫描述解决“用什么特征向量来刻画这个目标”。表示是骨架描述是血肉两者缺一不可。你只做表示不做描述那目标就只是一串坐标没法比较你只做描述不做表示那特征没有依附的载体算出来的数也不知道对应哪个区域。这一章适合谁看如果你正在做缺陷检测、字符识别、细胞计数、零件分拣、遥感地块提取这类任务那这一章就是你的核心工具箱。如果你只是做图像增强、去噪、超分那这一章可能暂时用不上但一旦你的任务从“改善图像”变成“理解图像”表示和描述就是绕不过去的坎。我见过太多项目分割做得漂漂亮亮最后卡在“怎么判断这个缺陷是划痕还是凹坑”上根子就在这一章没吃透。提示这一章的内容和“图像分割”“形态学处理”是强耦合的。如果你的分割结果本身有毛刺、有孔洞、有粘连后面表示和描述做得再精细也是白搭。先把分割质量稳住再来谈特征提取。2. 表示方法选型链码、多边形、骨架到底该用哪个2.1 链码表示最轻量的边界编码方案链码的核心思想特别朴素既然边界是一串相邻的像素点那我从某个起点出发每一步记录“往哪个方向走”就能把整条边界压缩成一串方向数字。四方向链码用0到3表示右、下、左、上八方向链码用0到7表示东、东南、南、西南、西、西北、北、东北。举个例子一条从(0,0)走到(2,0)再走到(2,2)的边界八方向链码就是0,0,2,2四个数字搞定比存六个坐标点省事得多。链码最大的好处是平移不变。你把目标整体挪十个像素链码一个数字都不变因为方向没变。但它对旋转和缩放敏感目标转45度链码全乱。所以实际用的时候通常要做归一化旋转归一化就是找链码的循环最小值缩放归一化就是按目标周长重新采样。我实测下来链码适合做形状检索的粗筛速度快、存储小但精细匹配还得靠后面的描述子。# 八方向链码的简单实现思路 # 方向编码0东, 1东北, 2北, 3西北, 4西, 5西南, 6南, 7东南 directions [(1,0), (1,1), (0,1), (-1,1), (-1,0), (-1,-1), (0,-1), (1,-1)] def boundary_chain_code(boundary_points): code [] for i in range(len(boundary_points)-1): dx boundary_points[i1][0] - boundary_points[i][0] dy boundary_points[i1][1] - boundary_points[i][1] code.append(directions.index((dx, dy))) return code注意链码的起点选择会影响编码结果。做匹配之前一定要做起点归一化否则同一个形状因为起点不同会得到完全不同的链码序列。我一般用“链码循环移位后字典序最小”作为归一化标准。2.2 多边形逼近用最少的顶点保住形状链码是逐像素走的粒度太细噪声一多链码就抖得厉害。多边形逼近的思路是我用少量直线段去拟合边界只要拟合误差在容忍范围内就用这些线段的端点来代表整个边界。最经典的算法是Douglas-Peucker算法思路是递归找离当前直线最远的点如果距离超过阈值就保留这个点把线段一分为二继续递归。这个方法的参数就一个逼近精度阈值。阈值设大了顶点少但形状失真阈值设小了顶点多但保留了细节。我的经验是对于尺寸在200×200左右的目标阈值取2到3个像素比较稳。如果你做的是工业零件匹配阈值可以再小一点因为零件边缘本身就很规整如果你做的是自然场景分割阈值可以放宽反正边缘本来就不规则。多边形逼近的好处是抗噪因为噪声引起的微小凸起会被阈值过滤掉。而且顶点序列可以直接当特征用比如计算相邻边的夹角、边长比这些对旋转和缩放都有一定的鲁棒性。2.3 骨架表示把“粗”目标压成“细”线条骨架也叫中轴变换思路是我把一个有一定宽度的区域反复腐蚀直到只剩一条单像素宽的线这条线就是骨架。骨架保留了目标的拓扑结构——有几个分支、有没有环、端点在哪——但丢掉了宽度信息。所以骨架特别适合做字符识别和血管提取这类任务因为你关心的是“笔画怎么走”而不是“笔画有多粗”。骨架的坑在于毛刺。由于边界的不规则腐蚀到最后经常会在骨架末端长出很多小分支这些分支不是真实结构是噪声引起的。处理办法是在骨架化之前先做平滑或者在骨架化之后做剪枝把长度小于某个阈值的分支砍掉。剪枝阈值一般取目标主体长度的5%到10%具体看你的目标形态。表示方法存储开销平移不变旋转不变抗噪性适用场景链码低是否弱形状检索粗筛多边形逼近中是部分强工业零件匹配骨架中是否中字符、血管、笔画边界跟踪点列高是否弱通用边界分析3. 描述子实战从边界特征到区域特征的完整拆解3.1 边界描述子周长、直径、偏心率怎么算才靠谱边界描述子是最直观的一类特征。周长就是边界像素的个数但这里有个坑八方向链码里水平垂直走一步算1对角走一步算根号2如果你直接数像素个数周长会被高估。正确做法是按链码方向加权求和。直径是边界上任意两点间的最大距离暴力算法是O(n²)但实际用的时候可以先用凸包把边界点压缩再在凸包顶点上算能快一个数量级。偏心率描述的是边界的“细长程度”通常用边界拟合椭圆的短轴和长轴之比来算。偏心率接近1说明形状接近圆接近0说明形状很扁。这个特征在细胞识别里特别有用因为正常细胞和病变细胞的偏心率分布有明显差异。我试过在同一个数据集上光靠偏心率一个特征就能把两类细胞分开到85%的准确率加上周长和面积后能到93%。3.2 区域描述子面积、紧凑度、不变矩的工程细节区域描述子比边界描述子更稳因为它用的是整个区域的像素统计对边界噪声不敏感。面积就是区域内的像素总数这个没什么好说的但要注意如果你的区域是用多边形逼近表示的面积要用鞋带公式算不能数像素。紧凑度的定义是周长平方除以面积这个值对圆是最小的形状越复杂值越大。紧凑度的好处是无量纲不受缩放影响。一个圆不管放大多少倍紧凑度都是4π。我经常用紧凑度做第一道过滤如果紧凑度超过某个阈值直接判定为“非目标”省得后面算复杂特征。不变矩是这一章的重头戏。Hu在1962年提出的七个不变矩对平移、旋转、缩放都保持不变是形状匹配的经典工具。但实际用的时候有几个坑第一不变矩的数值范围跨度极大直接拿来做距离计算会被大数值主导必须先做对数变换第二不变矩对噪声敏感尤其是高阶矩所以一般只用前四个第三不变矩描述的是全局形状对局部形变不敏感所以做精细匹配时还得配合其他特征。import numpy as np import cv2 def hu_moments_features(binary_image): moments cv2.moments(binary_image, binaryImageTrue) hu cv2.HuMoments(moments).flatten() # 对数变换压缩数值范围 hu_log -np.sign(hu) * np.log10(np.abs(hu) 1e-30) return hu_log提示Hu不变矩的第七个矩在有些实现里符号不稳定做匹配时建议只用前六个。另外不变矩是在连续域推导的离散图像上只有近似不变性别指望它能做到完全不变。3.3 纹理描述子当形状特征不够用时怎么办有些目标形状差不多但纹理不一样比如布匹上的不同织法、金属表面的不同处理工艺。这时候光靠边界和区域描述子分不开得上纹理描述子。最常用的三个灰度共生矩阵、局部二值模式、Gabor滤波响应。灰度共生矩阵的思路是统计“相隔某个距离的两个像素灰度分别是i和j的概率”从这个矩阵里可以算出对比度、相关性、能量、同质性四个指标。局部二值模式更简单对每个像素比较它和周围八个像素的大小大于等于记1小于记0得到一个八位二进制数这个数的直方图就是纹理特征。Gabor滤波则是用一组不同方向和频率的滤波器去卷图像响应强度就是纹理特征。我的经验是灰度共生矩阵适合周期性纹理比如织物、网格局部二值模式适合微观纹理比如粗糙度、颗粒感Gabor适合方向性纹理比如木纹、指纹。选哪个取决于你的目标纹理是什么类型没有万能方案。4. 主成分分析降维特征太多反而不好用4.1 为什么需要降维你辛辛苦苦提取了二十个特征结果发现分类器训练不动或者训练出来了但在测试集上表现很差。这大概率是维度灾难特征太多样本相对太少分类器在高维空间里找不到可靠的决策边界。而且很多特征之间是相关的比如周长和面积高度相关偏心率和不变矩也有相关性冗余特征不仅不提供新信息还会引入噪声。主成分分析的做法是把原始特征投影到一组新的正交基上让投影后的第一个维度方差最大第二个维度方差次大以此类推。你保留前几个主成分就能用很少的维度保住大部分信息。我一般看累计方差贡献率达到90%到95%就截断。4.2 实操中的参数选择与踩坑记录主成分分析的参数就一个保留几个主成分。但这个参数不能拍脑袋定。我的做法是画碎石图看方差贡献率的下降曲线拐点在哪就取到哪。另一个坑是标准化如果你的特征量纲不一样比如面积是几千偏心率是零点几不做标准化的话面积会主导主成分方向偏心率的信息就被淹没了。所以做PCA之前一定要做z-score标准化。还有一个容易被忽略的点PCA是在训练集上拟合的测试集必须用同样的均值和标准差做变换不能重新拟合。我见过有人把训练集和测试集拼在一起做PCA然后说效果特别好那是数据泄露实际部署的时候性能会崩。特征数量样本数量建议保留主成分数累计方差贡献率102004-690%205006-1092%50100010-1595%5. 关系描述当目标不止一个时怎么组织信息5.1 字符串描述与树结构描述前面讲的都是单个目标的表示和描述。但实际场景里一张图里往往有多个目标而且目标之间有空间关系。比如“A在B的左边”“C在D的上面”“E被F包围”。这些关系怎么编码最常用的两种结构是字符串和树。字符串描述适合线性排列的目标比如一行文字、一列零件。你把每个目标的特征按顺序排成一串就得到了字符串描述。匹配的时候用字符串编辑距离能容忍目标缺失和顺序微调。树结构适合层次关系比如一个电路板上有多个芯片每个芯片有多个引脚引脚和芯片是父子关系芯片和电路板也是父子关系。树匹配用子树同构算法能处理嵌套结构。5.2 关系描述的适用边界与性能权衡关系描述的好处是表达能力强能编码复杂的空间约束。但代价是匹配复杂度高。字符串匹配是O(n²)树匹配是指数级的目标一多就跑不动。所以实际用的时候我一般先用单目标特征做粗筛把候选目标压到几个再用关系描述做精匹配。这样既保住了表达能力又控制了计算量。另一个坑是关系定义的模糊性。“左边”到底是指目标中心点的左边还是目标最右像素的左边不同定义会导致完全不同的匹配结果。我的做法是在项目文档里把每个关系的定义写死比如“A的中心点x坐标小于B的中心点x坐标则称A在B左边”避免歧义。6. 常见问题与排查技巧实录6.1 特征提取结果不稳定怎么办这是最常见的问题同一类目标不同样本提取出来的特征值波动很大。排查思路分三步走。第一步检查分割质量把二值图叠加到原图上看边界是不是贴合真实边缘。如果分割本身就不稳后面特征肯定不稳。第二步检查特征计算实现特别是周长和面积这种基础特征用已知形状比如一个标准圆验证一下数值对不对。第三步检查归一化如果你的目标尺寸差异很大不做尺度归一化的话面积特征会直接反映尺寸而不是形状。6.2 不变矩数值溢出或精度丢失Hu不变矩的高阶矩在计算过程中会出现很大的中间值float64都可能溢出。解决办法有两个一是用对数域计算把乘法变成加法二是先对图像做归一化把像素坐标缩放到0到1之间。另外不变矩的第七个矩在离散情况下符号不稳定做匹配时建议只用前六个。6.3 主成分分析后分类效果反而变差这通常是因为保留的主成分太少把有判别力的信息丢掉了。PCA是无监督的它保留的是方差最大的方向但方差大不等于判别力强。如果两类目标的差异正好在方差小的方向上PCA会把这个方向丢掉。解决办法是改用线性判别分析它是有监督的会主动找类间差异最大的方向。或者干脆不降维改用带正则化的分类器。问题现象可能原因排查方法解决方案特征值波动大分割不稳叠加二值图看边界改进分割算法不变矩溢出高阶矩数值大打印中间值对数域计算PCA后效果差丢失判别信息对比降维前后改用LDA匹配速度慢特征维度高统计计算耗时先粗筛再精匹配旋转后匹配失败特征不具旋转不变性旋转测试用不变矩或归一化提示做形状匹配时我习惯先用紧凑度和偏心率做第一道过滤这两个特征计算快、鲁棒性强能过滤掉大部分明显不匹配的候选。剩下的再用不变矩做精细匹配这样整体速度能提升三到五倍。7. 一个完整的形状匹配流程从分割到识别7.1 流程设计与参数配置假设你要做一个零件分拣系统传送带上随机摆放几种零件相机拍图后要判断每个零件属于哪一类。完整流程是这样的第一步灰度化加高斯滤波去掉传感器噪声第二步Otsu阈值分割把零件从背景里分出来第三步形态学开闭运算去掉毛刺和填平小孔第四步连通域分析给每个零件分配独立标签第五步对每个连通域提取边界计算链码和多边形逼近第六步计算区域特征包括面积、紧凑度、偏心率、前六个Hu不变矩第七步特征标准化后做PCA降维到五维第八步用最近邻分类器匹配到已知类别。参数配置上高斯核取5×5sigma取1.5Otsu分割后做一次3×3的开运算多边形逼近阈值取2.5像素PCA保留累计方差95%的主成分。这套参数在我做过的几个零件分拣项目里都跑得比较稳但具体数值还得根据你的图像分辨率和零件尺寸微调。7.2 效果验证与迭代优化验证的时候不能只看准确率还要看混淆矩阵。如果某两类零件经常互相误判说明它们的特征空间有重叠需要加新特征或者换分类器。我遇到过一个案例两种螺丝外形几乎一样只有螺纹间距不同形状特征完全分不开最后加了纹理特征才解决。所以特征提取不是一劳永逸的得根据混淆矩阵反馈来迭代。迭代的方向有三个一是加特征比如加纹理、加颜色二是换分类器从最近邻换成支持向量机或者随机森林三是改分割有时候误判的根源在分割阶段两个零件粘在一起被当成一个目标了。我的经验是先排查分割再排查特征最后才动分类器因为前两者的影响更大。8. 工程落地中的性能优化与经验沉淀8.1 计算加速的几种实用手段链码和多边形逼近的计算量都不大真正耗时的是不变矩和PCA。不变矩的加速可以用积分图把矩的计算从O(n²)降到O(1)。PCA的加速可以用随机化SVD在样本量大的时候比标准SVD快很多。另外如果你的目标是实时处理建议把特征提取写成C扩展或者用CUDA加速Python循环算矩确实慢。还有一个容易被忽略的点边界跟踪的起点选择。如果每次跟踪都从最左上角的边界点开始链码的起点就是固定的省去了起点归一化的步骤。这个技巧在批量处理时能省不少时间。8.2 从项目里攒下来的几条硬经验第一条特征不是越多越好。我见过有人提取了五十个特征结果分类器过拟合严重测试集准确率还不如只用五个特征。特征选择比特征提取更重要。第二条归一化要统一。训练集和测试集必须用同一套归一化参数这个参数要在训练集上算然后存下来给测试集用。我习惯把归一化参数和分类器模型一起序列化保存部署的时候直接加载。第三条可视化调试。把提取到的边界、多边形、骨架画到原图上肉眼看一下对不对。很多bug肉眼一看就发现了比看代码快得多。第四条留出验证集。不要用测试集调参数否则你调出来的参数只对测试集好换个数据集就崩。我一般按6:2:2划分训练、验证、测试验证集用来调参测试集只在最后跑一次。这个内容后续还可以这样扩展把表示和描述跟深度学习结合起来用卷积网络自动学特征替代手工设计的描述子。但手工特征在样本少、可解释性要求高的场景下依然有优势两者不是替代关系是互补关系。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询