You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

黑色背景趋势线图像的有效聚类方法问询及现有实现优化请求

黑色背景趋势线图像的有效聚类方法问询及现有实现优化请求

嗨,刚看完你的问题,特别理解你现在的困扰——明明看起来趋势相似的折线图,聚类结果却只把几乎像素级一致的归到一起。核心问题其实出在你用的预训练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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.14 10:54:34