YOLOv8 极简分割代码并输出各类别像素占比

本文介绍了如何使用YOLOv8模型进行图像分割,并详细解析了计算各目标类别像素占比的代码实现过程,包括遍历目标、掩码处理、像素占比计算和可视化展示。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


前言

在计算机视觉领域,图像分割是一个重要的研究方向,它能帮助我们精确地提取图像中的各个目标物体,对于图像分析、自动驾驶等应用都具有重要意义。本文将介绍如何利用YOLOv8模型进行图像分割,并输出各类别像素占比。


功能概述

1. 选择需要分割的图像的文件夹
2. 加载 YOLOv8 模型并进行目标分割
3. 计算各类别像素占比
4. 可视化分割结果


必要环境

  1. 配置yolov8/10环境 可参考往期博客
    地址:https://blog.youkuaiyun.com/Dora_blank/article/details/139302363?spm=1001.2014.3001.5502

一、代码结构

1. 参数定义

parser = argparse.ArgumentParser()
# 分割参数
parser.add_argument('--seg_weights', default=r"yolov8n-seg.pt", type=str, help='segment weights path')
parser.add_argument('--source', default=r"test", type=str, help='img path')
parser.add_argument('--save', default=r"./save", type=str, help='save img or video path')
parser.add_argument('--conf_thre', type=float, default=0.5, help='conf_thre')
parser.add_argument('--iou_thre', type=float, default=0.5, help='iou_thre')
parser.add_argument('--vis', default=True, action='store_true', help='visualize image')
opt = parser.parse_args()
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')

参数作用如下:
–seg_weights:YOLOv8分割权重路径
–source:输入图像文件夹路径
–save:结果保存路径
–conf_thre:置信度阈值
–iou_thre:IoU阈值
–vis:可视化跟踪结果

2. 定义检测器类

初始化YOLOv8模型,设置分割参数,包含分割和绘制结果的功能

class Segmentor(object):
    def __init__(self, model_path, conf_threshold=0.5, iou_threshold=0.5, device='cpu'):
        self.device = device
        self.model = YOLO(model_path)
        self.conf_threshold = conf_threshold
        self.iou_threshold = iou_threshold
        self.names = self.model.names
        self.classes = [self.names[key] for key in self.names.keys()]

3. 计算各类别像素占比

call 方法中,核心部分是对图像进行分割并计算各类别像素占比,下面将详细介绍这部分代码的实现

for idx in range(len(bboxes_cls)):
    mask = masks[idx]
    box_cls = int(bboxes_cls[idx])
    bbox_label = self.names[box_cls]
    mask_polygon = mask.astype(np.int32)
    mask_img = np.zeros(img.shape[:2], dtype=np.uint8)
    cv2.fillPoly(mask_img, [mask_polygon], 1)
    mask_pixels = np.sum(mask_img)
    M = cv2.moments(mask_polygon)
    if M["m00"] != 0:
        cx = int(M["m10"] / M["m00"])
        cy = int(M["m01"] / M["m00"])
        pixel_ratio = mask_pixels / total_pixels
        class_counts[bbox_label] += pixel_ratio
        cv2.putText(res, f'{pixel_ratio:.1%}', (cx, cy),
                    cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)

3.1 遍历每个检测到的目标

bboxes_cls 包含了所有检测到的目标的类别索引,通过遍历它们,我们可以对每个目标进行处理

for idx in range(len(bboxes_cls)):

3.2 获取当前目标的掩码和类别

masks[idx] 获取当前目标的掩码,bboxes_cls[idx] 获取当前目标的类别索引并转换为整数,然后通过索引在 self.names 中获取类别标签

mask = masks[idx]
box_cls = int(bboxes_cls[idx])
bbox_label = self.names[box_cls]

3.3 将掩码转换为整数多边形

掩码数据通常是浮点数形式,为了绘制多边形,我们需要将其转换为整数

mask_polygon = mask.astype(np.int32)

3.4 创建空白掩码图像并填充多边形

创建一个与输入图像大小相同的空白掩码图像,然后使用 cv2.fillPoly 函数在掩码图像上填充多边形

mask_img = np.zeros(img.shape[:2], dtype=np.uint8)
cv2.fillPoly(mask_img, [mask_polygon], 1)

3.5 计算掩码像素数

通过对掩码图像求和,计算出掩码覆盖的像素数

mask_pixels = np.sum(mask_img)

3.6 计算掩码多边形的质心

使用 cv2.moments 计算多边形的矩,然后通过矩的计算公式得到质心坐标 (cx, cy)

M = cv2.moments(mask_polygon)
if M["m00"] != 0:
    cx = int(M["m10"] / M["m00"])
    cy = int(M["m01"] / M["m00"])

3.7 计算像素占比并更新类别计数

计算掩码像素数占总像素数的比例,并将其添加到 class_counts 字典中对应类别的计数中

pixel_ratio = mask_pixels / total_pixels
class_counts[bbox_label] += pixel_ratio

3.8 在结果图像上显示像素占比

使用 cv2.putText 在结果图像的质心位置显示当前目标的像素占比

cv2.putText(res, f'{pixel_ratio:.1%}', (cx, cy),
            cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)

3.9 完整计算像素比代码

    for idx in range(len(bboxes_cls)):
        mask = masks[idx]
        box_cls = int(bboxes_cls[idx])
        bbox_label = self.names[box_cls]
        mask_polygon = mask.astype(np.int32)
        mask_img = np.zeros(img.shape[:2], dtype=np.uint8)
        cv2.fillPoly(mask_img, [mask_polygon], 1)
        mask_pixels = np.sum(mask_img)
        M = cv2.moments(mask_polygon)
        if M["m00"] != 0:
            cx = int(M["m10"] / M["m00"])
            cy = int(M["m01"] / M["m00"])
            pixel_ratio = mask_pixels / total_pixels
            class_counts[bbox_label] += pixel_ratio
            cv2.putText(res, f'{pixel_ratio:.1%}', (cx, cy),
                        cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)

4. 显示像素占比

在完成图像分割和像素占比计算后,接下来我们要在图像上显示每个类别的像素占比信息

text_start_y = 35
for key, value in class_counts.items():
    label = f'{key}: {value:.1%}'
    print(label)
    cv2.putText(res, label, (20, text_start_y),
                cv2.FONT_HERSHEY_COMPLEX, 1.2, (0, 255, 0), thickness=3)
    text_start_y += 35
return res

二、完整代码

完整代码如下:

# -*- coding:utf-8 -*-
import cv2
from ultralytics import YOLO
import argparse
import torch
from collections import defaultdict
import os
import numpy as np

parser = argparse.ArgumentParser()
# 分割参数
parser.add_argument('--seg_weights', default=r"yolov8n-seg.pt", type=str, help='segment weights path')
parser.add_argument('--source', default=r"test", type=str, help='img path')
parser.add_argument('--save', default=r"./save", type=str, help='save img or video path')
parser.add_argument('--conf_thre', type=float, default=0.5, help='conf_thre')
parser.add_argument('--iou_thre', type=float, default=0.5, help='iou_thre')
parser.add_argument('--vis', default=True, action='store_true', help='visualize image')
opt = parser.parse_args()
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')


class Segmentor(object):
    def __init__(self, model_path, conf_threshold=0.5, iou_threshold=0.5, device='cpu'):
        self.device = device
        self.model = YOLO(model_path)
        self.conf_threshold = conf_threshold
        self.iou_threshold = iou_threshold
        self.names = self.model.names
        self.classes = [self.names[key] for key in self.names.keys()]

    def __call__(self, img):
        class_counts = defaultdict(int)
        total_pixels = img.shape[0] * img.shape[1]
        result = self.model(img, verbose=False, conf=self.conf_threshold,
                            iou=self.iou_threshold, device=self.device)[0]
        res = result.plot()  # 可视化
        bboxes_cls = result.boxes.cls
        masks = result.masks.xy

        for idx in range(len(bboxes_cls)):
            mask = masks[idx]
            box_cls = int(bboxes_cls[idx])
            bbox_label = self.names[box_cls]
            mask_polygon = mask.astype(np.int32)
            mask_img = np.zeros(img.shape[:2], dtype=np.uint8)
            cv2.fillPoly(mask_img, [mask_polygon], 1)
            mask_pixels = np.sum(mask_img)
            M = cv2.moments(mask_polygon)
            if M["m00"] != 0:
                cx = int(M["m10"] / M["m00"])
                cy = int(M["m01"] / M["m00"])
                pixel_ratio = mask_pixels / total_pixels
                class_counts[bbox_label] += pixel_ratio
                cv2.putText(res, f'{pixel_ratio:.1%}', (cx, cy),
                            cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)

        text_start_y = 35
        for key, value in class_counts.items():
            label = f'{key}: {value:.1%}'
            print(label)
            cv2.putText(res, label, (20, text_start_y),
                        cv2.FONT_HERSHEY_COMPLEX, 1.2, (0, 255, 0), thickness=3)
            text_start_y += 35
        return res


# Example usage
if __name__ == '__main__':
    model = Segmentor(opt.seg_weights, conf_threshold=opt.conf_thre, iou_threshold=opt.iou_thre)
    images_format = ['.png', '.jpg', '.jpeg', '.JPG', '.PNG', '.JPEG']

    image_names = [name for name in os.listdir(opt.source) for item in images_format if
                   os.path.splitext(name)[1] == item]

    for img_name in image_names:
        img_path = os.path.join(opt.source, img_name)
        img_ori = cv2.imread(img_path)
        img_vis = model(img_ori)
        img_vis = cv2.resize(img_vis, None, fx=1.0, fy=1.0, interpolation=cv2.INTER_NEAREST)
        cv2.imwrite(os.path.join(opt.save, img_name), img_vis)

        if opt.vis:
            cv2.imshow(img_name, img_vis)
            cv2.waitKey(0)
            cv2.destroyAllWindows()

三、效果展示

在这里插入图片描述
在这里插入图片描述


总结

本期博客就到这里啦,喜欢的小伙伴们可以点点关注,感谢!

最近经常在b站上更新一些有关目标检测的视频,大家感兴趣可以来看看 https://b23.tv/1upjbcG

学习交流群:995760755

### 计算YOLO模型输出分割区域面积 对于YOLO系列模型而言,原始版本主要专注于边界框的目标检测而非像素级别的语义分割。然而,在一些变体或扩展应用中确实涉及到了分割任务[^2]。 当涉及到计算YOLO模型输出的具体分割区域面积时,如果假设模型已经适配为能够提供类似于语义分割的结果——即每个像素都被分类为目标对象的一部分或是背景,则可以通过如下方式来实现: #### 方法一:二值化处理后统计 1. 对于给定的一张预测掩码图像(尺寸如\(160 \times 160\)),先将其转换成二值化的形式。 - 设定一个阈值,比如0.5,高于此概率值认为属于前景物体,低于则视为背景。 ```python import numpy as np def calculate_segmentation_area(mask, threshold=0.5): binary_mask = mask >= threshold area = np.sum(binary_mask) return area ``` 这种方法单直观,适用于那些直接输出连续数值作为类属可能性的地图情况[^4]。 #### 方法二:利用多边形近似法 另一种更精确的方法是在得到一系列边界点的基础上通过几何运算求得封闭图形内部的实际覆盖范围。这通常用于实例分割场景下,其中不仅有类别标签还有具体的轮廓信息。 ```python from shapely.geometry import Polygon def polygonal_approximation(contours): polygons = [] areas = [] for contour in contours: poly = Polygon(contour.reshape(-1, 2)) if not poly.is_valid or poly.area < 1e-3: continue polygons.append(poly) areas.append(poly.area) total_area = sum(areas) return total_area, polygons ``` 上述两种方案的选择取决于具体应用场景的需求以及所使用的YOLO衍生架构特性。如果是基于传统YOLO进行适当调整以适应特定类型的分割任务,则更多时候会依赖第一种方法;而对于更加复杂的实例级分割,则可能需要引入额外组件支持第二种途径。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

小猫发财

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

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

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

打赏作者

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

抵扣说明:

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

余额充值