黑色背景趋势线图像的有效聚类方法问询及现有实现优化请求
黑色背景趋势线图像的有效聚类方法问询及现有实现优化请求
嗨,刚看完你的问题,特别理解你现在的困扰——明明看起来趋势相似的折线图,聚类结果却只把几乎像素级一致的归到一起。核心问题其实出在你用的预训练ResNet是为通用图像分类设计的,它提取的特征更多关注纹理、边缘这些通用视觉信息,而不是折线的趋势语义。咱们一步步来调整优化:
一、先做针对性的图像预处理,剥离无关背景干扰
你的图像是黑背景+蓝线,首先要把折线从背景里单独提取出来,减少特征提取器对无关背景的关注:
import cv2 import numpy as np from PIL import Image def preprocess_trend_image(img): # 把PIL图像转成OpenCV格式 img_cv = np.array(img) img_cv = cv2.cvtColor(img_cv, cv2.COLOR_RGB2BGR) # 针对蓝线做颜色分割(HSV范围可根据实际图像微调) lower_blue = np.array([100, 50, 50]) upper_blue = np.array([130, 255, 255]) hsv = cv2.cvtColor(img_cv, cv2.COLOR_BGR2HSV) mask = cv2.inRange(hsv, lower_blue, upper_blue) # 生成只有折线的二值图(白线条+黑背景) binary_img = cv2.bitwise_and(img_cv, img_cv, mask=mask) binary_img = cv2.cvtColor(binary_img, cv2.COLOR_BGR2GRAY) binary_img = cv2.threshold(binary_img, 0, 255, cv2.THRESH_BINARY)[1] # 转回PIL格式供后续处理 return Image.fromarray(binary_img)
在load_images_from_directory函数里,加载图像后调用这个预处理:
img = Image.open(img_path).convert('RGB') img = preprocess_trend_image(img) # 新增这一行 images.append((img, filename))
二、替换特征提取方式,聚焦折线的趋势语义
通用CNN的特征不适合你的场景,建议换成直接提取折线的趋势相关特征:
方案1:提取折线的关键点坐标(最直接)
把折线的轮廓点提取出来,归一化后作为特征向量,直接反映折线的形状:
def extract_trend_features(binary_img): img_cv = np.array(binary_img) # 提取折线的轮廓 contours, _ = cv2.findContours(img_cv, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if not contours: return np.zeros(40) # 无轮廓时返回默认特征 # 取最大的轮廓(即目标折线) contour = max(contours, key=cv2.contourArea) # 简化轮廓并均匀采样20个点 sample_points = cv2.approxPolyDP(contour, epsilon=0.01*cv2.arcLength(contour, True), closed=False) sample_points = sample_points.reshape(-1, 2) # 统一特征长度为20个点(40维特征) if len(sample_points) > 20: indices = np.linspace(0, len(sample_points)-1, 20, dtype=int) sample_points = sample_points[indices] elif len(sample_points) < 20: # 插值补全点数 x = np.linspace(0, len(sample_points)-1, 20) xp = np.arange(len(sample_points)) yx = np.interp(x, xp, sample_points[:,0]) yy = np.interp(x, xp, sample_points[:,1]) sample_points = np.column_stack((yx, yy)) # 归一化坐标到[0,1]区间,消除图像尺寸影响 sample_points = (sample_points - np.min(sample_points, axis=0)) / (np.max(sample_points, axis=0) - np.min(sample_points, axis=0) + 1e-8) # 展平成一维特征向量 return sample_points.flatten()
然后把原来的extract_features函数替换成这个逻辑,直接返回关键点特征。
方案2:把折线转成时序数据提取特征
把图像的x轴看作时间轴,y轴看作数值,将折线转换成一维时序序列,再用时序特征工具提取趋势相关特征(斜率、峰值、整体趋势等):
import pandas as pd from tsfresh import extract_features def line_to_timeseries(binary_img): # 从二值图中提取每列的最高y坐标(代表折线在该x位置的高度) img_cv = np.array(binary_img) height, width = img_cv.shape timeseries = [] for x in range(width): col = img_cv[:, x] y_indices = np.where(col == 255)[0] if len(y_indices) > 0: # 取最上方的点(假设y轴向上为数值增大) timeseries.append(height - y_indices[0]) else: timeseries.append(0) return np.array(timeseries) def extract_ts_features(timeseries): # 转成tsfresh需要的格式 df = pd.DataFrame({'value': timeseries, 'time': range(len(timeseries))}) features = extract_features(df, column_id=None, column_sort='time') # 填充缺失值并归一化 features = features.fillna(0) features = (features - features.mean()) / features.std() return features.values.flatten()
三、调整聚类算法与参数
1. 优化DBSCAN参数
你当前用的eps=0.3可能太小,导致只有特征几乎完全一致的样本才会被聚类。可以用肘部法则选择合适的eps:
import matplotlib.pyplot as plt from sklearn.neighbors import NearestNeighbors def find_optimal_eps(feature_vectors): neighbors = NearestNeighbors(n_neighbors=5) neighbors_fit = neighbors.fit(feature_vectors) distances, indices = neighbors_fit.kneighbors(feature_vectors) distances = np.sort(distances, axis=0) distances = distances[:,1] # 绘制距离曲线,找拐点对应的eps值 plt.plot(distances) plt.show()
调用这个函数后,看曲线的拐点,比如如果拐点在1.2附近,就把eps设为1.2。
2. 尝试其他聚类算法
- 层次聚类(AgglomerativeClustering):可以直观控制聚类的粒度,适合你想把“相似趋势”归为一类的需求,比如设定
n_clusters=5(根据你的数据量调整)。 - K-Means:如果能大致预估聚类数量,K-Means的稳定性会比DBSCAN好,适合快速迭代测试。
四、现有代码的快速修改建议
把原来的extract_features替换成我们的关键点特征提取函数,再加上特征归一化:
def extract_features(images): features = [] for img, filename in images: feature = extract_trend_features(img) features.append((feature, filename)) # 特征归一化 feature_vectors = np.array([f[0] for f in features]) feature_vectors = (feature_vectors - feature_vectors.mean(axis=0)) / feature_vectors.std(axis=0) return [(feature_vectors[i], images[i][1]) for i in range(len(images))]
你刚接触图像处理就能写出这样的完整代码已经很棒了!先从预处理和特征提取入手调整,应该就能看到明显的聚类效果提升。如果调整过程中遇到具体问题,随时补充细节哦~
备注:内容来源于stack exchange,提问作者SpicyCurry
相关产品推荐
相关产品推荐

