1. 制作数据集
我做的是三目标检测,人狗马目标检测
然后使用精力标注助手对图像进行目标标注。到出为xml文件。
得到这样格式的数据集
1. 1 读取xml文件,求建议框,向量数据库
import os
from xml.dom.minidom import parse
from kmeans import kmeans, avg_iou
import numpy as np
'''
minidom.parse(filename) #加载和读取xml文件
doc.documentElement #获取xml文档对象
node.getAttribute(AttributeName) #获取xml节点属性值
node.getElementsByTagName(TagName) #获取xml节点对象集合
#.getElementsByTagName(),根据name查找根目录下的子节点
node.childNodes #获取子节点列表
node.childNodes[index].nodeValue #获取xml节点值
node.firstChild #访问第一个节点
n.childNodes[0].data #获得文本值
node.childNodes[index].nodeValue #获取XML节点值
doc=minidom.parse(filename)
doc.toxml('utf-8') #返回Node节点的xml表示的文本
'''
dirpath=r'D:/AI/study_ai/processed_images/outputs/'#文件地址
file_name_list=os.listdir(dirpath)#获取文件地址下的所有文件名称
# print(file_name)
dataset = []
for file_name in file_name_list:#循环列表中的所有文件名j=file_name.split('.')[0]xml_doc=os.path.join(dirpath,file_name)#拼接文件地址# print(xml_doc)dom=parse(xml_doc)#解析address文件,返回DOM对象,address为文件地址root=dom.documentElement# 创建根节点。每次都要用DOM对象来创建任何节点,获取根节点,赋值给root作为节点名称img_name=root.getElementsByTagName('path')[0].childNodes[0].data#获取某个元素节点的文本内容,先获取子文本节点,然后通过“data”属性获取文本内容,这里获取图片名字# print(img_name)img_size=root.getElementsByTagName('size')[0]#获取图片尺寸大小,返回一个列表的迭代器# print(img_size)objects=root.getElementsByTagName('object')#获取项目节点名称img_w=img_size.getElementsByTagName('width')[0].childNodes[0].data#获取图片的宽# print(img_w)img_h=img_size.getElementsByTagName('height')[0].childNodes[0].data#获取图片的高img_c=img_size.getElementsByTagName('depth')[0].childNodes[0].data#获取图片的通道数# print(img_name)# print(img_w,img_h,img_c)f = open("Parse_label.txt", 'a', encoding='utf-8') # 打开txt文件f.writelines("D:/AI/study_ai/processed_images/%s.jpg" % j) # 将"images/%s.jpg" % j写入txt文件for box in objects:#循环objects的所有标注的框# print(len(box.getElementsByTagName("name")))# print(len(box.getElementsByTagName("name")))for i in range(len(box.getElementsByTagName("name"))):#循环框的类别名字的长度方便下面判断cls_name=box.getElementsByTagName('name')[i].childNodes[0].data#获取框的类别的名字if cls_name=='人':#判断名字和分类是否相同cls_num=0#给每类编号elif cls_name=='马':cls_num=1elif cls_name=='狗':cls_num=2else:cls_num=3# print(cls_name,cls_num)x1=abs(int(box.getElementsByTagName('xmin')[i].childNodes[0].data))#获取框的左上角x1的坐标y1=abs(int(box.getElementsByTagName('ymin')[i].childNodes[0].data))#获取框的左上角y1的坐标x2=abs(int(box.getElementsByTagName('xmax')[i].childNodes[0].data))#获取框的右下角角x2的坐标y2=abs(int(box.getElementsByTagName('ymax')[i].childNodes[0].data))#获取框的右下角角y2的坐标print(cls_name,(x1,y1,x2,y2))box_w=x2-x1#计算框的宽高box_h=y2-y1cx=int(x1+box_w/2)#计算框的中心点坐标并转成int类型cy=int(y1+box_h/2)print(cls_name,(cx,cy,box_w,box_h))dataset.append([box_w, box_h]) #将图片宽高加入集合f.writelines(" {} {} {} {} {} \t".format(cls_num, cx, cy, box_w, box_h))#按行将起写入txt文件f.writelines("\n")#换行f.flush()##flush() 方法是用来刷新缓冲区的,即将缓冲区中的数据立刻写入文件,同时清空缓冲区,不需要是被动的等待输出缓冲区写入。#一般情况下,文件关闭后会自动刷新缓冲区,但有时你需要在关闭前刷新它,这时就可以使用 flush() 方法。f.close()dataset = np.array(dataset)
out = kmeans(dataset, 9)
# clusters =
print(out)
#data里有很多框,out里面只有9个框,avg_iou得出的是
print(f"Accuracy: {100*avg_iou(dataset, out):.2f}%") # 相似度比较
1.2 得到建议框
'建议框和图片尺寸'
IMG_HEIGHT = 416
IMG_WIDTH = 416
CLASS_NUM = 4"anchor box是对coco数据集聚类获得的建议框"
ANCHORS_GROUP_KMEANS = {52: [[10, 13], [16, 30], [33, 23]],26: [[30, 61], [62, 45], [59, 119]],13: [[116, 90], [156, 198], [373, 326]]}'自定义的建议框'
ANCHORS_GROUP = {13: [[321, 209], [247, 365], [213, 159]],26: [[204, 94], [196, 211], [154, 127]],52: [[140, 233], [75, 187], [51, 108]]}# 计算建议框的面积
ANCHORS_GROUP_AREA = {13: [x * y for x, y in ANCHORS_GROUP[13]],26: [x * y for x, y in ANCHORS_GROUP[26]],52: [x * y for x, y in ANCHORS_GROUP[52]],
}
if __name__ == '__main__':for feature_size, anchors in ANCHORS_GROUP.items(): # 循环字典里面的键值对,并将键和值分别赋值给两个参数print(feature_size)print(anchors)for feature_size, anchor_area in ANCHORS_GROUP_AREA.items():print(feature_size)print(anchor_area)
3.得到向量数据库
2. 自定义数据集
import torch
from torch.utils.data import Dataset,DataLoader
import torchvision
import numpy as np
import cfg
import os
from PIL import Image
import mathLABEL_FILE_PATH = "data/Parse_label.txt" # 标签数据地址
IMG_BASE_DIR = "D:/AI/study_ai/processed_images" # 数据总地址transforms = torchvision.transforms.Compose([torchvision.transforms.ToTensor()
])#对数据进行处理def one_hot(cls_num, i):#做一个onehot分类函数b = np.zeros(cls_num)#编一个类别数的一维o数组b[i] = 1.#在指定的位置填充1return bclass MyDataset(Dataset):def __init__(self):with open(LABEL_FILE_PATH,encoding="UTF-8") as f:self.dataset = f.readlines()def __len__(self):return len(self.dataset)def __getitem__(self, index):labels = {}line = self.dataset[index]# images/01.jpg 2 163 218 237 251 1 290 205 170 268strs = line.split()_img_data = Image.open(os.path.join(strs[0]))#打开图片数据img_data = transforms(_img_data)_boxes = np.float32(strs[1:])boxes = np.split(_boxes,len(_boxes)//5)for feature_size, anchors in cfg.ANCHORS_GROUP.items():#设置labels是的格式labels[feature_size] = np.zeros(shape=(feature_size, feature_size, 3, 5 + cfg.CLASS_NUM),dtype=np.float32)for box in boxes:#循环框的个数cls, cx, cy, w, h = boxcx_offset, cx_index = math.modf(cx * feature_size / cfg.IMG_WIDTH)cy_offset, cy_index = math.modf(cy * feature_size / cfg.IMG_WIDTH)for i, anchor in enumerate(anchors):#循环3种建议框,索引分别赋值给i和anchoranchor_area = cfg.ANCHORS_GROUP_AREA[feature_size][i]p_w, p_h = w / anchor[0], h / anchor[1]true_area = w * h#实际标签框的面积iou = min(true_area, anchor_area) / max(true_area, anchor_area)labels[feature_size][int(cy_index), int(cx_index), i] = np.array([iou, cx_offset, cy_offset, np.log(p_w), np.log(p_h),*one_hot(cfg.CLASS_NUM, int(cls))])return labels[13], labels[26], labels[52], img_data
if __name__ == '__main__':# x=one_hot(4,2)# print(x)data = MyDataset()dataloader = DataLoader(data,3,shuffle=True)for i,x in enumerate(dataloader):print(x[0].shape)print(x[1].shape)print(x[2].shape)print(x[3].shape)for i,(target_13, target_26, target_52, img_data) in enumerate(dataloader):print(target_13.shape)print(target_26.shape)print(target_52.shape)print(img_data.shape)
labels的格式:
labels[feature_size] = np.zeros(shape=(feature_size, feature_size, 3, 5 + cfg.CLASS_NUM),dtype=np.float32)
解释:这个是在做labels的格式,往矩阵中全部塞0
labels[feature_size][int(cy_index), int(cx_index), i] = np.array([iou, cx_offset, cy_offset, np.log(p_w), np.log(p_h),*one_hot(cfg.CLASS_NUM, int(cls))])
解释:这个是往有目标的网格中塞值,值为 [建议框和真实框的iou,中心点偏移量,宽高偏移量,类别] 5+class_nums个值
3.训练模型
import os
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader
from MyDatasets1 import MyDataset
from Net_Module import MainNet# ' torch.argmax(output, dim=1)'
class Trainner:#定义训练类def __init__(self):self.save_path='models/pet.pth'#实例化保存的地址self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu") # 判断用cudaself.net=MainNet().to(self.device)#实例化网络if os.path.exists(self.save_path):#判断是否有之前训练过的网络参数,有的话就加载参数接着训练self.net.load_state_dict(torch.load(self.save_path))self.traindata=MyDataset()#实例化制作的数据集self.trainloader=DataLoader(self.traindata,batch_size=2,shuffle=True)#加载数据集self.conf_loss_fn=nn.BCEWithLogitsLoss()#定义自信度的损失函数self.crood_loss_fn=nn.MSELoss()#定义偏移量的均方差损失函数self.cls_loss_fn=nn.CrossEntropyLoss()#定义多分类的交叉熵损失函数self.optimzer=optim.Adam(self.net.parameters())#定义网络优化器def loss_fn(self,output,target,alpha)# 数据形状[N,C,H,W]-->>标签形状[N,H,W,C]output=output.permute(0,2,3,1)#将形状转成和标签一样#通过reshape变换形状 [N,H,W,C]即[N,H,W,45]-->>[N,H,W,3,15],分成3个建议框每个建议框有15个值output=output.reshape(output.size(0),output.size(1),output.size(2),3,-1)# output=output.cuda().double()#将数据转入cuda并转成double(),因为交叉熵输入类型是double双精度类型mask_obj=target[...,0]>0#拿到自信大于0的掩码值返回其索引,省略号代表前面不变在最后的15个值里面取第0个,第0个为自信度# print(target.shape)# print(mask_obj.shape)output_obj=output[mask_obj]#通过掩码获取对应位置的输出值# print(output_obj.shape)target_obj=target[mask_obj]#通过掩码获取对应位置的标签# print(target_obj.shape)# exit()loss_obj_conf=self.conf_loss_fn(output_obj[:,0],target_obj[:,0])#算自信都损失loss_obj_crood=self.crood_loss_fn(output_obj[:,1:5],target_obj[:,1:5])#算偏移量损失loss_obj_cls=self.cls_loss_fn(output_obj[:,5:],torch.argmax(target_obj[:,5:],dim=1))#可以用这个也可以用下面这个,# 或用下面这个也可以,用下面这个不需要对标签取最大值所以,用上面这个要对标签取最大值所以,因为输出是标量# loss_obj_cls=self.conf_loss_fn(output_obj[:,5:],target_obj[:,5:])loss_obj=loss_obj_conf+loss_obj_crood+loss_obj_cls#正样本总的损失mask_noobj=target[...,0]==0#获取负样本的掩码output_noobj=output[mask_noobj]#根据掩码获取数据target_noobj=target[mask_noobj]#根据掩码获取标签loss_noobj=self.conf_loss_fn(output_noobj[:,0],target_noobj[:,0])#计算负样本损失,负样本自信度为0,因此这里差不多就是和0 做损失loss=alpha*loss_obj+(1-alpha)*loss_noobj #这里权重调整正负样本训练程度,如果负样本多久可以将权重给大点,负样本少就可以把权重给小点return loss#将损失返回给调用方def train(self):#定义训练函数self.net.train()#这个可用可不用表示训练,网络如果用到batchnormal及dropout等,但测试里面必须添加self.net.eval()epochs=0#训练批次while True:print(epochs)for target_13,target_26,target_52,img_data in self.trainloader:#循环trainloader,将3个返回值分别复制给对应变量target_13,target_26,target_52,img_data=(target_13.to(self.device),target_26.to(self.device),target_52.to(self.device),img_data.to(self.device))#将数据和标签转入cudaoutput_13,output_26,output_52=self.net(img_data)#将数据传入网络获得输出'''output_13 : N 45 13 13 output_26 : N 45 26 26 output_52: N 45 52 52 '''loss_13=self.loss_fn(output_13,target_13,0.7)loss_26=self.loss_fn(output_26,target_26,0.7)loss_52=self.loss_fn(output_52,target_52,0.7)loss=loss_13+loss_26+loss_52#总损失self.optimzer.zero_grad()#清空梯度loss.backward()#反向求导self.optimzer.step()#更新梯度print(loss.item())# torch.save(self.net.state_dict(),self.save_path)if epochs % 10==0:torch.save(self.net.state_dict(),self.save_path.format(epochs))#保存网络参数epochs+=1
if __name__ == '__main__':obj = Trainner()obj.train()
解释: 将数据传入网络获得输出三种侦测头的结果,然后求三种侦测头的损失结果求和,再进行梯度下降不断学习
3.1 损失函数
def loss_fn(self,output,target,alpha)# 数据形状[N,C,H,W]-->>标签形状[N,H,W,C]output=output.permute(0,2,3,1)#将形状转成和标签一样#通过reshape变换形状 [N,H,W,C]即[N,H,W,45]-->>[N,H,W,3,15],分成3个建议框每个建议框有15个值output=output.reshape(output.size(0),output.size(1),output.size(2),3,-1)# output=output.cuda().double()#将数据转入cuda并转成double(),因为交叉熵输入类型是double双精度类型mask_obj=target[...,0]>0#拿到自信大于0的掩码值返回其索引,省略号代表前面不变在最后的15个值里面取第0个,第0个为自信度# print(target.shape)# print(mask_obj.shape)output_obj=output[mask_obj]#通过掩码获取对应位置的输出值# print(output_obj.shape)target_obj=target[mask_obj]#通过掩码获取对应位置的标签# print(target_obj.shape)# exit()loss_obj_conf=self.conf_loss_fn(output_obj[:,0],target_obj[:,0])#算自信都损失loss_obj_crood=self.crood_loss_fn(output_obj[:,1:5],target_obj[:,1:5])#算偏移量损失loss_obj_cls=self.cls_loss_fn(output_obj[:,5:],torch.argmax(target_obj[:,5:],dim=1))#可以用这个也可以用下面这个,# 或用下面这个也可以,用下面这个不需要对标签取最大值所以,用上面这个要对标签取最大值所以,因为输出是标量# loss_obj_cls=self.conf_loss_fn(output_obj[:,5:],target_obj[:,5:])loss_obj=loss_obj_conf+loss_obj_crood+loss_obj_cls#正样本总的损失mask_noobj=target[...,0]==0#获取负样本的掩码output_noobj=output[mask_noobj]#根据掩码获取数据target_noobj=target[mask_noobj]#根据掩码获取标签loss_noobj=self.conf_loss_fn(output_noobj[:,0],target_noobj[:,0])#计算负样本损失,负样本自信度为0,因此这里差不多就是和0 做损失loss=alpha*loss_obj+(1-alpha)*loss_noobj #这里权重调整正负样本训练程度,如果负样本多久可以将权重给大点,负样本少就可以把权重给小点return loss#将损失返回给调用方
解释:
1.模型输出的预测结果格式为(N,C,H,W)转换为(N,H,W,3,5+class_nums)的格式,why?因为保持和labels也就是真实标签格式一致。
2.对真实标签中target[...,0]>0得到的是置信度大于0的部分返回其索引(其实就是将有目标位置作为正样本,返回正样本的索引),索引的格式是(N,H,W,3)这样就能定位到具体哪个网格上,坐标为多少的格子上去。得到例如下图的掩码
3. output_obj = output[mask_obj] # 通过正样本掩码获取模型预测结果对应位置的输出值 target_obj=target[mask_obj]#通过正样本掩码获取真实结果对应位置的标签
4. 对output_obj切片,分别对自信度,偏移量,分类进行求损失,再求正样本的损失和
5. 同理对负样本做以上同一操作
6.loss=alpha*loss_obj+(1-alpha)*loss_noobj这个的意思是,因为往往负样本数量远远大于正样本,为了让模型正确学习。所有使用一个参数来平衡正负样本的影响。
3.2 yolov3的不同
我认为yolov3相对于目标分类问题,变化在于
1. 在一张图片上画了网格,让卷积不再只是对整张图片卷积,而是具体到对单个格子卷积。
2.优化了主干网络,会有三种输出,即三种侦测头。使大目标用13的侦测头,小目标用52的侦测头
3. 优化了标签的格式,由原来的labels格式一维[0,1,2,1,1]用来只做分类,到yolov3中标签格式
不仅是从一维升维到4维度,且从数组变为了字典(k:v)k是侦测头类型,v为具体标签。
这样不仅可以将原本只能做分类问题,变为既可以做分类同时也能检测到目标具体位置。
4. 测试
from Net_Module import *
import cfg
import torch
import numpy as np
import PIL.Image as pimg
import PIL.ImageDraw as draw
import tool1
import os
os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE"
import matplotlib.pyplot as plt
from PIL import Image,ImageFont,ImageDrawdevice = "cuda" if torch.cuda.is_available() else "cpu"class Detector(torch.nn.Module):#定义侦测模块def __init__(self,save_path):super(Detector, self).__init__()self.net = MainNet()#实例化网络self.net.load_state_dict(torch.load(save_path,map_location=device))#加载网络参数self.net.eval()#固化参数即去掉batchnormal的作用def forward(self, input, thresh, anchors):#定义前向运算,并给三个参数分别是输入数据,自信度阀值,及建议框output_13, output_26, output_52 = self.net(input)#将数据传入网络并获得输出#(n,h,w,3,15)其中n,h,w,3做为索引,即这里的idxs_13,表示定义在那个格子上idxs_13, vecs_13 = self._filter(output_13, thresh)#赛选获取13*13特侦图自信度合格的自信度的索引和15个值,赛选函数下面自己定义的boxes_13 = self._parse(idxs_13, vecs_13, 32, anchors[13])#对上面输出的两个值进行解析,获得最终的输出框,解析函数在下面idxs_26, vecs_26 = self._filter(output_26, thresh)#赛选获取26*26特侦图自信度合格的自信度的索引和15个值,赛选函数下面自己定义的boxes_26 = self._parse(idxs_26, vecs_26, 16, anchors[26])#对上面输出的两个值进行解析,获得最终的输出框,解析函数在下面idxs_52, vecs_52 = self._filter(output_52, thresh)#赛选获取52*52特侦图自信度合格的自信度的索引和15个值,赛选函数下面自己定义的boxes_52 = self._parse(idxs_52, vecs_52, 8, anchors[52])#对上面输出的两个值进行解析,获得最终的输出框,解析函数在下面return torch.cat([boxes_13, boxes_26, boxes_52], dim=0)#将三种框在0维度按顺序拼接在一起并返回给调用方def _filter(self, output, thresh):#赛选过滤自信度函数,将自信度合格留下来output = output.permute(0, 2, 3, 1) # 数据形状[N,C,H,W]-->>标签形状[N,H,W,C],,因此这里通过换轴# 通过reshape变换形状 [N,H,W,C]即[N,H,W,45]-->>[N,H,W,3,15],分成3个建议框每个建议框有15个值output = output.reshape(output.size(0), output.size(1), output.size(2), 3, -1)mask = output[..., 0] > thresh#获取输出自信度大于自信度阀值的目标值的掩码(即布尔值),idxs = mask.nonzero()#将索引取出来其形状(N,H,W,3)vecs = output[mask]#通过掩码获取对应的数据# print(vecs)return idxs, vecs#将索引和数据返回给调用方def _parse(self, idxs, vecs, t, anchors):#定义解析函数,并给4个参数分别是上面筛选合格的框的索引,9个值(中心点偏移和框的偏移即类别数),# t是每个格子的大小,t=总图大小/特征图大小,anchors建议框anchors = torch.Tensor(anchors)#将建议框转为Tensor类型#idx形状(n,h,w,3)a = idxs[:, 3]#表示拿到3个框对应的索引confidence = torch.sigmoid(vecs[:, 0])#获取自信度vecs里面有5+类别数个元素,第一个为自信度,因此取所有的第0个,输出的置信度会大于1,这里可以用其压缩到0-1之间。_classify = vecs[:, 5:]#获取分类的类别数if len(_classify) == 0:#判断类别数的长的是否为0为0返回空,避免代码报错classify = torch.Tensor([])else:classify = torch.argmax(_classify, dim=1).float()#如果不为0,返回类别最大值的索引,这个索引就代表类别# idx形状(n,h,w,3),vecs(iou,p_x,p_y,p_w,p_h,类别)这里p_x,p_y代表中心点偏移量,p_w,p_h框偏移量cy = (idxs[:, 1].float() + vecs[:, 2]) * t#计算中心点cy(h+p_y)cx = (idxs[:, 2].float() + vecs[:, 1]) * t#计算中心点cx(h+p_x)#a = idxs[:, 3] # 表示拿到3个对应的索引w = anchors[a, 0] * torch.exp(vecs[:, 3])#计算实际框的宽为w,w=建议框的宽*框的偏移量p_wh = anchors[a, 1] * torch.exp(vecs[:, 4])#计算实际框的高为h,h=建议框的高*框的偏移量p_hx1 = cx - w / 2#计算框左上角x的坐标y1 = cy - h / 2#计算框左上角y的坐标x2 = x1 + w#计算框右下角x的坐标y2 = y1 + h#计算框右下角y的坐标out = torch.stack([confidence,x1,y1,x2,y2,classify], dim=1)#将自信度坐标和类别按照一轴即列的方向重组堆叠在一起return outif __name__ == '__main__':save_path = "models/pet.pth"detector = Detector(save_path)#实例化侦测模块img_path = 'D:\AI\study_ai\processed_images'img_name_list = os.listdir(img_path)name = {0: '人', 1: '马', 2: '狗'}color = {0: "red", 1: "blue", 2: "green"}font = ImageFont.truetype("simsun.ttc", 18, encoding="unic") # 设置字体for image_file in img_name_list:im = Image.open(os.path.join(img_path,image_file))# y = detector(torch.randn(3, 3, 416, 416), 0.3, cfg.ANCHORS_GROUP)# print(y.shape)# img1 = pimg.open(r'data\imageS\06.jpg')#打开图片img = im.convert('RGB')#将类型转成rgbimg = np.array(img) / 255#归一化img = torch.Tensor(img)#将数据转成Tensorimg = img.unsqueeze(0)#图片的形状为(h,w,c)在0维度加一个轴变成(1,h,w,c)即(n,h,w,c)的形状img = img.permute(0, 3, 1, 2)#换轴将nhwc变成nchw# img = img.cuda()#这里不能转cuda不然会报错out_value = detector(img, 0.3, cfg.ANCHORS_GROUP)#调用侦测函数并将数据,自信度阀值和建议框传入boxes = []#定义空列表用来装框for j in range(4):#循环判断类别数classify_mask = (out_value[..., -1] == j)#输出的类别如果和类别相同就做nms删掉iou的的框留下iou小的表示这不是同一个物体# ,如果不用这个可能会将不同类别因iou大于目标值而不被删除,因此这里做个判断,只在同一类别中做nms,这里是获取同个类别的掩码_boxes = out_value[classify_mask]#更具掩码索引对应的输出作为框boxes.append(tool1.nms(_boxes))#对同一类别做nms删掉不合格的框,并将框添加进列表for box in boxes:#3种特征图的框循环框torch.cat([boxes_13, boxes_26, boxes_52], dim=0),循环3次# print(i)try:# print(box.shape)img_draw = draw.ImageDraw(im)#制作画笔# cls=int(box[5])for i in range(len(box)):#循环单个特征图的框,循环框的个数次c,x1, y1, x2, y2,cls = box[i, :]#将自信度和坐标及类别分别解包出来# print(c,x1, y1, x2, y2)# print(int(cls.item()))# print(round(c.item(),4))#取值并保留小数点后4位img_draw.rectangle((x1, y1, x2, y2),outline=color[int(cls.item())],width=2)#画框img_draw.text((max(x1, 0) + 3, max(y1, 0) + 3), fill=color[int(cls.item())],text=str(int(cls.item())), font=font,width=2)img_draw.text((max(x1, 0) + 15, max(y1, 0) + 3), fill=color[int(cls.item())],text=name[int(cls.item())], font=font,width=2)img_draw.text((max(x1, 0) + 3, max(y1, 0) + 20), fill=color[int(cls.item())],text=str(round(c.item(),4)), font=font, width=2)except:continue# im.save(os.path.join('Detector_results/results1_img/',image_file))plt.clf()plt.ion()plt.axis('off')plt.imshow(im)plt.show()plt.pause(3)plt.close()# im.show()'============================================================================================================='
'''
nonzero(a)
nonzero函数是numpy中用于得到数组array中非零元素的位置(数组索引)的函数。它的返回值是一个长度为a.ndim(数组a的轴数)的元组,元组的每个元素都是一个整数数组,其值为非零元素的下标在对应轴上的值。(1)只有a中非零元素才会有索引值,那些零值元素没有索引值;(2)返回的索引值数组是一个2维tuple数组,该tuple数组中包含一维的array数组。其中,一维array向量的个数与a的维数是一致的。(3)索引值数组的每一个array均是从一个维度上来描述其索引值。比如,如果a是一个二维数组,则索引值数组有两个array,第一个array从行维度来描述索引值;第二个array从列维度来描述索引值。(4)transpose(np.nonzero(x))函数能够描述出每一个非零元素在不同维度的索引值。(5)通过a[nonzero(a)]得到所有a中的非零值
'''
解释:
1. 先打开图片,然后格式变为NCHW,传入训练好的模型卷积得出3种输出结果。
2. 将输出结果,通过一个阈值筛选出合格的框(这一步相当于过滤结果)
3. 再将合格框的数据中自信度进行归一化到0到1之间,在通过中心点和宽高算出左上角右下角的坐标,再将分类进行反onehot得到具体类别。
4.将框相同目标的多个框进行nms运算(nms算法就是将高度重合的多个框,取自信度最高的为最终框)
5. 最后就是根据数据在图上画框