全国服务热线:

15861139266

基于Pytorch的从零开始的目标检测(下)-苏州机器视觉学习,苏州上位机学习
发布时间:2023-12-20 09:47:06 点击次数:235

数据增强

数据增强是一种通过使用现有图像的不同变体创建新的训练图像来更好地概括我们的模型的技术。我们当前的训练集中只有 800 张图像,因此数据增强对于确保我们的模型不会过拟合非常重要。


对于这个问题,我使用了翻转、旋转、中心裁剪和随机裁剪。


这里唯一需要记住的是确保包围盒也以与图像相同的方式进行转换。


# modified from fast.ai

def crop(im, r, c, target_r, target_c): 

    return im[r:r+target_r, c:c+target_c]


# random crop to the original size

def random_crop(x, r_pix=8):

    """ Returns a random crop"""

    r, c,*_ = x.shape

    c_pix = round(r_pix*c/r)

    rand_r = random.uniform(0, 1)

    rand_c = random.uniform(0, 1)

    start_r = np.floor(2*rand_r*r_pix).astype(int)

    start_c = np.floor(2*rand_c*c_pix).astype(int)

    return crop(x, start_r, start_c, r-2*r_pix, c-2*c_pix)


def center_crop(x, r_pix=8):

    r, c,*_ = x.shape

    c_pix = round(r_pix*c/r)

    return crop(x, r_pix, c_pix, r-2*r_pix, c-2*c_pix)

def rotate_cv(im, deg, y=False, mode=cv2.BORDER_REFLECT, interpolation=cv2.INTER_AREA):

    """ Rotates an image by deg degrees"""

    r,c,*_ = im.shape

    M = cv2.getRotationMatrix2D((c/2,r/2),deg,1)

    if y:

        return cv2.warpAffine(im, M,(c,r), borderMode=cv2.BORDER_CONSTANT)

    return cv2.warpAffine(im,M,(c,r), borderMode=mode, flags=cv2.WARP_FILL_OUTLIERS+interpolation)


def random_cropXY(x, Y, r_pix=8):

    """ Returns a random crop"""

    r, c,*_ = x.shape

    c_pix = round(r_pix*c/r)

    rand_r = random.uniform(0, 1)

    rand_c = random.uniform(0, 1)

    start_r = np.floor(2*rand_r*r_pix).astype(int)

    start_c = np.floor(2*rand_c*c_pix).astype(int)

    xx = crop(x, start_r, start_c, r-2*r_pix, c-2*c_pix)

    YY = crop(Y, start_r, start_c, r-2*r_pix, c-2*c_pix)

    return xx, YY


def transformsXY(path, bb, transforms):

    x = cv2.imread(str(path)).astype(np.float32)

    x = cv2.cvtColor(x, cv2.COLOR_BGR2RGB)/255

    Y = create_mask(bb, x)

    if transforms:

        rdeg = (np.random.random()-.50)*20

        x = rotate_cv(x, rdeg)

        Y = rotate_cv(Y, rdeg, y=True)

        if np.random.random() > 0.5: 

            x = np.fliplr(x).copy()

            Y = np.fliplr(Y).copy()

        x, Y = random_cropXY(x, Y)

    else:

        x, Y = center_crop(x), center_crop(Y)

    return x, mask_to_bb(Y)

def create_corner_rect(bb, color='red'):

    bb = np.array(bb, dtype=np.float32)

    return plt.Rectangle((bb[1], bb[0]), bb[3]-bb[1], bb[2]-bb[0], color=color,

                         fill=False, lw=3)


def show_corner_bb(im, bb):

    plt.imshow(im)

    plt.gca().add_patch(create_corner_rect(bb))

4.png

PyTorch 数据集

现在我们已经有了数据增强,我们可以进行训练验证拆分并创建我们的 PyTorch 数据集。我们使用 ImageNet 统计数据对图像进行标准化,因为我们使用的是预训练的 ResNet 模型并在训练时在我们的数据集中应用数据增强。


X_train, X_val, y_train, y_val = train_test_split(X, Y, test_size=0.2, random_state=42)

def normalize(im):

    """Normalizes images with Imagenet stats."""

    imagenet_stats = np.array([[0.485, 0.456, 0.406], [0.229, 0.224, 0.225]])

    return (im - imagenet_stats[0])/imagenet_stats[1]

class RoadDataset(Dataset):

    def __init__(self, paths, bb, y, transforms=False):

        self.transforms = transforms

        self.paths = paths.values

        self.bb = bb.values

        self.y = y.values

    def __len__(self):

        return len(self.paths)

    

    def __getitem__(self, idx):

        path = self.paths[idx]

        y_class = self.y[idx]

        x, y_bb = transformsXY(path, self.bb[idx], self.transforms)

        x = normalize(x)

        x = np.rollaxis(x, 2)

        return x, y_class, y_bb

train_ds = RoadDataset(X_train['new_path'],X_train['new_bb'] ,y_train, transforms=True)

valid_ds = RoadDataset(X_val['new_path'],X_val['new_bb'],y_val)

batch_size = 64

train_dl = DataLoader(train_ds, batch_size=batch_size, shuffle=True)

valid_dl = DataLoader(valid_ds, batch_size=batch_size)


PyTorch 模型

对于这个模型,我使用了一个非常简单的预先训练的 resNet-34模型。由于我们有两个任务要完成,这里有两个最后的层: 包围盒回归器和图像分类器。


class BB_model(nn.Module):

    def __init__(self):

        super(BB_model, self).__init__()

        resnet = models.resnet34(pretrained=True)

        layers = list(resnet.children())[:8]

        self.features1 = nn.Sequential(*layers[:6])

        self.features2 = nn.Sequential(*layers[6:])

        self.classifier = nn.Sequential(nn.BatchNorm1d(512), nn.Linear(512, 4))

        self.bb = nn.Sequential(nn.BatchNorm1d(512), nn.Linear(512, 4))

        

    def forward(self, x):

        x = self.features1(x)

        x = self.features2(x)

        x = F.relu(x)

        x = nn.AdaptiveAvgPool2d((1,1))(x)

        x = x.view(x.shape[0], -1)

        return self.classifier(x), self.bb(x)


训练

对于损失,我们需要同时考虑分类损失和边界框回归损失,因此我们使用交叉熵和 L1 损失(真实值和预测坐标之间的所有绝对差之和)的组合。我已经将 L1 损失缩放了 1000 倍,因为分类和回归损失都在相似的范围内。除此之外,它是一个标准的 PyTorch 训练循环(使用 GPU):


def update_optimizer(optimizer, lr):

    for i, param_group in enumerate(optimizer.param_groups):

        param_group["lr"] = lr

def train_epocs(model, optimizer, train_dl, val_dl, epochs=10,C=1000):

    idx = 0

    for i in range(epochs):

        model.train()

        total = 0

        sum_loss = 0

        for x, y_class, y_bb in train_dl:

            batch = y_class.shape[0]

            x = x.cuda().float()

            y_class = y_class.cuda()

            y_bb = y_bb.cuda().float()

            out_class, out_bb = model(x)

            loss_class = F.cross_entropy(out_class, y_class, reduction="sum")

            loss_bb = F.l1_loss(out_bb, y_bb, reduction="none").sum(1)

            loss_bb = loss_bb.sum()

            loss = loss_class + loss_bb/C

            optimizer.zero_grad()

            loss.backward()

            optimizer.step()

            idx += 1

            total += batch

            sum_loss += loss.item()

        train_loss = sum_loss/total

        val_loss, val_acc = val_metrics(model, valid_dl, C)

        print("train_loss %.3f val_loss %.3f val_acc %.3f" % (train_loss, val_loss, val_acc))

    return sum_loss/total

def val_metrics(model, valid_dl, C=1000):

    model.eval()

    total = 0

    sum_loss = 0

    correct = 0 

    for x, y_class, y_bb in valid_dl:

        batch = y_class.shape[0]

        x = x.cuda().float()

        y_class = y_class.cuda()

        y_bb = y_bb.cuda().float()

        out_class, out_bb = model(x)

        loss_class = F.cross_entropy(out_class, y_class, reduction="sum")

        loss_bb = F.l1_loss(out_bb, y_bb, reduction="none").sum(1)

        loss_bb = loss_bb.sum()

        loss = loss_class + loss_bb/C

        _, pred = torch.max(out_class, 1)

        correct += pred.eq(y_class).sum().item()

        sum_loss += loss.item()

        total += batch

    return sum_loss/total, correct/total

model = BB_model().cuda()

parameters = filter(lambda p: p.requires_grad, model.parameters())

optimizer = torch.optim.Adam(parameters, lr=0.006)

train_epocs(model, optimizer, train_dl, valid_dl, epochs=15)


测试

现在我们已经完成了训练,我们可以选择一个随机图像并在上面测试我们的模型。尽管我们只有相当少量的训练图像,但是我们最终在测试图像上得到了一个相当不错的预测。


使用手机拍摄真实照片并测试模型将是一项有趣的练习。另一个有趣的实验是不执行任何数据增强并训练模型并比较两个模型。


# resizing test image

im = read_image('./road_signs/images_resized/road789.png')

im = cv2.resize(im, (int(1.49*300), 300))

cv2.imwrite('./road_signs/road_signs_test/road789.jpg', cv2.cvtColor(im, cv2.COLOR_RGB2BGR))

# test Dataset

test_ds = RoadDataset(pd.DataFrame([{'path':'./road_signs/road_signs_test/road789.jpg'}])['path'],pd.DataFrame([{'bb':np.array([0,0,0,0])}])['bb'],pd.DataFrame([{'y':[0]}])['y'])

x, y_class, y_bb = test_ds[0]

xx = torch.FloatTensor(x[None,])

xx.shape

# prediction

out_class, out_bb = model(xx.cuda())

out_class, out_bb

5.png


总结

现在我们已经介绍了目标检测的基本原理,并从头开始实现它,您可以将这些想法扩展到多对象情况,并尝试更复杂的模型,如 RCNN 和 YOLO!


立即咨询
  • 品质服务

    服务贴心周到

  • 快速响应

    全天24小时随时沟通

  • 专业服务

    授权率高,保密性强

  • 完善售后服务

    快速响应需求,及时性服务

直播课程
软件开发基础课程
上位机软件开发课
机器视觉软件开发课
专题课
联系方式
电话:15861139266
邮箱:75607082@qq.com
地址:苏州吴中区木渎镇尧峰路69号
关注我们

版权所有:江苏和讯自动化设备有限公司所有 备案号:苏ICP备2022010314号-1

技术支持: 易动力网络