【开源】基于PaddleHub和Labelimg的目标检测半自动标注方案

开源

  • 项目地址https://aistudio.baidu.com/aistudio/projectdetail/2303368

一、项目介绍

思路

利用PaddleHub目标检测预训练模型 生成相应的图像数据标注-VOC格式数据(XML文件),之后利用Labelimg 进行图像标注数据的查看与修改。

优点

  • 便捷性:在理论上,可以减少大批量图像数据的标注行为 ;
  • 可拓展性:对于特定场景的数据标注,可以人工标注少量数据并训练,利用该模型进行其余大量数据的辅助标注工作

依托

  • PaddleHub预训练模型库 ;
  • labelimg ;

贡献

  • 利用PaddleHub半自动标注;
  • 简易的数据集图片大小重置工具,将图片重置为一样大小;下载地址
  • VOC格式文件转化为yolov5可用于训练的txt标注文件格式;下载地址
  • 大量数据图片名称重命名;下载地址
  • 训练完成邮件通知提醒;下载地址

二、数据上传

本次我使用yolov3_darknet53_pedestrian 进行行人半自动标注演示,其它标注实现原理相同。关于yolov3_darknet53_pedestrian预训练模型:

  • 类别图像 - 目标检测
  • 网络YOLOv3
  • 数据集百度自建大规模行人数据集

我上传了五张行人的图片到work/images/

三、目标检测

# 单张图片
# 测试:work/images/1.jpg
import paddlehub as hub
import cv2

pedestrian_detector = hub.Module(name="yolov3_darknet53_pedestrian")
result = pedestrian_detector.object_detection(images=[cv2.imread('work/images/1.jpg')])
print(result)
print(type(result))
[2021-08-23 15:09:13,736] [ WARNING] - The _initialize method in HubModule will soon be deprecated, you can use the __init__() to handle the initialization of the object


[{'data': [{'label': 'pedestrian', 'confidence': 0.9487035870552063, 'left': 171.19189453125, 'top': 16.71392822265625, 'right': 441.5438232421875, 'bottom': 950.821533203125}, {'label': 'pedestrian', 'confidence': 0.32225432991981506, 'left': 378.99603271484375, 'top': 233.63763427734375, 'right': 509.3026123046875, 'bottom': 525.3494873046875}], 'save_path': 'yolov3_pedestrian_detect_output/image_numpy_0.jpg'}]

import paddlehub as hub
import cv2

# 预测单张图片中单个目标或者多个目标的位置以及标签,并以列表方式返回
def object_detec(images):
    coordinates_list = []
    pedestrian_detector = hub.Module(name="yolov3_darknet53_pedestrian")
    result = pedestrian_detector.object_detection(images=[cv2.imread(images)])
    
    for i in range(len(result[0]['data'])):
        '''
        left (int): 边界框的左上角x坐标;
        top (int): 边界框的左上角y坐标;
        right (int): 边界框的右下角x坐标;
        bottom (int): 边界框的右下角y坐标;
        '''
        xmin = int(result[0]['data'][i]['left'])
        ymin = int(result[0]['data'][i]['top'])
        xmax = int(result[0]['data'][i]['right'])
        ymax = int(result[0]['data'][i]['right'])
        pre_label = result[0]['data'][0]['label']
        coordinates_list.append([xmin,ymin,xmax,ymax,pre_label])
    return coordinates_list
/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/matplotlib/__init__.py:107: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working
  from collections import MutableMapping
/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/matplotlib/rcsetup.py:20: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working
  from collections import Iterable, Mapping
/opt/conda/envs/python35-paddle120-env/lib/python3.7/site-packages/matplotlib/colors.py:53: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working
  from collections import Sized
# 模块实现测试
r = object_detec('work/images/1.jpg')
r
[2021-08-23 16:24:36,208] [ WARNING] - The _initialize method in HubModule will soon be deprecated, you can use the __init__() to handle the initialization of the object





[[171, 16, 441, 441, 'pedestrian'], [378, 233, 509, 509, 'pedestrian']]

四、XML模板

import os
from os import getcwd
from xml.etree import ElementTree as ET

# 定义一个创建一级分支object的函数
def create_object(root,xi,yi,xa,ya,obj_name):   # 参数依次,树根,xmin,ymin,xmax,ymax
    #创建一级分支object
    _object=ET.SubElement(root,'object')
    #创建二级分支
    name=ET.SubElement(_object,'name')
    print(obj_name)
    name.text= str(obj_name)
    pose=ET.SubElement(_object,'pose')
    pose.text='Unspecified'
    truncated=ET.SubElement(_object,'truncated')
    truncated.text='0'
    difficult=ET.SubElement(_object,'difficult')
    difficult.text='0'
    #创建bndbox
    bndbox=ET.SubElement(_object,'bndbox')
    xmin=ET.SubElement(bndbox,'xmin')
    xmin.text='%s'%xi
    ymin = ET.SubElement(bndbox, 'ymin')
    ymin.text = '%s'%yi
    xmax = ET.SubElement(bndbox, 'xmax')
    xmax.text = '%s'%xa
    ymax = ET.SubElement(bndbox, 'ymax')
    ymax.text = '%s'%ya

# 创建xml文件的函数
def create_tree(image_name, h, w, imgdir):
    global annotation
    # 创建树根annotation
    annotation = ET.Element('annotation')
    #创建一级分支folder
    folder = ET.SubElement(annotation,'folder')
    #添加folder标签内容
    folder.text=(imgdir)

    #创建一级分支filename
    filename=ET.SubElement(annotation,'filename')
    filename.text=image_name

    #创建一级分支path
    path=ET.SubElement(annotation,'path')

    path.text= getcwd() + '/{}/{}'.format(imgdir,image_name)  # 用于返回当前工作目录
    
    #创建一级分支source
    source=ET.SubElement(annotation,'source')
    #创建source下的二级分支database
    database=ET.SubElement(source,'database')
    database.text='Unknown'

    #创建一级分支size
    size=ET.SubElement(annotation,'size')
    #创建size下的二级分支图像的宽、高及depth
    width=ET.SubElement(size,'width')
    width.text= str(w)
    height=ET.SubElement(size,'height')
    height.text= str(h)
    depth = ET.SubElement(size,'depth')
    depth.text = '3'

    #创建一级分支segmented
    segmented = ET.SubElement(annotation,'segmented')
    segmented.text = '0'
def pretty_xml(element, indent, newline, level=0):  # elemnt为传进来的Elment类,参数indent用于缩进,newline用于换行
    if element:  # 判断element是否有子元素
        if (element.text is None) or element.text.isspace():  # 如果element的text没有内容
            element.text = newline + indent * (level + 1)
        else:
            element.text = newline + indent * (level + 1) + element.text.strip() + newline + indent * (level + 1)
            # else:  # 此处两行如果把注释去掉,Element的text也会另起一行
            # element.text = newline + indent * (level + 1) + element.text.strip() + newline + indent * level
    temp = list(element)  # 将element转成list
    for subelement in temp:
        if temp.index(subelement) < (len(temp) - 1):  # 如果不是list的最后一个元素,说明下一个行是同级别元素的起始,缩进应一致
            subelement.tail = newline + indent * (level + 1)
        else:  # 如果是list的最后一个元素, 说明下一行是母元素的结束,缩进应该少一个
            subelement.tail = newline + indent * level
        pretty_xml(subelement, indent, newline, level=level + 1)  # 对子元素进行递归操作

五、数据半自动标注

imgdir = 'work/images'
outdir = './work/Annotation'

if not os.path.exists(outdir):  #判断是否存在文件夹如果不存在则创建为文件夹
    os.makedirs(outdir)

IMAGES_LIST = os.listdir(imgdir)
for image_name in IMAGES_LIST:
    #print(image_name)
    #break
    # 判断后缀只处理jpg文件
    if image_name.endswith('.jpg'):
        image = cv2.imread(os.path.join(imgdir, image_name))
        #print(image)
        coordinates_list = object_detec('./work/images/'+image_name)
        (h, w) = image.shape[:2]
        create_tree(image_name, h, w, imgdir)
        if coordinates_list:
            #print(image_name)
            for coordinate in coordinates_list:
                label_id = coordinate[4]
                create_object(annotation, coordinate[0], coordinate[1], coordinate[2], coordinate[3], label_id)

            # 将树模型写入xml文件
            tree = ET.ElementTree(annotation)
            root = tree.getroot()
            pretty_xml(root, '\t', '\n')
            #tree.write('.\{}\{}.xml'.format(outdir, image_name.strip('.jpg')), encoding='utf-8')
            tree.write('{}/{}.xml'.format(outdir, image_name.strip('.jpg')), encoding='utf-8')
        else:
            print(image_name)
[2021-08-23 15:55:05,995] [ WARNING] - The _initialize method in HubModule will soon be deprecated, you can use the __init__() to handle the initialization of the object


pedestrian
pedestrian


[2021-08-23 15:55:09,781] [ WARNING] - The _initialize method in HubModule will soon be deprecated, you can use the __init__() to handle the initialization of the object


pedestrian
pedestrian


[2021-08-23 15:55:13,763] [ WARNING] - The _initialize method in HubModule will soon be deprecated, you can use the __init__() to handle the initialization of the object


pedestrian


[2021-08-23 15:55:17,363] [ WARNING] - The _initialize method in HubModule will soon be deprecated, you can use the __init__() to handle the initialization of the object


pedestrian
pedestrian


[2021-08-23 15:55:21,189] [ WARNING] - The _initialize method in HubModule will soon be deprecated, you can use the __init__() to handle the initialization of the object


pedestrian
pedestrian

六、标注预览与修改

  1. 下载work/下的imagesAnnotation内容到本地;
home
aistudio
work
│
├─Annotation
│      1.xml
│      2.xml
│      3.xml
│	   ...
│
└─images
        1.jpg
        2.jpg
        3.jpg
        ...
  1. 下载labelimg并且设置predefined_classes.txt下的标签;
    labelimg下载地址
    打开labelimg_win/windows_v1.8.1/data/predefined_classes.txt,我这里只框选行人,所以txt内容为:
pedestrian

如果你的是车、人、手机…,你可以这样写:

car
people
phone
  1. 设置Open Dir和Change Save Dir;
    双击打开labelImg.exe,在左侧点击Open Dir选择到下载的images文件夹,点击Change Save Dir选择到下载的Annotation文件夹。
    之后就可以在主界面框看到框选的区域,如果框选错误可以删除或者修改

【开源】基于PaddleHub和Labelimg的目标检测半自动标注方案_第1张图片

上图就是我半自动标注的结果,嗯~还是挺感人的

trian

如果你的是车、人、手机...,你可以这样写:
```html
car
people
phone
  1. 设置Open Dir和Change Save Dir;
    双击打开labelImg.exe,在左侧点击Open Dir选择到下载的images文件夹,点击Change Save Dir选择到下载的Annotation文件夹。
    之后就可以在主界面框看到框选的区域,如果框选错误可以删除或者修改
    【开源】基于PaddleHub和Labelimg的目标检测半自动标注方案_第2张图片

上图就是我半自动标注的结果,嗯~还是挺感人的

你可能感兴趣的:(飞桨,目标检测,paddlepaddle,深度学习)