K-means聚类舌苔舌质分离及最终分割
在处理舌苔与舌质分离的图像分割问题时,采用K-means聚类方法
可以提供有效的解决方案。本方法的前沿包括几个关键的图像预处理步骤,确保聚类效果的准确性和效率。首先,进行彩色图像的均衡化,这一步骤有助于增强图像的对比度,使隐藏的细节更加明显。接着,转换到Lab颜色空间,并对a通道进行直方图均衡化,此举旨在突出图像的颜色差异,为后续的聚类分析提供更好的基础。图像归一化则是将数据缩放至特定范围内,通常是0到1之间,这有助于标准化输入数据,使模型更加稳定。最后,通过伽马变换调整图像的亮度,进一步优化视觉效果,为K-means聚类提供更均匀的数据分布,从而实现更精确的舌苔与舌质的分离。这一系列的预处理步骤不仅增强了图像的质量,也提高了聚类算法的分割性能,是现代医学图像处理中的重要技术之一。
- 彩色图像均衡化
- 转到Lab颜色空间,对a通道进行直方图均衡化,
- 图像归一化
- 伽马变换
def change_color_eH_gamma(img_path, color_pic_path):
# 彩色图像均衡化
img = cv2.imread(img_path, 1)
# Lab
img_lab = cv2.cvtColor(img, cv2.COLOR_BGR2Lab) # 转到Lab颜色空间
(l, a, b) = cv2.split(img_lab) # 通道分解
aH = cv2.equalizeHist(a) # 直方图均衡化
result_lab = cv2.merge((l, aH, b), ) # 通道合成
# 图像归一化
fi = result_lab / 255.0
# 伽马变换
gamma = 1 / 2.2
out = np.power(fi, gamma)
# image的数值本应在0-255,但是在保存时的已经被归一化了,设置在(0-1)之间,我们只需要将标准化的值乘上255
path = img_path.split('/')[-1]
path = os.path.basename(path)
save_color_path = os.path.join(color_pic_path, path)
cv2.imwrite(save_color_path, out * 255)
Kmeans
聚类为3类
def kmeans(X, n):
kmeans = MiniBatchKMeans(n_clusters=n)
kmeans.fit(X)
Y = kmeans.predict(X)
# cluster_centers = kmeans.cluster_centers_(X)
return Y
kmeans(X_, 3)
黑白分割颜色反转
舌苔、舌质分离开来
最终代码
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
from sklearn.cluster import MiniBatchKMeans
from PIL import Image
import PIL.Image as image
import cv2
import numpy as np
import os
def change_color_eH_gamma(img_path, color_pic_path):
# image = cv2.imread(IMAGE_PATH)
# # 颜色通道分离
# (B, G, R) = cv2.split(image)
# cv2.imwrite('./img/234-1_20220418_tongue_blue.png', B) # 注意顺序为B、G、R 图片主调颜色为蓝色,所以蓝色通道的图像肯定最亮\
# cv2.imwrite('./img/234-1_20220418_tongue_green.png', G)
# cv2.imwrite('./img/234-1_20220418_tongue_red.png', R) # 图片上就没什么红色,因此红色通道的图像肯定最暗
# # 观察R,G,B三通道图像的真实色彩
# zeros = np.zeros(image.shape[:2], dtype='uint8') # 注意是单通道图像,千万不能写image.shape,那就是三通道图像了
# actual_B = cv2.merge([B, zeros, zeros]) # 绿色通道,注意B与两个zeros矩阵的顺序噢,一定不能错
# actual_G = cv2.merge([zeros, G, zeros])
# actual_R = cv2.merge([zeros, zeros, G])
# cv2.imwrite('./img/234-1_20220418_tongue_actualb.png', actual_B)
# cv2.imwrite('./img/234-1_20220418_tongue_actualg.png', actual_G)
# cv2.imwrite('./img/234-1_20220418_tongue_actualr.png', actual_R)
# # 色彩空间
# hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV) # 转到HSV颜色空间
# cv2.imwrite('./img/234-1_20220418_tongue_hsv.png', hsv)
# lab = cv2.cvtColor(image, cv2.COLOR_BGR2Lab) # 转到Lab颜色空间
# cv2.imwrite('./img/234-1_20220418_tongue_lab.png', lab)
# yuv = cv2.cvtColor(image, cv2.COLOR_BGR2YUV) # 转到YUV颜色空间
# cv2.imwrite('./img/234-1_20220418_tongueyuv.png', yuv)
# 彩色图像均衡化
img = cv2.imread(img_path, 1)
# Lab
img_lab = cv2.cvtColor(img, cv2.COLOR_BGR2Lab) # 转到Lab颜色空间
(l, a, b) = cv2.split(img_lab) # 通道分解
aH = cv2.equalizeHist(a) # 直方图均衡化
result_lab = cv2.merge((l, aH, b), ) # 通道合成
# 图像归一化
fi = result_lab / 255.0
# 伽马变换
gamma = 1 / 2.2
out = np.power(fi, gamma)
# cv2.imshow("img_original", img)
# cv2.imshow("img_result_gamma_lab", out)
# image的数值本应在0-255,但是在保存时的已经被归一化了,设置在(0-1)之间,我们只需要将标准化的值乘上255
path = img_path.split('/')[-1]
path = os.path.basename(path)
save_color_path = os.path.join(color_pic_path, path)
cv2.imwrite(save_color_path, out * 255)
# cv2.imshow('result_lab', result_lab)
# cv2.imwrite('img_result_lab.png', result_lab)
# cv2.waitKey(0)
def kmeans(X, n):
kmeans = MiniBatchKMeans(n_clusters=n)
kmeans.fit(X)
Y = kmeans.predict(X)
# cluster_centers = kmeans.cluster_centers_(X)
return Y
# def MiniBatchKMeans_main(img_path):
# # 读取原始图像
# X = plt.imread(img_path)
# X = np.array(X)
# # print(X)
# X_ = X.reshape(-1, 3)
# label = MiniBatchKMeans(n_clusters=3)
# label = label.partial_fit(X_)
# label = label.predict()
# print('label: {}'.format(label))
def kmeans_main_cv2(img_path, mask_path):
img = cv2.imread(img_path)
X = cv2.imread(img_path, cv2.IMREAD_COLOR)
X = np.array(X)
row, col, dim = X.shape
X_ = X.reshape(-1, 3) # (将矩阵化为2维,才能使用聚类)
label = kmeans(X_, 3)
label = label.reshape(row, col)
# print('label: {}' .format(label.shape()))
# print('label:{}\n'.format(label))
# create a new image to save the result of K-Means
# pic_new = image.new("RGB", (col, row)) # 定义的是图像大小为y*x*3的图像,这里列在前面行在后面
pic_new = np.zeros([col, row, 3], np.uint8)
for i in range(0, col):
for j in range(0, row):
# (b, g, r) = img[i, j]
if label[j][i] == 0:
img[i, j] = (0, 0, 0) # 黑色
elif label[j][i] == 1:
img[i, j] = (0, 0, 0) # 黑色
elif label[j][i] == 2:
img[i, j] = (255, 255, 255) # 白色
pic_new[i, j] = img[i, j]
# print(pic_new)
# cv2.imshow("image", pic_new) # 显示图片,后面会讲解
img_name = img_path.split('/')[-1]
img_name = os.path.basename(img_name)
save_mask_path = os.path.join(mask_path, img_name)
cv2.imwrite(save_mask_path, pic_new)
cv2.waitKey(0) # 等待按键
def kmeans_main(img_path, mask_path):
# 读取原始图像
X = plt.imread(img_path)
X = np.array(X)
# print(X.shape)
row, col, dim = X.shape
X_ = X.reshape(-1, 3) # (将矩阵化为2维,才能使用聚类)
label = kmeans(X_, 3)
# print("label.shape=", label.shape)
# get the label of each pixel
label = label.reshape(row, col)
# print("label.shape=", label.shape)
# create a new image to save the result of K-Means
pic_new = image.new("RGB", (col, row)) # 定义的是图像大小为y*x*3的图像,这里列在前面行在后面
# print(pic_new)
for i in range(col):
for j in range(row):
if label[j][i] == 0:
pic_new.putpixel((i, j), (0, 0, 0)) # 填写的是位置为(j,i)位置的像素,列和行也是反的
elif label[j][i] == 1:
pic_new.putpixel((i, j), (0, 0, 0)) # putpixel(self, xy, color)
elif label[j][i] == 2:
pic_new.putpixel((i, j), (255, 255, 255)) # 白色
# print(pic_new)
# pic_new.save()
# pic_new = pic_new.convert('RBG')
# fig = plt.gcf()
# fig.set_size_inches(row / 300, col / 300) # dpi = 300, output = 700*700 pixels
# plt.figure(figsize=(col / 300, row / 300)) # 设置画板大小
plt.imshow(pic_new)
# plt.gca().xaxis.set_major_locator(plt.NullLocator())
# plt.gca().yaxis.set_major_locator(plt.NullLocator())
# plt.subplots_adjust(top=1, bottom=0, right=1, left=0, hspace=0, wspace=0)
# plt.margins(0, 0)
plt.axis('off') # 关掉坐标轴为 off
path = img_path.split('/')[-1]
path = os.path.basename(path)
save_mask_path = os.path.join(mask_path, path)
pic_new.save(save_mask_path)
# plt.savefig('img/7_2_tongue_1.png',dpi=500,bbox_inches = 'tight')
# eps = 0.003333
# plt.figure(figsize=(row * eps, col * eps))
# plt.savefig(save_mask_path, transparent=True, dpi=300)
# plt.xticks([])
# plt.yticks([])
# plt.show()
def add_mask2image_binary(images_path, masks_path, masked_path):
# Add binary masks to images
for img_item in os.listdir(images_path):
print(img_item)
img_path = os.path.join(images_path, img_item)
img = cv2.imread(img_path) # 1是以彩色图方式去读
jpg_img = cv2.cvtColor(img, cv2.COLOR_BGRA2BGR)
mask_path = os.path.join(masks_path, img_item[:-4] + '.png') # mask是.png格式的,image是.jpg格式的
# mask_path = cv2.imread(mask_path, 1) # 1是以彩色图方式去读
# jpg_img = cv2.cvtColor(mask_path, cv2.COLOR_BGRA2BGR)
# mask = cv2.imread(mask_path, cv2.IMREAD_GRAYSCALE)
mask = cv2.imread(mask_path, cv2.IMREAD_GRAYSCALE) # 将彩色mask以二值图像形式读取
masked = cv2.add(img, np.zeros(np.shape(img), dtype=np.uint8), mask=mask) # 将image的相素值和mask像素值相加得到结果
cv2.imwrite(os.path.join(masked_path, img_item), masked)
def picture(path, newpath):
files = os.listdir(path)
for i in files:
# print(i)
files = os.path.join(path, i)
img = Image.open(files).convert('RGB') # deep 从36位(4*8位像素, 多一位透明通道)转为24位(3*8位像素)
# print(img)
dirpath = newpath
file_name, file_extend = os.path.splitext(i)
dst = os.path.join(os.path.abspath(dirpath), file_name + '.png')
img.save(dst)
if __name__ == '__main__':
images_path = 'origin_02' # 原始文件 cv2.imread()不支持中文路径
color_pic_path = 'Lab_a_gamm_02'
# save_36_mask_path = 'gt_36'
save_24_mask_path = 'gt_fur_02'
masked_path = 'fur_02' # save image
i = 0
for img_item in os.listdir(images_path):
i = i + 1
print(img_item)
img_path = os.path.join(images_path, img_item)
change_color_eH_gamma(img_path, color_pic_path)
mask_pic_path = os.path.join(color_pic_path, img_item)
kmeans_main(mask_pic_path, save_24_mask_path) # MiniBatchKMeans_main(img_path)
# kmeans_main_cv2(mask_pic_path, save_24_mask_path)
print('finish: ', i)
# convert2jpg(masks_path, save_24_mask_path)
add_mask2image_binary(images_path, save_24_mask_path, masked_path)