基于深度学习的骨龄检测识别系统(PyTorch+Pyside6+YOLOv5模型)

骨龄是骨骼年龄的简称,需要借助于骨骼在X光摄像中的特定图像来确定。通常要拍摄左手手腕部位的X 光片,医生通过X光片观察来确定骨龄。

在2006年,《中华-05》骨龄标准被编入《中华人民共和国行业标准》,成为中国目前唯一的行业骨龄标准。而在《中华-05》中作者一般推荐使用RUS-CHN计分法。

图中黄色高亮的地方,每个高亮都是需要医生看的关节,医生做骨龄的时候,需要对每个关节,根据骨 骺的发育情况。判断对应的等级。再查表,计算13个关节的等级分数之和,到【RUS-CHN骨成熟百分位 数标准曲线】中,找到分数对应的年龄。

骨龄评估是衡量生长发育的重要方法,但传统方法繁琐耗时,依赖医生经验。

我们项目利用深度学习算法,训练模型自动识别分析X光片中的骨骼图像,实现骨龄评估的自动化与智能化。自动化评估提高工作效率,减轻医生负担,增加关注其他病情的时间。同时,提高评估准确性和客观性,避免人为误差和偏见。

此外,自动化评估有助于优化医疗资源配置,高效处理大量骨龄评估任务,释放资源关注更紧急病情,提高医疗服务质量和及时援助。

骨龄评估自动化与智能化提高效率和准确性,优化医疗资源配置,对未来医疗科技发展有重要作用,为人类健康事业贡献力量。


目录

1.数据处理

2.模型研发

3.关节筛选

4.小模型训练 

5.pyside6部署

 6.特殊功能设计

7.成果展示


在文章中,我会上传本次项目,项目中无数据集,附带txt格式标注文件,数据集和大多互联网平台数据相同,可以通过文件比对,二转请表明出处,出现任何额外收费等行为与本人无关

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 🚀 by Ultralytics, AGPL-3.0 license

# Parameters
nc: 7 # number of classes
depth_multiple: 0.67 # model depth multiple
width_multiple: 0.75 # layer channel multiple
anchors:
  - [10, 13, 16, 30, 33, 23] # P3/8
  - [30, 61, 62, 45, 59, 119] # P4/16
  - [116, 90, 156, 198, 373, 326] # P5/32

修改trian.py文件,需要修改 epochs=100 、workers=1、resume=False 的值。 

 parser.add_argument("--weights", type=str, default=ROOT / "yolov5s.pt", help="initial weights path")
    parser.add_argument("--cfg", type=str, default="", help="model.yaml path")
    parser.add_argument("--data", type=str, default=ROOT / "data/VOC.yaml", help="dataset.yaml path")
    parser.add_argument("--hyp", type=str, default=ROOT / "data/hyps/hyp.scratch-low.yaml", help="hyperparameters path")
    parser.add_argument("--epochs", type=int, default=5, help="total training epochs")
    parser.add_argument("--batch-size", type=int, default=16, help="total batch size for all GPUs, -1 for autobatch")

使用yolov5-master\detect.py来进行侦测

3.关节筛选

def bone_filter(data):
    if data.shape[0] == 21:
        # 0: Ulna
        ulna = filter(data, [0], 0)
        # 1: Radius
        radius = filter(data, [0], 1)
        # 2: MCPFirst
        mcpFirst = filter(data, [0], 2)
        # 3: MCP
        mcp = filter(data, [0, 2], 3)
        # 4: ProximalPhalanx
        proximalPhalanx = filter(data, [0, 2, 4], 4)
        # 5: DistalPhalanx
        distalPhalanx = filter(data, [0, 2, 4], 5)
        # 6: MiddlePhalanx
        middlePhalanx = filter(data, [0, 2], 6)

        return torch.cat([distalPhalanx, middlePhalanx, proximalPhalanx, mcp, mcpFirst, ulna, radius], 0)
    else:
        print("侦测结果错误")
        return
  • 检查数据形状:

    • if data.shape[0] == 21:: 确保数据的第一个维度大小为 21,这通常表示数据有 21 个类别。
  • 分类和过滤:

    • 通过调用 filter 函数,对不同的骨骼部位进行分类和过滤。每个骨骼部位的过滤条件不同:
      • ulna: 过滤类别为 0 的数据。
      • radius: 过滤类别为 1 的数据。
      • mcpFirst: 过滤类别为 2 的数据。
      • mcp: 过滤类别为 3,且类别为 0 或 2 的数据。
      • proximalPhalanx: 过滤类别为 4,且类别为 0、2 或 4 的数据。
      • distalPhalanx: 过滤类别为 5,且类别为 0、2 或 4 的数据。
      • middlePhalanx: 过滤类别为 6,且类别为 0 或 2 的数据。
  • 拼接和返回结果:

    • torch.cat(...): 将不同骨骼部位的过滤结果按指定顺序拼接起来,然后返回。

4.小模型训练 

import torch.optim
from tqdm import tqdm

import my_datasets
from torch.utils.data import DataLoader
from torchvision import models
from torch import nn


device = torch.device('cuda' if torch.cuda.is_available() else 'cup')

arthrosis = {'MCPFirst': ['MCPFirst', 11],  # 第一手指掌骨
             'DIPFirst': ['DIPFirst', 11],  # 第一手指远节指骨
             'PIPFirst': ['PIPFirst', 12],  # 第一手指近节指骨
             'MIP': ['MIP', 12],  # 中节指骨(除了拇指剩下四只手指)(第一手指【拇指】是没有中节指骨的))
             'Radius': ['Radius', 14],  # 桡骨
             'Ulna': ['Ulna', 12],  # 尺骨
             'PIP': ['PIP', 12],  # 近节指骨(除了拇指剩下四只手指)
             'DIP': ['DIP', 11],  # 远节指骨(除了拇指剩下四只手指)
             'MCP': ['MCP', 10]}  # 掌骨(除了拇指剩下四只手指)

def train(item):
    path = fr'data/arthrosis/{item}'
    # 加载数据
    train_dataset = my_datasets.My_dataset(path, mode='train')
    train_loader = DataLoader(train_dataset, batch_size=20, shuffle=True)

    test_dataset = my_datasets.My_dataset(path, mode='test')
    test_loader = DataLoader(test_dataset, batch_size=20, shuffle=True)

    # 网络
    net = models.resnet18()
    net.conv1 = nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3, bias=False)
    net.fc = nn.Linear(512, arthrosis[item][1])
    net = net.to(device)

    # 损失函数
    loss_func = nn.CrossEntropyLoss()
    # 优化器
    opt = torch.optim.Adam(net.parameters())

    best_acc = 0.
    epochs = 100
    for epoch in range(epochs):
        # 开始训练
        net.train()
        train_loss = 0.
        for img, lable in tqdm(train_loader):
            img, lable = img.to(device), lable.to(device)
            out = net(img)
            loss = loss_func(out, lable)

            opt.zero_grad()
            loss.backward()
            opt.step()
            train_loss = train_loss + loss.item()
        print(f"epoch == > {epoch}, train_loss = {train_loss/len(train_loader)}")


        # 开始验证
        net.eval()
        test_loss = 0.
        acc = 0.
        for img, label in tqdm(test_loader):
            img, label = img.to(device), label.to(device)
            out = net(img)
            loss = loss_func(out, label)
            # 准确率
            acc1 = torch.mean(torch.eq(out.argmax(dim=1), label).float())
            acc = acc + acc1.item()
            test_loss = test_loss + loss.item()
        print(f"epoch == > {epoch} , test_loss = {test_loss/len(test_loader)},  acc = {acc/len(test_loader)}")

        # 模型保存
        if acc > best_acc:
            torch.save(net.state_dict(), f"params/{item}_best.pt")
            best_acc = acc

        if acc/len(test_loader) > 0.98:
            break


if __name__ == '__main__':
    for item in arthrosis:
        print(item)
        train(item)
  • 数据准备:

    • my_datasets 模块中加载自定义的数据集类 My_dataset
    • 使用 DataLoader 加载训练集和测试集。
  • 模型定义:

    • 使用 torchvision.models 中的 resnet18 作为基础网络。
    • 修改网络的第一层卷积和全连接层,以适应单通道输入和特定类别数。
  • 训练和评估:

    • 使用交叉熵损失函数和 Adam 优化器进行训练。
    • 每个 epoch 计算训练损失和测试损失,并评估准确率。
    • 保存最佳模型,并在测试集准确率超过 98% 时停止训练。

5.pyside6部署

class MainWindow(QMainWindow, Ui_MainWindow):
    def __init__(self):
        super().__init__()
        self.setupUi(self)
        self.bing_slots()

        self.my_detect = load_files.My_Detect(r'C:\Users\Lenovo\Desktop\yolov5arth',
                                              r'C:\Users\Lenovo\Desktop\yolov5arth\best.pt')

        self.img_path = None

    #设置背景
    def set_background(self):
        background = QLabel(self)
        pixmap = QPixmap(r"C:\Users\Lenovo\Desktop\yolov5arth\555.jpg")  # 背景图片文件路径
        background.setPixmap(pixmap)
        background.resize(self.size())
        background.lower()

    def open_img(self):
        self.label_2.setText("")
        self.output.setPixmap(QPixmap())
        print("点击了【选择X光片】按钮")
        filepath = QFileDialog.getOpenFileName(self, dir=r'C:\Users\Lenovo\Desktop\yolov5arth\data\data_use', filter='*.png;*.jpg;*jpeg')
        print(filepath)
        self.img_path = filepath[0]
        self.input.setPixmap(QPixmap(filepath[0]))

    def detect_img(self):
        print("点击了【侦测】按钮")
        sex = 'boy' if self.radioButton.isChecked() else 'girl'

        path_yolov5 = r'C:\Users\Lenovo\Desktop\yolov5arth'
        path_pt = r'C:\Users\Lenovo\Desktop\yolov5arth\best.pt'
        img_path =  self.img_path
        self.model_v5 = torch.hub.load(path_yolov5,
                                       'custom',
                                       path=path_pt,
                                       source='local')
        results = self.model_v5(img_path)
        img = cv2.imread(img_path)
        # 获取检测的目标坐标点
        boxes = results.xyxy[0]  # 关节数量21
        boxes = common.bone_filter(boxes)  # 关节数量13

        
        print(self.img_path)
        result, img = self.my_detect.detect2(self.img_path, sex)

        self.label_2.setText(result)
        h, w, c = img.shape
        qimg = QImage(img, w, h, w*c, QImage.Format_RGB888)
        self.output.setPixmap(QPixmap.fromImage(qimg))


    # 绑定槽
    def bing_slots(self):
        self.pushButton.clicked.connect(self.open_img)
        self.pushButton_2.clicked.connect(self.detect_img)

在虚拟环境中部署pyside6,使用designer对ui进行设计,将ui文件转化为py文件,并绑定信号和槽

 6.特殊功能设计

在骨龄检测中,对导入图片是需要要求的,首先需要是x光片,其次是要求是左手。

    def detect_img(self):
        print("点击了【侦测】按钮")
        sex = 'boy' if self.radioButton.isChecked() else 'girl'

        # 左右手检测
        path_yolov5 = r'C:\Users\Lenovo\Desktop\yolov5arth'
        path_pt = r'C:\Users\Lenovo\Desktop\yolov5arth\best.pt'
        img_path =  self.img_path
        self.model_v5 = torch.hub.load(path_yolov5,
                                       'custom',
                                       path=path_pt,
                                       source='local')
        results = self.model_v5(img_path)
        img = cv2.imread(img_path)
        # 获取检测的目标坐标点
        boxes = results.xyxy[0]  # 关节数量21
        boxes = common.bone_filter(boxes)  # 关节数量13

        if boxes is None:
            self.label_2.setText("检测错误,请检查图片是否为x光片")
        else:
            MCPFirstCx = boxes[torch.where(boxes[:, 5] == 2)][0]
            ulnaCx = boxes[torch.where(boxes[:, 5] == 1)][0]
            if MCPFirstCx[0] < ulnaCx[0]:
                self.label_2.setText("检测到放入的是右手,请放入左手")


            else:
                print(self.img_path)
                result, img = self.my_detect.detect2(self.img_path, sex)

                self.label_2.setText(result)
                h, w, c = img.shape
                qimg = QImage(img, w, h, w*c, QImage.Format_RGB888)
                self.output.setPixmap(QPixmap.fromImage(qimg))

对函数进行重写,加入了两个if语句,便可以达到此效果。

7.成果展示

### 使用 YOLO 实现骨龄检测 #### 1. 数据准备 为了实现骨龄检测,需要收集并标注大量的 X 射线图像数据集。这些数据应包含手部或手腕部位的清晰骨骼结构,并标记出感兴趣的骨骼区域。常用的数据格式为 COCO 或 Pascal VOC 格式[^1]。 #### 2. 环境搭建 YOLOv5 的环境配置依赖 Python 和 PyTorch。以下是推荐的安装步骤: - 安装 Python 3.11.0 及其相关库。 - 安装 PyTorch 版本 `2.1.0.dev20230703` 并确保 GPU 支持 Metal(如果硬件支持),或者切换到 CPU 训练模式。 ```bash pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 ``` #### 3. 模型初始化与训练 使用官方提供的预训练权重文件 `yolov5s.pt` 进行迁移学习。这一步骤能够显著减少训练时间并提高模型性能。 ```python from yolov5 import train train.run( imgsz=640, # 输入图片尺寸 batch_size=16, epochs=50, data="path/to/data.yaml", # 数据配置文件路径 weights="yolov5s.pt" # 预训练权重路径 ) ``` 上述代码片段展示了如何调用 YOLOv5 的训练接口。 #### 4. 推理流程 完成训练后,可以通过加载最佳权重来进行推理操作。以下是一个简单的推理脚本示例: ```python import torch from PIL import Image from yolov5.models.experimental import attempt_load device = 'cuda' if torch.cuda.is_available() else 'cpu' model = attempt_load('runs/train/exp/weights/best.pt', map_location=device) def predict(image_path): image = Image.open(image_path).convert("RGB") results = model([image], size=640)[0].xyxy[0] return results.cpu().numpy() result = predict("test_image.jpg") print(result) ``` 此部分实现了从本地读取测试图片并通过已训练好的模型获取预测结果的功能。 #### 5. 结合 GUI 应用程序 对于实际应用需求,可进一步开发图形界面应用程序以方便用户交互。例如采用 Pyside6 构建桌面端工具[^2]。 --- ###
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Etherious_Young

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值