首页 分享 基于Resnet18的指甲病图像智能诊断模型 内附代码

基于Resnet18的指甲病图像智能诊断模型 内附代码

来源:花匠小妙招 时间:2026-03-17 02:04

一、社会背景

       随着我国社会经济的发展,人民群众的卫生服务需求也在不断变化升级,各种专科疾病都越来越强烈地要求能得到高质量的专业治疗。其中指(趾)甲功能的独特性和病变部位的特殊性,不同程度地影响着患者的生活质量。研究结果显示,大多数甲病患者认为甲的异常为他们的生活带来了负面的影响。同时,我国人口老龄化问题的不断加剧,老年人的指甲和趾甲也随着增龄不断老化,表现为甲板的色泽、厚度、硬度及形状等方面出现异常或病变,如甲癣、嵌甲等,特别是以下六种甲病(如下图)的临床分类诊疗,这些被甲病困扰着的老年人们需要得到及时帮助。

3375477b92b5428e8afd944538584877.png

       指甲病的发展现状迫切地要求我们建立一个指甲病临床分类的简易方法,本研究使用 Pytorch 深度学习框架,在ImageNet预训练图像分类模型搭建 ResNet网络模型,优化网络参数和结构,基于 深度学习 的图像分类方法,通过数据清洗、图像数据预处理、数据加载、模型设计与搭建、进行不同周期的训练与测试,在训练过程中,记录训练集和测试集的损失函数、准确率、Precision、Recall、f1-score等评估指标,使用wandb可视化面板监控。为后续的新图像预测、测试集评估、可解释性分析、模型部署,奠定算法基础。

       对模型进行评估,得到指甲病的 6 分类最佳模型及其参数,并建立网络应用,进行指甲病图片诊断和识别,最终能够应用于指甲病种类的分类诊断中。

       数据集为真实情况下手机拍摄的指甲病图片,其中拍照视角、图片背景以及大小均不一致,并且每种疾病类别的图片数量不一致。决赛阶段中,将提供参赛者训练集,包括图片和人工标注的疾病类型,其中图片为.jpg格式,并通过excel表格给定每个图片对应的疾病类型,包含银屑病甲、甲沟炎、甲真菌病、甲黑线和甲母痣等多种指甲病的临床图片。提供200张图片及其标记信息,具体文件夹结构如下:

|

|

|

|

|

 二、技术背景

       在一般神经网络中,普遍认为卷积层和池化层的层数越多,获取到的图片特征信息越全,学习效果也就越好。但是在实际的试验中发现,随着卷积层和池化层的叠加,不但没有出现学习效果越来越好的情况,反而两种问题:梯度消失、梯度爆炸以及退化问题。

       使用ResNet图像识别网络,较以往传统基于 CNN 的图像识别网络(如VGG网络等)不同,ResNet类型的网络增加了“短接”(shortcut)机制,也可理解为捷径(如下图)。让特征矩阵隔层相加。

d2541b5ca80e4878a81d9a37d6b71241.png

       为了解决深层网络中的退化问题,可以人为地让 神经网络 某些层跳过下一层神经元的连接,隔层相连,弱化每层之间的强联系。这种神经网络就被称为残差网络 ResNet。ResNet提出了 residual结构(残差结构)来减轻了一般神经网络模型随着层数的增加,预测效果反而越来越差的退化问题。下图是使用residual结构的卷积网络,可以看到随着网络的不断加深,效果并没有变差,而是变的更好了。

63995b65ff404bc5a5b12016dc2377a1.png

       梯度消失就是当每一层的误差梯度小于1,反向传播时,网络越深,梯度越趋近于0。梯度爆炸就是若每一层的误差梯度大于1,反向传播时,网络越深,梯度越来越大。为了解决梯度消失或梯度爆炸问题,ResNet提出通过数据的预处理以及在网络中使用 BN(Batch Normalization)层来解决。也就是批量标准化处理,将一批数据的feature map满足均值为0,方差为1的分布规律。

       本研究在指甲病分类问题上应用ResNet网络模型,采用迁移学习方法,即不使用较小的数据样本量直接训练模型巨大的随机初始化的参数,而是在装载了预训练参数的模型上,在模型输出端更换适合本研究分类情况的全连接层,然后再使用本研究数据训练网络,进行参数微调,避免因数据量与算力的不足导致模型不收敛。代码实现基于深度学习框架Pytorch,采用Python 3.7语言编写,代码运行环境为Windows 11系统,GPU为Nvidia Geforce GTX 1650 11GB, epoch 设置为500,batchsize设置为64,训练过程中,运行完所有的epoch,保存其中在验证集上准确率表现最好的那一次的模型参数。

三、技术路线

 1、图片信息统计和预处理

       首先,对所有图像的长宽进行统计,以方便后续对图片进行归一化处理。 

import os

import numpy as np

import pandas as pd

import cv2

from tqdm import tqdm

import matplotlib.pyplot as plt

from scipy.stats import gaussian_kde

dataset_path = 'data_full'

os.chdir(dataset_path)

os.listdir()

df = pd.DataFrame()

for fruit in tqdm(os.listdir()):

os.chdir(fruit)

for file in os.listdir():

try:

img = cv2.imread(file)

df = df.append({'类别':fruit, '文件名':file, '图像宽':img.shape[1], '图像高':img.shape[0]}, ignore_index=True)

except:

print(os.path.join(fruit, file), '读取错误')

os.chdir('../')

os.chdir('../')

x = df['图像宽']

y = df['图像高']

xy = np.vstack([x,y])

z = gaussian_kde(xy)(xy)

idx = z.argsort()

x, y, z = x[idx], y[idx], z[idx]

plt.figure(figsize=(10,10))

plt.scatter(x, y, c=z, s=40, cmap='Spectral_r')

plt.tick_params(labelsize=15)

xy_max = max(max(df['图像宽']), max(df['图像高']))

plt.xlim(xmin=0, xmax=xy_max)

plt.ylim(ymin=0, ymax=xy_max)

plt.ylabel('height', fontsize=25)

plt.xlabel('width', fontsize=25)

plt.savefig('图像尺寸分布.pdf', dpi=120, bbox_inches='tight')

plt.show()

python

       可以画出图片长宽散点图。画出图片长宽散点图后,发现原始图像长宽像素分布十分离散,如下图所示。

4d44497ff2f944ecaacb8af4ae28425d.png

       发现原始图像长宽像素分布十分离散,不适合进行统一裁剪再进行归一化处理,如果这样做,会导致部分指甲病灶区域消失。故本研究先将图片统一压缩为720×720像素的矩形图片,方便后续处理。

       然后,将所有类型的所有图像按照1:3划分测试集和训练集,如下图所示。得到了划分后的 数据集 ‘data_split’

457d9c11f4f6497e918654b022763f4d.png

import os

import shutil

import random

import pandas as pd

dataset_path = 'data_full'

dataset_name = dataset_path.split('_')[0]

print('数据集', dataset_name)

classes = os.listdir(dataset_path)

len(classes)

classes

os.mkdir(os.path.join(dataset_path, 'train'))

os.mkdir(os.path.join(dataset_path, 'val'))

for fruit in classes:

os.mkdir(os.path.join(dataset_path, 'train', fruit))

os.mkdir(os.path.join(dataset_path, 'val', fruit))

test_frac = 0.3

random.seed(123)

df = pd.DataFrame()

print('{:^18} {:^18} {:^18}'.format('类别', '训练集数据个数', '测试集数据个数'))

for fruit in classes:

old_dir = os.path.join(dataset_path, fruit)

images_filename = os.listdir(old_dir)

random.shuffle(images_filename)

testset_numer = int(len(images_filename) * test_frac)

testset_images = images_filename[:testset_numer]

trainset_images = images_filename[testset_numer:]

for image in testset_images:

old_img_path = os.path.join(dataset_path, fruit, image)

new_test_path = os.path.join(dataset_path, 'val', fruit, image)

shutil.move(old_img_path, new_test_path)

for image in trainset_images:

old_img_path = os.path.join(dataset_path, fruit, image)

new_train_path = os.path.join(dataset_path, 'train', fruit, image)

shutil.move(old_img_path, new_train_path)

assert len(os.listdir(old_dir)) == 0

shutil.rmtree(old_dir)

print('{:^18} {:^18} {:^18}'.format(fruit, len(trainset_images), len(testset_images)))

df = df.append({'class': fruit, 'trainset': len(trainset_images), 'testset': len(testset_images)},

ignore_index=True)

shutil.move(dataset_path, dataset_name + '_split')

df['total'] = df['trainset'] + df['testset']

df.to_csv('数据量统计.csv', index=False)

python

        分割后的数据集结构如下:

|--data_split

|--train

|--jiagouyan

|--jiaheixian

|--jiamuzhi

|--jiazhenjunbing

|--qianjia

|--yinxuebingjia

|--val

|--jiagouyan

|--jiaheixian

|--jiamuzhi

|--jiazhenjunbing

|--qianjia

|--yinxuebingjia

python

 2、训练

(1)调用 cuda

       (还不会安装和调用cuda的小伙伴可以自行搜索学习)

import torch

print(torch.__version__)

print(torch.cuda.is_available())

device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')

print('device', device)

python

       显示‘cuda:0’即表示cuda可用。

(2)对训练集

       ①进行裁剪缩放,将给定图像随机裁剪为不同的大小和宽高比,然后缩放所裁剪得到的图像为制定的大小;即先随机采集,然后对裁剪得到的图像缩放为同一大小。该操作的含义在于:即使只是该病变地方的一部分,我们也认为这是该类疾病。

       ②以给定的概率随机水平旋转给定的PIL的图像,默认为0.5。目的在于使得疾病图片反转或旋转之后,仍能认为是该类疾病。

       ③对图像进行归一化处理,将图片转成神经网络能够接受的tensor格式。再用Normalize使原像素值分布到[-1,1]之间。Mean和std的数值采用imagenet中得出的平均数,即mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]。

train_transform = transforms.Compose([transforms.RandomResizedCrop(224),

transforms.RandomHorizontalFlip(),

transforms.ToTensor(),

transforms.Normalize([0.485, 0.456, 0.406],

[0.229, 0.224, 0.225])

])

python

(3)对测试集

       ①对图片进行准确的裁剪缩放,将给定图像裁剪为相同的大小和宽高比,然后缩放所裁剪得到的图像为制定的大小,对裁剪得到的图像缩放为同一大小。

       ②取每张图片中心224×224区域矩形的图片。

       ③对图像进行归一化处理,将图片转成神经网络能够接受的tensor格式。再用Normalize使原像素值分布到[-1,1]之间。Mean和std的数值采用imagenet中得出的平均数,即mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]。

test_transform = transforms.Compose([transforms.Resize(224),

transforms.CenterCrop(200),

transforms.ToTensor(),

transforms.Normalize(

mean=[0.485, 0.456, 0.406],

std=[0.229, 0.224, 0.225])

])

python

(4)载入数据集

       数据集中有标注好的图片名称和对应的甲病类型。根据这个数据集,将每一项的类别和索引号一一对应,生成‘idx_to_labels.npy’,‘idx_to_labels.npy’的两个索引。

       分别定义训练集和测试集的数据加载器Dataloader。

dataset_dir = 'data_split'

train_path = os.path.join(dataset_dir, 'train')

test_path = os.path.join(dataset_dir, 'val')

print('训练集路径', train_path)

print('测试集路径', test_path)

from torchvision import datasets

train_dataset = datasets.ImageFolder(train_path, train_transform)

test_dataset = datasets.ImageFolder(test_path, test_transform)

class_names = train_dataset.classes

n_class = len(class_names)

train_dataset.class_to_idx

idx_to_labels = {y:x for x,y in train_dataset.class_to_idx.items()}

np.save('idx_to_labels.npy', idx_to_labels)

np.save('labels_to_idx.npy', train_dataset.class_to_idx)

from torch.utils.data import DataLoader

BATCH_SIZE = 64

train_loader = DataLoader(train_dataset,

batch_size=BATCH_SIZE,

shuffle=True,

num_workers=0

)

test_loader = DataLoader(test_dataset,

batch_size=BATCH_SIZE,

shuffle=False,

num_workers=0

)

python

       注意:dataloader中的num_workers多线程运行数量在Windows运行环境中只能设置为0,在Linux和Mac中可根据实际情况选择。

一个batch的测试:

       以训练过程中的一个batch为例。在一个batch中,返回了64个3通道的224×224的彩色图像,其中一次标注如下所示:

de609835511f4dafa4dac22b7e08f997.png

        如第一个数字1表示类别1:甲母痣。该图像的像素分布是以0为均值的,有正有负的值。如下图所示:

0836162b10524734a2d5ff0ddde6d434.png

       该图像如图所示:

029346026648403796b6060cb3300d35.png

c3ed6b00f8834fc798f7c6d01b1c2de8.png

        调出原始图像,如上图,发现是甲母痣的疾病图片。

images, labels = next(iter(train_loader))

print(images.shape)

print(labels)

images = images.numpy()

print(images[5].shape)

plt.hist(images[5].flatten(), bins=50)

plt.show()

idx = 1

plt.imshow(images[idx].transpose((1,2,0)))

plt.title('label:'+str(labels[idx].item()))

plt.show()

label = labels[idx].item()

print(label)

pred_classname = idx_to_labels[label]

print(pred_classname)

idx = 1

mean = np.array([0.485, 0.456, 0.406])

std = np.array([0.229, 0.224, 0.225])

plt.imshow(np.clip(images[idx].transpose((1,2,0)) * std + mean, 0, 1))

plt.title('label:'+ pred_classname)

plt.show()

python

(5)训练模型

        指甲病预测模型与imagenet中预置的模型有较大差别,但是仍是处于现实生活中,且也是处于太阳光线或者人造光线下,故不能采用微调模型最后一层全链接分类层的方法,也不能采用随机初始化模型全部参数,从头训练所有层的方法。前者会导致准确率不高,后者会导致训练用时过长。故选择以imagenet权重为初始化的,微调训练所有层的方法。

        在每个batch中,先获得一个batch的数据和标注信息,然后输入模型,执行一次前项预测,获得当前batch所有图像的预测类别logit分数,然后由logit分数计算当前batch中,每个样本的平均交叉熵损失函数值。在反向传播前,先清除梯度,然后优化更新。我们可以获得当前batch所有图像的预测类别结果。按照上述方法遍历每一个epoch,得到最终的类别预测结果。

from torchvision import models

import torch.optim as optim

model = models.resnet18(pretrained=True)

model.fc = nn.Linear(model.fc.in_features, n_class)

optimizer = optim.Adam(model.fc.parameters())

model = model.to(device)

criterion = nn.CrossEntropyLoss()

EPOCHS = 500

images, labels = next(iter(train_loader))

images = images.to(device)

labels = labels.to(device)

outputs = model(images)

print(outputs.shape)

loss = criterion(outputs, labels)

optimizer.zero_grad()

loss.backward()

optimizer.step()

_, preds = torch.max(outputs, 1)

print(preds)

for epoch in tqdm(range(EPOCHS)):

model.train()

for images, labels in train_loader:

images = images.to(device)

labels = labels.to(device)

outputs = model(images)

loss = criterion(outputs, labels)

optimizer.zero_grad()

loss.backward()

optimizer.step()

python

        训练结束后,保存模型,使用torch.save保存为后缀名为.pth的模型文件。后续可以使用模型对其他图像进行预测。

torch.save(model, 'checkpoint/data_pytorch_C1.pth')

python

3、测试

        在测试集上初步测试模型的准确率。在上述预测步骤中,不再回传和计算梯度,从测试集data_loder中迭代地获取每一个batch的数据和标注,得到每一个batch的预测结果,当预测正确时进行累加,计算模型预测准确率。

        最终得到模型预测准确率为86.962%左右。

model.eval()

with torch.no_grad():

correct = 0

total = 0

for images, labels in tqdm(test_loader):

images = images.to(device)

labels = labels.to(device)

outputs = model(images)

_, preds = torch.max(outputs, 1)

total += labels.size(0)

correct += (preds == labels).sum()

print('测试集上的准确率为 {:.3f} %'.format(100 * correct / total))

python

        得到训练集的损失函数图像如下:

2a02bfc08f314fafa3254cf504730664.png

        得到训练集的准确率图像如下:

e69be3efa1984aa79e2c1aa81a35b2a3.png

 四、算法说明

        Resnet残差网络是由来自Microsoft Research的4位学者提出的卷积神经网络,在2015年的ImageNet大规模视觉识别竞赛(ImageNet Large Scale Visual Recognition Challenge, ILSVRC)中获得了图像分类和物体识别的优胜。 残差网络的特点是容易优化,并且能够通过增加相当的深度来提高准确率。其内部的残差块使用了跳跃连接,缓解了在深度神经网络中增加深度带来的梯度消失问题。该网络出自论文《Deep Residual Learning for Image Recognition》。

        测试结果仅代表在基于深度学习框架Pytorch,采用Python 3.7语言编写,代码运行环境为Windows 11系统,GPU为Nvidia Geforce GTX 1650 11GB,epoch设置为500,batchsize设置为64下的运行结果。且由于随机裁剪的随机性,甚至在同一环境每一次不同运行轮数中都会产生不同结果,经过100次运行结果的统计,准确率范围在:78.223%至88.161%之间,平均值为86.962%。

        在本文中,可以进一步验证其roc曲线和auc值等统计值,以进一步验证模型准确性。

        声明:本文仅为算法应用、复现以及交流学习所用。

相关知识

一种基于深度学习的柑橘黄龙病检测方法、装置及系统与流程
【图像分割】基于阈值法实现大脑图像分割附Matlab代码
基于智能诊断技术的果园远程咨询系统
基于切面识别的房间隔缺损智能辅助诊断
基于机器学习算法的农作物病虫害智能诊断与防治研究
花卉病害智能检测与防控系统
基于残差网络的花卉及其病害识别的研究与应用
面向新冠肺炎的全诊疗流程的智能筛查、诊断与分级系统
基于Zigbee的智能温室大棚系统(附详细使用教程+完整代码+原理图+完整课设报告)
基于OpenCV的鲜花的图像分类系统详细设计与具体代码实现

网址: 基于Resnet18的指甲病图像智能诊断模型 内附代码 https://www.huajiangbk.com/newsview2580718.html

所属分类:花卉
上一篇: 几种龟头炎的图片区分和4种龟头炎
下一篇: SUNFLOWER MATCH

推荐分享