文章内容结构:
一. 如何确定最佳聚类类别数?
二. 简单Kmeans例子,找到最佳的聚类类别数,并用该类别数进行聚类。
三. 对所有的图像特征找到最佳的聚类类别数,用该类别数进行聚类。
(注: 这里全部是个人经验,能提升样本标注和清洗效率,不是标准的数据处理方式,希望对您有帮助。)
--------
总结:
<1> 在《二.以聚类的方式清洗图像数据集,解决图像冗余问题 (图像特征提取+Kmeans聚类篇)》文章基础上,对向量数据找最佳聚类类别数,以确定聚类的最佳效益,最好的效果。
<2> 根据最佳聚类类别数聚类完图像特征后,从每个聚类文件夹里面取小图,并根据小图名找到对应的大图,用这样方式取到的图的类别均衡,且是最有效的图,每次新的数据集进来,都可以以这样的方式对图像进行筛选。
(注: 这里全部是个人经验,能提升样本标注和清洗效率,不是标准的数据处理方式,希望对您有帮助。)
一. 如何确定最佳聚类类别数?
(1)当设置的聚类类别数从1开始增加,聚类的效果会慢慢变好到慢慢变差,当设置的聚类类别数大于数据本身类别数时,聚类效果的收益最差,所以找到最佳的聚类类别数,也是聚类效果最好的那个拐点,是至关重要的。
(2)上一轮inner减去下一轮的inner的差值,差值越大,聚类效果越好,直到差值连续变小时,出现的拐点就是最佳的聚类数量。
二. 简单Kmeans例子,找到最佳的聚类类别数,并用该类别数进行聚类。
# -*- encoding:utf-8 -*-
from sklearn.datasets import make_blobs
from sklearn.cluster import KMeans
import math
import matplotlib.pyplot as plt
def cal_distance(v1,v2):
return sum([ math.pow(s1-s2,2) for [s1,s2] in zip(v1,v2)])
def cal_cluster_distance(label_X,centers):
center_distance_num=[[0,0] for _ in centers]
for label,x in label_X:
center=centers[label]
d=cal_distance(center,x)
center_distance_num[label][0]+=d
center_distance_num[label][1]+=1
d=sum([ sum_distance/num for [sum_distance,num] in center_distance_num])
return d
def find_clusters_num(X,max_num,min_num):
k_inner={}
#从k=2遍历到k=10
for k in range(min_num,max_num+1):
model=KMeans(n_clusters=k)
model.fit(X)
y_pred = model.predict(X)
label_X=zip(y_pred,X)
centers=model.cluster_centers_.tolist()
inner=cal_cluster_distance(label_X,centers)
k_inner[k]=inner
# 绘制k值与内聚度的关系图
plt.plot(list(k_inner.keys()), list(k_inner.values()), marker='o')
plt.xlabel('Number of clusters')
plt.ylabel('Inertia')
plt.title('Elbow Method For Optimal k')
plt.savefig("./test_kmeans_besk_k.png")
plt.show()
# 用于存储拐点的列表
turning_points = []
# 遍历 k_inner_items,寻找拐点
k_inner_items = list(k_inner.items()) # 转为 [(key1, value1), (key2, value2), ...]
for i in range(1, len(k_inner_items) - 1):
prev_value = k_inner_items[i - 1][1]
current_value = k_inner_items[i][1]
next_value = k_inner_items[i + 1][1]
# 判断拐点
if current_value > prev_value and current_value > next_value:
turning_points.append((k_inner_items[i], '向下拐点'))
elif current_value < prev_value and current_value < next_value:
turning_points.append((k_inner_items[i], '向上拐点'))
# 输出拐点
for point in turning_points:
print(f'拐点: {point[0]}, 类型: {point[1]}')
return turning_points[0][0][0]
# X为样本特征,Y为样本簇类别,共1000个样本,每个样本2个特征,对应x和y轴,共4个簇,
# 簇中心在[-1,-1], [0,0],[1,1], [2,2], 簇方差分别为[0.4, 0.2, 0.2]
X, y = make_blobs(n_samples=1000, n_features=2, centers=[[-1, -1], [0, 0], [1, 1], [2, 2]],cluster_std=[0.4, 0.2, 0.2, 0.2])
best_k=find_clusters_num(X,1000,2) # 从最小值2开始到最大值1000
print("best_k=", best_k)
# 找到最佳的聚类类别数之后,开始正式聚类
model=KMeans(n_clusters=best_k)
model.fit(X)
y_pred = model.predict(X)
print(y_pred)
下面是输出结果:
最佳聚类效果,效益最高的聚类类别数是4(是从2开始)。然后把聚类类别数设置为4,再进行聚类。
三. 对所有的图像特征找到最佳的聚类类别数,用该类别数进行聚类。
import os
import cv2
import numpy as np
from PIL import Image
import onnxruntime as ort
import shutil
from sklearn.cluster import KMeans
from sklearn.preprocessing import Normalizer
from tqdm import tqdm
import math
import matplotlib.pyplot as plt
# 图像预处理函数
def preprocess_image(image_path):
roi_frame= cv2.imread(image_path)
width = roi_frame.shape[1]
height = roi_frame.shape[0]
if (width != CLASSIFY_SIZE) or (height != CLASSIFY_SIZE) :
# 图像瓶子矫正,长边缩放到224,短边同比例缩放,再一起放到224*224的灰边图上
if width > height:
# 将图像逆时针旋转90度
roi_frame = cv2.rotate(roi_frame, cv2.ROTATE_90_COUNTERCLOCKWISE)
new_height = CLASSIFY_SIZE
new_width = int(roi_frame.shape[1] * (CLASSIFY_SIZE / roi_frame.shape[0]))
roi_frame = cv2.resize(roi_frame, (new_width, new_height))
# 计算上下左右偏移量
y_offset = (CLASSIFY_SIZE - roi_frame.shape[0]) // 2
x_offset = (CLASSIFY_SIZE - roi_frame.shape[1]) // 2
gray_image = np.full((CLASSIFY_SIZE, CLASSIFY_SIZE, 3), 128, dtype=np.uint8)
# 将调整大小后的目标图像放置到灰度图上
gray_image[y_offset:y_offset + roi_frame.shape[0], x_offset:x_offset + roi_frame.shape[1]] = roi_frame
# # 显示结果
# cv2.imshow("gray_image", gray_image)
# cv2.waitKey(1)
# 将图像转为 rgb
gray_image = cv2.cvtColor(gray_image, cv2.COLOR_BGR2RGB)
else:
gray_image = cv2.cvtColor(roi_frame, cv2.COLOR_BGR2RGB)
img_np = np.array(gray_image).transpose(2, 0, 1).astype(np.float32)
# 假设模型需要[0,1]归一化
img_np = img_np / 255.0
# 均值,方差
mean = np.array([0.485, 0.456, 0.406],dtype=np.float32).reshape(3, 1, 1)
std = np.array([0.229, 0.224, 0.225],dtype=np.float32).reshape(3, 1, 1)
img_np= (img_np - mean)/std
return np.expand_dims(img_np, axis=0)
# 安装 pip install onnxruntime-gpu
def get_onnx_providers():
# 检查是否安装了GPU版本的ONNX Runtime
all_provider = ort.get_available_providers()
if "CUDAExecutionProvider" in all_provider:
providers = [
("CUDAExecutionProvider", {
"device_id": 0,
"arena_extend_strategy": "kNextPowerOfTwo",
"gpu_mem_limit": 6 * 1024 * 1024 * 1024, # 限制GPU内存使用为2GB
"cudnn_conv_algo_search": "EXHAUSTIVE",
"do_copy_in_default_stream": True,
}),
"CPUExecutionProvider"
]
print("检测到NVIDIA GPU,使用CUDA加速")
return providers
else:
print("未检测到NVIDIA GPU,使用CPU")
return ["CPUExecutionProvider"]
def cal_distance(v1,v2):
return sum([math.pow(s1-s2,2) for [s1,s2] in zip(v1,v2)])
def cal_cluster_distance(label_X,centers):
center_distance_num=[[0,0] for _ in centers]
for label,x in label_X:
center=centers[label]
d=cal_distance(center,x)
center_distance_num[label][0]+=d
center_distance_num[label][1]+=1
d=sum([ sum_distance/num for [sum_distance,num] in center_distance_num])
return d
def find_best_clusters_num(normalized_features,max_num,min_num):
k_inner={}
#从k=2遍历到k=10
# for k in tqdm(range(min_num,max_num+1)):
for k in tqdm(range(min_num, max_num + 2, 100)):
# K-means聚类(基于余弦相似度)
kmeans_model = KMeans(n_clusters=k, random_state=42)
y_pred_clusters = kmeans_model.fit_predict(normalized_features)
# model=KMeans(n_clusters=k)
# model.fit(X)
# y_pred = model.predict(X)
label_X=zip(y_pred_clusters, normalized_features)
centers=kmeans_model.cluster_centers_.tolist()
inner=cal_cluster_distance(label_X,centers)
k_inner[k]=inner
# 绘制k值与内聚度的关系图
plt.plot(list(k_inner.keys()), list(k_inner.values()), marker='o')
plt.xlabel('Number of clusters')
plt.ylabel('Inertia')
plt.title('Elbow Method For Optimal k')
plt.savefig("./8_kmeans_besk_k.png")
plt.show()
# 用于存储拐点的列表
turning_points = []
# 遍历 k_inner_items,寻找拐点
k_inner_items = list(k_inner.items()) # 转为 [(key1, value1), (key2, value2), ...]
for i in range(1, len(k_inner_items) - 1):
prev_value = k_inner_items[i - 1][1]
current_value = k_inner_items[i][1]
next_value = k_inner_items[i + 1][1]
# 判断拐点
if current_value > prev_value and current_value > next_value:
turning_points.append((k_inner_items[i], '向下拐点'))
elif current_value < prev_value and current_value < next_value:
turning_points.append((k_inner_items[i], '向上拐点'))
# 输出拐点
for point in turning_points:
print(f'拐点: {point[0]}, 类型: {point[1]}')
return turning_points[0][0][0]
if __name__ =="__main__":
# ONNX模型路径
MODEL_PATH = "/home/xxx/Downloads/best_190labels_cls_modified.onnx"
# 原图像文件夹路径
IMAGE_DIR = "08以图搜图_找相似度/99_test_datasets/xxx/8"
# 分类结果输出路径
OUTPUT_DIR = "08以图搜图_找相似度/99_test_datasets/xxx/8_kmeans_besk_k_classify"
# 推断图像尺寸
CLASSIFY_SIZE = 224
# 手动划分分类数量
NUM_CLUSTERS = 3000
# 创建输出文件夹
os.makedirs(OUTPUT_DIR, exist_ok=True)
print("ONNX Runtime版本:", ort.__version__)
print("可用执行器:", ort.get_available_providers())
# 可用执行器: ['TensorrtExecutionProvider', 'CUDAExecutionProvider', 'AzureExecutionProvider', 'CPUExecutionProvider']
# 加载ONNX模型(动态获取输入/输出名称)
ort_session = ort.InferenceSession(
MODEL_PATH,
providers=get_onnx_providers()
)
# 确保输出名称正确
input_name = ort_session.get_inputs()[0].name
output_name = ort_session.get_outputs()[0].name
# 提取特征向量
features = []
image_paths = []
print("====开始对所有图像推理, 提取特征====")
for filename in tqdm(os.listdir(IMAGE_DIR)):
if filename.lower().endswith((".png", ".jpg", ".jpeg")):
path = os.path.join(IMAGE_DIR, filename)
try:
# 前处理
input_tensor = preprocess_image(path)
# 推断
feature = ort_session.run([output_name], {input_name: input_tensor})[0]
# 确保特征展平为1D
features.append(feature.reshape(-1))
image_paths.append(path)
except Exception as e:
print(f"Error processing {filename}: {str(e)}")
print("+++++提取特征结束+++++")
# L2归一化(使欧氏距离等价于余弦相似度)
normalizer = Normalizer(norm="l2")
normalized_features = normalizer.fit_transform(features)
print("==== 开始: 寻找聚类最佳的聚类类别数量 ====")
# 寻找聚类最佳的聚类类别数量
best_num_clusters=find_best_clusters_num(normalized_features,3000,1) # normalized_features为归一化后的所有图像样本特征, 3000最大聚类数,1为最小聚类数
print ("最佳的聚类类别数量为:", best_num_clusters)
print("+++++ 结束: 寻找聚类最佳的聚类类别数量 +++++")
print("==== 开始: 正式聚类 ====")
# K-means聚类
kmeans = KMeans(n_clusters=best_num_clusters, random_state=42)
clusters = kmeans.fit_predict(normalized_features)
print("+++++ 结束: 正式聚类 +++++")
print("==== 开始: 移动分类小图 ====")
# 保存结果
for cluster_id in range(best_num_clusters): # NUM_CLUSTERS
cluster_dir = os.path.join(OUTPUT_DIR, f"cluster_{cluster_id}")
os.makedirs(cluster_dir, exist_ok=True)
for path, cluster_id in zip(image_paths, clusters):
dest_dir = os.path.join(OUTPUT_DIR, f"cluster_{cluster_id}")
shutil.copy(path, dest_dir)
print(f"++++ 分类完成!结果保存在 {OUTPUT_DIR} +++++")
以下得到最佳的聚类数量是2501类别。
我用的是1.9w图像数据运行得到的图像特征在不同聚类类别数下的结果,在聚类类别数在2500类附近时,聚类效果收益已经很小了。
四. 根据聚类结果取图像数据
根据上述“三. 对所有的图像特征找到最佳的聚类类别数,用该类别数进行聚类。”聚类完图像特征后,从每个聚类文件夹里面取小图,并根据小图名找到对应的大图,用这样方式取到的图的类别均衡,且是最有效的图,每次新的数据集进来,可以以这样的方式对图像进行筛选。