骨龄是骨骼年龄的简称,需要借助于骨骼在X光摄像中的特定图像来确定。通常要拍摄左手手腕部位的X 光片,医生通过X光片观察来确定骨龄。
在2006年,《中华-05》骨龄标准被编入《中华人民共和国行业标准》,成为中国目前唯一的行业骨龄标准。而在《中华-05》中作者一般推荐使用RUS-CHN计分法。
图中黄色高亮的地方,每个高亮都是需要医生看的关节,医生做骨龄的时候,需要对每个关节,根据骨 骺的发育情况。判断对应的等级。再查表,计算13个关节的等级分数之和,到【RUS-CHN骨成熟百分位 数标准曲线】中,找到分数对应的年龄。
骨龄评估是衡量生长发育的重要方法,但传统方法繁琐耗时,依赖医生经验。
我们项目利用深度学习算法,训练模型自动识别分析X光片中的骨骼图像,实现骨龄评估的自动化与智能化。自动化评估提高工作效率,减轻医生负担,增加关注其他病情的时间。同时,提高评估准确性和客观性,避免人为误差和偏见。
此外,自动化评估有助于优化医疗资源配置,高效处理大量骨龄评估任务,释放资源关注更紧急病情,提高医疗服务质量和及时援助。
骨龄评估自动化与智能化提高效率和准确性,优化医疗资源配置,对未来医疗科技发展有重要作用,为人类健康事业贡献力量。
目录
1.数据处理
通过对下载的数据集进行观察,可以得到,数据集是存在明显瑕疵的:数据模糊不清晰,数据量小(注意数据量虽然少,但是不增样。如果增样的化,就需要重新标注数据)。
OpenCV 直方图均衡化
什么是直方图均衡化? 是一种提高图像对比度的方法,拉伸图像灰度值范围。 简单来说, 一般情况,可以看到像素主要集中在中间的一些强度值上。 直方图均衡化要做的就是拉伸这个范围,就是像素主要几种区间。 像素分布率较低像素值,对其应用均衡化后(将像素分布较高的区间拉伸,得到 了新的直方图。
因为直方图均衡化处理之后,原来比较少像素的灰度会被分配到别的灰度去,像素相对集中, 处理后 灰度范围变大,对比度变大,清晰度变大,所以能有效增强图像。
而在直方图均衡化的对比度明显提高时,也同时提高了噪声的对比度。
通过自适应直方图均衡化,我们将输入图像划分为M × N网格。然后我们对网格中的每个单元进行均衡处理,从而获得更高质量的输出图像。
缺点是,自适应直方图均衡化的计算复杂度更高
import os
import cv2
from tqdm import tqdm
path = r"data/voc/VOCdevkit/VOC2007/JPEGImages"
if __name__ == '__main__':
for name in tqdm(os.listdir(path)):
img_path = os.path.join(path,name)
img=cv2.imread(img_path,0)
clahe=cv2.createCLAHE(tileGridSize=(3,3))
dst =clahe.apply(img)
通过循环遍历,对收集到的手掌X光片图片文件进行直方图均衡化处理,以提高图像对比度,拉伸图像灰度值范围。处理后,图像灰度范围变大,对比度变大,清晰度变大,有效增强图像质量。 直方图均衡化的作用包括:
* 增强图像对比度:扩展灰度级范围,使图像细节更清晰可见。
* 增强图像细节:使纹理、边缘等细微特征更明显。
* 提升图像质量:改善视觉效果,使图像更鲜明、生动。
* 应用于图像增强和预处理:改善视觉效果,提升后续算法性能。
而以下代码旨在从 Pascal VOC 格式的 XML 注释文件中提取所有独特的目标类别名称。通过遍历指定目录下的 XML 文件,并解析每个文件中的目标标签,代码将收集并输出所有出现过的类别。此过程有助于构建数据集中目标类别的全面列表,为后续的模型训练和数据分析提供支持。
import os
from tqdm import tqdm
import xml.etree.ElementTree as ET
path="data/voc/VOCdevkit/VOC2007/Annotations"
if __name__ == '__main__':
list_cls = []
for name in tqdm(os.listdir(path)):
xml_path = os.path.join(path,name)
tree=ET.parse(xml_path)
root=tree.getroot()
list_cls=[]
for obj in root.iter('object'):
cls=obj.findtext('name')
if cls not in list_cls:
list_cls.append(cls)
print(list_cls)
Data_getclasses:遍历指定路径下的XML标签文件,提取出所有不重复的目标类别,并将其保存在一个列表中。这对于后期数据集的统计分析、模型训练和评估等任务都很有用。
'''
拆分数据集: 训练集:验证集:测试集 = 7:2:1
'''
import os
import random
train_val_percent = 0.9
train_percent = 0.9 #这里的 train_percent 是指占 train_val_percent 中的
# xml 标签文件
base_path='data/voc/VOCdevkit/VOC2007'
xml_file_path =base_path+ r'\Annotations'
txt_save_path =base_path+r'\ImageSets'
# 判断txt_save_path文件夹路径如果不存在,就创建出来
if not os.path.exists(txt_save_path):
os.makedirs(txt_save_path)
# 统计标签文件xml的总数
total_xml = os.listdir(xml_file_path)
num = len(total_xml)
list_index = range(num) # 获取随机0 到 num-1的数字,作为索引值
# 计算训练集和验证集的数据量
train_val_num = int(num * train_val_percent)
# 计算训练集的数据量
train_num = int(train_val_num * train_percent)
# 获取列表 list_index 中 指定长度的随机数
train_val_indexs = random.sample(list_index, train_val_num)
train_indexs = random.sample(train_val_indexs, train_num)
# 打开四个文件 trainval.txt、test.txt、train.txt、val.txt 用来保存分出来的数据名称
file_train_val = open(os.path.join(txt_save_path, 'trainval.txt'), 'w')
file_train = open(os.path.join(txt_save_path, 'train.txt'), 'w')
file_test = open(os.path.join(txt_save_path, 'test.txt'), 'w')
file_val = open(os.path.join(txt_save_path, 'val.txt'), 'w')
for i in list_index:
# 获取到xml的文件名称,不要后缀
name = total_xml[i][:-4] + '\n'
# 判断当前的索引值i是在哪部分
if i in train_val_indexs:
# 在训练集或者验证集中
file_train_val.write(name)
if i in train_indexs:
# 在训练集中
file_train.write(name)
else:
# 在验证集中
file_val.write(name)
else:
# 在测试集中
file_test.write(name)
# 关闭资源
file_train_val.close()
file_train.close()
file_test.close()
file_val.close()
data_split:把数据集拆分成为:训练集、验证集、测试集 ,且拆分比例为7:2:1。将数据集拆分为训练集、验证集和测试集可以用于模型的训练和评估。训练集用于训练模型,验证集用于调整模型的超参数和防止过拟合,测试集用于最终评估模型的性能。拆分数据集可以有效地帮助训练和评估机器学习模型,提高模型的泛化能力,并确保模型在真实场景中的可靠性和效果。
import os
import shutil
import xml.etree.ElementTree as ET
from tqdm import tqdm
sets = ['train', 'test', 'val']
classes = ['Radius', 'Ulna', 'MCPFirst', 'MCP', 'ProximalPhalanx', 'DistalPhalanx', 'MiddlePhalanx']
def voc2yolo(image_id):
in_file = open(annotations_path % image_id, encoding='utf-8')
out_file = open(labels_path + r'\%s.txt' % image_id, 'w', encoding='utf-8')
tree = ET.parse(in_file)
root = tree.getroot()
size = root.find('size')
img_w = int(size.findtext('width'))
img_h = int(size.findtext('height'))
for obj in root.iter('object'):
cls = obj.findtext('name')
difficult = obj.findtext('difficult')
if cls not in classes or int(difficult) == 1:
continue
cls_id = classes.index(cls)
bndbox = obj.find('bndbox')
xmin = int(bndbox.findtext('xmin'))
ymin = int(bndbox.findtext('ymin'))
xmax = int(bndbox.findtext('xmax'))
ymax = int(bndbox.findtext('ymax'))
if xmax > img_w:
xmax = img_w
if ymax > img_h:
ymax = img_h
w = xmax - xmin
h = ymax - ymin
cx = xmin + w / 2
cy = ymin + h / 2
w = round(w / img_w, 6)
h = round(h / img_h, 6)
cx = round(cx / img_w, 6)
cy = round(cy / img_h, 6)
out_file.write(f"{cls_id} {cx} {cy} {w} {h}\n")
out_file.close()
def read_files():
for img_set in sets:
img_path = imageSets_path % (img_set)
with open(img_path, 'r', encoding='utf-8') as file:
image_ids = [item.strip() for item in file.readlines()]
list_file = open(os.path.join(base_path, f'{img_set}.txt'), 'w')
for image_id in tqdm(image_ids):
voc2yolo(image_id)
list_file.write(os.path.join(images_path, f'{image_id}.png\n'))
list_file.close()
base_path = 'data/voc/VOCdevkit/VOC2007'
imageSets_path = os.path.join(base_path, r'ImageSets\%s.txt')
annotations_path = os.path.join(base_path, r'Annotations\%s.xml')
labels_path = os.path.join(base_path, 'labels')
images_path = os.path.join(base_path, 'images')
JPEGImages_path = os.path.join(base_path, 'JPEGImages')
if __name__ == '__main__':
print("数据处理 开始")
if not os.path.exists(labels_path):
os.makedirs(labels_path)
if os.path.exists(images_path):
shutil.rmtree(images_path)
shutil.copytree(JPEGImages_path, images_path)
read_files()
print("数据处理 完成")
将VOC格式的数据集转换为YOLO格式,为后续目标检测模型训练做准备。将VOC格式的数据集转换为YOLO格式能够与YOLO算法和相关模型兼容,以便进行目标检测模型的训练和使用。
import cv2
img = cv2.imread('images/1841.png', flags=0)# 直方图均衡化
dst = cv2.equalizeHist(img)#cv2.imshow('dst', dst)
# cv2.waitKey(0)
cv2.imwrite("images/dst.png",dst)
# 自适应直方图均衡化
chahe =cv2.createCLAHE(tileGridSize=(3,3))
dst2 = chahe.apply(img)
cv2.imwrite('images/dst2.png',dst2)
对输入的灰度图像进行直方图均衡化和自适应直方图均衡化处理,并将处理后的图像保存到指定的文件中,以增强图像对比度和细节。
import os.path
import torch
import common
import numpy as np
from PIL import Image
from torch.utils.data import Dataset,DataLoader
from torchvision.transforms import Compose, ToTensor, Resize, ToPILImage, InterpolationMode
data_transforms = Compose([Resize(size=(224, 224), interpolation=InterpolationMode.NEAREST),
ToTensor()])
class My_dataset(Dataset):
def __init__(self, file_path, mode):
super().__init__()
self.data_list = []
if mode == 'train':
file_path = os.path.join(file_path, 'train.txt')
elif mode == 'test':
file_path = os.path.join(file_path, 'test.txt')
else:
print('mode 不正确')
return
with open(file_path, 'r') as f:
lines = f.readlines()
for line in lines:
# D:\myfiles\datasets\arthrosis3\arthrosis\DIP\5\DIP_1306251.png 4
info = line.strip().split()
img_path, label = info[0], info[1]
self.data_list.append([img_path, int(label)])
def __getitem__(self, idx):
img_path, label = self.data_list[idx]
img = Image.open(img_path)
img = common.trans_square(img) # 正方形
img = img.convert('L') # 灰度图
img = data_transforms(img) # CHW
label = torch.tensor([label]).squeeze()
return img, label
def __len__(self):
return len(self.data_list)
if __name__ == '__main__':
data = My_dataset("data/arthrosis/DIP", 'train')
train_loader = DataLoader(data, batch_size=10, shuffle=True)
for x,y in train_loader:
print(x.shape)
print(y.shape)
break
自定义一个数据集类,用于加载图像数据集并准备成可以用于 PyTorch 模型训练的数据格式,为模型训练提供了数据准备和加载的基础功能。
import os
import random
import glob
import cv2
from PIL import Image
def opt_img(img_path):
'''
数据预处理:自适应直方图均衡化
'''
# 自适应直方图均衡化
img = cv2.imread(img_path, 0)
clahe = cv2.createCLAHE(tileGridSize=(3, 3), clipLimit=2.0)
dst = clahe.apply(img)
cv2.imwrite(img_path, dst)
# img_path = 'DIP_58532.png'
def img_rotate(img_path, flag=5):
'''
数据增样:左右15度内选择
'''
img = Image.open(img_path)
for i in range(flag):
rota = random.randint(-15, 15)
dst = img.rotate(rota)
# img_path = 'DIP_58532{i}.png'
file_path_name, _ = img_path.split(".")
dst.save(file_path_name + f"{i}.png")
def data_img(path):
'''
图像处理
'''
# path = r'D:\myfiles\datasets\arthrosis2\arthrosis\MCP'
for folder in os.listdir(path):
folder_path = os.path.join(path, folder)
if os.path.isfile(folder_path):
continue
print(folder_path)
list_img_name = glob.glob(folder_path + '/*')
count = len(list_img_name)
print("增样前:" + str(count))
# print(folder_path)
for img_name in os.listdir(folder_path):
img_path = os.path.join(folder_path, img_name)
# print(img_path)
# 自适应直方图均衡化(去雾操作)
opt_img(img_path)
# 数据增样
img_rotate(img_path)
# 获取图片数据的数量
list_img_name = glob.glob(folder_path+'/*')
count = len(list_img_name)
# 当前等级下数量不足1000就增样
if count < 1000:
# 数据增样
img_rotate(img_path, flag=1000-count)
list_img_name = glob.glob(folder_path + '/*')
count = len(list_img_name)
print("增样后:" + str(count))
def data_split(path):
'''
数据拆分:训练集train和验证集test
'''
# path = r'D:\myfiles\datasets\arthrosis2\arthrosis\MCP'
train_ratio = 0.9
train_list = []
test_list = []
for folder in os.listdir(path):
folder_path = os.path.join(path, folder)
if os.path.isfile(folder_path):
continue
img_names = os.listdir(folder_path)
train_num = len(img_names) * train_ratio
random.shuffle(img_names)
for index, img_name in enumerate(img_names):
if index < train_num:
train_list.append(os.path.join(folder_path, img_name) + " " + str(int(folder) - 1) + '\n')
else:
test_list.append(os.path.join(folder_path, img_name) + " " + str(int(folder) - 1) + '\n')
random.shuffle(train_list)
random.shuffle(test_list)
with open(os.path.join(path, "train.txt"), 'w') as f:
f.writelines(train_list)
with open(os.path.join(path, "test.txt"), 'w') as f:
f.writelines(test_list)
if __name__ == '__main__':
path = 'data/arthrosis'
for cat in os.listdir(path):
cat_path = os.path.join(path, cat)
data_img(cat_path)
data_split(cat_path)
首先进行自适应直方图均衡化去雾,再进行数据增样,随机旋转生成多角度图像,并保存。接着遍历指定文件夹下图像进行同样处理,数量不足1000的类别进行更多增样。最后,将处理后的数据拆分为训练集和验证集,并写入对应文件。对医学影像数据进行预处理、增强和拆分,以便进行后续的机器学习模型训练和验证。
2.模型研发
我们使用的模型是yolov5。
在文件夹yolov5-master\data\coco128.yaml, 文件也可以保存在其他路径
# Classes
names:
0: Radius
1: Ulna
2: MCPFirst
3: MCP
4: ProximalPhalanx
5: DistalPhalanx
6: MiddlePhalanx
修改:yolov5-master\models\yolov5s.yaml
# YOLOv5
标签:YOLOv5,img,data,self,Pyside6,train,path,os,骨龄
From: https://blog.csdn.net/NatsuD/article/details/142055922