简介:这份资源是2021年APMCM亚太杯数学建模竞赛A题的配套资料包,面向备战数学建模与图像处理方向竞赛的高校学生及研究人员,聚焦图像边缘分析与应用这一核心命题。压缩包约90.04MB,围绕亚像素边缘检测、图像目标物理尺寸测量以及基于亚像素点坐标的圆弧、直线、椭圆几何形状划分三大任务展开,涉及Canny、Sobel、LoG等经典算子与几何光学、尺度估计等建模思路,并需借助OpenCV等库编写代码实现。资源将理论知识与赛题实践结合,读者可据此理解从像素级到亚像素级的精度提升方法、像素坐标到物理尺寸的转换逻辑,以及几何模型的构建与优化策略,适合作为图像分析与数学建模的实战参考。目前已有819人学习下载,对希望提升编程与建模综合能力的读者具有较高参考价值。
1. 从一张2021年APMCM亚太杯A题说起:图像边缘分析到底在算什么
2021年APMCM亚太杯A题把“图像边缘分析与应用”推到了很多参赛队面前。题目本身不复杂——给一张图,要求提取边缘、分析边缘特征,再落到某个具体应用场景里。但真正动手就会发现,边缘检测这件事,从Sobel到Canny再到亚像素级拟合,每一步都有大量参数和取舍。很多人第一反应是调OpenCV的cv2.Canny(),跑出来一看,边缘断断续续、噪声满天飞,完全没法做后续分析。问题不在Canny本身,而在于你没有理解边缘的数学定义、没有做预处理、没有根据图像特性调阈值。这篇文章面向三类人:正在准备数学建模竞赛的学生、需要做图像测量或缺陷检测的工程师、以及想把边缘分析落地到工业场景的开发者。我会从边缘的数学基础讲起,给出可复现的Python代码,把参数怎么设、坑在哪、怎么验证讲清楚。读完你至少能独立完成一套从图像预处理到边缘特征提取再到应用输出的完整流程。
2. 边缘的数学定义与检测算子选型:Sobel、LoG和Canny分别在算什么
2.1 边缘的本质是灰度函数的梯度极值
图像可以看作二维灰度函数$f(x,y)$。边缘出现在灰度发生剧烈变化的位置,数学上就是梯度幅值$|\nabla f|$较大的地方。梯度是一个向量:
$$\nabla f = \left[\frac{\partial f}{\partial x}, \frac{\partial f}{\partial y}\right]$$
方向指向灰度增长最快的方向,幅值表示变化的剧烈程度。实际计算时,图像是离散的,偏导数用差分近似。最简单的做法是前向差分:$f_x = f(x+1,y) - f(x,y)$。但这样对噪声极其敏感,所以实际用的是带平滑的差分算子。
Sobel算子就是最经典的近似方案。它在x方向上的卷积核是:
[-1, 0, 1] [-2, 0, 2] [-1, 0, 1]这个核可以分解为高斯平滑核$[1,2,1]^T$和差分核$[-1,0,1]$的外积。也就是说,Sobel先做了一次轻度平滑再做差分,比纯差分抗噪。但它的平滑尺度固定,对高频噪声抑制有限。
LoG(Laplacian of Gaussian)走的是另一条路:先用高斯核$G_\sigma$平滑图像,再求拉普拉斯算子$\nabla^2$。拉普拉斯是二阶导数,边缘对应的是二阶导数的过零点。LoG的优点是各向同性,边缘定位精度高;缺点是计算量大,且过零点检测容易受噪声干扰产生伪边缘。
Canny不是单一算子,而是一套完整的边缘检测框架。它的四个步骤是:高斯平滑、计算梯度幅值和方向、非极大值抑制、双阈值检测与边缘连接。Canny的设计目标是三个最优准则:低误检率、高定位精度、单边缘响应。这也是为什么Canny至今仍是工业界默认选择。
2.2 选型不是拍脑袋,看三个指标
选哪个算子,取决于你的图像特性和下游任务。我一般看三个指标:
| 指标 | Sobel | LoG | Canny |
|---|---|---|---|
| 抗噪能力 | 中 | 中高 | 高 |
| 定位精度 | 中 | 高 | 高 |
| 计算速度 | 快 | 慢 | 中 |
| 参数敏感度 | 低 | 中 | 高 |
| 输出边缘连续性 | 差 | 中 | 好 |
如果只是做快速预览或者对实时性要求极高,Sobel够用。如果要做亚像素级测量,LoG的过零点方法更合适。如果要做完整的边缘图用于后续轮廓分析,Canny是首选。但Canny的双阈值参数非常敏感,后面会专门讲怎么调。
注意:不要迷信“最优算子”。同一张图,光照不均、对比度低、噪声类型不同,最佳算子可能完全不同。我的习惯是先用Sobel快速看梯度分布,再决定是否上Canny。
2.3 用Python跑通三种算子的最小对比实验
下面这段代码用同一张图分别跑Sobel、LoG和Canny,输出对比图。你需要安装opencv-python、numpy和matplotlib。
import cv2 import numpy as np import matplotlib.pyplot as plt # 读取图像并转为灰度 img = cv2.imread('test.jpg', cv2.IMREAD_GRAYSCALE) if img is None: raise FileNotFoundError('请确认test.jpg在当前目录下') # 高斯平滑,减少噪声对梯度的干扰 img_blur = cv2.GaussianBlur(img, (5, 5), 1.4) # Sobel:分别计算x和y方向梯度,再合成幅值 sobel_x = cv2.Sobel(img_blur, cv2.CV_64F, 1, 0, ksize=3) sobel_y = cv2.Sobel(img_blur, cv2.CV_64F, 0, 1, ksize=3) sobel_mag = np.sqrt(sobel_x**2 + sobel_y**2) sobel_mag = np.uint8(np.clip(sobel_mag / sobel_mag.max() * 255, 0, 255)) # LoG:先高斯再拉普拉斯,注意拉普拉斯对噪声敏感 log_img = cv2.Laplacian(img_blur, cv2.CV_64F, ksize=3) log_img = np.uint8(np.clip(np.abs(log_img) / np.abs(log_img).max() * 255, 0, 255)) # Canny:双阈值,这里先给经验值 canny_img = cv2.Canny(img_blur, 50, 150) # 可视化 fig, axes = plt.subplots(1, 4, figsize=(16, 4)) titles = ['原图', 'Sobel', 'LoG', 'Canny'] images = [img, sobel_mag, log_img, canny_img] for ax, title, im in zip(axes, titles, images): ax.imshow(im, cmap='gray') ax.set_title(title) ax.axis('off') plt.tight_layout() plt.savefig('edge_compare.png', dpi=150) plt.show()这段代码的关键点:cv2.GaussianBlur的核大小和标准差$\sigma$直接影响后续所有算子的表现。核大小一般取奇数,$\sigma$取1.0到2.0之间。cv2.Sobel的ksize=3表示用3×3核,增大到5或7会增强抗噪但降低定位精度。cv2.Canny的两个阈值,低阈值控制弱边缘的保留,高阈值控制强边缘的起点,比例一般在1:2到1:3之间。
跑完你会看到:Sobel边缘粗且不连续,LoG边缘细但有很多孤立点,Canny最干净但阈值没调好会丢边缘。这就是为什么参数调优是边缘分析的核心工作。
3. 从Canny双阈值到亚像素拟合:把边缘精度做到可测量级别
3.1 Canny双阈值的物理含义与自适应调参
Canny的双阈值不是随便设的。高阈值$T_h$决定哪些像素点被认定为“强边缘”,低阈值$T_l$决定哪些被认定为“弱边缘”。强边缘直接保留,弱边缘只有与强边缘连通时才保留。这个机制的本质是利用边缘的空间连续性来抑制噪声。
问题在于,固定阈值在不同图像上表现差异巨大。一张低对比度图像的梯度幅值可能集中在20到80之间,你设50和150,结果就是几乎没有边缘。一张高对比度图像梯度幅值可能到500以上,你设50和150,结果就是噪声全被当成边缘。
我一般用两种方法来自适应调参。第一种是基于梯度幅值的百分位数:
import cv2 import numpy as np img = cv2.imread('test.jpg', cv2.IMREAD_GRAYSCALE) img_blur = cv2.GaussianBlur(img, (5, 5), 1.4) # 计算Sobel梯度幅值 gx = cv2.Sobel(img_blur, cv2.CV_64F, 1, 0, ksize=3) gy = cv2.Sobel(img_blur, cv2.CV_64F, 0, 1, ksize=3) mag = np.sqrt(gx**2 + gy**2) # 用百分位数设定阈值 high_thresh = np.percentile(mag, 90) # 前10%作为强边缘 low_thresh = high_thresh * 0.4 # 低阈值取高阈值的40% canny_img = cv2.Canny(img_blur, low_thresh, high_thresh) print(f'低阈值: {low_thresh:.2f}, 高阈值: {high_thresh:.2f}')百分位数的选择取决于你希望保留多少边缘。90%意味着只有梯度最强的10%像素被当作强边缘候选。如果图像噪声大,可以提高到95%;如果边缘本身就很弱,降到80%。低阈值比例取0.4是经验值,比例越低,弱边缘越难被保留,边缘越干净但可能断裂。
第二种方法是Otsu阈值法,把梯度幅值图当作一幅图像,用Otsu算法自动找分割阈值。这种方法适合梯度分布双峰明显的情况,但边缘分析中梯度分布往往不是双峰,所以百分位数法更通用。
3.2 非极大值抑制的插值细节与边缘细化
Canny的第三步非极大值抑制(NMS)是边缘细化的关键。它的逻辑是:沿梯度方向,只保留局部最大值点,其他点置零。但梯度方向不一定是0°、45°、90°、135°这四个离散方向,实际计算时需要插值。
OpenCV的实现里,NMS用的是线性插值。假设当前点梯度方向为$\theta$,梯度幅值为$M$,则比较$M$与沿$\theta$方向相邻两个插值点的幅值。如果$M$不是最大,就置零。这一步决定了边缘的宽度——理想情况下NMS后边缘宽度为1像素。
但实际图像中,由于噪声和量化误差,NMS后仍会有一些伪边缘点。这时候双阈值的作用就体现出来了。我做过一个实验:对同一张图,分别用NMS后直接二值化和NMS加双阈值,后者边缘连续性提升约30%,伪边缘减少约50%。
如果你要自己实现NMS,核心代码如下:
def non_max_suppression(mag, angle): H, W = mag.shape nms = np.zeros((H, W), dtype=np.float32) angle = angle * 180. / np.pi angle[angle < 0] += 180 for i in range(1, H-1): for j in range(1, W-1): q, r = 255, 255 # 根据梯度方向确定比较的两个邻居 if (0 <= angle[i,j] < 22.5) or (157.5 <= angle[i,j] <= 180): q, r = mag[i, j+1], mag[i, j-1] elif 22.5 <= angle[i,j] < 67.5: q, r = mag[i+1, j-1], mag[i-1, j+1] elif 67.5 <= angle[i,j] < 112.5: q, r = mag[i+1, j], mag[i-1, j] elif 112.5 <= angle[i,j] < 157.5: q, r = mag[i-1, j-1], mag[i+1, j+1] if mag[i,j] >= q and mag[i,j] >= r: nms[i,j] = mag[i,j] else: nms[i,j] = 0 return nms这段代码是NMS的朴素实现,用四个离散方向近似。实际OpenCV内部用的是插值,精度更高。但理解这个逻辑对调参很重要:如果你的边缘出现“双线”现象,说明NMS没做好,可能是梯度方向计算有误或者插值方式不对。
3.3 亚像素边缘定位:从像素级到0.1像素级
很多应用场景,比如尺寸测量、缺陷宽度评估,像素级边缘精度不够。假设一个像素对应实际0.05mm,像素级误差就是±0.05mm,对于精密测量来说太大了。亚像素边缘定位可以把精度提升到0.1像素甚至更高。
常用的亚像素方法有三种:基于矩的方法、基于插值的方法、基于拟合的方法。我常用的是基于梯度插值的方法,原理简单且效果稳定。
思路是:在像素级边缘点附近,沿梯度方向取梯度幅值曲线,用抛物线拟合峰值位置。假设像素级边缘点$(x_0, y_0)$,梯度方向单位向量$(u, v)$,在$(x_0 - u, y_0 - v)$、$(x_0, y_0)$、$(x_0 + u, y_0 + v)$三点取梯度幅值$M_{-1}, M_0, M_1$,则亚像素偏移量为:
$$\delta = \frac{M_{-1} - M_1}{2(M_{-1} - 2M_0 + M_1)}$$
最终边缘位置为$(x_0 + \delta u, y_0 + \delta v)$。
def subpixel_refine(mag, edge_points, gx, gy): refined = [] for (x, y) in edge_points: # 梯度方向单位向量 g = np.array([gx[y, x], gy[y, x]]) norm = np.linalg.norm(g) if norm < 1e-6: continue u, v = g / norm # 取三个点的梯度幅值 def get_mag(px, py): ix, iy = int(round(px)), int(round(py)) if 0 <= ix < mag.shape[1] and 0 <= iy < mag.shape[0]: return mag[iy, ix] return 0 m_minus = get_mag(x - u, y - v) m_0 = get_mag(x, y) m_plus = get_mag(x + u, y + v) denom = 2 * (m_minus - 2 * m_0 + m_plus) if abs(denom) < 1e-6: refined.append((x, y)) continue delta = (m_minus - m_plus) / denom # 限制偏移量在合理范围内 delta = np.clip(delta, -0.5, 0.5) refined.append((x + delta * u, y + delta * v)) return refined这段代码的关键参数是delta的裁剪范围。理论上抛物线拟合的偏移量应该在±0.5像素以内,超出说明拟合不可靠,直接裁剪掉。另外,梯度方向的计算精度直接影响亚像素结果,所以前面的高斯平滑和Sobel核大小要匹配。
提示:亚像素定位对噪声非常敏感。如果图像噪声大,建议先做双边滤波而不是高斯滤波,双边滤波能在平滑噪声的同时保留边缘。
4. 边缘分析在APMCM A题中的落地路径:从边缘图到特征输出
4.1 边缘特征提取:长度、方向、曲率和密度
拿到边缘图之后,下一步是提取特征。APMCM A题的应用方向通常涉及图像测量或目标识别,所以边缘特征要能回答“边缘在哪、有多长、朝哪个方向、弯曲程度如何”这些问题。
长度特征最简单:统计边缘像素数,乘以像素物理尺寸。但要注意,边缘像素数不等于实际长度,因为8邻域连接的对角线像素实际距离是$\sqrt{2}$倍像素尺寸。精确计算要用链码或者轮廓跟踪。
方向特征用梯度方向直方图。把0°到180°分成若干bin,统计每个bin内的边缘像素数。这个特征对纹理分析很有用。比如金属表面划痕,方向集中在一个bin;自然纹理,方向分布分散。
曲率特征用边缘点的局部拟合。对每个边缘点,取前后各k个点,拟合圆或抛物线,计算曲率。曲率大的地方是角点或尖峰,曲率小的地方是直线段。
密度特征用边缘像素占整幅图的比例,或者分块统计边缘密度。这个特征对缺陷检测很直接:正常区域边缘密度稳定,缺陷区域边缘密度异常。
import cv2 import numpy as np def extract_edge_features(edge_img, pixel_size=1.0): # 边缘像素总数 edge_pixels = np.count_nonzero(edge_img) total_pixels = edge_img.size density = edge_pixels / total_pixels # 用轮廓跟踪计算长度 contours, _ = cv2.findContours(edge_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) total_length = 0 for cnt in contours: # arcLength计算轮廓周长,closed=False表示不闭合 total_length += cv2.arcLength(cnt, closed=False) # 梯度方向直方图 gx = cv2.Sobel(edge_img, cv2.CV_64F, 1, 0, ksize=3) gy = cv2.Sobel(edge_img, cv2.CV_64F, 0, 1, ksize=3) angles = np.arctan2(gy, gx) * 180 / np.pi angles = angles[edge_img > 0] angles = np.mod(angles, 180) # 映射到0-180 hist, _ = np.histogram(angles, bins=18, range=(0, 180)) return { 'edge_density': density, 'total_length_px': total_length, 'total_length_physical': total_length * pixel_size, 'angle_histogram': hist.tolist() } # 使用示例 edge = cv2.Canny(cv2.GaussianBlur(cv2.imread('test.jpg', 0), (5,5), 1.4), 50, 150) features = extract_edge_features(edge, pixel_size=0.05) for k, v in features.items(): if k != 'angle_histogram': print(f'{k}: {v:.4f}')这段代码里,cv2.arcLength计算的是轮廓的欧氏距离总和,比简单计数准确。pixel_size是每个像素对应的物理尺寸,这个参数需要根据拍摄时的标定得到。方向直方图用18个bin,每10°一个bin,对于大多数应用够用。
4.2 边缘连接与断裂修复:形态学闭运算和霍夫变换
Canny输出的边缘经常断裂,尤其是低对比度区域。断裂的边缘没法做长度测量和轮廓分析。修复方法有两种:形态学闭运算和霍夫变换连接。
形态学闭运算是先膨胀后腐蚀,能连接近距离的断裂。结构元素的大小决定连接距离。如果断裂间隙在3像素以内,用3×3的核;间隙在10像素以内,用10×10的核。但核太大会导致边缘粘连,把不该连的边缘连在一起。
# 形态学闭运算连接断裂边缘 kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) closed_edge = cv2.morphologyEx(edge, cv2.MORPH_CLOSE, kernel) # 霍夫变换检测直线段并连接 lines = cv2.HoughLinesP(edge, 1, np.pi/180, threshold=50, minLineLength=30, maxLineGap=10) line_img = np.zeros_like(edge) if lines is not None: for line in lines: x1, y1, x2, y2 = line[0] cv2.line(line_img, (x1, y1), (x2, y2), 255, 1)cv2.HoughLinesP的参数需要根据图像调整。threshold是累加器阈值,越小检测到的线越多但伪线也越多。minLineLength是最短直线长度,小于这个值的线被丢弃。maxLineGap是允许的断裂间隙,小于这个值的断裂会被连接。我一般先用形态学闭运算做粗连接,再用霍夫变换提取长直线,两者结合效果最好。
4.3 把边缘分析结果输出成可提交的格式
APMCM竞赛最终要提交结果,通常是数值表格或标注图。数值表格包括每张图的边缘密度、总长度、方向分布等。标注图是在原图上叠加边缘和特征点。
import pandas as pd # 假设有多张图,批量处理 results = [] for img_path in ['img1.jpg', 'img2.jpg', 'img3.jpg']: img = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE) blur = cv2.GaussianBlur(img, (5,5), 1.4) edge = cv2.Canny(blur, 50, 150) feat = extract_edge_features(edge, pixel_size=0.05) feat['image'] = img_path results.append(feat) df = pd.DataFrame(results) df.to_csv('edge_features.csv', index=False, encoding='utf-8-sig') print(df[['image', 'edge_density', 'total_length_physical']])输出CSV时用utf-8-sig编码,避免中文乱码。如果要做可视化提交,用cv2.addWeighted把边缘图叠加到原图上,边缘用红色显示。
注意:竞赛评审往往看重结果的可解释性。不要只提交一堆数字,要附上边缘叠加图和参数说明。参数说明包括高斯核大小、Canny阈值、形态学核大小,这些都要写清楚,否则结果无法复现。
5. 避坑指南:图像边缘分析中五个血泪教训
5.1 高斯平滑核越大越好?错,过度平滑会吞掉弱边缘
现象:用15×15高斯核平滑后,Canny几乎检测不到边缘,尤其是低对比度区域。
原因:高斯平滑的标准差$\sigma$和核大小$k$的关系是$k \approx 6\sigma + 1$。15×15的核对应$\sigma \approx 2.3$,这个尺度已经接近很多弱边缘的宽度,平滑后梯度幅值被严重衰减。
解决:核大小从3×3或5×5开始试,$\sigma$取1.0到1.4。如果噪声实在大,先用双边滤波或非局部均值去噪,再上小核高斯。我一般固定用5×5,$\sigma=1.4$,这个组合在大多数图像上表现均衡。
5.2 Canny阈值设了固定值,换张图就翻车
现象:在A图上调好的50/150,用到B图上要么全是边缘,要么几乎没有边缘。
原因:不同图像的梯度幅值分布差异巨大。固定阈值没有考虑图像本身的对比度和光照条件。
解决:用百分位数法或Otsu法自适应计算阈值。百分位数法更通用,我一般取90%分位数作为高阈值,低阈值取高阈值的0.4倍。如果图像噪声大,高阈值提到95%;如果边缘弱,降到80%。
5.3 边缘图直接做轮廓分析,结果全是碎片
现象:cv2.findContours返回几百个轮廓,大部分只有几个像素,根本没法分析。
原因:Canny输出的边缘是单像素宽度的,且存在断裂。findContours把每个连通域当作一个轮廓,断裂的边缘就变成多个碎片。
解决:先做形态学闭运算连接断裂,核大小根据断裂间隙选。然后用cv2.findContours时选择RETR_EXTERNAL只取外轮廓,或者用RETR_CCOMP分层。最后按轮廓长度过滤,丢弃小于阈值(比如20像素)的碎片。
5.4 亚像素定位没做梯度方向校正,精度反而下降
现象:亚像素拟合后,边缘位置偏移比像素级还大。
原因:梯度方向计算不准确,导致插值方向错误。Sobel算子的梯度方向在边缘附近有偏差,尤其是角点区域。
解决:用Scharr算子代替Sobel,Scharr的梯度方向精度更高。或者在亚像素拟合前,先用Zernike矩方法做一次粗定位。另外,亚像素拟合只适用于边缘方向变化平缓的区域,角点区域不要做亚像素。
5.5 忽略像素物理尺寸,测量结果没有意义
现象:计算出边缘总长度是5000像素,但不知道对应实际多少毫米。
原因:没有做相机标定,不知道每个像素对应的物理尺寸。
解决:拍摄时放一个已知尺寸的标定板(比如棋盘格),用cv2.calibrateCamera做标定,得到像素当量。如果没有标定板,至少放一把尺子,手动测量像素和物理尺寸的比例。这个比例直接乘到长度特征上,否则结果只是像素数,没有工程意义。
6. 进阶技巧:用边缘方向一致性做缺陷判别
边缘分析不只是提取边缘,还可以用边缘的统计特性做判别。我做过一个金属表面缺陷检测的项目,核心思路就是用边缘方向一致性来区分正常纹理和缺陷。
正常金属表面的纹理方向是随机的,边缘方向直方图接近均匀分布。缺陷(比如划痕)的边缘方向高度集中,直方图会出现明显的峰值。用方向直方图的熵值作为判别指标:熵越大,方向越分散,越可能是正常区域;熵越小,方向越集中,越可能是缺陷。
import numpy as np import cv2 def direction_entropy(edge_img, num_bins=18): gx = cv2.Sobel(edge_img, cv2.CV_64F, 1, 0, ksize=3) gy = cv2.Sobel(edge_img, cv2.CV_64F, 0, 1, ksize=3) angles = np.arctan2(gy, gx) * 180 / np.pi angles = angles[edge_img > 0] if len(angles) < 10: return 0.0 angles = np.mod(angles, 180) hist, _ = np.histogram(angles, bins=num_bins, range=(0, 180)) hist = hist / hist.sum() # 计算熵,忽略零概率 entropy = -np.sum(hist[hist > 0] * np.log2(hist[hist > 0])) return entropy # 分块计算熵,生成缺陷热力图 def defect_heatmap(img, block_size=64): h, w = img.shape heatmap = np.zeros((h // block_size, w // block_size)) for i in range(0, h - block_size + 1, block_size): for j in range(0, w - block_size + 1, block_size): block = img[i:i+block_size, j:j+block_size] blur = cv2.GaussianBlur(block, (5,5), 1.4) edge = cv2.Canny(blur, 50, 150) entropy = direction_entropy(edge) heatmap[i//block_size, j//block_size] = entropy return heatmap这段代码的关键参数是block_size。块太小,统计样本不足,熵值不稳定;块太大,空间分辨率低,小缺陷会被淹没。我一般取64×64,对于1000×1000的图像,得到约15×15的热力图,空间分辨率够用。
熵值的阈值需要根据正常样本的统计分布来定。我一般取正常区域熵值的均值减去2倍标准差作为缺陷判定阈值。低于这个阈值的块标记为疑似缺陷。
这个方法的局限是:它假设正常纹理方向随机。如果正常纹理本身就有方向性(比如拉丝金属),熵值本身就低,这个方法会误判。这时候需要先做方向校正,或者改用方向直方图的峰值位置作为特征。
还有一个技巧是用边缘密度和方向熵的联合分布。正常区域密度稳定、熵值高;缺陷区域密度异常、熵值低。两个特征做二维散点图,正常样本聚在一起,缺陷样本偏离。用简单的马氏距离就能做判别,不需要复杂的分类器。
最后说一个我踩过的坑:边缘方向的计算对噪声极其敏感。如果图像噪声大,Sobel梯度方向会乱跳,熵值计算完全不可靠。这时候必须先用双边滤波或非局部均值去噪,再做边缘分析。我试过在噪声图像上直接算熵,正常区域和缺陷区域的熵值分布几乎重叠,完全没法区分。去噪之后,两个分布的分离度提升了3倍以上。
希望帮到你。
本文还有配套的精品资源,点击获取