第96步 深度学习图像目标检测:FCOS建模

基于WIN10的64位系统演示

一、写在前面

本期开始,我们继续学习深度学习图像目标检测系列,FCOS(Fully Convolutional One-Stage Object Detection)模型。

二、FCOS简介

FCOS(Fully Convolutional One-Stage Object Detection)是一种无锚框的目标检测方法,由 Tian et al. 在 2019 年提出。与传统的基于锚框的目标检测方法(如 Faster R-CNN 和 SSD)不同,FCOS 完全摒弃了锚框的概念,使得模型结构更为简洁和高效。

以下是 FCOS 模型的主要特点:

(1)无锚框设计:

FCOS 不使用预定义的锚框来生成候选框。相反,它直接在特征图上的每个位置进行预测。这消除了与锚框大小和形状相关的超参数,简化了模型设计。

(2)位置编码:

对于特征图上的每个位置,FCOS 不仅预测类别分数,还预测与真实边界框的四个边的距离。这四个距离值为:左、右、上、下,与目标中心的相对距离。

(3)训练时的位置限制:

为了使每个位置只对特定大小的目标负责,FCOS 在训练时为特征图的每个层级引入了一个目标大小的范围。这确保了大的物体由底层的特征图来检测,小的物体由高层的特征图来检测。

(4)中心性偏置:

由于物体的中心位置通常包含更明确的语义信息,FCOS 引入了一个中心性分支来预测每个位置是否接近物体的中心。这有助于减少检测的假阳性。

(5)简洁与高效:

由于其无锚框的设计,FCOS 的结构相对简单,计算量较小,但在多个标准数据集上的性能与其他一流的目标检测方法相当或更好。

三、数据源

来源于公共数据,文件设置如下:

大概的任务就是:用一个框框标记出MTB的位置。

四、FCOS实战

直接上代码:

import os
import random
import torch
import torchvision
from torchvision.models.detection import fcos_resnet50_fpn
from torchvision.models.detection.fcos import FCOS_ResNet50_FPN_Weights
from torchvision.transforms import functional as F
from PIL import Image
from torch.utils.data import DataLoader
import xml.etree.ElementTree as ET
import matplotlib.pyplot as plt
from torchvision import transforms
import albumentations as A
from albumentations.pytorch import ToTensorV2
import numpy as np

# Function to parse XML annotations
def parse_xml(xml_path):
    tree = ET.parse(xml_path)
    root = tree.getroot()

    boxes = []
    for obj in root.findall("object"):
        bndbox = obj.find("bndbox")
        xmin = int(bndbox.find("xmin").text)
        ymin = int(bndbox.find("ymin").text)
        xmax = int(bndbox.find("xmax").text)
        ymax = int(bndbox.find("ymax").text)

        # Check if the bounding box is valid
        if xmin < xmax and ymin < ymax:
            boxes.append((xmin, ymin, xmax, ymax))
        else:
            print(f"Warning: Ignored invalid box in {xml_path} - ({xmin}, {ymin}, {xmax}, {ymax})")

    return boxes

# Function to split data into training and validation sets
def split_data(image_dir, split_ratio=0.8):
    all_images = [f for f in os.listdir(image_dir) if f.endswith(".jpg")]
    random.shuffle(all_images)
    split_idx = int(len(all_images) * split_ratio)
    train_images = all_images[:split_idx]
    val_images = all_images[split_idx:]
    
    return train_images, val_images


# Dataset class for the Tuberculosis dataset
class TuberculosisDataset(torch.utils.data.Dataset):
    def __init__(self, image_dir, annotation_dir, image_list, transform=None):
        self.image_dir = image_dir
        self.annotation_dir = annotation_dir
        self.image_list = image_list
        self.transform = transform

    def __len__(self):
        return len(self.image_list)

    def __getitem__(self, idx):
        image_path = os.path.join(self.image_dir, self.image_list[idx])
        image = Image.open(image_path).convert("RGB")
        
        xml_path = os.path.join(self.annotation_dir, self.image_list[idx].replace(".jpg", ".xml"))
        boxes = parse_xml(xml_path)
        
        # Check for empty bounding boxes and return None
        if len(boxes) == 0:
            return None
        
        boxes = torch.as_tensor(boxes, dtype=torch.float32)
        labels = torch.ones((len(boxes),), dtype=torch.int64)
        iscrowd = torch.zeros((len(boxes),), dtype=torch.int64)
        
        target = {}
        target["boxes"] = boxes
        target["labels"] = labels
        target["image_id"] = torch.tensor([idx])
        target["iscrowd"] = iscrowd
        
        # Apply transformations
        if self.transform:
            image = self.transform(image)
    
        return image, target

# Define the transformations using torchvision
data_transform = torchvision.transforms.Compose([
    torchvision.transforms.ToTensor(),  # Convert PIL image to tensor
    torchvision.transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])  # Normalize the images
])


# Adjusting the DataLoader collate function to handle None values
def collate_fn(batch):
    batch = list(filter(lambda x: x is not None, batch))
    return tuple(zip(*batch))


def get_fcos_model_for_finetuning(num_classes):
    # Load an FCOS model with a ResNet-50-FPN backbone without pre-trained weights
    model = fcos_resnet50_fpn(weights=None, num_classes=num_classes)
    
    return model


# Function to save the model
def save_model(model, path="fcos_mtb.pth", save_full_model=False):
    if save_full_model:
        torch.save(model, path)
    else:
        torch.save(model.state_dict(), path)
    print(f"Model saved to {path}")

# Function to compute Intersection over Union
def compute_iou(boxA, boxB):
    xA = max(boxA[0], boxB[0])
    yA = max(boxA[1], boxB[1])
    xB = min(boxA[2], boxB[2])
    yB = min(boxA[3], boxB[3])
    
    interArea = max(0, xB - xA + 1) * max(0, yB - yA + 1)
    boxAArea = (boxA[2] - boxA[0] + 1) * (boxA[3] - boxA[1] + 1)
    boxBArea = (boxB[2] - boxB[0] + 1) * (boxB[3] - boxB[1] + 1)
    
    iou = interArea / float(boxAArea + boxBArea - interArea)
    return iou

# Adjusting the DataLoader collate function to handle None values and entirely empty batches
def collate_fn(batch):
    batch = list(filter(lambda x: x is not None, batch))
    if len(batch) == 0:
        # Return placeholder batch if entirely empty
        return [torch.zeros(1, 3, 224, 224)], [{}]
    return tuple(zip(*batch))

#Training function with modifications for collecting IoU and loss
def train_model(model, train_loader, optimizer, device, num_epochs=10):
    model.train()
    model.to(device)
    loss_values = []
    iou_values = []
    for epoch in range(num_epochs):
        epoch_loss = 0.0
        total_ious = 0
        num_boxes = 0
        for images, targets in train_loader:
            # Skip batches with placeholder data
            if len(targets) == 1 and not targets[0]:
                continue
            # Skip batches with empty targets
            if any(len(target["boxes"]) == 0 for target in targets):
                continue
            images = [image.to(device) for image in images]
            targets = [{k: v.to(device) for k, v in t.items()} for t in targets]
            
            loss_dict = model(images, targets)
            losses = sum(loss for loss in loss_dict.values())
            
            optimizer.zero_grad()
            losses.backward()
            optimizer.step()
            
            epoch_loss += losses.item()
            
            # Compute IoU for evaluation
            with torch.no_grad():
                model.eval()
                predictions = model(images)
                for i, prediction in enumerate(predictions):
                    pred_boxes = prediction["boxes"].cpu().numpy()
                    true_boxes = targets[i]["boxes"].cpu().numpy()
                    for pred_box in pred_boxes:
                        for true_box in true_boxes:
                            iou = compute_iou(pred_box, true_box)
                            total_ious += iou
                            num_boxes += 1
                model.train()
        
        avg_loss = epoch_loss / len(train_loader)
        avg_iou = total_ious / num_boxes if num_boxes != 0 else 0
        loss_values.append(avg_loss)
        iou_values.append(avg_iou)
        print(f"Epoch {epoch+1}/{num_epochs} Loss: {avg_loss} Avg IoU: {avg_iou}")
    
    # Plotting loss and IoU values
    plt.figure(figsize=(12, 5))
    plt.subplot(1, 2, 1)
    plt.plot(loss_values, label="Training Loss")
    plt.title("Training Loss across Epochs")
    plt.xlabel("Epochs")
    plt.ylabel("Loss")
    
    plt.subplot(1, 2, 2)
    plt.plot(iou_values, label="IoU")
    plt.title("IoU across Epochs")
    plt.xlabel("Epochs")
    plt.ylabel("IoU")
    plt.show()

    # Save model after training
    save_model(model)

# Validation function
def validate_model(model, val_loader, device):
    model.eval()
    model.to(device)
    
    with torch.no_grad():
        for images, targets in val_loader:
            images = [image.to(device) for image in images]
            targets = [{k: v.to(device) for k, v in t.items()} for t in targets]
            model(images)

# Paths to your data
image_dir = "tuberculosis-phonecamera"
annotation_dir = "tuberculosis-phonecamera"

# Split data
train_images, val_images = split_data(image_dir)

# Create datasets and dataloaders
train_dataset = TuberculosisDataset(image_dir, annotation_dir, train_images, transform=data_transform)
val_dataset = TuberculosisDataset(image_dir, annotation_dir, val_images, transform=data_transform)

# Updated DataLoader with new collate function
train_loader = DataLoader(train_dataset, batch_size=4, shuffle=True, collate_fn=collate_fn)
val_loader = DataLoader(val_dataset, batch_size=4, shuffle=False, collate_fn=collate_fn)

# Model and optimizer
model = get_fcos_model_for_finetuning(2)
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)


# Train and validate
train_model(model, train_loader, optimizer, device="cuda", num_epochs=100)
validate_model(model, val_loader, device="cuda")


#######################################Print Metrics######################################
def calculate_metrics(predictions, ground_truths, iou_threshold=0.5):
    TP = 0  # True Positives
    FP = 0  # False Positives
    FN = 0  # False Negatives
    total_iou = 0  # to calculate mean IoU

    for pred, gt in zip(predictions, ground_truths):
        pred_boxes = pred["boxes"].cpu().numpy()
        gt_boxes = gt["boxes"].cpu().numpy()

        # Match predicted boxes to ground truth boxes
        for pred_box in pred_boxes:
            max_iou = 0
            matched = False
            for gt_box in gt_boxes:
                iou = compute_iou(pred_box, gt_box)
                if iou > max_iou:
                    max_iou = iou
                    if iou > iou_threshold:
                        matched = True

            total_iou += max_iou
            if matched:
                TP += 1
            else:
                FP += 1

        FN += len(gt_boxes) - TP

    precision = TP / (TP + FP) if (TP + FP) != 0 else 0
    recall = TP / (TP + FN) if (TP + FN) != 0 else 0
    f1_score = (2 * precision * recall) / (precision + recall) if (precision + recall) != 0 else 0
    mean_iou = total_iou / (TP + FP)

    return precision, recall, f1_score, mean_iou

def evaluate_model(model, dataloader, device):
    model.eval()
    model.to(device)
    all_predictions = []
    all_ground_truths = []

    with torch.no_grad():
        for images, targets in dataloader:
            images = [image.to(device) for image in images]
            predictions = model(images)

            all_predictions.extend(predictions)
            all_ground_truths.extend(targets)

    precision, recall, f1_score, mean_iou = calculate_metrics(all_predictions, all_ground_truths)
    return precision, recall, f1_score, mean_iou


train_precision, train_recall, train_f1, train_iou = evaluate_model(model, train_loader, "cuda")
val_precision, val_recall, val_f1, val_iou = evaluate_model(model, val_loader, "cuda")

print("Training Set Metrics:")
print(f"Precision: {train_precision:.4f}, Recall: {train_recall:.4f}, F1 Score: {train_f1:.4f}, Mean IoU: {train_iou:.4f}")

print("\nValidation Set Metrics:")
print(f"Precision: {val_precision:.4f}, Recall: {val_recall:.4f}, F1 Score: {val_f1:.4f}, Mean IoU: {val_iou:.4f}")

#sheet
header = "| Metric    | Training Set | Validation Set |"
divider = "+----------+--------------+----------------+"

train_metrics = f"| Precision | {train_precision:.4f}      | {val_precision:.4f}          |"
recall_metrics = f"| Recall    | {train_recall:.4f}      | {val_recall:.4f}          |"
f1_metrics = f"| F1 Score  | {train_f1:.4f}      | {val_f1:.4f}          |"
iou_metrics = f"| Mean IoU  | {train_iou:.4f}      | {val_iou:.4f}          |"

print(header)
print(divider)
print(train_metrics)
print(recall_metrics)
print(f1_metrics)
print(iou_metrics)
print(divider)

#######################################Train Set######################################
import numpy as np
import matplotlib.pyplot as plt

def plot_predictions_on_image(model, dataset, device, title):
    # Select a random image from the dataset
    idx = np.random.randint(5, len(dataset))
    image, target = dataset[idx]
    img_tensor = image.clone().detach().to(device).unsqueeze(0)

    # Use the model to make predictions
    model.eval()
    with torch.no_grad():
        prediction = model(img_tensor)

    # Inverse normalization for visualization
    inv_normalize = transforms.Normalize(
        mean=[-0.485/0.229, -0.456/0.224, -0.406/0.225],
        std=[1/0.229, 1/0.224, 1/0.225]
    )
    image = inv_normalize(image)
    image = torch.clamp(image, 0, 1)
    image = F.to_pil_image(image)

    # Plot the image with ground truth boxes
    plt.figure(figsize=(10, 6))
    plt.title(title + " with Ground Truth Boxes")
    plt.imshow(image)
    ax = plt.gca()

    # Draw the ground truth boxes in blue
    for box in target["boxes"]:
        rect = plt.Rectangle(
            (box[0], box[1]), box[2]-box[0], box[3]-box[1],
            fill=False, color='blue', linewidth=2
        )
        ax.add_patch(rect)
    plt.show()

    # Plot the image with predicted boxes
    plt.figure(figsize=(10, 6))
    plt.title(title + " with Predicted Boxes")
    plt.imshow(image)
    ax = plt.gca()

    # Draw the predicted boxes in red
    for box in prediction[0]["boxes"].cpu():
        rect = plt.Rectangle(
            (box[0], box[1]), box[2]-box[0], box[3]-box[1],
            fill=False, color='red', linewidth=2
        )
        ax.add_patch(rect)
    plt.show()

# Call the function for a random image from the train dataset
plot_predictions_on_image(model, train_dataset, "cuda", "Selected from Training Set")


#######################################Val Set######################################

# Call the function for a random image from the validation dataset
plot_predictions_on_image(model, val_dataset, "cuda", "Selected from Validation Set")

这回是从头训练的,因此结果不理想:

(1)loss曲线图:

(2)性能指标:

(3)训练的图片测试结果:

(4)验证集的图片测试结果:

五、写在后面

这回没有使用预训练模型,因为在运行过程中有个问题还没解决,因此只能从头训练,但默认参数也没达到很好的效果。哪位大佬解决了告诉我一声~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/187388.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

iOS强引用引起的内存泄漏

项目中遇到一个问题&#xff1a; 1.在A页面的ViewDidLoad 方法里写了一个接收通知的方法&#xff0c;如下图&#xff1a; 然后在B页面发送通知 &#xff08;注&#xff1a;下图的NOTI 是 [NSNotificationCenter defaultCenter] 的宏&#xff0c; 考虑一下可能有小白看这篇文章…

物联网中基于信任的安全性调查研究:挑战与问题

A survey study on trust-based security in Internet of Things: Challenges and issues 文章目录 a b s t r a c t1. Introduction2. Related work3. IoT security from the one-stop dimension3.1. Output data related security3.1.1. Confidentiality3.1.2. Authenticity …

【vue_2】创建一个弹出权限不足的提示框

定义了一个名为 getUserRole 的 JavaScript 函数&#xff0c;该函数接受一个参数 authorityId&#xff0c;根据这个参数的不同值返回相应的用户角色字符串。这段代码的目的是根据传入的 authorityId 值判断用户的角色&#xff0c;然后返回相应的角色名称。 如果 authorityId 的…

Visual Studio 中文注释乱码解决方案

在公司多人开发项目中经常遇到拉到最新代码&#xff0c;发现中文注释都是乱码&#xff0c;很是emjoy..... 这是由于编码格式不匹配造成的&#xff0c;如果你的注释是 UTF-8 编码&#xff0c;而文件编码是 GBK 或者其他编码&#xff0c;那么就会出现乱码现象。一般的解决办法是…

STM32-使用固件库新建工程

参考链接: 【入门篇】11-新建工程—固件库版本&#xff08;初学者必须认认真真看&#xff09;_哔哩哔哩_bilibili 使用的MCU是STM32F103ZET6 。 这篇参考的是野火的资料&#xff0c;可以在“野火大学堂”或者它的论坛上下载。&#xff08;我通常是野火和正点原子的资料混着看的…

【AI认证笔记】NO.2人工智能的发展

目录 一、人工智能的发展里程碑 二、当前人工智能的发展特点 1.人工智能进入高速发展阶段 2.人工智能元年 三、人工智能高速发展的三大引擎 1.算法突破 2.算力飞跃 3.数据井喷 四、AI的机遇 五、AI人才的缺口 六、行业AI 人工智能算法&#xff0c;万物互联&#xff…

基于mediapipe的人手21点姿态检测模型—CPU上检测速度惊人

前期的文章,我们介绍了MediaPipe对象检测与对象分类任务,也分享了MediaPipe的人手手势识别。在进行人手手势识别前,MediaPipe首先需要进行人手的检测与人手坐标点的检测,经过以上的检测后,才能把人手的坐标点与手势结合起来,进行相关的手势识别。 MediaPipe人手坐标点检测…

案例022:基于微信小程序的行政复议在线预约系统

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;SSM JDK版本&#xff1a;JDK1.8 数据库&#xff1a;mysql 5.7 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.5.4 小程序框架&#xff1a;uniapp 小程序开发软件&#xff1a;HBuilder X 小程序…

将 Hexo 部署到阿里云轻量服务器(保姆级教程)

将 Hexo 部署到阿里云轻量服务器(保姆级教程) 顺哥轻创 1 前言 作为有梦想的,有追求的程序员,有一个自己的个人博客简直就是必须品。你可以选择 wordpress 这种平台,直接使用,在任何地方只要有网络就能写博客。还可以选择 hexo 这种静态博客,但是发文章就没有那么随心…

CentOS7安装Docker运行环境

1 引言 Docker 是一个用于开发&#xff0c;交付和运行应用程序的开放平台。Docker 使您能够将应用程序与基础架构分开&#xff0c;从而可以快速交付软件。借助 Docker&#xff0c;您可以与管理应用程序相同的方式来管理基础架构。通过利用 Docker 的方法来快速交付&#xff0c;…

爬虫项目实战:利用基于selenium框架的爬虫模板爬取豆瓣电影Top250

&#x1f44b; Hi, I’m 货又星&#x1f440; I’m interested in …&#x1f331; I’m currently learning …&#x1f49e; I’m looking to collaborate on …&#x1f4eb; How to reach me … README 目录&#xff08;持续更新中&#xff09; 各种错误处理、爬虫实战及模…

VRRP的交换机VRRP主备配置例子

拓朴如下&#xff1a; 主要配置如下&#xff1a; [S1] vlan batch 10 20 # interface Vlanif10ip address 10.1.1.1 255.255.255.0vrrp vrid 1 virtual-ip 10.1.1.254vrrp vrid 1 priority 200vrrp vrid 1 preempt-mode timer delay 20 # interface Vlanif20ip address 13.1.1…

死磕Nacos系列:Nacos在我的SpringCloud项目中做了什么?

Nacos服务注册 我们一个SpringCloud项目中集成了Nacos&#xff0c;当项目启动成功后&#xff0c;就可以在Nacos管理界面上看到我们项目的注册信息&#xff0c;还可以看到项目的健康状态等等信息&#xff1a; 那Nacos是什么时候进行了哪些操作的呢&#xff1f;今天我们来一探究…

【从浅识到熟知Linux】基本指定之find、grep、head和tail

&#x1f388;归属专栏&#xff1a;从浅学到熟知Linux &#x1f697;个人主页&#xff1a;Jammingpro &#x1f41f;每日一句&#xff1a;一篇又一篇&#xff0c;学写越上头。 文章前言&#xff1a;本文介绍find、grep、head和tail指令用法并给出示例和截图。 文章目录 find基本…

Jquery ajax 进行网络请求,同步阻塞引起的UI线程阻塞 (loading图片不显示 )

jax重新获取数据刷新页面功能&#xff0c;因为ajax属于耗时操作&#xff0c;想在获取数据且加载页面时显示加载遮罩层&#xff0c;结果发现了ajax的好多坑。 ajax 执行http网络请示时时&#xff0c;让遮罩层显示&#xff0c;ajax加载完毕后遮罩层消失。 因为我想让loadChart()…

mysql 更改密码

由于两台设备的mysql数据库的密码不一样&#xff0c;开发时每次连接数据库都需要更改配置文件&#xff0c;所以想修改一下mysql数据库的密码。 mysql 修改密码千万不要直接修改&#xff0c;直接修改的话会出现两种情况&#xff1a; 1&#xff0c;修改成功&#xff0c;无法登录。…

elasticsearch 索引库操作和文档操作

文章目录 索引库操作mapping映射属性索引库的CRUD&#xff08;创建&#xff0c;读取&#xff0c;更新&#xff0c;删除&#xff09;创建索引库和映射基本语法&#xff1a;示例&#xff1a; 查询索引库修改索引库删除索引库 文档操作新增文档查询文档删除文档修改文档全量修改增…

通过互联网代理部署Docker+Kubernetes 1.28.1

一、背景 在公司环境中&#xff0c;我们往往都是无法直接连接外网的&#xff0c;之前写过一篇文章&#xff0c;是通过外网自建的中转机器下载需要的离线包&#xff0c;并在内网搭建一个harbor&#xff0c;通过harbor的方式搭建了一个kubernetes&#xff0c;但是这种方式还是有…

蓝牙运动耳机哪个好?蓝牙运动耳机排行榜前十名

​在运动中&#xff0c;音乐可以激发你的热情和动力&#xff0c;而一款好的运动耳机则可以让你更好地享受音乐。然而&#xff0c;市面上的运动耳机品牌和型号众多&#xff0c;质量参差不齐。所以&#xff0c;今天精选了5款市面上比较优秀的运动耳机给大家参考&#xff0c;是你运…

【鬼鬼鬼iiARPG开发记录】

鬼鬼鬼ARPG开发记录 一、创建项目1、创建3D(URP)项目2、导入新的输入系统&#xff08;input system&#xff09;3、勾选Enter Play Mode Options 二、导入资源1、创建若干文件夹 一、创建项目 1、创建3D(URP)项目 2、导入新的输入系统&#xff08;input system&#xff09; …