YOLO标签可视化

将YOLO的 txt 格式标签画到对应的图片上面

import os
import cv2
import time
import math
import torch
import random
import matplotlib
import numpy as np
from PIL import Image
from tqdm import tqdm
from threading import Thread
from pathlib import Path


# Settings
matplotlib.rc('font', **{'size': 11})
matplotlib.use('Agg')  # for writing to files only


def color_list():
    """给不同类别的框配置不同的颜色"""
    # Return first 10 plt colors as (r,g,b) https://stackoverflow.com/questions/51350872/python-from-color-name-to-rgb
    def hex2rgb(h):
        return tuple(int(h[1 + i:1 + i + 2], 16) for i in (0, 2, 4))

    return [hex2rgb(h) for h in matplotlib.colors.TABLEAU_COLORS.values()]  # or BASE_ (8), CSS4_ (148), XKCD_ (949)


def xywh2xyxy(x):
    # Convert nx4 boxes from [x, y, w, h] to [x1, y1, x2, y2] where xy1=top-left, xy2=bottom-right
    y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x)
    y[:, 0] = x[:, 0] - x[:, 2] / 2  # top left x
    y[:, 1] = x[:, 1] - x[:, 3] / 2  # top left y
    y[:, 2] = x[:, 0] + x[:, 2] / 2  # bottom right x
    y[:, 3] = x[:, 1] + x[:, 3] / 2  # bottom right y
    return y


def plot_one_box(x, img, color=None, label=None, line_thickness=3):
    # Plots one bounding box on image img
    tl = line_thickness or round(0.002 * (img.shape[0] + img.shape[1]) / 2) + 1  # line/font thickness
    color = color or [random.randint(0, 255) for _ in range(3)]
    c1, c2 = (int(x[0]), int(x[1])), (int(x[2]), int(x[3]))
    cv2.rectangle(img, c1, c2, color, thickness=tl, lineType=cv2.LINE_AA)
    if label:
        tf = max(tl - 1, 1)  # font thickness
        t_size = cv2.getTextSize(label, 0, fontScale=tl / 3, thickness=tf)[0]
        c2 = c1[0] + t_size[0], c1[1] - t_size[1] - 3
        cv2.rectangle(img, c1, c2, color, -1, cv2.LINE_AA)  # filled
        cv2.putText(img, label, (c1[0], c1[1] - 2), 0, tl / 3, [225, 255, 255], thickness=tf, lineType=cv2.LINE_AA)


def get_xywh(path):
    try:
        with open(path, "r") as f:
            # 读取每一行label,并按空格划分数据
            label = np.array([x.split() for x in f.read().splitlines()], dtype=np.float32)
            # return l
    except Exception as e:
        print("An error occurred while loading the file {}: {}".format(path, e))

    # print(label.shape)
    # 如果标注信息不为空的话
    if label.shape[0]:
        # 标签信息每行必须是五个值[class, x, y, w, h], 报出空标签,未标准化的标签也报出
        assert label.shape[1] == 5, "> 5 label columns: %s" % path
        assert (label >= 0).all(), "negative labels: %s" % path
        assert (label[:, 1:] <= 1).all(), "non-normalized or out of bounds coordinate labels: %s" % path

    return label


def plot_images(images, targets, paths=None, fname='images.jpg', names=None, max_size=640, max_subplots=16):
    """
    @param images: 图像 ==> 格式为:[batch_size, _, h, w]
    @param targets: 标签 ==> 格式:[batch_size, image_index, box_info]
    @param paths: 图像名字, 列表格式
    @param fname: 画图之后的保存名字,字符串路径
    @param names: 标签名
    @param max_size:
    @param max_subplots:
    @return:
    """
    # Plot image grid with labels
    if isinstance(images, torch.Tensor):
        images = images.cpu().float().numpy()
    if isinstance(targets, torch.Tensor):
        targets = targets.cpu().numpy()

    # un-normalise
    if np.max(images[0]) <= 1:
        images *= 255

    tl = 3  # line thickness
    tf = max(tl - 1, 1)  # font thickness
    bs, _, h, w = images.shape  # batch size, _, height, width
    bs = min(bs, max_subplots)  # limit plot images
    ns = np.ceil(bs ** 0.5)  # number of subplots (square)

    # Check if we should resize
    scale_factor = max_size / max(h, w)
    if scale_factor < 1:
        h = math.ceil(scale_factor * h)
        w = math.ceil(scale_factor * w)

    colors = color_list()  # list of colors
    mosaic = np.full((int(ns * h), int(ns * w), 3), 255, dtype=np.uint8)  # init
    for i, img in enumerate(images):
        if i == max_subplots:  # if last batch has fewer images than we expect
            break

        block_x = int(w * (i // ns))
        block_y = int(h * (i % ns))

        img = img.transpose(1, 2, 0)
        if scale_factor < 1:
            img = cv2.resize(img, (w, h))

        mosaic[block_y:block_y + h, block_x:block_x + w, :] = img
        if len(targets) > 0:
            image_targets = targets[targets[:, 0] == i]
            boxes = xywh2xyxy(image_targets[:, 2:6]).T
            classes = image_targets[:, 1].astype('int')
            labels = image_targets.shape[1] == 6  # labels if no conf column
            conf = None if labels else image_targets[:, 6]  # check for confidence presence (label vs pred)

            if boxes.shape[1]:
                if boxes.max() <= 1.01:  # if normalized with tolerance 0.01
                    boxes[[0, 2]] *= w  # scale to pixels
                    boxes[[1, 3]] *= h
                elif scale_factor < 1:  # absolute coords need scale if image scales
                    boxes *= scale_factor
            boxes[[0, 2]] += block_x
            boxes[[1, 3]] += block_y
            for j, box in enumerate(boxes.T):
                cls = int(classes[j])
                color = colors[cls % len(colors)]
                cls = names[cls] if names else cls
                if labels or conf[j] > 0.25:  # 0.25 conf thresh
                    label = '%s' % cls if labels else '%s %.1f' % (cls, conf[j])
                    plot_one_box(box, mosaic, label=label, color=color, line_thickness=tl)

        # Draw image filename labels
        if paths:
            label = Path(paths[i]).name[:40]  # trim to 40 char
            t_size = cv2.getTextSize(label, 0, fontScale=tl / 3, thickness=tf)[0]
            cv2.putText(mosaic, label, (block_x + 5, block_y + t_size[1] + 5), 0, tl / 3, [220, 220, 220], thickness=tf,
                        lineType=cv2.LINE_AA)

        # Image border
        cv2.rectangle(mosaic, (block_x, block_y), (block_x + w, block_y + h), (255, 255, 255), thickness=3)

    if fname:
        r = min(1280. / max(h, w) / ns, 1.0)  # ratio to limit image size
        mosaic = cv2.resize(mosaic, (int(ns * w * r), int(ns * h * r)), interpolation=cv2.INTER_AREA)
        # cv2.imwrite(fname, cv2.cvtColor(mosaic, cv2.COLOR_BGR2RGB))  # cv2 save
        Image.fromarray(mosaic).save(fname)  # PIL save
    return mosaic


def draw_prepare(label, label_dir, img_dir, class_names, save_dir, img_type):
    # 准备图像
    image_name = label.replace(".txt", img_type)  # 根据标签获取图像名字
    image_name_use = [image_name]  # 将图片名字转为勾画函数需要的格式 list
    # 确定图片路径,并检查
    image_path = os.path.join(img_dir, image_name)
    assert os.path.exists(image_path), "检查标签对应的图片路径是否正确?"
    # 读入图片并转为 numpy格式
    img = np.array(Image.open(image_path))  # [w, h, c]
    image = np.expand_dims(img, axis=0)  # 因为我想每次只画一张图,所以需要手动添加一个batch-size的维度: [w, h, c] ==> [batch_size, w, h, c]
    images = image.transpose((0, 3, 1, 2))  # 更改维度的位置,适配后面的输入: [batch_size, w, h, c] ==> [batch_size, c, w, h]
    # print(images.shape)  # 查看数据形状对不对

    # 读取标签并处理
    label_path = os.path.join(label_dir, label)
    labels = get_xywh(label_path)  # 根据图片路径读取标签,并进行简单检查
    # print('\n before:\n', labels)  # 检查标签
    # 配置勾画函数需要的标签的格式:[class, x, y, w, h] ==> [img_index, class, x, y, w, h]
    labels = np.insert(labels, 0, 0, axis=1)  # [需要插入的矩阵, 位置, 插入的内容, 在那个方向] 在每一行标签的 0 的位置上插入当前图像序号,由于我只画一张,所以就插入0
    # print("\n after:\n", labels)  # 检查标签

    # 准备保存名字
    save_name = os.path.join(save_dir, image_name)

    # 传入程序开始画图
    plot_images(images, labels, image_name_use, save_name, class_names)


def main():
    """修改这几个变量就可以了:image_dir, labels_dir, names, img_type"""
    image_dir = r'D:\YoungMaster\YOLOv7\tmp\draw_test\images'  # 图片存储文件夹
    labels_dir = r'D:\YoungMaster\YOLOv7\tmp\draw_test\labels'  # 标签存储文件夹
    image_box_save_dir = image_dir+"_box"  # 根据图片存放文件夹自动生成勾画box的文件夹
    if not os.path.exists(image_box_save_dir):
        os.makedirs(image_box_save_dir)

    # 类别名称 ==> 注意: 需要和 txt 文件里面的顺序对应,可以根据 PE_stage2.yaml 进行核对
    names = ["bird","cat", "dog"]
    img_type = ".png"
    # -------------------------------------------------------------------------------------------------------------

    # 列出所有标签的的名字
    label_list = os.listdir(labels_dir)
    start = time.time()
    # 遍历标签名字,并根据标签名字找到对应的图片,将标签的里面的 bbox 画到图像上
    for label in tqdm(label_list):
        # 单线程
        # draw_prepare(label, labels_dir, image_dir, names, image_box_save_dir, img_type)
        # 多线程
        Thread(target=draw_prepare, args=(label, labels_dir, image_dir, names, image_box_save_dir, img_type)).start()

    end = time.time()
    time_use = end - start
    print(f"用时:{time_use // 3600} hour {(time_use - time_use // 3600) // 60} min {time_use - (time_use - time_use // 3600) // 60} seconds")

    print(f"\n勾画结果保存位置:   {image_box_save_dir}")


if __name__ == '__main__':
    main()

  • 4
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值