论文(MICCAI2021_PART5_85-95)、代码
检测解剖学Landmarks有重要意义
近些年很多深度神经网络都是高度专业化但single task with特定解剖区域
YOLO:universal anatomical landmark detection model-实现基于混合数据的端到端训练的多landmark检测任务
组成:local&global网络
local:基于UNet 去学习多域局部特征
global:并行重复的扩张卷积dilated convolutions序列,提取全局特征 进一步消除landmark位置歧义
特点:更少的参数
train:1588张、头&手&胸的X-ray、62个点
results:在多个数据集上yolo better很多
重要的参考文献:4、16、12 || 22、24
数据集:
1. 头部数据集
可用☆、说明、
2. 手部数据集
hand、手部数据集有问题、数据集目录☆、
3.胸部数据集
chest、
论文
翻译-有数据集
git clone https://github.com/ICT-MIRACLE-lab/YOLO_Universal_Anatomical_Landmark_Detection
(这里是我已经跑过之后的目录)
tree -L 2 、 tree -P *.py
data目录
数据集 数量 像素/尺寸 原图格式 标签格式 chest 279 不固定 jpg txt hand 1389 不固定w png csv ceph 400 固定1935*2400 bmp txt(两人) universal_landmark_detection目录:
├── datasets
├── networks
├── utils
usage: main.py [-h] [-C CONFIG] [-c CHECKPOINT] [-g CUDA_DEVICES] [-m MODEL]
[-l LOCALNET] [-n NAME_LIST [NAME_LIST ...]] [-e EPOCHS]
[-L LR] [-w WEIGHT_DECAY] [-s SIGMA] [-x MIX_STEP] [-u] -r
RUN_NAME -d RUN_DIR -p {train,validate,test}
optional arguments:
-h, --help show this help message and exit
-C CONFIG, --config CONFIG
-c CHECKPOINT, --checkpoint CHECKPOINT
checkpoint path
-g CUDA_DEVICES, --cuda_devices CUDA_DEVICES
-m MODEL, --model MODEL
-l LOCALNET, --localNet LOCALNET
-n NAME_LIST [NAME_LIST ...], --name_list NAME_LIST [NAME_LIST ...]
-e EPOCHS, --epochs EPOCHS
-L LR, --lr LR
-w WEIGHT_DECAY, --weight_decay WEIGHT_DECAY
-s SIGMA, --sigma SIGMA
-x MIX_STEP, --mix_step MIX_STEP
-u, --use_background_channel
-r RUN_NAME, --run_name RUN_NAME
-d RUN_DIR, --run_dir RUN_DIR
-p {train,validate,test}, --phase {train,validate,test}
2-1)示例自己完整训练-测试过程:
Train训练网络
(1)readme中训练一个U-Net模型:
python3 main.py -d ../runs -r unet2d_runs -p train -m unet2d -e 100
main.py在uLD目录下:
required参数:①-d参数:../ 返回上一级主目录创建默认名为runs文件夹作为运行目录run_dir;②-r参数:在其中创建运行文件unet2d_runs存储运行中的文件;checkpoints和yaml文件;③-p参数:指明什么阶段{train,validate,test}
optional 参数:④-m参数:选择模型model-networks-xxx ⑤-e参数:遍历次数epoch=100
(2)训练GU2Net模型:
python3 main.py -d ../runs -r GU2Net_runs -p train -m gln -l u2net -e 100
(3)载入-c checkpoint,感觉类似于将我们训练好的pth载入的过程
python3 main.py -d ../runs -r GU2Net_runs -p train -m gln -l u2net -e 100 -c CHECKPOINT_PATH
训练方式:①可以使用命令行直接训练;②也可以在run里配置参数再run;③如果想直接运行main.py,那就把requires参数里的required=True改成defult='内容'就行了
Test测试模型:readme中会自动test,也可以将-p参数改为test模式手动测试
Eval评估模型:
2-2) 也可以直接下载作者训练好的模型权重进行test和eval,给了example:
①下载并解压best.pt在YOLO/universal_landmark_detection目录下;
②test和eval模型
main.py -d ../runs -r GU2Net -p test -C config.yaml -m gln -l u2net -n chest cephalometric hand -c best.pt
evaluation.py -i ../runs/GU2Net/results/test_epoch067
补充一下argparse模块★:是一个Python模块:命令行选项、参数和子命令解析器。 argparse 模块可以让人轻松编写用户友好的命令行接口。程序定义它需要的参数,然后将弄清如何从sys.argv 解析出那些参数。 argparse 模块还会自动生成帮助和使用手册,并在用户给程序传入无效参数时报出错误信息。
使用步骤×3:
①创建解析器:parser = argparse.ArgumentParser()
②添加参数:parser.add_argument()
给属性名前加上“--”使其成为可选参数,常用方法/文档:default=‘’、type、
name or flags - 一个命名或者一个选项字符串的列表,例如foo或-f,--foo
action - 当参数在命令行中出现时使用的动作基本类型。
nargs - 命令行参数应当消耗的数目。
const - 被一些 action 和 nargs 选择所需求的常数。
default - 当参数未在命令行中出现并且也不存在于命名空间对象时所产生的值。
type - 命令行参数应当被转换成的类型。
choices - A sequence of the allowable values for the argument.
required - 此命令行选项是否可省略 (仅选项可用)。
help - 一个此选项作用的简单描述。
metavar - 在使用方法消息中使用的参数值示例。
dest - 被添加到 parse_args() 所返回对象上的属性名。
③解析参数:parser.parse_args()
在readme的结尾作者提供了yamlConfig.py和Pytorch-UNet
把训练好的best.pt放到YOLO/universal_landmark_detection目录下,配置参数,运行,报错。
(1) main.py运行命令如下:
main.py -d ../runs -r GU2Net_runs -p test -C config.yaml -m gln -l u2net -n chest cephalometric hand -c best.pt
解决遇到的问题:
安装requirments.txt有报错(不是pip而是安装包的问题,可更新一下),我单独安装了
No module named 'SimpleTK'
pip install SimpleITK
No module named 'skimage'解决1
pip install scikit-image
然后新报错:ImportError: cannot import name 'compare_psnr' from 'skimage.measure'
原因及解决:我的版本是0.19.x,原来的compare_psnr和compare_ssim已经被移除,把.measure改成.metrics并且import后面相应修改下即可
from skimage.metrics import peak_signal_noise_ratio as PSNR
from skimage.metrics import structural_similarity as SSIM
from skimage.metrics import mean_squared_error as MSE
No module named 'yaml'(在前面加一个py)
pip install pyyaml
之前遇到的一些问题及解决
AttributeError: module 'yaml' has no attribute 'FullLoader'解决
pip install --ignore-installed PyYAML
(2) evaluation.py运行命令如下:
①因为运行生成的是runs/GU2Net_runs但readme-eg中给的是GU2Net所以会有一个报错(github√)
evaluation.py-i ../runs/GU2Net_runs/results/test_epoch067
②1报错:AttributeError: module 'numpy' has no attribute 'asscalar'
原因是NumPy 1.16.0 后无asscalar了,用代替(numpy文档)
解决方案就是找到报错位置用np.ndarray.item(obj)代替了原来的.asscalar(obj)
2新报错:TypeError: descriptor 'item' for 'numpy.ndarray' objects doesn't apply to a 'numpy. float64' object类型错误:“numpy.ndarray”对象的描述符“item”不适用于“numpy.float64”对象,所以修改如下,但依旧报错。百度之后是numpy1.16之后就用Item代替原来的asscalar,(但是yolo作者给的requirments.txt中numpy的版本是1.18.5),我uninstall自己原来1.23.5版本之后安装了作者给的版本,新报错3.
numpy.asscalar(a)函数:将大小为1的数组转换为其等效的标量(scalar),从版本1.16开始不推荐使用:使用numpy.ndarray.item()代替。
参数 :
a :
ndarray
输入数组的大小为1。
返回值 :
out :
scalar
标量表示a。 输出数据类型与输入的item方法返回的类型相同。
示例>>> np.asscalar(np.array([24])) 24将数组的元素复制到标准Python标量(scalar)并返回(numpy文档),也就是获取在numpy数组上给定索引处找到的数据元素。
参数 :
*args :Arguments (variable number and type)
none:在这种情况下,该方法仅适用于具有一个元素(a.size == 1)的数组,
该元素被复制到标准Python标量对象中并返回。
int_type:此参数被解释为数组的平面索引,指定要复制并返回的元素。
int_types的tuple:功能与单个int_type参数相同,不同之处在于该参数被解释为数组的nd-index。
返回值 :
z :Standard Python scalar object数组的指定元素的副本,作为合适的Python标量
Notes
当a的数据类型为longdouble或clongdouble时,
item()
返回一个标量数组对象,因为没有可用的Python标量不会丢失信息。 除非定义了字段,否则无效数组将为item()
返回一个缓冲区对象,在这种情况下,将返回一个元组。item与
a[args]
非常相似,除了返回标准的Python标量而不是数组标量。 这有助于加快对数组元素的访问,并使用Python的优化数学对数组元素进行算术运算。示例>>> np.random.seed(123) >>> x = np.random.randint(9, size=(3, 3)) >>> x array([[2, 2, 6], [1, 3, 6], [1, 0, 1]]) >>> x.item(3) 1 >>> x.item(7) 0 >>> x.item((0, 1)) 2 >>> x.item((2, 2)) 1看了Numpy1.6.0文档之后:懵圈.jpg
3新报错:RuntimeError: implement_array_function method already has a docstring
pandas,matplotlib基于numpy开发,那么这个问题应该是matplotlib的安装版本不兼容导致。(归根结底还是numpy的问题,保存一下自己的现在的版本,不行再装回来吧)=> 卸载matplotlib安装报错,pandas也是一样的问题 =>全部卸载numpy、scipy、matplotlib、pandas安装作者给的版本安装也还是报错,所以干脆还是全部安装回来了(注意安装顺序就是上面提到的n-s-m)
没有成功,继续回到解决问题2,强行改成Int类型报错
如果要判断两个类型是否相同推荐使用isinstance()
Python中的iterable:iterable意思为迭代,可以理解为连续的一组数据,可以遍历的数据,包含内置的string、list、dict、tuple、set()
标量(scalar),与矢量相对,是只有大小,没有方向的量。
将numpy dtype转换为本机python类型:
numpy数据类型dtype转换:
python numpy 数据类型转换:
numpy 数据类型转换:
TypeError: descriptor 'item' for 'numpy.ndarray' objects doesn't apply to a 'numpy.int32' object
已知obj是元素为float64类型的list
ndarray
分析:这是一个数据类型转换,numpy-to-python,如果obj是可遍历数组型,那就遍历返回;如果是标量型,就按照标量返回,但是问题出在.asscalar函数在np1.16之后的版本被ndarray.item替代,而其无法处理标量类型的数据;所以不如直接删掉if分支直接return obj 不进行这个数据转换
③ ==|| 运行成功 but看这SDR成功检出率简直会哭出来
分析原因:在(2)的evaluation.py文化参数中epoch000就很离谱,会不会是没有训练好?=> best.pt应该不是放在主目录下,应该放在生成的runs/~/checkpoint/best.pt,因为我这里是直接test,没有train所以runs目录下没有这个需要手动创建文件夹checkpoints,414.(结果同上,一模一样) =>那应该是epoch000那里出现出现问题 =>排查之后是main.py运行命令那里错误。【不够仔细是一方面,另一方面就是将来拿到像YOLO这样readme给了比较多的示例运行命令的内容,要有耐心读完,需要整理好并知道哪个才是我要用的。】【你看嘛其实也没有那么难的,继续加油闯关!我能我行我可以!】
1、PIL.UnidentifiedImageError: cannot identify image file '../data/pelvic/jpgs/00013.jpg'
解决:方法①图片存在,但是由于某种原因打不开,可能是你复制图片的时候出了问题。(不行)方法②找到这个图片,删除,然后把原图片重新复制到这个文件夹,就解决啦。(后续是一整个不断报错,全部换了,可能是我把jpgs拖到MobaXterm的时候发生损坏,还是upload了)
2、line 84, in
TypeError: unsupported operand type(s) for /: 'str' and 'int'
尝试1:读取的landmark计算时转换为float(landmark)
报错:TypeError: float() argument must be a string or a number, not 'list' 分析:我f.readline()读取的是字符串结果,所以要先把点由str转成float,再进行计算,解决如下:
landmark1 = f.readline().rstrip('\n').split(',')
landmark = [float(i) for i in landmark1]
3、TypeError: cannot pickle 'WeakMethod' object
在torch.save(data, dest)有报错,是训练过程中的data字典保存有问题,它是weakmethod不能序列化;然后可能是scheduler这里保存出了问题。
所以注释了runner.py里面字典data中的271行:# 'scheduler': self.scheduler,可以训练了
尝试解决问题的过程:
pickler.dump(obj)是Python中pickle模块的一个函数,它可以将Python对象序列化,以便可以将其存储在文件中或传输到其他程序。
解决 TypeError: cannot pickle ‘_thread.RLock‘ object ,以 PyTorch DDP 为例
Pytorch 并行训练(DP, DDP)的原理和应用
报错是在283行,save(data,dest)出现问题,注释不保存就没有问题了!
好吧最后还是有问题,注释 逃避不掉了
PyTorch torch.optim.lr_scheduler 学习率设置 调参-- CyclicLR_zisuina_2的博客
深度学习学习率调整小结_球场书生的博客-CSDN博客
Torch.save cause TypeError cannot picke 'WeakMethod' object - PyTorch Forums
报错:
Traceback (most recent call last): File “main.py”, line 231, in main() File “main.py”, line 152, in main torch.save({ File “/opt/conda/lib/python3.8/site-packages/torch/serialization.py”, line 423, in save _save(obj, opened_zipfile, pickle_module, pickle_protocol) File “/opt/conda/lib/python3.8/site-packages/torch/serialization.py”, line 635, in _save pickler.dump(obj) TypeError: cannot pickle ‘WeakMethod’ object Traceback (most recent call last): File “main.py”, line 231, in main() File “main.py”, line 152, in main torch.save({ File “/opt/conda/lib/python3.8/site-packages/torch/serialization.py”, line 423, in save _save(obj, opened_zipfile, pickle_module, pickle_protocol) File “/opt/conda/lib/python3.8/site-packages/torch/serialization.py”, line 635, in _save pickler.dump(obj) TypeError: cannot pickle ‘WeakMethod’ object
解决:
import torch import torchvision.models as models model = models.resnet50() optimizer = torch.optim.Adam(model.parameters(), lr=1.) scheduler = torch.optim.lr_scheduler.CyclicLR(optimizer, base_lr=1., max_lr=10, step_size_up=10, cycle_momentum=False) torch.save({'model': model.state_dict(), 'optimizer': optimizer.state_dict(), 'epoch': 0, 'lr_scheduler': scheduler.state_dict(), }, "tmp.pt")
checkpoint_path ='./ckpt/'+start_time+'_rank_'+str(int(os.environ['RANK']))+'.tar' device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") model = resnet50(weights="IMAGENET1K_V2") criterion = nn.BCELoss(reduction='sum') # nn.BCEWithLogitsLoss() if args.mode=='DDP': args.distributed = True model = with_distributed(model, device, mode=args.mode) if args.resume: checkpoint_path ='./ckpt/'+args.resume+'.tar' checkpoint = torch.load(checkpoint_path, map_location=device) model.load_state_dict(checkpoint['model']) optimizer = optim.Adam(model.parameters(), lr = args.lr) #LAMB(model.parameters(), lr = args.lr) scheduler = optim.lr_scheduler.CyclicLR(optimizer, base_lr=args.lr, max_lr=args.lr*10, \ step_size_up=10, cycle_momentum=False) acc_metric = AccuracyMetric(device).to(device) data_set_start = time.time() train_bsz = args.train_batch_size test_bsz = args.test_batch_size train_data = LymphDataset(transformer=transformer_select(train=True), td_data=True) valid_data = LymphDataset(transformer=transformer_select(train=True), td_data=True, valid=True) if args.distributed: train_sampler = torch.utils.data.distributed.DistributedSampler(train_data) else: train_sampler = None data_set_end = time.time() print ("Data SET TIME", data_set_end-data_set_start) data_load_start = time.time() train_loader = torch.utils.data.DataLoader( dataset = train_data, batch_size = train_bsz, num_workers = args.workers, collate_fn = train_data.collate_fn, pin_memory=True, drop_last=True, shuffle=False, sampler=train_sampler ) valid_loader = torch.utils.data.DataLoader( dataset = valid_data, batch_size = test_bsz, num_workers = args.workers, collate_fn = valid_data.collate_fn, pin_memory=True, drop_last=True, ) data_load_end = time.time() print ("Data Loading TIME", data_load_end-data_load_start) epoch_loss , grad_norm, param_norm = [], [], [] f1_init = 0 acc_init = 0 for e in range(args.epochs): start_time = time.time() #train(model, optimizer, criterion, device, train_loader, train_bsz, epoch_loss, grad_norm , param_norm) #scheduler.step() end_time = time.time() log.info(f'Time for 1 Epoch {end_time - start_time} sec') if args.mode == 'DDP': model_without_ddp = model.module else: model_without_ddp = model if e % 1 == 0: print ("DOING") #f1, acc = test(model, criterion, device, valid_loader, test_bsz, acc_metric ) #log.info(f"F1 SCORE : {f1} , ACCURACY : {acc} ") #print ("f1") #if f1_init < f1: # f1_init = f1 # if args.mode == 'DDP': # model_without_ddp = model.module # else: # model_without_ddp = model # log.info(f'Save the weight of model with f1 score : {f1}') torch.save({ 'model': model_without_ddp .state_dict(), 'optimizer': optimizer.state_dict(), 'epoch': e, 'lr_scheduler': scheduler.state_dict(), #'args': args, }, checkpoint_path) scheduler.step()
1、训练:
2、测试 (有时候train结束会自己test,如果没有的话就自己test一下)
3、eval(得到结果与可视化)
python读取并可视化npy格式的深度图文件以及将其保存为jpg图片的方法_zeeq_的博客:npy文件是无法直接打开的,它里面包含的是一个矩阵格式的数据,可以用于存储图像。为了对其进行可视化,需要将其转换为图像。需要用到numpy包以及matplotlib包
Python实例 -- 如何将.npy文件转换为图片_-DevPress官方社区 (csdn.net)为了节省空间,有时会将.jpg文件转换为.npy文件,这样便于存储,然后需要图片的时候,再将其转为.jpg文件,这个过程是如何实现的呢?为了演示这个案例,我们分为2步。第1步:将.jpg文件保存为.npy文件import PILfrom PIL import Imagepath = './example.jpg' ## 将其保存为.npy文件image = Image.open(path)imag
main.py
1、argparse模块:命令行接口:①创建解析器②添加参数③解析参数
2、torch.autograd.detect_anomaly():torch本身提供调试模式功能,
# 正向传播时:开启自动求导的异常侦测
torch.autograd.set_detect_anomaly(True)
# 反向传播时:在求导时开启侦测
with torch.autograd.detect_anomaly():
loss.backward()
3、Runner函数、def run()
config.yaml
设置参数:epoch、transform_params、dataset/landmark/resize-dataloader-对应net的in/out-channels、gln参数、loss-lr-
损失函数reduction 有三个取值,分别为 'none'、'mean'、'sum' (以L1loss为例)① 取 'none',则返回与预测值或者真实值形状一致的 张量(直接返回n分样本的loss)②取 'mean'返回标量(会对N个样本的loss进行平均之后返回)③取 'mean'返回标量(对N个样本的loss求和)
scheduler: 'cycliclr'这里的CLR周期性学习率是Leslie Smith于2015年提出的,是一种调节LR的方法,在该方法中,设定一个LR上限和下限,LR的值在上限和下限的区间里周期性地变化。看上去,CLR似乎是自适应LR技术和SGD的竞争者,事实上,CLR技术是可以和上述提到的改进的优化器一起使用来进行参数更新的。一个cycle定义为学习率从低到高,然后从高到低走一轮所用的iteration数,stepsize指的是cycle迭代步数的一半。注意,cycle不一定必须和epoch相同,但实践上通常将cycle和epoch对应相同的iteration,有论文做过实验,他们将stepsize设成一个epoch包含的iteration数量的2-10倍,作者刚好是这样设置的(有关LR的blog整理)。
weight_decay:权重衰减系数
evaluation.py
path_dic =
font_path = './times.ttf'
threshold = [2, 2.5, 3, 4, 6, 9, 10]
ceph_physical_factor = 0.46875
wrist_width = 50 # mm
draw_text_size_factor = { 'cephalometric': 1.13, 'hand': 1, 'chest': 1.39}
一些知识点补充:
zip() 函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。如果各个迭代器的元素个数不一致,则返回列表长度与最短的对象相同,利用 * 号操作符,可以将元组解压为列表。
round()函数返回浮点数x四舍五入到指定的位数; 当参数n不存在时,round()函数的输出为整数。
但是自己尝试有一些奇怪的地方原因可能是round函数的小坑:当保留到整数的时候:python3.5的doc中,文档变成了"values are rounded to the closest multiple of 10 to the power minus ndigits; if two multiples are equally close, rounding is done toward the even choice." 如果距离两边一样远,会保留到偶数的一边。比如round(0.5)和round(-0.5)都会保留到0,而round(1.5)会保留到2。所以如果有项目是从py2迁移到py3的,可要注意一下round的地方(当然,还要注意/和//,还有print,还有一些比较另类的库)//当有保留精度要求的时候:round(2.675, 2) =2.67,这跟浮点数的精度有关。我们知道在机器中浮点数不一定能精确表达,因为换算成一串1和0后可能是无限位数的,机器已经做出了截断处理。那么在机器中保存的2.675这个数字就比实际数字要小那么一点点。这一点点就导致了它离2.67要更近一点点,所以保留两位小数时就近似到了2.67。SO:除非对精确度没什么要求,否则尽量避开用round()函数。近似计算我们还有其他的选择:使用math模块中的一些函数,比如math.ceiling(天花板除法)。 python自带整除,python2中是/,3中是//,还有div函数。 字符串格式化可以做截断使用,例如 "%.2f" % value(保留两位小数并变成字符串……如果还想用浮点数请披上float()的外衣)。 当然,对浮点数精度要求如果很高的话,请用嘚瑟馍,不对不对,请用decimal模块。
ImageDraw 模块:二维图形上,创建新图像、对现有图像进行注释或润色,以及动态生成图形以供Web使用。.retctangle是绘制矩形、.text是在给定位置绘制字符串。
universal_landmark_detection = =>dataset:各个数据集的类
chest.py:读Landmark和Image
个人总结chest.py:
①在init中初始化参数,并创建file index(根据指定参数进行筛选),进行数据集划分及索引下标对应,高斯热图。
②getitem返回index对应的值:读取图像和关键点,并生成热图。
③readLandmark读取点xy相对坐标轴位置、size、乘积表示的坐标,打包添加points中。④readImage读原图信息,resize、维度/数据类型变换之后对np.array(img)进行标准化.
存储的原图是png,标签是txt:第一行n=6,后面6行是每个店xy坐标的相对比例位置。
垃圾桶阅读笔记:
1、prefix前缀、phase、sigma大噪声多(胸部本身遮挡严重所以这里取了5另外取的10)landmark、size、use_background_channel
2、# file index 文件索引(标点工具(与二维图反)):这段代码是用来创建文件索引的,它首先从指定的文件夹中获取文件列表,然后根据指定的参数(chest_set,exclude_list,use_abnormal,phase)进行筛选,最后根据phase参数将筛选后的文件列表分别分配到训练集,验证集和测试集中,最后使用gaussianHeatmap函数创建高斯热力图,用于后续的数据增强。
files = [i[:-4] for i in sorted(os.listdir(self.pth_Image))]是指从指定的路径中获取文件列表,并对其进行排序,然后从每个文件名中删除最后四个字符,最后将结果存储在一个列表中。其中i[:-4]是Python中的切片操作,它表示从字符串i的开头到倒数第4个字符(不包括倒数第4个字符),即取出字符串i的前n-4个字符。 files = [f for f in files if any( f.startswith(st) for st in chest_set)]:创建一个列表,其中的元素是files中任何以chest_set中任何一个字符串开头的文件。其中chest_set在config.yaml中设置了 if not use_abnormal: files = [f for f in files if f[-1] == '0']:如果不使用异常值,则从文件列表中只取出最后一个字符为“0”的文件。
数据集划分7:1:2; phase根据不同的阶段(训练、验证、测试),从文件列表中取出不同数量的文件,并将其赋值给self.indexes(用于存储从文件列表中取出的文件索引)。阶段未知,则抛出异常。
self.genHeatmap = gaussianHeatmap(sigma, dim=len(size)):使用高斯热图函数gaussianHeatmap创建一个热图,其中sigma为高斯函数的标准差,dim为热图的维度,size为热图的大小,并赋值(Python中热图heatmap的维度是指热图的行数和列数,它们可以是任意数字,但必须是正整数。)
3、getitem(self, index)返回index对应的值:使用索引从self.indexes中获取文件名,然后读取图像和标记点,并使用self.genHeatmap函数生成热图,如果使用背景通道,则将热图求和,并将结果转换为tensor,最后返回ret.(根据index读取图片信息和points,生成热图并存储)
等号左边变量用逗号隔开:是Python中的解包语法,它允许将一个变量解包为多个变量,从而可以将一个变量的值赋值给多个变量。 Python中将变量转换为浮点张量的原因是,浮点张量可以更有效地处理浮点数据,而且可以更快地进行计算。 Python中热图heatmap的维度是指热图的行数和列数,它们可以是任意数字,但必须是正整数。
4、def readLandmark(self, name, origin_size):定义一个readLandmark函数,读取指定文件夹下的指定文件,将其中的坐标按照比例转换为原始大小,并将转换后的坐标存入points列表中,最后返回points列表。
比如第一个txt,这里第一行表示有6个点,第二行有两个浮点数,分别表示关键点坐标x和y的相对位置,也就是x_ratio = x_coords[i]/max_x y_ratio = y_coords[i]/max_y,这样的好处就是不管你对原图进行怎样的resize,我都可以标记出点的位置,因为我存储的就是相对位置/比例。比较复杂的就是需要对常规labels最开始只标记点的绝对坐标位置进行一个数据预处理部分。每个点存3个值:xy相对坐标轴的比例、图片size、二者乘积即坐标值
【path-文件路径字符串,f是其中的每一个文件对象/txt标签文档,因为chest的labels里txt第一行都是6也就是这里的n即landmark数量,在for循环里分别读取接下来每一行的关键点坐标,ratios就是这个点横纵坐标比率;同时zip将ratios和size按照下标对应的方式组合成,并加入ratios和图像size乘积round之后的结果/准确性保证;将pt添加到points列表中,那么原图中的点的位置/比例信息就得以保存了】代码对坐标进行了从比率到实际像素坐标的转换操作,具体步骤是:首先读取文件中的比率坐标,然后将比率坐标乘以origin_size,最后使用round()函数将结果四舍五入,最后将结果添加到points列表中。points中存储的是经过比例变化之后的坐标,也就是实际像素坐标。(实际像素和原图不是同一个东西,实际像素是指图像中每个像素的实际大小,而原图是指图像的原始大小。|| 原图也是由像素表示的,但是原图中的像素大小是固定的,而实际像素大小可以根据比率进行变化,因此实际像素和原图的像素大小是不一样的。)with open(path, 'r') as f:f是一个文件对象,它是用来读取path字符串中指定的文件内容的,而path是一个字符串,由文件夹中具体文件名+txt组成,它是通过os.path.join()函数拼接而成的。 path是一个字符串,它指定了文件的路径,而f是一个文件对象,它是用来读取path字符串中指定的文件内容的。path只是指定了文件的路径,而f则是用来读取文件内容的,所以它们的内容是不一样的。
chest对应的txt文档中一共有7行,第一行是6,也就是这里的n,被读取后进行for循环;然后剩下的6行分别由两个浮点数值,分别代表6个点的坐标。
ratios和pt是从文件中读取的两个浮点数值,它们分别代表6个点的坐标。for循环里计算的ratios和pt是通过计算每个点的横纵坐标之间的比值来计算的,比如第一个点的横纵坐标分别为x1和y1,那么ratios就是x1/y1,pt就是(x1,y1)round(r*sz)是一个四舍五入函数;tuple是Python中的一种数据结构,它是一种不可变的序列,可以用来存储一组数据,比如点的横纵坐标。round函数的作用是将ratios和self.size中的元素按照下标对应的方式组合成的元组,这样可以确保zip里的内容的存储准确性。 这段代码中的tuple里面存储了多个元素,它是通过zip函数将ratios和self.size中的元素按照下标对应的方式组合成一个元组,然后将这些元组组成一个新的可迭代对象。
计算每个特征点的x和y坐标之间的比例: for i in range(len(x_coords)):x_ratio = x_coords[i]/max_x y_ratio = y_coords[i]/max_y label.append([x_ratio, y_ratio])
5、def readImage(self, path):从给定的路径读取图像,并将其调整为cxwxh的形状,然后将其转换为numpy.ndarray,并将其转换为浮点数,最后返回该数组和原始大小。
img = img.resize(self.size)中的self.size是在定义类时传入的参数,用于指定图像的尺寸,resize函数会将图像的尺寸调整为self.size指定的尺寸。其中在init函数中,self.size = tuple(size)用于指定图像的尺寸,tuple(size)表示将size转换为元组类型。 将图像转换成numpy.ndarray格式的原因是,numpy.ndarray格式可以更方便地进行数据处理,比如图像的缩放、旋转、裁剪等操作。 mat格式是Matlab的数据格式,它可以存储多种类型的数据,而numpy.ndarray格式只能存储数值型数据。此外,mat格式支持多维数组,而numpy.ndarray格式只支持一维数组。这段代码之所以使用numpy.ndarray格式,是因为它更适合处理图像数据,因为它可以更容易地处理图像的尺寸,并且可以更容易地进行归一化处理。 通道和维度是不一样的,通道表示图像中的颜色,而维度表示图像的尺寸
np.expand_dims(np.transpose(arr, (1, 0)), 0)表示将arr的维度进行转置,即将arr的第一个维度和第二个维度互换,然后在第一个维度上增加一个维度,最后将arr的数据类型转换为float类型。
一些知识点补充:
class中的函数:
__getitem__(self,key):
返回键对应的值。 可以对对象进行[]操作,如切片,索引,iterd等高级操作。__setitem__(self,key,value):
设置给定键的值__delitem__(self,key):
删除给定键对应的元素。__len__():
返回元素的数量(查看对象长度)如果在类中定义了
__getitem__()
方法,那么他的实例对象(假设为P)就可以这样P[key]取值。当实例对象做P[key]运算时,就会调用类中的__getitem__()
方法。init初始化类去创建实例:一般为整个class提供全局变量,在这里就是为后面的getitem方法和len提供需要的变量(可以放在后面再来写)
一个函数中的变量不能传递个另一个函数中的变量,而self可以把self中指定的内容给后面的函数使用,就相当于指定了一个类中的全局变量。
getitem(idx作为索引):通过idx索引获取图片地址
channel分为3种:①最初输入的图片样本的 channels ,取决于图片类型,比如RGB;
②卷积操作完成后输出的 out_channels ,取决于卷积核的数量。此时的 out_channels 也会作为下一次卷积时的卷积核的 in_channels;③卷积核中的 in_channels ,2中已经说了,就是上一次卷积的 out_channels ,如果是第一次做卷积,就是1中样本图片的 channels 。os.listdir() 方法:返回指定的文件夹包含的文件或文件夹的名字的列表。
sorted() 函数:是应用在list上的方法,sorted 可以对所有可迭代的对象进行排序操作。list 的 sort 方法返回的是对已经存在的列表进行操作,无返回值,而内建函数 sorted 方法返回的是一个新的 list,而不是在原来的基础上进行的操作。
python切片指南:sorted函数-排序;[ ]列表;a[ : n]表示从第0个元素到第n个元素(不包括n),a[1: ] 表示该列表中的第1个元素到最后一个元素。
{ }表示字典,它是一种键值对的数据结构,用于存储键和相应的值。
图像作为函数 | Sigma对高斯噪声的影响:(1)噪音的大小是由sigma决定的。我们会将一些最小值映射到黑色,将一些最大值映射到白色,然后我们将它们分布在两者之间,假设我们有值,从负20到正20,在图像中,我们可以使-20变成黑色,+20变成白色,零变成灰色。(这里只是展示噪声,所以如果有一个非常小的sigma,几乎看不到,只是一个不变的灰色。)随着Sigma变得越来越大,可以看到越来越多的斑点。
(2)应用:output = img + noise;注意事项:在图像中减去127为黑色,加上128为白色,当我们根据强度来讨论图像中的噪声量时,它必须与图像整体范围有关。[0-1和0-255对比]
set函数是集合,里面不能包含重复的元素,接收一个list作为参数
np.expand_dims函数可以用来在指定的轴上增加一个维度,比如,如果有一个数组arr,shape为(2,3),那么使用np.expand_dims(arr, 0)可以将其转换为shape为(1,2,3)的数组。
hand.py:读Landmark和Image
存储的原图是jpg,标签是910行x75列的表all.csv文件;其中第一列为索引index,每一行相邻两个为某一个点的xy实际坐标,一幅图片共有37个点。
个人总结hand.py:√√√
①init设置参数,创建fileindex文件索引(index_set),数据集划分及索引下标对应,创高斯热图。
②getitem返回Index对应信息:index-name-字典ret,读原图信息-array;points原图li热图,统一类型array后同步数据增强,floatTensor后存到ret中(相当于把原图信息转换成方便训练的数据类型)。
③readLandmark:将热图li与原图points点定位,计算图像宽和高的缩放比例,进而得到其在热图中的位置(一个点的信息存两位置)放到points中,最后points存name, origin_size, li ④readImage:读原图img,resize后新img信息转换成array类型-arr,arr维度扩展-数据类型转换,最后返回变换后的图片array格式信息和原图size
垃圾桶阅读笔记:
1、__init__函数的作用是初始化类的属性,定义类的行为,并读取图片的数值信息,将其保存下来,以便后续处理。
DataFrame是pandas库中的一种数据结构,它是一个二维表格,可以用来存储和操作数据。DataFrame中的每一行代表一个观测,每一列代表一个变量。
i[:-4]是从字符串中去掉最后四个字符,也就是去掉文件的扩展名。
files = [i[:-4] for i in sorted(os.listdir(self.pth_Image))]:从图片文件夹中读取所有图片的文件名,并将其中的文件扩展名去掉,以列表的形式返回
.format()是Python中字符串格式化函数,它可以将变量替换到字符串中的占位符中,以便格式化字符串。它可以接受多个参数,并且可以使用索引或关键字参数来指定参数的位置。
gaussianHeatmap(sigma, dim=len(size))使用高斯热图函数gaussianHeatmap创建一个热图,其中sigma为高斯函数的标准差,dim为热图的维度,size为热图的大小。
2、getitem和chest一致:根据index创建name字典,读取原图img和origin_size;从原图中读取points并生成相应热图。即:从数据集中获取图像和标签,并对其进行数据增强,然后将其转换为FloatTensor,以便用于训练模型。
3、def readLandmark(self, name, origin_size):
r1, r2 = [i/j for i, j in zip(self.size, origin_size)]是一个列表推导式,用于计算图像的缩放比例,其中self.size表示缩放后的图像大小,origin_size表示原始图像大小,zip()函数用于将两个列表中的元素一一对应,然后使用for循环将两个列表中的元素相除,最后将结果存储在r1和r2变量中。 r1和r2变量分别存储缩放后图像的宽度和高度比例,即缩放后图像的宽度除以原始图像的宽度,缩放后图像的高度除以原始图像的高度。 points中本来包含的是原图信息,readlandmark函数用于读取热图数据,并将其与原图信息结合,最终将结果存储在points变量中。【li是前面得到的points的热图信息,采用顺序存储的一个列表;根据计算的图像缩放比例,计算出当前图中点的位置,并打包;最后points中包含的有原图name, origin_size, 热图li】
4、def readImage(self, path): 先读图;resize并将img转成np.array;cwh;类型转换
cephalometric.py:读Landmark和Image
原图是固定尺寸的bmp格式图片,标注有2个文件分别初级/高级标注txt,每个txt有27行,前19行为这19landmarks的坐标(绘图),最后8行的数值是landmark的标签,它们分别表示landmark的类型,比如1表示眼睛,2表示鼻子,3表示嘴巴,4表示耳朵等。前19行表示19个landmark坐标,他们的顺序与位置是固定的,比如第一个坐标是左眼,第二个坐标是右眼,以此类推。
垃圾桶阅读笔记:
# # todo是一种python注释语法,它的作用是提醒程序员在这里需要做一些更改或者添加一些代码。
num_landmark的参数设置方式可以为我所用(•̀ •́y
complex:复数
()元组、[ ]列表、{ }字典
(chest-labels-0011变绿色了)
yaml文件:JSON 可以看作是 YAML 的子集;YAML 能表示得比 JSON 更加简单和阅读,例如“字符串不需要引号”。所以 YAML 容易可以写成 JSON 的格式,但并不建议这种做;YAML 能够描述比 JSON 更加复杂的结构,例如“关系锚点”可以表示数据引用(如重复数据的引用)
为我所用过程中 遇到的问题记录:
1、from ..utils import gaussianHeatmap, transformer # 报错 相对导入问题ImportError: attempted relative import with no known parent package
解决:
init.py
导入networks下的模块
get_net函数用于获取不同的网络模型;
get_loss函数用于获取不同的损失函数;
get_optim函数用于获取不同的优化器;
get_scheduler函数用于获取不同的调度器。
unet2d.py:常规UNET
s.lower()是一个Python字符串函数,用于将字符串转换为小写。
第一个if not语句用于检查mid_channels变量是否为None,如果是,则将mid_channels设置为out_channels,这样做的原因:通常,在构建双卷积层时,输入通道数和输出通道数是不同的,但是中间通道数可以与输出通道数相同,这样可以减少参数的数量,提高模型的效率。
因为后面用BN所以bias设置成false?但是YOLO的unet2d里并没有=>答疑:卷积之后,如果要接BN操作,最好是不设置偏置,因为不起作用,而且占显卡内存。【】
nn.ReLU(inplace=True)是一个PyTorch函数,它将输入的数据进行非线性变换,使其落入0到1之间的范围,inplace=True表示将输入数据的值替换为变换后的值,而不是创建一个新的变量来存储变换后的值。
因为unet是分类所以最后OutConv定义的是in_channels和num_classes;这里点检测,输入in_channels输出是out_channels,这里kernel_size=1
U2Net、U2NetP分割模型训练---自定义dataset、训练代码训练自己的数据集
Q&A:
1、在unet2d中:DoubleConv加了bias=false
2、为什么Down那里nn.MaxPool2d参数stride没有设置,就是=1,正确吗?
感觉networks里面需要修改的是 count_parameters.py
plot.py可视化
mixlter.py定义了一个
MixIter
迭代器类,可以将多个迭代器混合在一起,返回的元素是这些迭代器中的随机组合,每次返回的元素个数为mix_step
可以说这个迭代器的设计初衷是为了将多个数据集混合在一起,并以一定的比例随机取样,实现数据增强的目的。在训练神经网络时,如果只使用一个数据集进行训练,可能会导致过拟合,即模型在训练集上表现好,但在测试集上表现差。因此,常常会将多个数据集混合在一起进行训练,这样可以增加模型的泛化能力,提高模型在测试集上的表现。此外,将多个数据集混合在一起还可以增加数据的多样性,从而增强模型的鲁棒性,提高模型在各种情况下的表现。因此,定义这个迭代器就是为了方便将多个数据集混合在一起,并以一定的比例随机取样,实现数据增强的目的。
可分离卷积
可分离卷积主要有两种类型:空间可分离卷积和深度可分离卷积。
空间可分离卷积:将一个卷积分成两部分(两个卷积核)的想法。之所以如此命名,是因为它主要处理图像和卷积核(kernel)的空间维度:宽度和高度。 (另一个维度,“深度”维度,是每个图像的通道数)。优点:比起卷积,空间可分离卷积要执行的矩阵乘法运算也更少。缺点:空间可分卷积的主要问题是并非所有卷积核都可以“分离”成两个较小的卷积核。 这在训练期间变得特别麻烦,因为网络可能采用所有可能的卷积核,它最终只能使用可以分成两个较小卷积核的一小部分。
深度可分离卷积:与空间可分离卷积不同,深度可分离卷积与卷积核无法“分解”成两个较小的内核。 因此,它更常用。 这是在keras.layers.SeparableConv2D或tf.layers.separable_conv2d中看到的可分离卷积的类型。|| 深度可分离卷积之所以如此命名,是因为它不仅涉及空间维度,还涉及深度维度(信道数量)。 输入图像可以具有3个信道:R、G、B。 在几次卷积之后,图像可以具有多个信道。 你可以将每个信道想象成对该图像特定的解释说明(interpret); 例如,“红色”信道解释每个像素的“红色”,“蓝色”信道解释每个像素的“蓝色”,“绿色”信道解释每个像素的“绿色”。 具有64个通道的图像具有对该图像的64种不同解释。
深度可分离卷积:一些轻量级的网络,如mobilenet中,会有深度可分离卷积depthwise separable convolution,由depthwise(DW)和pointwise(PW)两个部分结合起来,用来提取特征feature map.逐通道卷积和逐点卷积。
Depthwise Convolution完成后的Feature map数量与输入层的通道数相同,无法扩展Feature map。而且这种运算对输入层的每个通道独立进行卷积运算,没有有效的利用不同通道在相同空间位置上的feature信息。因此需要Pointwise Convolution来将这些Feature map进行组合生成新的Feature map.
扩张卷积
扩张卷积dilated conv:最早出现在DeeplLab系列中,作用是可以在不改变特征图尺寸的同时增大感受野,摈弃了pool的做法(丢失信息)。原理其实也比较简单,就是在kernel各个像素点之间加入0值像素点,变向的增大核的尺寸从而增大感受野。扩张卷积可用于图像分割、文本分析、语音识别等领域。
我们设: kernel size = k, dilation rate = d, input size = W1, output size = W2, stride=s, padding=p;
对于棋盘格问题,使用锯齿状的dilation rate如[1,2,3]
神经元感受野的值越大表示其能接触到的原始图像范围就越大,也意味着它可能蕴含更为全局,语义层次更高的特征;相反,值越小则表示其所包含的特征越趋向局部和细节。因此感受野的值可以用来大致判断每一层的抽象层次。|| 卷积层(conv)和池化层(pooling)都会影响感受野,而激活函数层通常对于感受野没有影响,当前层的步长并不影响当前层的感受野,感受野和填补(padding)没有关系
视频讲解、从概念到应用
增大感受野的方法
Leaky ReLU:首先ReLU对正数原样输出,负数直接置零。在正数不饱和,在负数硬饱和。|| 为了解决上述的dead ReLU现象。这里选择一个数,让负数区域不在饱和死掉。这里的斜率都是确定的。DL十大激活函数
机器学习中的数学 blog
局部特征与全部特征
全局图像特征是指图像的整体属性,常见的全局特征包括颜色特征、纹理特征和形状特征,比如强度直方图等。由于是像素级的低层可视特征,因此,全局特征具有良好的不变性、计算简单、表示直观等特点,但特征维数高、计算量大是其致命弱点。此外,全局特征描述不适用于图像混叠和有遮挡的情况。局部特征则是从图像局部区域中抽取的特征,包括边缘、角点、线、曲线和特别属性的区域等。常见的局部特征包括角点类和区域类两大类描述方式。
与线特征、纹理特征、结构特征等全局图像特征相比,局部图像特征具有在图像中蕴含数量丰富 ,特征间相关度小,遮挡情况下不会因为部分特征的消失而影响其他特征的检测和匹配等特点。近年来 ,局部图像特征在人脸识别 、三维重建、目标识别及跟踪 、影视制作 、全景图像拼接 等领域得到了广泛的应用。典型的局部图像特征生成应包括图像极值点检测和描述两个阶段。好的局部图像特征应具有特征检测重复率高、速度快 ,特征描述对光照、旋转、视点变化等图像变换具有鲁棒性。