使用VggNet进行相机源识别

一、概述

VGGNet 是一种深度学习模型,专注于图像识别和处理领域,由牛津大学的 Visual Geometry Group 开发,因此得名 VGG。该模型因其简单而有效的架构在计算机视觉领域赢得了广泛的认可,尤其是在 2014 年的 ImageNet 图像识别挑战赛(ILSVRC)中表现出色。

二、 架构细节

VGGNet 的主要贡献在于展示了通过增加网络深度可以显著提高网络的性能。它主要使用了以下几个关键的架构创新:

  1. 统一的卷积核大小:VGGNet 使用多个卷积层,每个卷积层中使用的卷积核大小均为 3x3,步长为 1,并且使用了 SAME padding(填充),即卷积操作输出与输入具有相同的空间维度。这种小尺寸卷积核的堆叠可以在不丢失图像特征的前提下,增加网络的深度。

  2. 固定的池化大小:在连续的卷积层之后,VGGNet 使用了 2x2 的最大池化层,步长为 2,用于降低特征图的空间维度,同时保留重要的特征。

  3. 增加深度:VGGNet 有多个版本,不同版本的区别主要在于层数的不同。最著名的版本是 VGG16 和 VGG19,其中数字代表网络中具有可学习权重的层的总数:VGG16 有 13 个卷积层和 3 个全连接层,而 VGG19 则包含 16 个卷积层和 3 个全连接层。

  4. ReLU 激活函数:在每个卷积层之后,VGGNet 使用了 Rectified Linear Unit (ReLU) 激活函数,以增加非线性特性,这有助于网络学习更复杂的模式。

三、VggNet进行相机源识别

VGGNet,以其深邃的网络结构和强大的特征提取能力,在相机源识别这一领域中扮演着关键角色。相机源识别旨在通过分析图像的内在特征来确定拍摄该图像所使用的相机型号。VGGNet 的卷积神经网络(CNN)架构非常适合于捕捉这些细微的、相机特有的图像特征。

VGGNet 在相机源识别中的应用

  1. 特征提取:VGGNet 通过其多个卷积层和池化层,能够提取图像中的高级特征。这些特征包括纹理、颜色分布、噪点模式等,这些都是不同相机型号独有的。例如,某些相机的传感器可能会产生特定的噪点模式,而VGGNet能够学习并识别这些模式。

  2. 网络训练:为了使VGGNet能够识别不同的相机源,需要使用大量带有标签的图像数据进行训练。这些数据集包含了由不同相机型号拍摄的图像,并且每张图像都标注了其相机型号。通过反向传播算法和随机梯度下降(SGD)等优化技术,VGGNet 会不断调整其权重,以最小化预测与真实标签之间的差异。

  3. 迁移学习:由于训练一个深层的VGGNet模型需要大量的计算资源和时间,因此在实际应用中,常常采用迁移学习的方法。这意味着首先在大型数据集(如ImageNet)上预训练VGGNet,然后将这个预训练模型作为起点,在特定的相机源识别任务上进行微调。这种方法可以显著减少所需的训练时间和数据量。

  4. 性能评估:在相机源识别任务中,VGGNet的性能通常通过准确率、召回率和F1分数等指标来评估。这些指标反映了模型在识别不同相机型号时的准确性和鲁棒性。

挑战与前景

尽管VGGNet在相机源识别中表现出色,但也面临一些挑战,例如不同光照条件、图像质量变化和相机设置的差异都可能影响识别的准确性。未来的研究可能会集中在提高模型的泛化能力,使其能够更好地适应各种拍摄条件。

总之,VGGNet通过其深层次的网络结构,为相机源识别提供了一个强大的工具。它不仅能够帮助摄影师保护自己的作品版权,还能够为摄影爱好者和专业人士提供一个学习和比较不同相机性能的平台。随着技术的不断进步,VGGNet在相机源识别领域的应用将更加广泛和精准。

四、代码部分

模型代码

import torch.nn as nn
import torch

# official pretrain weights
model_urls = {
    'vgg11': 'https://download.pytorch.org/models/vgg11-bbd30ac9.pth',
    'vgg13': 'https://download.pytorch.org/models/vgg13-c768596a.pth',
    'vgg16': 'https://download.pytorch.org/models/vgg16-397923af.pth',
    'vgg19': 'https://download.pytorch.org/models/vgg19-dcbb9e9d.pth'
}
#需要什么层数的网络结构自行选择下载预训练模型,本代码使用的是vgg19


class VGG(nn.Module):
    def __init__(self, features, num_classes=1000, init_weights=False):
        super(VGG, self).__init__()
        self.features = features
        self.classifier = nn.Sequential(
            nn.Linear(512*7*7, 4096),
            nn.ReLU(True),
            nn.Dropout(p=0.5),
            nn.Linear(4096, 4096),
            nn.ReLU(True),
            nn.Dropout(p=0.5),
            nn.Linear(4096, num_classes)
        )
        if init_weights:
            self._initialize_weights()

    def forward(self, x):
        # N x 3 x 224 x 224
        x = self.features(x)
        # N x 512 x 7 x 7
        x = torch.flatten(x, start_dim=1)
        # N x 512*7*7
        x = self.classifier(x)
        return x

    def _initialize_weights(self):
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                # nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
                nn.init.xavier_uniform_(m.weight)
                if m.bias is not None:
                    nn.init.constant_(m.bias, 0)
            elif isinstance(m, nn.Linear):
                nn.init.xavier_uniform_(m.weight)
                # nn.init.normal_(m.weight, 0, 0.01)
                nn.init.constant_(m.bias, 0)


def make_features(cfg: list):
    layers = []
    in_channels = 3
    for v in cfg:
        if v == "M":
            layers += [nn.MaxPool2d(kernel_size=2, stride=2)]
        else:
            conv2d = nn.Conv2d(in_channels, v, kernel_size=3, padding=1)
            layers += [conv2d, nn.ReLU(True)]
            in_channels = v
    return nn.Sequential(*layers)


cfgs = {
    'vgg11': [64, 'M', 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'],
    'vgg13': [64, 64, 'M', 128, 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'],
    'vgg16': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'M', 512, 512, 512, 'M', 512, 512, 512, 'M'],
    'vgg19': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 256, 'M', 512, 512, 512, 512, 'M', 512, 512, 512, 512, 'M'],
}


def vgg(model_name="vgg19", **kwargs):
    assert model_name in cfgs, "Warning: model number {} not in cfgs dict!".format(model_name)
    cfg = cfgs[model_name]

    model = VGG(make_features(cfg), **kwargs)
    return model

训练代码

import argparse
import math
import os
import torch
from torch.optim import lr_scheduler
import torch.optim as optim

from model import vgg
from utils import LoadData, train_one_epoch, evaluate, write_result


def main(args):
    print(args)
    if os.path.exists("./weights") is False:
        os.makedirs("./weights")

    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
    print("using {} device.".format(device))

    train_dataset = LoadData("datapaths/spc2018paths/train.txt", True)
    val_dataset = LoadData("datapaths/spc2018paths/val.txt", False)

    train_num = len(train_dataset)
    val_num = len(val_dataset)

    nw = min([os.cpu_count(), args.batch_size if args.batch_size > 1 else 0, 8])  # number of workers
    print('Using {} dataloader workers every process'.format(nw))

    train_loader = torch.utils.data.DataLoader(train_dataset,
                                               batch_size=args.batch_size, shuffle=True,
                                               num_workers=nw)

    val_loader = torch.utils.data.DataLoader(val_dataset,
                                             batch_size=args.batch_size, shuffle=False,
                                             num_workers=nw)
    print("using {} images for training, {} images for validation.".format(train_num,
                                                                           val_num))

    model = vgg(model_name="vgg19", num_classes=args.num_classes)
    model.to(device)
    if args.weights != "":
        if os.path.exists(args.weights):
            weights_dict = torch.load(args.weights, map_location=device)
            load_weights_dict = {k: v for k, v in weights_dict.items()
                                 if model.state_dict()[k].numel() == v.numel()}
            print(model.load_state_dict(load_weights_dict, strict=False))
        else:
            raise FileNotFoundError("not found weights file: {}".format(args.weights))

    if args.freeze_layers:
        for i, param in enumerate(model.parameters()):
            if i < 27:  # Freeze the first few layers
                param.requires_grad = False

    optimizer = optim.SGD(model.parameters(), lr=args.lr, momentum=0.9, weight_decay=1e-4)
    lf = lambda x: ((1 + math.cos(x * math.pi / args.epochs)) / 2) * (1 - args.lrf) + args.lrf  # cosine schedule
    scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lf)

    for epoch in range(args.epochs):
        train_loss, train_acc = train_one_epoch(model=model,
                                                optimizer=optimizer,
                                                data_loader=train_loader,
                                                device=device,
                                                epoch=epoch)
        scheduler.step()
        val_loss, val_acc = evaluate(model=model,
                                     data_loader=val_loader,
                                     device=device,
                                     epoch=epoch)

        print(
            f'Epoch [{epoch + 1}/{args.epochs}], Train Loss: {train_loss}, Train Acc: {train_acc}, Val Loss: {val_loss}, Val Acc: {val_acc}, LR: {optimizer.param_groups[0]["lr"]}')

        write_result("runres/model_train_spc_data.txt", epoch, train_loss, train_acc, val_loss, val_acc)
        torch.save(model.state_dict(), "./weights/model-{}.pth".format(epoch))


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('--num_classes', type=int, default=10)
    parser.add_argument('--epochs', type=int, default=50)
    parser.add_argument('--batch-size', type=int, default=32)
    parser.add_argument('--lr', type=float, default=0.0001)
    parser.add_argument('--lrf', type=float, default=0.1)
    parser.add_argument('--weights', type=str, default='preweights/pre_vgg19.pth', help='initial weights path')
    parser.add_argument('--freeze-layers', type=bool, default=False)
    parser.add_argument('--device', default='cuda:0', help='device id (i.e. 0 or 0,1 or cpu)')

    opt = parser.parse_args()
    main(opt)

预测代码

import pandas as pd
import torch
from torch.utils.data import DataLoader
from tqdm import tqdm

from model import vgg as create_modell
from utils import LoadData


def eval(dataloader, model):
    label_list = []
    likest_list = []
    pred_list = []

    model.eval()
    with torch.no_grad():
        # predict class
        for X, y in tqdm(dataloader, desc="Model is predicting, please wait"):
            # 将数据转到GPU
            X = X.cuda()
            # 将图片传入到模型当中就,得到预测的值pred
            pred = model(X)
            pred_softmax = torch.softmax(pred, 1).cpu().numpy()
            # 获取可能性最大的标签
            label = torch.softmax(pred, 1).cpu().numpy().argmax()
            label_list.append(label)
            # 获取可能性最大的值(即概率)
            likest = torch.softmax(pred, 1).cpu().numpy().max()
            likest_list.append(likest)
            pred_list.append(pred_softmax.tolist()[0])
        return label_list, likest_list, pred_list


if __name__ == "__main__":
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

    model = create_modell(model_name="vgg19", num_classes=10).to(device)
    # load model weights
    model_weight_path = "weights/model-49.pth"

    model.load_state_dict(torch.load(model_weight_path, map_location=device))
    # load image

    # test_dataset = LoadData("best/data_path/test.txt", train_flag=False, img_size=img_size,
    #                         num_model=num_model)
    test_dataset = LoadData("datapaths/spc2018paths/test.txt", train_flag=False)

    test_loader = DataLoader(test_dataset,
                             batch_size=1,
                             num_workers=8,
                             shuffle=False,
                             )
    # 获取结果
    label_list, likest_list, pred = eval(test_loader, model)
    # 将输出保存到exel中,方便后续分析
    label_names = ["HTC-1-M7",
                   "iPhone-4s",
                   "iPhone-6",
                   "LG-Nexus-5x",
                   "Motorola-Droid-Maxx",
                   "Motorola-Nexus-6",
                   "Motorola-X",
                   "Samsung-Galaxy-Note3",
                   "Samsung-Galaxy-S4",
                   "Sony-NEX-7"
                   ]
    df_pred = pd.DataFrame(data=pred, columns=label_names)
    df_pred.to_csv('predres/spc_pred_result.csv', encoding='gbk', index=False)
    print("Done!")

评估代码

"""
    模型性能度量
"""
from sklearn.metrics import *  # pip install scikit-learn
import matplotlib.pyplot as plt  # pip install matplotlib
import numpy as np  # pip install numpy
from numpy import interp
from sklearn.preprocessing import label_binarize
import pandas as pd  # pip install pandas

'''
读取数据
需要读取模型输出的标签(predict_label)以及原本的标签(true_label)
'''
target_loc = "datapaths/spc2018paths/test.txt"  # 真实标签所在的文件
target_data = pd.read_csv(target_loc, sep="\t", names=["loc", "type"])
true_label = [i for i in target_data["type"]]
print(true_label)

predict_loc = "predres/spc_pred_result.csv"  # 3.预测代码生成的文件
predict_data = pd.read_csv(predict_loc)
predict_label = predict_data.to_numpy().argmax(axis=1)
predict_score = predict_data.to_numpy().max(axis=1)
print(predict_label)
'''
    常用指标:精度,查准率,召回率,F1-Score
'''
# 精度,准确率, 预测正确地占所有样本种的比例
accuracy = accuracy_score(true_label, predict_label)
print("精度: ", accuracy)

# 查准率P(准确率),precision(查准率)=TP/(TP+FP)

precision = precision_score(true_label, predict_label, labels=None, pos_label=1,
                            average='macro')  # 'micro', 'macro', 'weighted'
print("准确率: ", precision)

# 查全率R(召回率),原本为对的,预测正确的比例;recall(查全率)=TP/(TP+FN)
recall = recall_score(true_label, predict_label, average='macro')  # 'micro', 'macro', 'weighted'
print("召回率: ", recall)

# F1-Score
f1 = f1_score(true_label, predict_label, average='macro')  # 'micro', 'macro', 'weighted'
print("F1 Score: ", f1)
'''
混淆矩阵
'''
label_names = ["0",
               "1",
               "2",
               "3",
               "4",
               "5",
               "6",
               "7",
               "8",
               "9"
               ]
confusion = confusion_matrix(true_label, predict_label, labels=[i for i in range(len(label_names))])
plt.figure(figsize=(10, 10), dpi=120)
plt.matshow(confusion, fignum=0, cmap=plt.cm.Blues)  # Greens, Blues, Oranges, Reds
plt.colorbar()
for i in range(len(confusion)):
    for j in range(len(confusion)):
        plt.annotate(confusion[j, i], xy=(i, j), horizontalalignment='center', verticalalignment='center')
plt.ylabel('True label')
plt.xlabel('Predicted label')
plt.xticks(range(len(label_names)), label_names)
plt.yticks(range(len(label_names)), label_names)
# plt.title("Confusion Matrix")
plt.savefig("./res_figs/dresden/ConMatrix.png")
plt.show()

n_classes = len(label_names)
# binarize_predict = label_binarize(predict_label, classes=[i for i in range(n_classes)])
binarize_predict = label_binarize(true_label, classes=[i for i in range(n_classes)])

五、评价指标结果部分

  • 精度: 0.819047619047619
  • 准确率: 0.8357843956271577
  • 召回率: 0.819047619047619
  • F1 Score: 0.820496929418313
混淆矩阵

在这里插入图片描述

  • 17
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ttbigcute

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值