diff --git a/VOCdevkit/VOC2007/ImageSets/Segmentation/README.md b/VOCdevkit/VOC2007/ImageSets/Segmentation/README.md new file mode 100644 index 0000000..9042c5f --- /dev/null +++ b/VOCdevkit/VOC2007/ImageSets/Segmentation/README.md @@ -0,0 +1,2 @@ +存放的是指向文件名称的txt + diff --git a/VOCdevkit/VOC2007/JPEGImages/README.md b/VOCdevkit/VOC2007/JPEGImages/README.md new file mode 100644 index 0000000..528096d --- /dev/null +++ b/VOCdevkit/VOC2007/JPEGImages/README.md @@ -0,0 +1 @@ +这里面存放的是训练用的图片文件。 diff --git a/VOCdevkit/VOC2007/SegmentationClass/README.md b/VOCdevkit/VOC2007/SegmentationClass/README.md new file mode 100644 index 0000000..3581546 --- /dev/null +++ b/VOCdevkit/VOC2007/SegmentationClass/README.md @@ -0,0 +1 @@ +这里面存放的是训练过程中产生的权重。 diff --git a/VOCdevkit/voc2pspnet.py b/VOCdevkit/voc2pspnet.py new file mode 100644 index 0000000..010bf58 --- /dev/null +++ b/VOCdevkit/voc2pspnet.py @@ -0,0 +1,44 @@ +import os +import random + +segfilepath=r'./VOCdevkit/VOC2007/SegmentationClass' +saveBasePath=r"./VOCdevkit/VOC2007/ImageSets/Segmentation/" + +trainval_percent=1 +train_percent=0.9 + +temp_seg = os.listdir(segfilepath) +total_seg = [] +for seg in temp_seg: + if seg.endswith(".png"): + total_seg.append(seg) + +num=len(total_seg) +list=range(num) +tv=int(num*trainval_percent) +tr=int(tv*train_percent) +trainval= random.sample(list,tv) +train=random.sample(trainval,tr) + +print("train and val size",tv) +print("traub suze",tr) +ftrainval = open(os.path.join(saveBasePath,'trainval.txt'), 'w') +ftest = open(os.path.join(saveBasePath,'test.txt'), 'w') +ftrain = open(os.path.join(saveBasePath,'train.txt'), 'w') +fval = open(os.path.join(saveBasePath,'val.txt'), 'w') + +for i in list: + name=total_seg[i][:-4]+'\n' + if i in trainval: + ftrainval.write(name) + if i in train: + ftrain.write(name) + else: + fval.write(name) + else: + ftest.write(name) + +ftrainval.close() +ftrain.close() +fval.close() +ftest .close() diff --git a/datasets/JPEGImages/1.jpg b/datasets/JPEGImages/1.jpg new file mode 100644 index 0000000..ebc7659 Binary files /dev/null and b/datasets/JPEGImages/1.jpg differ diff --git a/datasets/SegmentationClass/1.png b/datasets/SegmentationClass/1.png new file mode 100644 index 0000000..b5c8317 Binary files /dev/null and b/datasets/SegmentationClass/1.png differ diff --git a/datasets/before/1.jpg b/datasets/before/1.jpg new file mode 100644 index 0000000..797ee10 Binary files /dev/null and b/datasets/before/1.jpg differ diff --git a/datasets/before/1.json b/datasets/before/1.json new file mode 100644 index 0000000..27d580b --- /dev/null +++ b/datasets/before/1.json @@ -0,0 +1,135 @@ +{ + "version": "3.16.7", + "flags": {}, + "shapes": [ + { + "label": "cat", + "line_color": null, + "fill_color": null, + "points": [ + [ + 202.77358490566036, + 626.0943396226414 + ], + [ + 178.24528301886792, + 552.5094339622641 + ], + [ + 195.22641509433961, + 444.9622641509434 + ], + [ + 177.30188679245282, + 340.2452830188679 + ], + [ + 173.52830188679243, + 201.56603773584905 + ], + [ + 211.2641509433962, + 158.16981132075472 + ], + [ + 226.35849056603772, + 87.41509433962264 + ], + [ + 208.43396226415092, + 6.283018867924525 + ], + [ + 277.3018867924528, + 57.226415094339615 + ], + [ + 416.92452830188677, + 80.81132075471697 + ], + [ + 497.1132075471698, + 64.77358490566037 + ], + [ + 578.2452830188679, + 6.283018867924525 + ], + [ + 599.0, + 35.52830188679245 + ], + [ + 589.566037735849, + 96.84905660377359 + ], + [ + 592.3962264150944, + 133.64150943396226 + ], + [ + 679.188679245283, + 174.2075471698113 + ], + [ + 723.5283018867924, + 165.71698113207546 + ], + [ + 726.3584905660377, + 222.32075471698113 + ], + [ + 759.377358490566, + 262.88679245283015 + ], + [ + 782.9622641509434, + 350.62264150943395 + ], + [ + 766.9245283018868, + 428.92452830188677 + ], + [ + 712.2075471698113, + 465.71698113207543 + ], + [ + 695.2264150943396, + 538.3584905660377 + ], + [ + 657.4905660377358, + 601.566037735849 + ], + [ + 606, + 633 + ], + [ + 213, + 633 + ] + ], + "shape_type": "polygon", + "flags": {} + } + ], + "lineColor": [ + 0, + 255, + 0, + 128 + ], + "fillColor": [ + 255, + 0, + 0, + 128 + ], + "imagePath": "1.jpg", + "imageData": "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", + "imageHeight": 634, + "imageWidth": 950 +} \ No newline at end of file diff --git a/get_miou_prediction.py b/get_miou_prediction.py new file mode 100644 index 0000000..10a5a58 --- /dev/null +++ b/get_miou_prediction.py @@ -0,0 +1,46 @@ +from pspnet import PSPNet +from torch import nn +from PIL import Image +from torch.autograd import Variable +import torch.nn.functional as F +import numpy as np +import colorsys +import torch +import copy +import os + +class miou_Pspnet(PSPNet): + def detect_image(self, image): + orininal_h = np.array(image).shape[0] + orininal_w = np.array(image).shape[1] + + image, nw, nh = self.letterbox_image(image,(self.model_image_size[1],self.model_image_size[0])) + images = [np.array(image)/255] + images = np.transpose(images,(0,3,1,2)) + + with torch.no_grad(): + images = Variable(torch.from_numpy(images).type(torch.FloatTensor)) + if self.cuda: + images = images.cuda() + pr = self.net(images)[0] + pr = F.softmax(pr.permute(1,2,0),dim = -1).cpu().numpy().argmax(axis=-1) + + pr = pr[int((self.model_image_size[0]-nh)//2):int((self.model_image_size[0]-nh)//2+nh), int((self.model_image_size[1]-nw)//2):int((self.model_image_size[1]-nw)//2+nw)] + + image = Image.fromarray(np.uint8(pr)).resize((orininal_w,orininal_h),Image.NEAREST) + + return image + +pspnet = miou_Pspnet() + +image_ids = open(r"VOCdevkit\VOC2007\ImageSets\Segmentation\val.txt",'r').read().splitlines() + +if not os.path.exists("./miou_pr_dir"): + os.makedirs("./miou_pr_dir") + +for image_id in image_ids: + image_path = "./VOCdevkit/VOC2007/JPEGImages/"+image_id+".jpg" + image = Image.open(image_path) + image = pspnet.detect_image(image) + image.save("./miou_pr_dir/" + image_id + ".png") + print(image_id," done!") diff --git a/img/street.jpg b/img/street.jpg new file mode 100644 index 0000000..6750d37 Binary files /dev/null and b/img/street.jpg differ diff --git a/json_to_dataset.py b/json_to_dataset.py new file mode 100644 index 0000000..93e144c --- /dev/null +++ b/json_to_dataset.py @@ -0,0 +1,63 @@ +import argparse +import json +import os +import os.path as osp +import warnings + +import PIL.Image +import yaml +import numpy as np +from labelme import utils +import base64 + +if __name__ == '__main__': + jpgs_path = "datasets/JPEGImages" + pngs_path = "datasets/SegmentationClass" + classes = ["_background_","aeroplane", "bicycle", "bird", "boat", "bottle", "bus", "car", "cat", "chair", "cow", "diningtable", "dog", "horse", "motorbike", "person", "pottedplant", "sheep", "sofa", "train", "tvmonitor"] + # classes = ["_background_","cat","dog"] + + count = os.listdir("./datasets/before/") + for i in range(0, len(count)): + path = os.path.join("./datasets/before", count[i]) + + if os.path.isfile(path) and path.endswith('json'): + data = json.load(open(path)) + + if data['imageData']: + imageData = data['imageData'] + else: + imagePath = os.path.join(os.path.dirname(path), data['imagePath']) + with open(imagePath, 'rb') as f: + imageData = f.read() + imageData = base64.b64encode(imageData).decode('utf-8') + + img = utils.img_b64_to_arr(imageData) + label_name_to_value = {'_background_': 0} + for shape in data['shapes']: + label_name = shape['label'] + if label_name in label_name_to_value: + label_value = label_name_to_value[label_name] + else: + label_value = len(label_name_to_value) + label_name_to_value[label_name] = label_value + + # label_values must be dense + label_values, label_names = [], [] + for ln, lv in sorted(label_name_to_value.items(), key=lambda x: x[1]): + label_values.append(lv) + label_names.append(ln) + assert label_values == list(range(len(label_values))) + + lbl = utils.shapes_to_label(img.shape, data['shapes'], label_name_to_value) + + + PIL.Image.fromarray(img).save(osp.join(jpgs_path, count[i].split(".")[0]+'.jpg')) + + new = np.zeros([np.shape(img)[0],np.shape(img)[1]]) + for name in label_names: + index_json = label_names.index(name) + index_all = classes.index(name) + new = new + index_all*(np.array(lbl) == index_json) + + utils.lblsave(osp.join(pngs_path, count[i].split(".")[0]+'.png'), new) + print('Saved ' + count[i].split(".")[0] + '.jpg and ' + count[i].split(".")[0] + '.png') \ No newline at end of file diff --git a/logs/README.MD b/logs/README.MD new file mode 100644 index 0000000..fb58387 --- /dev/null +++ b/logs/README.MD @@ -0,0 +1,2 @@ +这一部分用来存放训练后的文件。 +This part is used to store post training documents. \ No newline at end of file diff --git a/miou.py b/miou.py new file mode 100644 index 0000000..5774222 --- /dev/null +++ b/miou.py @@ -0,0 +1,71 @@ +import numpy as np +import argparse +import json +from PIL import Image +from os.path import join + +# 设标签宽W,长H +def fast_hist(a, b, n): + # a是转化成一维数组的标签,形状(H×W,);b是转化成一维数组的标签,形状(H×W,) + k = (a >= 0) & (a < n) + # np.bincount计算了从0到n**2-1这n**2个数中每个数出现的次数,返回值形状(n, n) + # 返回中,写对角线上的为分类正确的像素点 + return np.bincount(n * a[k].astype(int) + b[k], minlength=n ** 2).reshape(n, n) + +def per_class_iu(hist): + # 矩阵的对角线上的值组成的一维数组/矩阵的所有元素之和,返回值形状(n,) + return np.diag(hist) / (hist.sum(1) + hist.sum(0) - np.diag(hist)) + +def per_class_PA(hist): + # 矩阵的对角线上的值组成的一维数组/矩阵的所有元素之和,返回值形状(n,) + return np.diag(hist) / hist.sum(1) + +def compute_mIoU(gt_dir, pred_dir, png_name_list, num_classes, name_classes): + # 计算mIoU的函数 + print('Num classes', num_classes) + ## 1 + hist = np.zeros((num_classes, num_classes)) + + gt_imgs = [join(gt_dir, x + ".png") for x in png_name_list] # 获得验证集标签路径列表,方便直接读取 + pred_imgs = [join(pred_dir, x + ".png") for x in png_name_list] # 获得验证集图像分割结果路径列表,方便直接读取 + + # 读取每一个(图片-标签)对 + for ind in range(len(gt_imgs)): + # 读取一张图像分割结果,转化成numpy数组 + pred = np.array(Image.open(pred_imgs[ind])) + # 读取一张对应的标签,转化成numpy数组 + label = np.array(Image.open(gt_imgs[ind])) + + # 如果图像分割结果与标签的大小不一样,这张图片就不计算 + if len(label.flatten()) != len(pred.flatten()): + print( + 'Skipping: len(gt) = {:d}, len(pred) = {:d}, {:s}, {:s}'.format( + len(label.flatten()), len(pred.flatten()), gt_imgs[ind], + pred_imgs[ind])) + continue + # 对一张图片计算19×19的hist矩阵,并累加 + hist += fast_hist(label.flatten(), pred.flatten(),num_classes) + # 每计算10张就输出一下目前已计算的图片中所有类别平均的mIoU值 + if ind > 0 and ind % 10 == 0: + print('{:d} / {:d}: mIou-{:0.2f}; mPA-{:0.2f}'.format(ind, len(gt_imgs), + 100 * np.mean(per_class_iu(hist)), + 100 * np.mean(per_class_PA(hist)))) + # 计算所有验证集图片的逐类别mIoU值 + mIoUs = per_class_iu(hist) + mPA = per_class_PA(hist) + # 逐类别输出一下mIoU值 + for ind_class in range(num_classes): + print('===>' + name_classes[ind_class] + ':\tmIou-' + str(round(mIoUs[ind_class] * 100, 2)) + '; mPA-' + str(round(mPA[ind_class] * 100, 2))) + # 在所有验证集图像上求所有类别平均的mIoU值,计算时忽略NaN值 + print('===> mIoU: ' + str(round(np.nanmean(mIoUs) * 100, 2)) + '; mPA: ' + str(round(np.nanmean(mPA) * 100, 2))) + return mIoUs + + +if __name__ == "__main__": + gt_dir = "./VOCdevkit/VOC2007/SegmentationClass" + pred_dir = "./miou_pr_dir" + png_name_list = open(r"VOCdevkit\VOC2007\ImageSets\Segmentation\val.txt",'r').read().splitlines() + + num_classes = 21 + name_classes = ["background","aeroplane", "bicycle", "bird", "boat", "bottle", "bus", "car", "cat", "chair", "cow", "diningtable", "dog", "horse", "motorbike", "person", "pottedplant", "sheep", "sofa", "train", "tvmonitor"] + compute_mIoU(gt_dir, pred_dir, png_name_list, num_classes, name_classes) # 执行计算mIoU的函数 diff --git a/model_data/pspnet_mobilenetv2.pth b/model_data/pspnet_mobilenetv2.pth new file mode 100644 index 0000000..b138b90 Binary files /dev/null and b/model_data/pspnet_mobilenetv2.pth differ diff --git a/nets/mobilenetv2.py b/nets/mobilenetv2.py new file mode 100644 index 0000000..1e18d37 --- /dev/null +++ b/nets/mobilenetv2.py @@ -0,0 +1,157 @@ +import torch +import torch.nn.functional as F +import torch.nn as nn +import math +import os +import torch.utils.model_zoo as model_zoo +BatchNorm2d = nn.BatchNorm2d + +def conv_bn(inp, oup, stride): + return nn.Sequential( + nn.Conv2d(inp, oup, 3, stride, 1, bias=False), + BatchNorm2d(oup), + nn.ReLU6(inplace=True) + ) + + +def conv_1x1_bn(inp, oup): + return nn.Sequential( + nn.Conv2d(inp, oup, 1, 1, 0, bias=False), + BatchNorm2d(oup), + nn.ReLU6(inplace=True) + ) + + +class InvertedResidual(nn.Module): + def __init__(self, inp, oup, stride, expand_ratio): + super(InvertedResidual, self).__init__() + self.stride = stride + assert stride in [1, 2] + + hidden_dim = round(inp * expand_ratio) + self.use_res_connect = self.stride == 1 and inp == oup + + if expand_ratio == 1: + self.conv = nn.Sequential( + # dw + nn.Conv2d(hidden_dim, hidden_dim, 3, stride, 1, groups=hidden_dim, bias=False), + BatchNorm2d(hidden_dim), + nn.ReLU6(inplace=True), + # pw-linear + nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False), + BatchNorm2d(oup), + ) + else: + self.conv = nn.Sequential( + # pw + nn.Conv2d(inp, hidden_dim, 1, 1, 0, bias=False), + BatchNorm2d(hidden_dim), + nn.ReLU6(inplace=True), + # dw + nn.Conv2d(hidden_dim, hidden_dim, 3, stride, 1, groups=hidden_dim, bias=False), + BatchNorm2d(hidden_dim), + nn.ReLU6(inplace=True), + # pw-linear + nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False), + BatchNorm2d(oup), + ) + + def forward(self, x): + if self.use_res_connect: + return x + self.conv(x) + else: + return self.conv(x) + + +class MobileNetV2(nn.Module): + def __init__(self, n_class=1000, input_size=224, width_mult=1.): + super(MobileNetV2, self).__init__() + block = InvertedResidual + input_channel = 32 + last_channel = 1280 + + interverted_residual_setting = [ + # t, c, n, s + # 473,473,3 -> 237,237,32 + # 237,237,32 -> 237,237,16 + [1, 16, 1, 1], + # 237,237,16 -> 119,119,24 + [6, 24, 2, 2], + # 119,119,24 -> 60,60,32 + [6, 32, 3, 2], + # 60,60,32 -> 30,30,64 + [6, 64, 4, 2], + # 30,30,64 -> 30,30,96 + [6, 96, 3, 1], + # 30,30,96 -> 15,15,160 + [6, 160, 3, 2], + # 15,15,160 -> 15,15,320 + [6, 320, 1, 1], + ] + + assert input_size % 32 == 0 + # 建立stem层 + input_channel = int(input_channel * width_mult) + self.last_channel = int(last_channel * width_mult) if width_mult > 1.0 else last_channel + + self.features = [conv_bn(3, input_channel, 2)] + + # 根据上述列表进行循环,构建mobilenetv2的结构 + for t, c, n, s in interverted_residual_setting: + output_channel = int(c * width_mult) + for i in range(n): + if i == 0: + self.features.append(block(input_channel, output_channel, s, expand_ratio=t)) + else: + self.features.append(block(input_channel, output_channel, 1, expand_ratio=t)) + input_channel = output_channel + + # mobilenetv2结构的收尾工作 + self.features.append(conv_1x1_bn(input_channel, self.last_channel)) + self.features = nn.Sequential(*self.features) + + # 最后的分类部分 + self.classifier = nn.Sequential( + nn.Dropout(0.2), + nn.Linear(self.last_channel, n_class), + ) + + self._initialize_weights() + + def forward(self, x): + x = self.features(x) + x = x.mean(3).mean(2) + x = self.classifier(x) + return x + + def _initialize_weights(self): + for m in self.modules(): + if isinstance(m, nn.Conv2d): + n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels + m.weight.data.normal_(0, math.sqrt(2. / n)) + if m.bias is not None: + m.bias.data.zero_() + elif isinstance(m, BatchNorm2d): + m.weight.data.fill_(1) + m.bias.data.zero_() + elif isinstance(m, nn.Linear): + n = m.weight.size(1) + m.weight.data.normal_(0, 0.01) + m.bias.data.zero_() + + +def load_url(url, model_dir='./model_data', map_location=None): + if not os.path.exists(model_dir): + os.makedirs(model_dir) + filename = url.split('/')[-1] + cached_file = os.path.join(model_dir, filename) + if os.path.exists(cached_file): + return torch.load(cached_file, map_location=map_location) + else: + return model_zoo.load_url(url,model_dir=model_dir) + +def mobilenetv2(pretrained=False, **kwargs): + model = MobileNetV2(n_class=1000, **kwargs) + if pretrained: + model.load_state_dict(load_url('http://sceneparsing.csail.mit.edu/model/pretrained_resnet/mobilenet_v2.pth.tar'), strict=False) + return model diff --git a/nets/pspnet.py b/nets/pspnet.py new file mode 100644 index 0000000..67012de --- /dev/null +++ b/nets/pspnet.py @@ -0,0 +1,202 @@ +import math +import torch +import torch.nn.functional as F +from torch import nn +from torchvision import models +from nets.resnet import resnet50 +from nets.mobilenetv2 import mobilenetv2 + + +class Resnet(nn.Module): + def __init__(self, dilate_scale=8, pretrained=True): + super(Resnet, self).__init__() + from functools import partial + model = resnet50(pretrained) + + if dilate_scale == 8: + model.layer3.apply( + partial(self._nostride_dilate, dilate=2)) + model.layer4.apply( + partial(self._nostride_dilate, dilate=4)) + elif dilate_scale == 16: + model.layer4.apply( + partial(self._nostride_dilate, dilate=2)) + + # take pretrained resnet, except AvgPool and FC + self.conv1 = model.conv1 + self.bn1 = model.bn1 + self.relu1 = model.relu1 + self.conv2 = model.conv2 + self.bn2 = model.bn2 + self.relu2 = model.relu2 + self.conv3 = model.conv3 + self.bn3 = model.bn3 + self.relu3 = model.relu3 + self.maxpool = model.maxpool + self.layer1 = model.layer1 + self.layer2 = model.layer2 + self.layer3 = model.layer3 + self.layer4 = model.layer4 + + def _nostride_dilate(self, m, dilate): + classname = m.__class__.__name__ + if classname.find('Conv') != -1: + # the convolution with stride + if m.stride == (2, 2): + m.stride = (1, 1) + if m.kernel_size == (3, 3): + m.dilation = (dilate//2, dilate//2) + m.padding = (dilate//2, dilate//2) + # other convoluions + else: + if m.kernel_size == (3, 3): + m.dilation = (dilate, dilate) + m.padding = (dilate, dilate) + + def forward(self, x): + x = self.relu1(self.bn1(self.conv1(x))) + x = self.relu2(self.bn2(self.conv2(x))) + x = self.relu3(self.bn3(self.conv3(x))) + x = self.maxpool(x) + + x = self.layer1(x) + x = self.layer2(x) + x_aux = self.layer3(x) + x = self.layer4(x_aux) + return x_aux, x + +class MobileNetV2(nn.Module): + def __init__(self, downsample_factor=8, pretrained=True): + super(MobileNetV2, self).__init__() + from functools import partial + + model = mobilenetv2(pretrained) + self.features = model.features[:-1] + + self.total_idx = len(self.features) + self.down_idx = [2, 4, 7, 14] + + if downsample_factor == 8: + for i in range(self.down_idx[-2], self.down_idx[-1]): + self.features[i].apply( + partial(self._nostride_dilate, dilate=2) + ) + for i in range(self.down_idx[-1], self.total_idx): + self.features[i].apply( + partial(self._nostride_dilate, dilate=4) + ) + elif downsample_factor == 16: + for i in range(self.down_idx[-1], self.total_idx): + self.features[i].apply( + partial(self._nostride_dilate, dilate=2) + ) + + + def _nostride_dilate(self, m, dilate): + classname = m.__class__.__name__ + if classname.find('Conv') != -1: + # the convolution with stride + if m.stride == (2, 2): + m.stride = (1, 1) + if m.kernel_size == (3, 3): + m.dilation = (dilate//2, dilate//2) + m.padding = (dilate//2, dilate//2) + # other convoluions + else: + if m.kernel_size == (3, 3): + m.dilation = (dilate, dilate) + m.padding = (dilate, dilate) + + def forward(self, x): + x_aux = self.features[:14](x) + x = self.features[14:](x_aux) + # x -> 30x30x320 + return x_aux, x + +class _PSPModule(nn.Module): + def __init__(self, in_channels, pool_sizes, norm_layer): + super(_PSPModule, self).__init__() + out_channels = in_channels // len(pool_sizes) + self.stages = nn.ModuleList([self._make_stages(in_channels, out_channels, pool_size, norm_layer) + for pool_size in pool_sizes]) + self.bottleneck = nn.Sequential( + nn.Conv2d(in_channels+(out_channels * len(pool_sizes)), out_channels, + kernel_size=3, padding=1, bias=False), + norm_layer(out_channels), + nn.ReLU(inplace=True), + nn.Dropout2d(0.1) + ) + + def _make_stages(self, in_channels, out_channels, bin_sz, norm_layer): + prior = nn.AdaptiveAvgPool2d(output_size=bin_sz) + conv = nn.Conv2d(in_channels, out_channels, kernel_size=1, bias=False) + bn = norm_layer(out_channels) + relu = nn.ReLU(inplace=True) + return nn.Sequential(prior, conv, bn, relu) + + def forward(self, features): + h, w = features.size()[2], features.size()[3] + pyramids = [features] + pyramids.extend([F.interpolate(stage(features), size=(h, w), mode='bilinear', + align_corners=True) for stage in self.stages]) + output = self.bottleneck(torch.cat(pyramids, dim=1)) + return output + + +class PSPNet(nn.Module): + def __init__(self, num_classes, downsample_factor, backbone="resnet50", pretrained=True, aux_branch=True): + super(PSPNet, self).__init__() + norm_layer = nn.BatchNorm2d + if backbone=="resnet50": + self.backbone = Resnet(downsample_factor, pretrained) + aux_channel = 1024 + out_channel = 2048 + elif backbone=="mobilenet": + self.backbone = MobileNetV2(downsample_factor, pretrained) + aux_channel = 96 + out_channel = 320 + else: + raise ValueError('Unsupported backbone - `{}`, Use mobilenet, resnet50.'.format(backbone)) + + self.master_branch = nn.Sequential( + _PSPModule(out_channel, pool_sizes=[1, 2, 3, 6], norm_layer=norm_layer), + nn.Conv2d(out_channel//4, num_classes, kernel_size=1) + ) + + self.aux_branch = aux_branch + + if self.aux_branch: + self.auxiliary_branch = nn.Sequential( + nn.Conv2d(aux_channel, out_channel//8, kernel_size=3, padding=1, bias=False), + norm_layer(out_channel//8), + nn.ReLU(inplace=True), + nn.Dropout2d(0.1), + nn.Conv2d(out_channel//8, num_classes, kernel_size=1) + ) + + self.initialize_weights(self.master_branch) + + def forward(self, x): + input_size = (x.size()[2], x.size()[3]) + x_aux, x = self.backbone(x) + + output = self.master_branch(x) + output = F.interpolate(output, size=input_size, mode='bilinear', align_corners=True) + if self.aux_branch: + output_aux = self.auxiliary_branch(x_aux) + output_aux = F.interpolate(output_aux, size=input_size, mode='bilinear', align_corners=True) + return output_aux, output + else: + return output + + def initialize_weights(self, *models): + for model in models: + for m in model.modules(): + if isinstance(m, nn.Conv2d): + nn.init.kaiming_normal_(m.weight.data, nonlinearity='relu') + elif isinstance(m, nn.BatchNorm2d): + m.weight.data.fill_(1.) + m.bias.data.fill_(1e-4) + elif isinstance(m, nn.Linear): + m.weight.data.normal_(0.0, 0.0001) + m.bias.data.zero_() diff --git a/nets/pspnet_training.py b/nets/pspnet_training.py new file mode 100644 index 0000000..2c0a743 --- /dev/null +++ b/nets/pspnet_training.py @@ -0,0 +1,41 @@ +import torch +import torch.nn.functional as F +import numpy as np +from torch import nn +from torch.autograd import Variable +from random import shuffle +from matplotlib.colors import rgb_to_hsv, hsv_to_rgb +from PIL import Image +import cv2 + +def CE_Loss(inputs, target, num_classes=21): + n, c, h, w = inputs.size() + nt, ht, wt = target.size() + if h != ht and w != wt: + inputs = F.interpolate(inputs, size=(ht, wt), mode="bilinear", align_corners=True) + + temp_inputs = inputs.transpose(1, 2).transpose(2, 3).contiguous().view(-1, c) + temp_target = target.view(-1) + + CE_loss = nn.NLLLoss(ignore_index=num_classes)(F.log_softmax(temp_inputs, dim = -1), temp_target) + return CE_loss + +def Dice_loss(inputs, target, beta=1, smooth = 1e-5): + n, c, h, w = inputs.size() + nt, ht, wt, ct = target.size() + + if h != ht and w != wt: + inputs = F.interpolate(inputs, size=(ht, wt), mode="bilinear", align_corners=True) + temp_inputs = torch.softmax(inputs.transpose(1, 2).transpose(2, 3).contiguous().view(n, -1, c),-1) + temp_target = target.view(n, -1, ct) + + #--------------------------------------------# + # 计算dice loss + #--------------------------------------------# + tp = torch.sum(temp_target[...,:-1] * temp_inputs, axis=[0,1]) + fp = torch.sum(temp_inputs , axis=[0,1]) - tp + fn = torch.sum(temp_target[...,:-1] , axis=[0,1]) - tp + + score = ((1 + beta ** 2) * tp + smooth) / ((1 + beta ** 2) * tp + beta ** 2 * fn + fp + smooth) + dice_loss = 1 - torch.mean(score) + return dice_loss \ No newline at end of file diff --git a/nets/resnet.py b/nets/resnet.py new file mode 100644 index 0000000..5fb0db3 --- /dev/null +++ b/nets/resnet.py @@ -0,0 +1,138 @@ +import math +import os +import torch +import torch.nn as nn +import torchvision +import torch.utils.model_zoo as model_zoo +BatchNorm2d = nn.BatchNorm2d + +model_urls = { + 'resnet50': 'http://sceneparsing.csail.mit.edu/model/pretrained_resnet/resnet50-imagenet.pth', +} + + +def conv3x3(in_planes, out_planes, stride=1): + "3x3 convolution with padding" + return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride, + padding=1, bias=False) + +class Bottleneck(nn.Module): + expansion = 4 + + def __init__(self, inplanes, planes, stride=1, downsample=None): + super(Bottleneck, self).__init__() + self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False) + self.bn1 = BatchNorm2d(planes) + self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride, + padding=1, bias=False) + self.bn2 = BatchNorm2d(planes) + self.conv3 = nn.Conv2d(planes, planes * 4, kernel_size=1, bias=False) + self.bn3 = BatchNorm2d(planes * 4) + self.relu = nn.ReLU(inplace=True) + self.downsample = downsample + self.stride = stride + + def forward(self, x): + residual = x + + out = self.conv1(x) + out = self.bn1(out) + out = self.relu(out) + + out = self.conv2(out) + out = self.bn2(out) + out = self.relu(out) + + out = self.conv3(out) + out = self.bn3(out) + + if self.downsample is not None: + residual = self.downsample(x) + + out += residual + out = self.relu(out) + + return out + + +class ResNet(nn.Module): + + def __init__(self, block, layers, num_classes=1000): + self.inplanes = 128 + super(ResNet, self).__init__() + self.conv1 = conv3x3(3, 64, stride=2) + self.bn1 = BatchNorm2d(64) + self.relu1 = nn.ReLU(inplace=True) + self.conv2 = conv3x3(64, 64) + self.bn2 = BatchNorm2d(64) + self.relu2 = nn.ReLU(inplace=True) + self.conv3 = conv3x3(64, 128) + self.bn3 = BatchNorm2d(128) + self.relu3 = nn.ReLU(inplace=True) + self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1) + + self.layer1 = self._make_layer(block, 64, layers[0]) + self.layer2 = self._make_layer(block, 128, layers[1], stride=2) + self.layer3 = self._make_layer(block, 256, layers[2], stride=2) + self.layer4 = self._make_layer(block, 512, layers[3], stride=2) + self.avgpool = nn.AvgPool2d(7, stride=1) + self.fc = nn.Linear(512 * block.expansion, num_classes) + + for m in self.modules(): + if isinstance(m, nn.Conv2d): + n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels + m.weight.data.normal_(0, math.sqrt(2. / n)) + elif isinstance(m, BatchNorm2d): + m.weight.data.fill_(1) + m.bias.data.zero_() + + def _make_layer(self, block, planes, blocks, stride=1): + downsample = None + if stride != 1 or self.inplanes != planes * block.expansion: + downsample = nn.Sequential( + nn.Conv2d(self.inplanes, planes * block.expansion, + kernel_size=1, stride=stride, bias=False), + BatchNorm2d(planes * block.expansion), + ) + + layers = [] + layers.append(block(self.inplanes, planes, stride, downsample)) + self.inplanes = planes * block.expansion + for i in range(1, blocks): + layers.append(block(self.inplanes, planes)) + + return nn.Sequential(*layers) + + def forward(self, x): + x = self.relu1(self.bn1(self.conv1(x))) + x = self.relu2(self.bn2(self.conv2(x))) + x = self.relu3(self.bn3(self.conv3(x))) + x = self.maxpool(x) + + x = self.layer1(x) + x = self.layer2(x) + x = self.layer3(x) + x = self.layer4(x) + + x = self.avgpool(x) + x = x.view(x.size(0), -1) + x = self.fc(x) + + return x + +def load_url(url, model_dir='./model_data', map_location=None): + if not os.path.exists(model_dir): + os.makedirs(model_dir) + filename = url.split('/')[-1] + cached_file = os.path.join(model_dir, filename) + if os.path.exists(cached_file): + return torch.load(cached_file, map_location=map_location) + else: + return model_zoo.load_url(url,model_dir=model_dir) + + +def resnet50(pretrained=False, **kwargs): + model = ResNet(Bottleneck, [3, 4, 6, 3], **kwargs) + if pretrained: + model.load_state_dict(load_url(model_urls['resnet50']), strict=False) + return model \ No newline at end of file diff --git a/predict.py b/predict.py new file mode 100644 index 0000000..81f1308 --- /dev/null +++ b/predict.py @@ -0,0 +1,18 @@ +#-------------------------------------# +# 对单张图片进行预测 +#-------------------------------------# +from pspnet import PSPNet +from PIL import Image + +pspnet = PSPNet() + +while True: + img = input('Input image filename:') + try: + image = Image.open(img) + except: + print('Open Error! Try again!') + continue + else: + r_image = pspnet.detect_image(image) + r_image.show() diff --git a/pspnet.py b/pspnet.py new file mode 100644 index 0000000..46dcf80 --- /dev/null +++ b/pspnet.py @@ -0,0 +1,108 @@ +from nets.pspnet import PSPNet as pspnet +from torch import nn +from PIL import Image +from torch.autograd import Variable +import torch.nn.functional as F +import numpy as np +import colorsys +import torch +import copy +import os + +class PSPNet(object): + #-----------------------------------------# + # 注意修改model_path、num_classes + # 和backbone + # 使其符合自己的模型 + #-----------------------------------------# + _defaults = { + "model_path" : 'model_data/pspnet_mobilenetv2.pth', + "model_image_size" : (473, 473, 3), + "backbone" : "mobilenet", + "downsample_factor" : 16, + "num_classes" : 21, + "cuda" : True, + "blend" : True, + } + + #---------------------------------------------------# + # 初始化UNET + #---------------------------------------------------# + def __init__(self, **kwargs): + self.__dict__.update(self._defaults) + self.generate() + + #---------------------------------------------------# + # 获得所有的分类 + #---------------------------------------------------# + def generate(self): + os.environ["CUDA_VISIBLE_DEVICES"] = '0' + self.net = pspnet(num_classes=self.num_classes, downsample_factor=self.downsample_factor, pretrained=False, backbone=self.backbone, aux_branch=False) + self.net = self.net.eval() + + state_dict = torch.load(self.model_path) + self.net.load_state_dict(state_dict, strict=False) + if self.cuda: + self.net = nn.DataParallel(self.net) + self.net = self.net.cuda() + + print('{} model, anchors, and classes loaded.'.format(self.model_path)) + # 画框设置不同的颜色 + if self.num_classes <= 21: + self.colors = [(0, 0, 0), (128, 0, 0), (0, 128, 0), (128, 128, 0), (0, 0, 128), (128, 0, 128), (0, 128, 128), + (128, 128, 128), (64, 0, 0), (192, 0, 0), (64, 128, 0), (192, 128, 0), (64, 0, 128), (192, 0, 128), + (64, 128, 128), (192, 128, 128), (0, 64, 0), (128, 64, 0), (0, 192, 0), (128, 192, 0), (0, 64, 128), (128, 64, 12)] + else: + # 画框设置不同的颜色 + hsv_tuples = [(x / len(self.class_names), 1., 1.) + for x in range(len(self.class_names))] + self.colors = list(map(lambda x: colorsys.hsv_to_rgb(*x), hsv_tuples)) + self.colors = list( + map(lambda x: (int(x[0] * 255), int(x[1] * 255), int(x[2] * 255)), + self.colors)) + + def letterbox_image(self ,image, size): + '''resize image with unchanged aspect ratio using padding''' + iw, ih = image.size + w, h = size + scale = min(w/iw, h/ih) + nw = int(iw*scale) + nh = int(ih*scale) + + image = image.resize((nw,nh), Image.BICUBIC) + new_image = Image.new('RGB', size, (128,128,128)) + new_image.paste(image, ((w-nw)//2, (h-nh)//2)) + return new_image,nw,nh + #---------------------------------------------------# + # 检测图片 + #---------------------------------------------------# + def detect_image(self, image): + old_img = copy.deepcopy(image) + orininal_h = np.array(image).shape[0] + orininal_w = np.array(image).shape[1] + + image, nw, nh = self.letterbox_image(image,(self.model_image_size[1],self.model_image_size[0])) + images = [np.array(image)/255] + images = np.transpose(images,(0,3,1,2)) + + with torch.no_grad(): + images = Variable(torch.from_numpy(images).type(torch.FloatTensor)) + if self.cuda: + images =images.cuda() + + pr = self.net(images)[0] + pr = F.softmax(pr.permute(1,2,0),dim = -1).cpu().numpy().argmax(axis=-1) + pr = pr[int((self.model_image_size[0]-nh)//2):int((self.model_image_size[0]-nh)//2+nh), int((self.model_image_size[1]-nw)//2):int((self.model_image_size[1]-nw)//2+nw)] + + seg_img = np.zeros((np.shape(pr)[0],np.shape(pr)[1],3)) + for c in range(self.num_classes): + seg_img[:,:,0] += ((pr[:,: ] == c )*( self.colors[c][0] )).astype('uint8') + seg_img[:,:,1] += ((pr[:,: ] == c )*( self.colors[c][1] )).astype('uint8') + seg_img[:,:,2] += ((pr[:,: ] == c )*( self.colors[c][2] )).astype('uint8') + + image = Image.fromarray(np.uint8(seg_img)).resize((orininal_w,orininal_h)) + if self.blend: + image = Image.blend(old_img,image,0.7) + + return image + diff --git a/test.py b/test.py new file mode 100644 index 0000000..ce99279 --- /dev/null +++ b/test.py @@ -0,0 +1,7 @@ +import torch +from nets.pspnet import PSPNet +from torchsummary import summary + +model = PSPNet(num_classes=21,backbone="mobilenet",downsample_factor=16,aux_branch=False,pretrained=False).train().cuda() + +summary(model,(3,473,473)) \ No newline at end of file diff --git a/train.py b/train.py new file mode 100644 index 0000000..8c187e3 --- /dev/null +++ b/train.py @@ -0,0 +1,256 @@ +import time +import torch +import torch.optim as optim +import torch.backends.cudnn as cudnn +import torchvision.models as models +import numpy as np +from tqdm import tqdm +from torchvision import models +from torch.autograd import Variable +from PIL import Image +from torch import nn +from nets.pspnet import PSPNet +from nets.pspnet_training import CE_Loss,Dice_loss +from utils.metrics import f_score +from torch.utils.data import DataLoader +from utils.dataloader import pspnet_dataset_collate, PSPnetDataset + + +def get_lr(optimizer): + for param_group in optimizer.param_groups: + return param_group['lr'] + +def fit_one_epoch(net,epoch,epoch_size,epoch_size_val,gen,genval,Epoch,cuda,aux_branch): + net = net.train() + total_loss = 0 + total_f_score = 0 + + val_toal_loss = 0 + val_total_f_score = 0 + start_time = time.time() + with tqdm(total=epoch_size,desc=f'Epoch {epoch + 1}/{Epoch}',postfix=dict,mininterval=0.3) as pbar: + for iteration, batch in enumerate(gen): + if iteration >= epoch_size: + break + imgs, pngs, labels = batch + + with torch.no_grad(): + imgs = Variable(torch.from_numpy(imgs).type(torch.FloatTensor)) + pngs = Variable(torch.from_numpy(pngs).type(torch.FloatTensor)).long() + labels = Variable(torch.from_numpy(labels).type(torch.FloatTensor)) + if cuda: + imgs = imgs.cuda() + pngs = pngs.cuda() + labels = labels.cuda() + + #-------------------------------# + # 判断是否使用辅助分支并回传 + #-------------------------------# + optimizer.zero_grad() + if aux_branch: + aux_outputs, outputs = net(imgs) + aux_loss = CE_Loss(aux_outputs, pngs, num_classes = NUM_CLASSES) + main_loss = CE_Loss(outputs, pngs, num_classes = NUM_CLASSES) + loss = aux_loss + main_loss + if dice_loss: + aux_dice = Dice_loss(aux_outputs, labels) + main_dice = Dice_loss(outputs, labels) + loss = loss + aux_dice + main_dice + + else: + outputs = net(imgs) + loss = CE_Loss(outputs, pngs, num_classes = NUM_CLASSES) + if dice_loss: + main_dice = Dice_loss(outputs, labels) + loss = loss + main_dice + + with torch.no_grad(): + #-------------------------------# + # 计算f_score + #-------------------------------# + _f_score = f_score(outputs, labels) + + loss.backward() + optimizer.step() + + total_loss += loss.item() + total_f_score += _f_score.item() + + waste_time = time.time() - start_time + pbar.set_postfix(**{'total_loss': total_loss / (iteration + 1), + 'f_score' : total_f_score / (iteration + 1), + 's/step' : waste_time, + 'lr' : get_lr(optimizer)}) + pbar.update(1) + + start_time = time.time() + + print('Start Validation') + with tqdm(total=epoch_size_val, desc=f'Epoch {epoch + 1}/{Epoch}',postfix=dict,mininterval=0.3) as pbar: + for iteration, batch in enumerate(genval): + if iteration >= epoch_size_val: + break + imgs, pngs, labels = batch + with torch.no_grad(): + imgs = Variable(torch.from_numpy(imgs).type(torch.FloatTensor)) + pngs = Variable(torch.from_numpy(pngs).type(torch.FloatTensor)).long() + labels = Variable(torch.from_numpy(labels).type(torch.FloatTensor)) + if cuda: + imgs = imgs.cuda() + pngs = pngs.cuda() + labels = labels.cuda() + #-------------------------------# + # 判断是否使用辅助分支 + #-------------------------------# + if aux_branch: + aux_outputs, outputs = net(imgs) + aux_loss = CE_Loss(aux_outputs, pngs, num_classes = NUM_CLASSES) + main_loss = CE_Loss(outputs, pngs, num_classes = NUM_CLASSES) + val_loss = aux_loss + main_loss + if dice_loss: + aux_dice = Dice_loss(aux_outputs, labels) + main_dice = Dice_loss(outputs, labels) + val_loss = val_loss + aux_dice + main_dice + + else: + outputs = net(imgs) + val_loss = CE_Loss(outputs, pngs, num_classes = NUM_CLASSES) + if dice_loss: + main_dice = Dice_loss(outputs, labels) + val_loss = val_loss + main_dice + + #-------------------------------# + # 计算f_score + #-------------------------------# + _f_score = f_score(outputs, labels) + + val_toal_loss += val_loss.item() + val_total_f_score += _f_score.item() + + + pbar.set_postfix(**{'total_loss': val_toal_loss / (iteration + 1), + 'f_score' : val_total_f_score / (iteration + 1), + 'lr' : get_lr(optimizer)}) + pbar.update(1) + + print('Finish Validation') + print('Epoch:'+ str(epoch+1) + '/' + str(Epoch)) + print('Total Loss: %.4f || Val Loss: %.4f ' % (total_loss/(epoch_size+1),val_toal_loss/(epoch_size_val+1))) + + print('Saving state, iter:', str(epoch+1)) + torch.save(model.state_dict(), 'logs/Epoch%d-Total_Loss%.4f-Val_Loss%.4f.pth'%((epoch+1),total_loss/(epoch_size+1),val_toal_loss/(epoch_size_val+1))) + + + +if __name__ == "__main__": + inputs_size = [473,473,3] + log_dir = "logs/" + #---------------------# + # 分类个数+1 + # 2+1 + #---------------------# + NUM_CLASSES = 21 + #--------------------------------------------------------------------# + # 建议选项: + # 种类少(几类)时,设置为True + # 种类多(十几类)时,如果batch_size比较大(10以上),那么设置为True + # 种类多(十几类)时,如果batch_size比较小(10以下),那么设置为False + #---------------------------------------------------------------------# + dice_loss = False + #-------------------------------# + # 主干网络预训练权重的使用 + # mobilenet和resnet50 + #-------------------------------# + pretrained = False + backbone = "mobilenet" + #---------------------# + # 是否使用辅助分支 + # 会占用大量显存 + #---------------------# + aux_branch = False + #---------------------# + # 下采样的倍数 + # 8和16 + #---------------------# + downsample_factor = 16 + #-------------------------------# + # Cuda的使用 + #-------------------------------# + Cuda = True + + model = PSPNet(num_classes=NUM_CLASSES, backbone=backbone, downsample_factor=downsample_factor, pretrained=pretrained, aux_branch=aux_branch).train() + + # voc数据集下进行训练的 + model_path = r"model_data/pspnet_mobilenetv2.pth" + # 加快模型训练的效率 + print('Loading weights into state dict...') + model_dict = model.state_dict() + pretrained_dict = torch.load(model_path) + pretrained_dict = {k: v for k, v in pretrained_dict.items() if np.shape(model_dict[k]) == np.shape(v)} + model_dict.update(pretrained_dict) + model.load_state_dict(model_dict) + print('Finished!') + + if Cuda: + net = torch.nn.DataParallel(model) + cudnn.benchmark = True + net = net.cuda() + + # 打开数据集的txt + with open(r"VOCdevkit/VOC2007/ImageSets/Segmentation/train.txt","r") as f: + train_lines = f.readlines() + + # 打开数据集的txt + with open(r"VOCdevkit/VOC2007/ImageSets/Segmentation/val.txt","r") as f: + val_lines = f.readlines() + + if True: + lr = 1e-4 + Init_Epoch = 0 + Interval_Epoch = 50 + Batch_size = 8 + optimizer = optim.Adam(model.parameters(),lr) + lr_scheduler = optim.lr_scheduler.StepLR(optimizer,step_size=1,gamma=0.9) + + train_dataset = PSPnetDataset(train_lines, inputs_size, NUM_CLASSES, True) + val_dataset = PSPnetDataset(val_lines, inputs_size, NUM_CLASSES, False) + gen = DataLoader(train_dataset, batch_size=Batch_size, num_workers=1, pin_memory=True, + drop_last=True, collate_fn=pspnet_dataset_collate) + gen_val = DataLoader(val_dataset, batch_size=Batch_size, num_workers=4,pin_memory=True, + drop_last=True, collate_fn=pspnet_dataset_collate) + + epoch_size = max(1, len(train_lines)//Batch_size) + epoch_size_val = max(1, len(val_lines)//Batch_size) + + for param in model.backbone.parameters(): + param.requires_grad = False + + for epoch in range(Init_Epoch,Interval_Epoch): + fit_one_epoch(model,epoch,epoch_size,epoch_size_val,gen,gen_val,Interval_Epoch,Cuda,aux_branch) + lr_scheduler.step() + + if True: + lr = 1e-5 + Interval_Epoch = 50 + Epoch = 100 + Batch_size = 4 + optimizer = optim.Adam(model.parameters(),lr) + lr_scheduler = optim.lr_scheduler.StepLR(optimizer,step_size=1,gamma=0.9) + + train_dataset = PSPnetDataset(train_lines, inputs_size, NUM_CLASSES, True) + val_dataset = PSPnetDataset(val_lines, inputs_size, NUM_CLASSES, False) + gen = DataLoader(train_dataset, batch_size=Batch_size, num_workers=4, pin_memory=True, + drop_last=True, collate_fn=pspnet_dataset_collate) + gen_val = DataLoader(val_dataset, batch_size=Batch_size, num_workers=4,pin_memory=True, + drop_last=True, collate_fn=pspnet_dataset_collate) + + epoch_size = max(1, len(train_lines)//Batch_size) + epoch_size_val = max(1, len(val_lines)//Batch_size) + + for param in model.backbone.parameters(): + param.requires_grad = True + + for epoch in range(Interval_Epoch,Epoch): + fit_one_epoch(model,epoch,epoch_size,epoch_size_val,gen,gen_val,Epoch,Cuda,aux_branch) + lr_scheduler.step() + diff --git a/utils/dataloader.py b/utils/dataloader.py new file mode 100644 index 0000000..37ac5ca --- /dev/null +++ b/utils/dataloader.py @@ -0,0 +1,144 @@ +from random import shuffle +import numpy as np +import torch +import torch.nn as nn +import math +import torch.nn.functional as F +from PIL import Image +from torch.autograd import Variable +from torch.utils.data import DataLoader +from torch.utils.data.dataset import Dataset +from matplotlib.colors import rgb_to_hsv, hsv_to_rgb +import cv2 + +def letterbox_image(image, label , size): + label = Image.fromarray(np.array(label)) + '''resize image with unchanged aspect ratio using padding''' + iw, ih = image.size + w, h = size + scale = min(w/iw, h/ih) + nw = int(iw*scale) + nh = int(ih*scale) + + image = image.resize((nw,nh), Image.BICUBIC) + new_image = Image.new('RGB', size, (128,128,128)) + new_image.paste(image, ((w-nw)//2, (h-nh)//2)) + + label = label.resize((nw,nh), Image.NEAREST) + new_label = Image.new('L', size, (0)) + new_label.paste(label, ((w-nw)//2, (h-nh)//2)) + + return new_image, new_label + +def rand(a=0, b=1): + return np.random.rand()*(b-a) + a + +class PSPnetDataset(Dataset): + def __init__(self,train_lines,image_size,num_classes,random_data): + super(PSPnetDataset, self).__init__() + + self.train_lines = train_lines + self.train_batches = len(train_lines) + self.image_size = image_size + self.num_classes = num_classes + self.random_data = random_data + + def __len__(self): + return self.train_batches + + def rand(self, a=0, b=1): + return np.random.rand() * (b - a) + a + + def get_random_data(self, image, label, input_shape, jitter=.3, hue=.1, sat=1.5, val=1.5): + label = Image.fromarray(np.array(label)) + + h, w = input_shape + # resize image + rand_jit1 = rand(1-jitter,1+jitter) + rand_jit2 = rand(1-jitter,1+jitter) + new_ar = w/h * rand_jit1/rand_jit2 + + scale = rand(0.5,1.5) + if new_ar < 1: + nh = int(scale*h) + nw = int(nh*new_ar) + else: + nw = int(scale*w) + nh = int(nw/new_ar) + image = image.resize((nw,nh), Image.BICUBIC) + label = label.resize((nw,nh), Image.NEAREST) + label = label.convert("L") + + # flip image or not + flip = rand()<.5 + if flip: + image = image.transpose(Image.FLIP_LEFT_RIGHT) + label = label.transpose(Image.FLIP_LEFT_RIGHT) + + # place image + dx = int(rand(0, w-nw)) + dy = int(rand(0, h-nh)) + new_image = Image.new('RGB', (w,h), (128,128,128)) + new_label = Image.new('L', (w,h), (0)) + new_image.paste(image, (dx, dy)) + new_label.paste(label, (dx, dy)) + image = new_image + label = new_label + + # distort image + hue = rand(-hue, hue) + sat = rand(1, sat) if rand()<.5 else 1/rand(1, sat) + val = rand(1, val) if rand()<.5 else 1/rand(1, val) + x = cv2.cvtColor(np.array(image,np.float32)/255, cv2.COLOR_RGB2HSV) + x[..., 0] += hue*360 + x[..., 0][x[..., 0]>1] -= 1 + x[..., 0][x[..., 0]<0] += 1 + x[..., 1] *= sat + x[..., 2] *= val + x[x[:,:, 0]>360, 0] = 360 + x[:, :, 1:][x[:, :, 1:]>1] = 1 + x[x<0] = 0 + image_data = cv2.cvtColor(x, cv2.COLOR_HSV2RGB)*255 + return image_data,label + + + def __getitem__(self, index): + if index == 0: + shuffle(self.train_lines) + + annotation_line = self.train_lines[index] + name = annotation_line.split()[0] + # 从文件中读取图像 + jpg = Image.open(r"./VOCdevkit/VOC2007/JPEGImages" + '/' + name + ".jpg") + png = Image.open(r"./VOCdevkit/VOC2007/SegmentationClass" + '/' + name + ".png") + + if self.random_data: + jpg, png = self.get_random_data(jpg,png,(int(self.image_size[1]),int(self.image_size[0]))) + else: + jpg, png = letterbox_image(jpg, png, (int(self.image_size[1]),int(self.image_size[0]))) + + # 从文件中读取图像 + png = np.array(png) + png[png >= self.num_classes] = self.num_classes + + # 转化成one_hot的形式 + seg_labels = np.eye(self.num_classes+1)[png.reshape([-1])] + seg_labels = seg_labels.reshape((int(self.image_size[1]),int(self.image_size[0]),self.num_classes+1)) + jpg = np.transpose(np.array(jpg),[2,0,1])/255 + + return jpg, png, seg_labels + + +# DataLoader中collate_fn使用 +def pspnet_dataset_collate(batch): + images = [] + pngs = [] + seg_labels = [] + for img, png, labels in batch: + images.append(img) + pngs.append(png) + seg_labels.append(labels) + images = np.array(images) + pngs = np.array(pngs) + seg_labels = np.array(seg_labels) + return images, pngs, seg_labels \ No newline at end of file diff --git a/utils/metrics.py b/utils/metrics.py new file mode 100644 index 0000000..f9fa969 --- /dev/null +++ b/utils/metrics.py @@ -0,0 +1,23 @@ +import torch +import torch.nn.functional as F + +def f_score(inputs, target, beta=1, smooth = 1e-5, threhold = 0.5): + n, c, h, w = inputs.size() + nt, ht, wt, ct = target.size() + + if h != ht and w != wt: + inputs = F.interpolate(inputs, size=(ht, wt), mode="bilinear", align_corners=True) + temp_inputs = torch.softmax(inputs.transpose(1, 2).transpose(2, 3).contiguous().view(n, -1, c),-1) + temp_target = target.view(n, -1, ct) + + #--------------------------------------------# + # 计算dice系数 + #--------------------------------------------# + temp_inputs = torch.gt(temp_inputs, threhold).float() + tp = torch.sum(temp_target[...,:-1] * temp_inputs, axis=[0,1]) + fp = torch.sum(temp_inputs , axis=[0,1]) - tp + fn = torch.sum(temp_target[...,:-1] , axis=[0,1]) - tp + + score = ((1 + beta ** 2) * tp + smooth) / ((1 + beta ** 2) * tp + beta ** 2 * fn + fp + smooth) + score = torch.mean(score) + return score \ No newline at end of file diff --git a/video.py b/video.py new file mode 100644 index 0000000..a29f71e --- /dev/null +++ b/video.py @@ -0,0 +1,39 @@ +#-------------------------------------# +# 调用摄像头检测 +#-------------------------------------# +from pspnet import PSPNet +from PIL import Image +import numpy as np +import cv2 +import time + +pspnet = PSPNet() +# 调用摄像头 +capture=cv2.VideoCapture(0) # capture=cv2.VideoCapture("1.mp4") + +fps = 0.0 +while(True): + t1 = time.time() + # 读取某一帧 + ref,frame=capture.read() + # 格式转变,BGRtoRGB + frame = cv2.cvtColor(frame,cv2.COLOR_BGR2RGB) + # 转变成Image + frame = Image.fromarray(np.uint8(frame)) + + # 进行检测 + frame = np.array(pspnet.detect_image(frame)) + + # RGBtoBGR满足opencv显示格式 + frame = cv2.cvtColor(frame,cv2.COLOR_RGB2BGR) + + fps = ( fps + (1./(time.time()-t1)) ) / 2 + print("fps= %.2f"%(fps)) + frame = cv2.putText(frame, "fps= %.2f"%(fps), (0, 40), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2) + + cv2.imshow("video",frame) + + c= cv2.waitKey(30) & 0xff + if c==27: + capture.release() + break