目标检测YOLO数据集的三种格式及转换

目标检测YOLO数据集的三种格式

在目标检测领域,YOLO(You Only Look Once)算法是一个流行的选择。为了训练和测试YOLO模型,需要将数据集格式化为YOLO可以识别的格式。以下是三种常见的YOLO数据集格式及其特点和转换方法。

1. YOLO的TXT格式

YOLO的TXT格式是最简单的数据集格式之一。它要求图片和标签分别存放在两个文件夹中,并按照一定的比例分为训练集和验证集。每个TXT文件包含目标的类别和边界框的坐标,格式如下:
在这里插入图片描述如图中所示,以.txt存放边界框信息

类别 [x_center, y_center, w, h]
  • 类别:目标的类别编号。
  • x_center, y_center, w, h:边界框的中心坐标和宽度、高度,这些值都是相对于整张图片的比例,小于1。

例如,使用makesense.ai标注后直接输出的就是TXT标签文件。为了训练,需要编写一个custom.yaml配置文件,指定路径和类别名称,然后使用官方的训练脚本。

2. VOC格式

VOC格式是一种更为复杂的数据集格式,它包含图片、标注和数据集分割。VOC格式由以下部分组成:
在这里插入图片描述VOC格式是以xml方式給出标注信息的

  • JPEGImages:存放数据集图片的文件夹。
  • Annotations:存放与图片对应的XML标注文件的文件夹。
  • ImageSets/Main:包含train.txt和val.txt文件,用于区分训练集和验证集。
<annotation>
  <folder>17</folder> # 图片所处文件夹
  <filename>77258.bmp</filename> # 图片名
  <path>~/frcnn-image/61/ADAS/image/frcnn-image/17/77258.bmp</path>
  <source>  #图片来源相关信息
    <database>Unknown</database>  
  </source>
  <size> #图片尺寸
    <width>640</width>
    <height>480</height>
    <depth>3</depth>
  </size>
  <segmented>0</segmented>  #是否有分割label
  <object> 包含的物体
    <name>car</name>  #物体类别
    <pose>Unspecified</pose>  #物体的姿态
    <truncated>0</truncated>  #物体是否被部分遮挡(>15%)
    <difficult>0</difficult>  #是否为难以辨识的物体, 主要指要结体背景才能判断出类别的物体。虽有标注, 但一般忽略这类物体
    <bndbox>  #物体的bound box
      <xmin>2</xmin>     #左
      <ymin>156</ymin>   #上
      <xmax>111</xmax>   #右
      <ymax>259</ymax>   #下
    </bndbox>
  </object>
</annotation>
'''
XML文件记录的是像素坐标,不是比例,因此在图像尺寸变化时可能会有轻微误差。可以使用Python脚本来将TXT格式的标注转换为XML格式,并进一步生成用于VOC格式的train.txt和val.txt文件。

#### 3. COCO格式
COCO格式是一种广泛使用的数据集格式,它将多个TXT文件转换为单个JSON文件。COCO格式的转换涉及以下步骤:

- 创建一个包含图像、标注和类别信息的字典。
- 将类别信息添加到JSON字典中。
- 将图像信息添加到JSON字典中。
- 将标注信息添加到JSON字典中。

COCO格式的转换通常通过编写Python脚本完成,该脚本读取TXT标注文件,并将它们转换为JSON格式。转换后,可以使用YOLOv4或YOLOr等YOLO变体进行训练。

### 转换示例
以下是一些用于转换数据集格式的Python代码示例:

#### TXT转XML
```python
def makexml(picPath, txtPath, xmlPath):
    # 省略了函数的具体实现,用于将YOLO格式的TXT标注文件转换为VOC格式的XML文件
    pass

格式转换

代码来源于知乎高赞文章,亲测好用无bug~

coco转voc

from pycocotools.coco import COCO
import os
from lxml import etree, objectify
import shutil
from tqdm import tqdm
import sys
import argparse
'''

# 将类别名字和id建立索引
def catid2name(coco):
    classes = dict()
    for cat in coco.dataset['categories']:
        classes[cat['id']] = cat['name']
    return classes


# 将标签信息写入xml
def save_anno_to_xml(filename, size, objs, save_path):
    E = objectify.ElementMaker(annotate=False)
    anno_tree = E.annotation(
        E.folder("DATA"),
        E.filename(filename),
        E.source(
            E.database("The VOC Database"),
            E.annotation("PASCAL VOC"),
            E.image("flickr")
        ),
        E.size(
            E.width(size['width']),
            E.height(size['height']),
            E.depth(size['depth'])
        ),
        E.segmented(0)
    )
    for obj in objs:
        E2 = objectify.ElementMaker(annotate=False)
        anno_tree2 = E2.object(
            E.name(obj[0]),
            E.pose("Unspecified"),
            E.truncated(0),
            E.difficult(0),
            E.bndbox(
                E.xmin(obj[1]),
                E.ymin(obj[2]),
                E.xmax(obj[3]),
                E.ymax(obj[4])
            )
        )
        anno_tree.append(anno_tree2)
    anno_path = os.path.join(save_path, filename[:-3] + "xml")
    etree.ElementTree(anno_tree).write(anno_path, pretty_print=True)


# 利用cocoAPI从json中加载信息
def load_coco(anno_file, xml_save_path):
    if os.path.exists(xml_save_path):
        shutil.rmtree(xml_save_path)
    os.makedirs(xml_save_path)

    coco = COCO(anno_file)
    classes = catid2name(coco)
    imgIds = coco.getImgIds()
    classesIds = coco.getCatIds()
    for imgId in tqdm(imgIds):
        size = {}
        img = coco.loadImgs(imgId)[0]
        filename = img['file_name']
        width = img['width']
        height = img['height']
        size['width'] = width
        size['height'] = height
        size['depth'] = 3
        annIds = coco.getAnnIds(imgIds=img['id'], iscrowd=None)
        anns = coco.loadAnns(annIds)
        objs = []
        for ann in anns:
            object_name = classes[ann['category_id']]
            # bbox:[x,y,w,h]
            bbox = list(map(int, ann['bbox']))
            xmin = bbox[0]
            ymin = bbox[1]
            xmax = bbox[0] + bbox[2]
            ymax = bbox[1] + bbox[3]
            obj = [object_name, xmin, ymin, xmax, ymax]
            objs.append(obj)
        save_anno_to_xml(filename, size, objs, xml_save_path)


def parseJsonFile(data_dir, xmls_save_path):
    assert os.path.exists(data_dir), "data dir:{} does not exits".format(data_dir)

    if os.path.isdir(data_dir):
        data_types = ['train2017', 'val2017']
        for data_type in data_types:
            ann_file = 'instances_{}.json'.format(data_type)
            xmls_save_path = os.path.join(xmls_save_path, data_type)
            load_coco(ann_file, xmls_save_path)
    elif os.path.isfile(data_dir):
        anno_file = data_dir
        load_coco(anno_file, xmls_save_path)


if __name__ == '__main__':
    """
    脚本说明:
        该脚本用于将coco格式的json文件转换为voc格式的xml文件
    参数说明:
        data_dir:json文件的路径
        xml_save_path:xml输出路径
    """

    parser = argparse.ArgumentParser()
    parser.add_argument('-d', '--data-dir', type=str, default='./data/labels/coco/train.json', help='json path')
    parser.add_argument('-s', '--save-path', type=str, default='./data/convert/voc', help='xml save path')
    opt = parser.parse_args()
    print(opt)

    if len(sys.argv) > 1:
        parseJsonFile(opt.data_dir, opt.save_path)
    else:
        data_dir = './data/labels/coco/train.json'
        xml_save_path = './data/convert/voc'
        parseJsonFile(data_dir=data_dir, xmls_save_path=xml_save_path)

coco转yolo

from pycocotools.coco import COCO
import os
import shutil
from tqdm import tqdm
import sys
import argparse

images_nums = 0
category_nums = 0
bbox_nums = 0

# 将类别名字和id建立索引
def catid2name(coco):
    classes = dict()
    for cat in coco.dataset['categories']:
        classes[cat['id']] = cat['name']
    return classes


# 将[xmin,ymin,xmax,ymax]转换为yolo格式[x_center, y_center, w, h](做归一化)
def xyxy2xywhn(object, width, height):
    cat_id = object[0]
    xn = object[1] / width
    yn = object[2] / height
    wn = object[3] / width
    hn = object[4] / height
    out = "{} {:.5f} {:.5f} {:.5f} {:.5f}".format(cat_id, xn, yn, wn, hn)
    return out


def save_anno_to_txt(images_info, save_path):
    filename = images_info['filename']
    txt_name = filename[:-3] + "txt"
    with open(os.path.join(save_path, txt_name), "w") as f:
        for obj in images_info['objects']:
            line = xyxy2xywhn(obj, images_info['width'], images_info['height'])
            f.write("{}\n".format(line))


# 利用cocoAPI从json中加载信息
def load_coco(anno_file, xml_save_path):
    if os.path.exists(xml_save_path):
        shutil.rmtree(xml_save_path)
    os.makedirs(xml_save_path)

    coco = COCO(anno_file)
    classes = catid2name(coco)
    imgIds = coco.getImgIds()
    classesIds = coco.getCatIds()

    with open(os.path.join(xml_save_path, "classes.txt"), 'w') as f:
        for id in classesIds:
            f.write("{}\n".format(classes[id]))

    for imgId in tqdm(imgIds):
        info = {}
        img = coco.loadImgs(imgId)[0]
        filename = img['file_name']
        width = img['width']
        height = img['height']
        info['filename'] = filename
        info['width'] = width
        info['height'] = height
        annIds = coco.getAnnIds(imgIds=img['id'], iscrowd=None)
        anns = coco.loadAnns(annIds)
        objs = []
        for ann in anns:
            object_name = classes[ann['category_id']]
            # bbox:[x,y,w,h]
            bbox = list(map(float, ann['bbox']))
            xc = bbox[0] + bbox[2] / 2.
            yc = bbox[1] + bbox[3] / 2.
            w = bbox[2]
            h = bbox[3]
            obj = [ann['category_id'], xc, yc, w, h]
            objs.append(obj)
        info['objects'] = objs
        save_anno_to_txt(info, xml_save_path)


def parseJsonFile(json_path, txt_save_path):
    assert os.path.exists(json_path), "json path:{} does not exists".format(json_path)
    if os.path.exists(txt_save_path):
        shutil.rmtree(txt_save_path)
    os.makedirs(txt_save_path)

    assert json_path.endswith('json'), "json file:{} It is not json file!".format(json_path)

    load_coco(json_path, txt_save_path)


if __name__ == '__main__':
    """
    脚本说明:
        该脚本用于将coco格式的json文件转换为yolo格式的txt文件
    参数说明:
        json_path:json文件的路径
        txt_save_path:txt保存的路径
    """
    parser = argparse.ArgumentParser()
    parser.add_argument('-jp', '--json-path', type=str, default='./data/labels/coco/train.json', help='json path')
    parser.add_argument('-s', '--save-path', type=str, default='./data/convert/yolo', help='txt save path')
    opt = parser.parse_args()

    if len(sys.argv) > 1:
        print(opt)
        parseJsonFile(opt.json_path, opt.save_path)
        # print("image nums: {}".format(images_nums))
        # print("category nums: {}".format(category_nums))
        # print("bbox nums: {}".format(bbox_nums))
    else:
        json_path = './data/labels/coco/train.json'  # r'D:\practice\compete\goodsDec\data\train\train.json'
        txt_save_path = './data/convert/yolo'
        parseJsonFile(json_path, txt_save_path)
        # print("image nums: {}".format(images_nums))
        # print("category nums: {}".format(category_nums))
        # print("bbox nums: {}".format(bbox_nums))
        

voc转coco

import xml.etree.ElementTree as ET
import os
import json
from datetime import datetime
import sys
import argparse

coco = dict()
coco['images'] = []
coco['type'] = 'instances'
coco['annotations'] = []
coco['categories'] = []

category_set = dict()
image_set = set()

category_item_id = -1
image_id = 000000
annotation_id = 0


def addCatItem(name):
    global category_item_id
    category_item = dict()
    category_item['supercategory'] = 'none'
    category_item_id += 1
    category_item['id'] = category_item_id
    category_item['name'] = name
    coco['categories'].append(category_item)
    category_set[name] = category_item_id
    return category_item_id


def addImgItem(file_name, size):
    global image_id
    if file_name is None:
        raise Exception('Could not find filename tag in xml file.')
    if size['width'] is None:
        raise Exception('Could not find width tag in xml file.')
    if size['height'] is None:
        raise Exception('Could not find height tag in xml file.')
    image_id += 1
    image_item = dict()
    image_item['id'] = image_id
    image_item['file_name'] = file_name
    image_item['width'] = size['width']
    image_item['height'] = size['height']
    image_item['license'] = None
    image_item['flickr_url'] = None
    image_item['coco_url'] = None
    image_item['date_captured'] = str(datetime.today())
    coco['images'].append(image_item)
    image_set.add(file_name)
    return image_id


def addAnnoItem(object_name, image_id, category_id, bbox):
    global annotation_id
    annotation_item = dict()
    annotation_item['segmentation'] = []
    seg = []
    # bbox[] is x,y,w,h
    # left_top
    seg.append(bbox[0])
    seg.append(bbox[1])
    # left_bottom
    seg.append(bbox[0])
    seg.append(bbox[1] + bbox[3])
    # right_bottom
    seg.append(bbox[0] + bbox[2])
    seg.append(bbox[1] + bbox[3])
    # right_top
    seg.append(bbox[0] + bbox[2])
    seg.append(bbox[1])

    annotation_item['segmentation'].append(seg)

    annotation_item['area'] = bbox[2] * bbox[3]
    annotation_item['iscrowd'] = 0
    annotation_item['ignore'] = 0
    annotation_item['image_id'] = image_id
    annotation_item['bbox'] = bbox
    annotation_item['category_id'] = category_id
    annotation_id += 1
    annotation_item['id'] = annotation_id
    coco['annotations'].append(annotation_item)


def read_image_ids(image_sets_file):
    ids = []
    with open(image_sets_file, 'r') as f:
        for line in f.readlines():
            ids.append(line.strip())
    return ids


def parseXmlFilse(data_dir, json_save_path, split='train'):
    assert os.path.exists(data_dir), "data path:{} does not exist".format(data_dir)
    labelfile = split + ".txt"
    image_sets_file = os.path.join(data_dir, "ImageSets", "Main", labelfile)
    xml_files_list = []
    if os.path.isfile(image_sets_file):
        ids = read_image_ids(image_sets_file)
        xml_files_list = [os.path.join(data_dir, "Annotations", f"{i}.xml") for i in ids]
    elif os.path.isdir(data_dir):
        # 修改此处xml的路径即可
        # xml_dir = os.path.join(data_dir,"labels/voc")
        xml_dir = data_dir
        xml_list = os.listdir(xml_dir)
        xml_files_list = [os.path.join(xml_dir, i) for i in xml_list]

    for xml_file in xml_files_list:
        if not xml_file.endswith('.xml'):
            continue

        tree = ET.parse(xml_file)
        root = tree.getroot()

        # 初始化
        size = dict()
        size['width'] = None
        size['height'] = None

        if root.tag != 'annotation':
            raise Exception('pascal voc xml root element should be annotation, rather than {}'.format(root.tag))

        # 提取图片名字
        file_name = root.findtext('filename')
        assert file_name is not None, "filename is not in the file"

        # 提取图片 size {width,height,depth}
        size_info = root.findall('size')
        assert size_info is not None, "size is not in the file"
        for subelem in size_info[0]:
            size[subelem.tag] = int(subelem.text)

        if file_name is not None and size['width'] is not None and file_name not in image_set:
            # 添加coco['image'],返回当前图片ID
            current_image_id = addImgItem(file_name, size)
            print('add image with name: {}\tand\tsize: {}'.format(file_name, size))
        elif file_name in image_set:
            raise Exception('file_name duplicated')
        else:
            raise Exception("file name:{}\t size:{}".format(file_name, size))

        # 提取一张图片内所有目标object标注信息
        object_info = root.findall('object')
        if len(object_info) == 0:
            continue
        # 遍历每个目标的标注信息
        for object in object_info:
            # 提取目标名字
            object_name = object.findtext('name')
            if object_name not in category_set:
                # 创建类别索引
                current_category_id = addCatItem(object_name)
            else:
                current_category_id = category_set[object_name]

            # 初始化标签列表
            bndbox = dict()
            bndbox['xmin'] = None
            bndbox['xmax'] = None
            bndbox['ymin'] = None
            bndbox['ymax'] = None
            # 提取box:[xmin,ymin,xmax,ymax]
            bndbox_info = object.findall('bndbox')
            for box in bndbox_info[0]:
                bndbox[box.tag] = int(box.text)

            if bndbox['xmin'] is not None:
                if object_name is None:
                    raise Exception('xml structure broken at bndbox tag')
                if current_image_id is None:
                    raise Exception('xml structure broken at bndbox tag')
                if current_category_id is None:
                    raise Exception('xml structure broken at bndbox tag')
                bbox = []
                # x
                bbox.append(bndbox['xmin'])
                # y
                bbox.append(bndbox['ymin'])
                # w
                bbox.append(bndbox['xmax'] - bndbox['xmin'])
                # h
                bbox.append(bndbox['ymax'] - bndbox['ymin'])
                print('add annotation with object_name:{}\timage_id:{}\tcat_id:{}\tbbox:{}'.format(object_name,
                                                                                                   current_image_id,
                                                                                                   current_category_id,
                                                                                                   bbox))
                addAnnoItem(object_name, current_image_id, current_category_id, bbox)

    json_parent_dir = os.path.dirname(json_save_path)
    if not os.path.exists(json_parent_dir):
        os.makedirs(json_parent_dir)
    json.dump(coco, open(json_save_path, 'w'))
    print("class nums:{}".format(len(coco['categories'])))
    print("image nums:{}".format(len(coco['images'])))
    print("bbox nums:{}".format(len(coco['annotations'])))


if __name__ == '__main__':
    """
   
    """
    parser = argparse.ArgumentParser()
    parser.add_argument('-d', '--voc-dir', type=str, default='data/label/voc', help='voc path')
    parser.add_argument('-s', '--save-path', type=str, default='./data/convert/coco/train.json', help='json save path')
    parser.add_argument('-t', '--type', type=str, default='train', help='only use in voc2012/2007')
    opt = parser.parse_args()
    if len(sys.argv) > 1:
        print(opt)
        parseXmlFilse(opt.voc_dir, opt.save_path, opt.type)
    else:
        # voc_data_dir = r'D:\dataset\VOC2012\VOCdevkit\VOC2012'
        voc_data_dir = './data/labels/voc'
        json_save_path = './data/convert/coco/train.json'
        split = 'train'
        parseXmlFilse(data_dir=voc_data_dir, json_save_path=json_save_path, split=split)

voc转yolo

import os
import json
import argparse
import sys
import shutil
from lxml import etree
from tqdm import tqdm

category_set = set()
image_set = set()
bbox_nums = 0


def parse_xml_to_dict(xml):
    """
    将xml文件解析成字典形式,参考tensorflow的recursive_parse_xml_to_dict
    Args:
        xml: xml tree obtained by parsing XML file contents using lxml.etree

    Returns:
        Python dictionary holding XML contents.
    """
    if len(xml) == 0:  # 遍历到底层,直接返回tag对应的信息
        return {xml.tag: xml.text}

    result = {}
    for child in xml:
        child_result = parse_xml_to_dict(child)  # 递归遍历标签信息
        if child.tag != 'object':
            result[child.tag] = child_result[child.tag]
        else:
            if child.tag not in result:  # 因为object可能有多个,所以需要放入列表里
                result[child.tag] = []
            result[child.tag].append(child_result[child.tag])
    return {xml.tag: result}


def write_classIndices(category_set):
    class_indices = dict((k, v) for v, k in enumerate(category_set))
    json_str = json.dumps(dict((val, key) for key, val in class_indices.items()), indent=4)
    with open('class_indices.json', 'w') as json_file:
        json_file.write(json_str)


def xyxy2xywhn(bbox, size):
    bbox = list(map(float, bbox))
    size = list(map(float, size))
    xc = (bbox[0] + (bbox[2] - bbox[0]) / 2.) / size[0]
    yc = (bbox[1] + (bbox[3] - bbox[1]) / 2.) / size[1]
    wn = (bbox[2] - bbox[0]) / size[0]
    hn = (bbox[3] - bbox[1]) / size[1]
    return (xc, yc, wn, hn)


def parser_info(info: dict, only_cat=True, class_indices=None):
    filename = info['annotation']['filename']
    image_set.add(filename)
    objects = []
    width = int(info['annotation']['size']['width'])
    height = int(info['annotation']['size']['height'])
    for obj in info['annotation']['object']:
        obj_name = obj['name']
        category_set.add(obj_name)
        if only_cat:
            continue
        xmin = int(obj['bndbox']['xmin'])
        ymin = int(obj['bndbox']['ymin'])
        xmax = int(obj['bndbox']['xmax'])
        ymax = int(obj['bndbox']['ymax'])
        bbox = xyxy2xywhn((xmin, ymin, xmax, ymax), (width, height))
        if class_indices is not None:
            obj_category = class_indices[obj_name]
            object = [obj_category, bbox]
            objects.append(object)

    return filename, objects


def parseXmlFilse(voc_dir, save_dir):
    assert os.path.exists(voc_dir), "ERROR {} does not exists".format(voc_dir)
    if os.path.exists(save_dir):
        shutil.rmtree(save_dir)
    os.makedirs(save_dir)

    xml_files = [os.path.join(voc_dir, i) for i in os.listdir(voc_dir) if os.path.splitext(i)[-1] == '.xml']
    for xml_file in xml_files:
        with open(xml_file) as fid:
            xml_str = fid.read()
        xml = etree.fromstring(xml_str)
        info_dict = parse_xml_to_dict(xml)
        parser_info(info_dict, only_cat=True)

    with open(save_dir + "/classes.txt", 'w') as classes_file:
        for cat in sorted(category_set):
            classes_file.write("{}\n".format(cat))

    class_indices = dict((v, k) for k, v in enumerate(sorted(category_set)))

    xml_files = tqdm(xml_files)
    for xml_file in xml_files:
        with open(xml_file) as fid:
            xml_str = fid.read()
        xml = etree.fromstring(xml_str)
        info_dict = parse_xml_to_dict(xml)
        filename, objects = parser_info(info_dict, only_cat=False, class_indices=class_indices)
        if len(objects) != 0:
            global bbox_nums
            bbox_nums += len(objects)
            with open(save_dir + "/" + filename.split(".")[0] + ".txt", 'w') as f:
                for obj in objects:
                    f.write(
                        "{} {:.5f} {:.5f} {:.5f} {:.5f}\n".format(obj[0], obj[1][0], obj[1][1], obj[1][2], obj[1][3]))


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('--voc-dir', type=str, default='./data/labels/voc')
    parser.add_argument('--save-dir', type=str, default='./data/convert/yolo')
    opt = parser.parse_args()
    if len(sys.argv) > 1:
        print(opt)
        parseXmlFilse(**vars(opt))
        print("image nums: {}".format(len(image_set)))
        print("category nums: {}".format(len(category_set)))
        print("bbox nums: {}".format(bbox_nums))
    else:
        voc_dir = './data/labels/voc'
        save_dir = './data/convert/yolo'
        parseXmlFilse(voc_dir, save_dir)
        print("image nums: {}".format(len(image_set)))
        print("category nums: {}".format(len(category_set)))
        print("bbox nums: {}".format(bbox_nums))

yolo转coco

import argparse
import json
import os
import sys
import shutil
from datetime import datetime

import cv2

coco = dict()
coco['images'] = []
coco['type'] = 'instances'
coco['annotations'] = []
coco['categories'] = []

category_set = dict()
image_set = set()

image_id = 000000
annotation_id = 0


def addCatItem(category_dict):
    for k, v in category_dict.items():
        category_item = dict()
        category_item['supercategory'] = 'none'
        category_item['id'] = int(k)
        category_item['name'] = v
        coco['categories'].append(category_item)


def addImgItem(file_name, size):
    global image_id
    image_id += 1
    image_item = dict()
    image_item['id'] = image_id
    image_item['file_name'] = file_name
    image_item['width'] = size[1]
    image_item['height'] = size[0]
    image_item['license'] = None
    image_item['flickr_url'] = None
    image_item['coco_url'] = None
    image_item['date_captured'] = str(datetime.today())
    coco['images'].append(image_item)
    image_set.add(file_name)
    return image_id


def addAnnoItem(object_name, image_id, category_id, bbox):
    global annotation_id
    annotation_item = dict()
    annotation_item['segmentation'] = []
    seg = []
    # bbox[] is x,y,w,h
    # left_top
    seg.append(bbox[0])
    seg.append(bbox[1])
    # left_bottom
    seg.append(bbox[0])
    seg.append(bbox[1] + bbox[3])
    # right_bottom
    seg.append(bbox[0] + bbox[2])
    seg.append(bbox[1] + bbox[3])
    # right_top
    seg.append(bbox[0] + bbox[2])
    seg.append(bbox[1])

    annotation_item['segmentation'].append(seg)

    annotation_item['area'] = bbox[2] * bbox[3]
    annotation_item['iscrowd'] = 0
    annotation_item['ignore'] = 0
    annotation_item['image_id'] = image_id
    annotation_item['bbox'] = bbox
    annotation_item['category_id'] = category_id
    annotation_id += 1
    annotation_item['id'] = annotation_id
    coco['annotations'].append(annotation_item)


def xywhn2xywh(bbox, size):
    bbox = list(map(float, bbox))
    size = list(map(float, size))
    xmin = (bbox[0] - bbox[2] / 2.) * size[1]
    ymin = (bbox[1] - bbox[3] / 2.) * size[0]
    w = bbox[2] * size[1]
    h = bbox[3] * size[0]
    box = (xmin, ymin, w, h)
    return list(map(int, box))


def parseXmlFilse(image_path, anno_path, save_path, json_name='train.json'):
    assert os.path.exists(image_path), "ERROR {} dose not exists".format(image_path)
    assert os.path.exists(anno_path), "ERROR {} dose not exists".format(anno_path)
    if os.path.exists(save_path):
        shutil.rmtree(save_path)
    os.makedirs(save_path)
    json_path = os.path.join(save_path, json_name)

    category_set = []
    with open(anno_path + '/classes.txt', 'r') as f:
        for i in f.readlines():
            category_set.append(i.strip())
    category_id = dict((k, v) for k, v in enumerate(category_set))
    addCatItem(category_id)

    images = [os.path.join(image_path, i) for i in os.listdir(image_path)]
    files = [os.path.join(anno_path, i) for i in os.listdir(anno_path)]
    images_index = dict((v.split(os.sep)[-1][:-4], k) for k, v in enumerate(images))
    for file in files:
        if os.path.splitext(file)[-1] != '.txt' or 'classes' in file.split(os.sep)[-1]:
            continue
        if file.split(os.sep)[-1][:-4] in images_index:
            index = images_index[file.split(os.sep)[-1][:-4]]
            img = cv2.imread(images[index])
            shape = img.shape
            filename = images[index].split(os.sep)[-1]
            current_image_id = addImgItem(filename, shape)
        else:
            continue
        with open(file, 'r') as fid:
            for i in fid.readlines():
                i = i.strip().split()
                category = int(i[0])
                category_name = category_id[category]
                bbox = xywhn2xywh((i[1], i[2], i[3], i[4]), shape)
                addAnnoItem(category_name, current_image_id, category, bbox)

    json.dump(coco, open(json_path, 'w'))
    print("class nums:{}".format(len(coco['categories'])))
    print("image nums:{}".format(len(coco['images'])))
    print("bbox nums:{}".format(len(coco['annotations'])))


if __name__ == '__main__':
    """
    脚本说明:
        本脚本用于将yolo格式的标注文件.txt转换为coco格式的标注文件.json
    参数说明:
        anno_path:标注文件txt存储路径
        save_path:json文件输出的文件夹
        image_path:图片路径
        json_name:json文件名字
    """
    parser = argparse.ArgumentParser()
    parser.add_argument('-ap', '--anno-path', type=str, default='./data/labels/yolo', help='yolo txt path')
    parser.add_argument('-s', '--save-path', type=str, default='./data/convert/coco', help='json save path')
    parser.add_argument('--image-path', default='./data/images')
    parser.add_argument('--json-name', default='train.json')

    opt = parser.parse_args()
    if len(sys.argv) > 1:
        print(opt)
        parseXmlFilse(**vars(opt))
    else:
        anno_path = './data/labels/yolo'
        save_path = './data/convert/coco'
        image_path = './data/images'
        json_name = 'train.json'
        parseXmlFilse(image_path, anno_path, save_path, json_name)

yolo转voc

import argparse
import os
import sys
import shutil

import cv2
from lxml import etree, objectify

# 将标签信息写入xml
from tqdm import tqdm

images_nums = 0
category_nums = 0
bbox_nums = 0


def save_anno_to_xml(filename, size, objs, save_path):
    E = objectify.ElementMaker(annotate=False)
    anno_tree = E.annotation(
        E.folder("DATA"),
        E.filename(filename),
        E.source(
            E.database("The VOC Database"),
            E.annotation("PASCAL VOC"),
            E.image("flickr")
        ),
        E.size(
            E.width(size[1]),
            E.height(size[0]),
            E.depth(size[2])
        ),
        E.segmented(0)
    )
    for obj in objs:
        E2 = objectify.ElementMaker(annotate=False)
        anno_tree2 = E2.object(
            E.name(obj[0]),
            E.pose("Unspecified"),
            E.truncated(0),
            E.difficult(0),
            E.bndbox(
                E.xmin(obj[1][0]),
                E.ymin(obj[1][1]),
                E.xmax(obj[1][2]),
                E.ymax(obj[1][3])
            )
        )
        anno_tree.append(anno_tree2)
    anno_path = os.path.join(save_path, filename[:-3] + "xml")
    etree.ElementTree(anno_tree).write(anno_path, pretty_print=True)


def xywhn2xyxy(bbox, size):
    bbox = list(map(float, bbox))
    size = list(map(float, size))
    xmin = (bbox[0] - bbox[2] / 2.) * size[1]
    ymin = (bbox[1] - bbox[3] / 2.) * size[0]
    xmax = (bbox[0] + bbox[2] / 2.) * size[1]
    ymax = (bbox[1] + bbox[3] / 2.) * size[0]
    box = [xmin, ymin, xmax, ymax]
    return list(map(int, box))


def parseXmlFilse(image_path, anno_path, save_path):
    global images_nums, category_nums, bbox_nums
    assert os.path.exists(image_path), "ERROR {} dose not exists".format(image_path)
    assert os.path.exists(anno_path), "ERROR {} dose not exists".format(anno_path)
    if os.path.exists(save_path):
        shutil.rmtree(save_path)
    os.makedirs(save_path)

    category_set = []
    with open(anno_path + '/classes.txt', 'r') as f:
        for i in f.readlines():
            category_set.append(i.strip())
    category_nums = len(category_set)
    category_id = dict((k, v) for k, v in enumerate(category_set))

    images = [os.path.join(image_path, i) for i in os.listdir(image_path)]
    files = [os.path.join(anno_path, i) for i in os.listdir(anno_path)]
    images_index = dict((v.split(os.sep)[-1][:-4], k) for k, v in enumerate(images))
    images_nums = len(images)

    for file in tqdm(files):
        if os.path.splitext(file)[-1] != '.txt' or 'classes' in file.split(os.sep)[-1]:
            continue
        if file.split(os.sep)[-1][:-4] in images_index:
            index = images_index[file.split(os.sep)[-1][:-4]]
            img = cv2.imread(images[index])
            shape = img.shape
            filename = images[index].split(os.sep)[-1]
        else:
            continue
        objects = []
        with open(file, 'r') as fid:
            for i in fid.readlines():
                i = i.strip().split()
                category = int(i[0])
                category_name = category_id[category]
                bbox = xywhn2xyxy((i[1], i[2], i[3], i[4]), shape)
                obj = [category_name, bbox]
                objects.append(obj)
        bbox_nums += len(objects)
        save_anno_to_xml(filename, shape, objects, save_path)


if __name__ == '__main__':
    """
    脚本说明:
        本脚本用于将yolo格式的标注文件.txt转换为voc格式的标注文件.xml
    参数说明:
        anno_path:标注文件txt存储路径
        save_path:json文件输出的文件夹
        image_path:图片路径
    """
    parser = argparse.ArgumentParser()
    parser.add_argument('-ap', '--anno-path', type=str, default='./data/labels/yolo', help='yolo txt path')
    parser.add_argument('-s', '--save-path', type=str, default='./data/convert/voc', help='xml save path')
    parser.add_argument('--image-path', default='./data/images')

    opt = parser.parse_args()
    if len(sys.argv) > 1:
        print(opt)
        parseXmlFilse(**vars(opt))
        print("image nums: {}".format(images_nums))
        print("category nums: {}".format(category_nums))
        print("bbox nums: {}".format(bbox_nums))
    else:
        anno_path = './data/labels/yolo'
        save_path = './data/convert/voc1'
        image_path = './data/images'
        parseXmlFilse(image_path, anno_path, save_path)
        print("image nums: {}".format(images_nums))
        print("category nums: {}".format(category_nums))
        print("bbox nums: {}".format(bbox_nums))

图片可视化

coco格式,图片可视化

import argparse
import os
import sys
from collections import defaultdict
from xml import etree
from pycocotools.coco import COCO

import cv2
import matplotlib

matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
from tqdm import tqdm

category_set = dict()
image_set = set()
every_class_num = defaultdict(int)

category_item_id = -1


def addCatItem(name):
   global category_item_id
   category_item = dict()
   category_item_id += 1
   category_item['id'] = category_item_id
   category_item['name'] = name
   category_set[name] = category_item_id
   return category_item_id


def draw_box(img, objects, draw=True):
   for object in objects:
       category_name = object[0]
       every_class_num[category_name] += 1
       if category_name not in category_set:
           category_id = addCatItem(category_name)
       else:
           category_id = category_set[category_name]
       xmin = int(object[1])
       ymin = int(object[2])
       xmax = int(object[3])
       ymax = int(object[4])
       if draw:
           def hex2rgb(h):  # rgb order (PIL)
               return tuple(int(h[1 + i:1 + i + 2], 16) for i in (0, 2, 4))

           hex = ('FF3838', 'FF9D97', 'FF701F', 'FFB21D', 'CFD231', '48F90A', '92CC17', '3DDB86', '1A9334', '00D4BB',
                  '2C99A8', '00C2FF', '344593', '6473FF', '0018EC', '8438FF', '520085', 'CB38FF', 'FF95C8', 'FF37C7')

           palette = [hex2rgb('#' + c) for c in hex]
           n = len(palette)
           c = palette[int(category_id) % n]
           bgr = False
           color = (c[2], c[1], c[0]) if bgr else c

           cv2.rectangle(img, (xmin, ymin), (xmax, ymax), color)
           cv2.putText(img, category_name, (xmin, ymin), cv2.FONT_HERSHEY_SIMPLEX, 1, color)
   return img


# 将类别名字和id建立索引
def catid2name(coco):
   classes = dict()
   for cat in coco.dataset['categories']:
       classes[cat['id']] = cat['name']
   return classes


def show_image(image_path, anno_path, show=False, plot_image=False):
   assert os.path.exists(image_path), "image path:{} dose not exists".format(image_path)
   assert os.path.exists(anno_path), "annotation path:{} does not exists".format(anno_path)
   if not anno_path.endswith(".json"):
       raise RuntimeError("ERROR {} dose not a json file".format(anno_path))

   coco = COCO(anno_path)
   classes = catid2name(coco)
   imgIds = coco.getImgIds()
   classesIds = coco.getCatIds()
   for imgId in tqdm(imgIds):
       size = {}
       img = coco.loadImgs(imgId)[0]
       filename = img['file_name']
       image_set.add(filename)
       width = img['width']
       height = img['height']
       size['width'] = width
       size['height'] = height
       size['depth'] = 3
       annIds = coco.getAnnIds(imgIds=img['id'], iscrowd=None)
       anns = coco.loadAnns(annIds)
       objs = []
       for ann in anns:
           object_name = classes[ann['category_id']]
           # bbox:[x,y,w,h]
           bbox = list(map(int, ann['bbox']))
           xmin = bbox[0]
           ymin = bbox[1]
           xmax = bbox[0] + bbox[2]
           ymax = bbox[1] + bbox[3]
           obj = [object_name, xmin, ymin, xmax, ymax]
           objs.append(obj)

       file_path = os.path.join(image_path, filename)
       img = cv2.imread(file_path)
       if img is None:
           continue
       img = draw_box(img, objs, show)
       if show:
           cv2.imshow(filename, img)
           cv2.waitKey()
           cv2.destroyAllWindows()
   if plot_image:
       # 绘制每种类别个数柱状图
       plt.bar(range(len(every_class_num)), every_class_num.values(), align='center')
       # 将横坐标0,1,2,3,4替换为相应的类别名称
       plt.xticks(range(len(every_class_num)), every_class_num.keys(), rotation=90)
       # 在柱状图上添加数值标签
       for index, (i, v) in enumerate(every_class_num.items()):
           plt.text(x=index, y=v, s=str(v), ha='center')
       # 设置x坐标
       plt.xlabel('image class')
       # 设置y坐标
       plt.ylabel('number of images')
       # 设置柱状图的标题
       plt.title('class distribution')

       plt.savefig("class_distribution.png")
       plt.show()


if __name__ == '__main__':
   """
   脚本说明:
       该脚本用于coco标注格式(.json)的标注框可视化
   参数明说:
       image_path:图片数据路径
       anno_path:json标注文件路径
       show:是否展示标注后的图片
       plot_image:是否对每一类进行统计,并且保存图片
   """
   parser = argparse.ArgumentParser()
   parser.add_argument('-ip', '--image-path', type=str, default='./data/images', help='image path')
   parser.add_argument('-ap', '--anno-path', type=str, default='./data/labels/coco/train.json', help='annotation path')
   parser.add_argument('-s', '--show', action='store_true', help='weather show img')
   parser.add_argument('-p', '--plot-image', action='store_true')
   opt = parser.parse_args()

   if len(sys.argv) > 1:
       print(opt)
       show_image(opt.image_path, opt.anno_path, opt.show, opt.plot_image)
       print(every_class_num)
       print("category nums: {}".format(len(category_set)))
       print("image nums: {}".format(len(image_set)))
       print("bbox nums: {}".format(sum(every_class_num.values())))
   else:
       image_path = './data/images'
       anno_path = './data/labels/coco/train.json'
       show_image(image_path, anno_path, show=True, plot_image=True)
       print(every_class_num)
       print("category nums: {}".format(len(category_set)))
       print("image nums: {}".format(len(image_set)))
       print("bbox nums: {}".format(sum(every_class_num.values())))

voc格式,图片可视化

import os
import cv2
import matplotlib.pyplot as plt
from tqdm import tqdm
from lxml import etree
from collections import defaultdict
import argparse
import sys

category_set = dict()
image_set = set()
every_class_num = defaultdict(int)

category_item_id = -1


def draw_box(img, objects, draw=True):
    for object in objects:
        category_name = object['name']
        every_class_num[category_name] += 1
        if category_name not in category_set:
            category_id = addCatItem(category_name)
        else:
            category_id = category_set[category_name]
        xmin = int(object['bndbox']['xmin'])
        ymin = int(object['bndbox']['ymin'])
        xmax = int(object['bndbox']['xmax'])
        ymax = int(object['bndbox']['ymax'])
        if draw:
            def hex2rgb(h):  # rgb order (PIL)
                return tuple(int(h[1 + i:1 + i + 2], 16) for i in (0, 2, 4))

            hex = ('FF3838', 'FF9D97', 'FF701F', 'FFB21D', 'CFD231', '48F90A', '92CC17', '3DDB86', '1A9334', '00D4BB',
                   '2C99A8', '00C2FF', '344593', '6473FF', '0018EC', '8438FF', '520085', 'CB38FF', 'FF95C8', 'FF37C7')

            palette = [hex2rgb('#' + c) for c in hex]
            n = len(palette)
            c = palette[int(category_id) % n]
            bgr = False
            color = (c[2], c[1], c[0]) if bgr else c

            cv2.rectangle(img, (xmin, ymin), (xmax, ymax), color)
            cv2.putText(img, category_name, (xmin, ymin), cv2.FONT_HERSHEY_SIMPLEX, 1, color)
    return img


def addCatItem(name):
    global category_item_id
    category_item = dict()
    category_item_id += 1
    category_item['id'] = category_item_id
    category_item['name'] = name
    category_set[name] = category_item_id
    return category_item_id


def parse_xml_to_dict(xml):
    """
    将xml文件解析成字典形式,参考tensorflow的recursive_parse_xml_to_dict
    Args:
        xml: xml tree obtained by parsing XML file contents using lxml.etree

    Returns:
        Python dictionary holding XML contents.
    """
    if len(xml) == 0:  # 遍历到底层,直接返回tag对应的信息
        return {xml.tag: xml.text}

    result = {}
    for child in xml:
        child_result = parse_xml_to_dict(child)  # 递归遍历标签信息
        if child.tag != 'object':
            result[child.tag] = child_result[child.tag]
        else:
            if child.tag not in result:  # 因为object可能有多个,所以需要放入列表里
                result[child.tag] = []
            result[child.tag].append(child_result[child.tag])
    return {xml.tag: result}


def show_image(image_path, anno_path, show=False, plot_image=False):
    assert os.path.exists(image_path), "image path:{} dose not exists".format(image_path)
    assert os.path.exists(anno_path), "annotation path:{} does not exists".format(anno_path)
    anno_file_list = [os.path.join(anno_path, file) for file in os.listdir(anno_path) if file.endswith(".xml")]

    for xml_file in tqdm(anno_file_list):
        if not xml_file.endswith('.xml'):
            continue

        with open(xml_file) as fid:
            xml_str = fid.read()
        xml = etree.fromstring(xml_str)
        xml_info_dict = parse_xml_to_dict(xml)

        filename = xml_info_dict['annotation']['filename']
        image_set.add(filename)
        file_path = os.path.join(image_path, filename)
        if not os.path.exists(file_path):
            continue

        img = cv2.imread(file_path)
        if img is None:
            continue
        img = draw_box(img, xml_info_dict['annotation']['object'], show)
        if show:
            cv2.imshow(filename, img)
            cv2.waitKey()
            cv2.destroyAllWindows()
    if plot_image:
        # 绘制每种类别个数柱状图
        plt.bar(range(len(every_class_num)), every_class_num.values(), align='center')
        # 将横坐标0,1,2,3,4替换为相应的类别名称
        plt.xticks(range(len(every_class_num)), every_class_num.keys(), rotation=90)
        # 在柱状图上添加数值标签
        for index, (i, v) in enumerate(every_class_num.items()):
            plt.text(x=index, y=v, s=str(v), ha='center')
        # 设置x坐标
        plt.xlabel('image class')
        # 设置y坐标
        plt.ylabel('number of images')
        # 设置柱状图的标题
        plt.title('class distribution')

        plt.savefig("class_distribution.png")
        plt.show()


if __name__ == '__main__':
    
    脚本说明:
        该脚本用于voc标注格式(.xml)的标注框可视化
    参数明说:
        image_path:图片数据路径
        anno_path:xml标注文件路径
        show:是否展示标注后的图片
        plot_image:是否对每一类进行统计,并且保存图片
    """
    parser = argparse.ArgumentParser()
    parser.add_argument('-ip', '--image-path', type=str, default='./data/images', help='image path')
    parser.add_argument('-ap', '--anno-path', type=str, default='./data/labels/voc', help='annotation path')
    parser.add_argument('-s', '--show', action='store_true', help='weather show img')
    parser.add_argument('-p', '--plot-image', action='store_true')
    opt = parser.parse_args()

    if len(sys.argv) > 1:
        print(opt)
        show_image(opt.image_path, opt.anno_path, opt.show, opt.plot_image)
        print(every_class_num)
        print("category nums: {}".format(len(category_set)))
        print("image nums: {}".format(len(image_set)))
        print("bbox nums: {}".format(sum(every_class_num.values())))
    else:
        image_path = './data/images'
        anno_path = './data/convert/voc'
        show_image(image_path, anno_path, show=True, plot_image=True)
        print(every_class_num)
        print("category nums: {}".format(len(category_set)))
        print("image nums: {}".format(len(image_set)))
        print("bbox nums: {}".format(sum(every_class_num.values())))

总结

选择合适的数据集格式对于训练和部署YOLO模型至关重要。TXT格式简单易用,适合初学者和快速原型开发;VOC格式适合需要更详细标注信息的项目;而COCO格式则因其标准化和通用性,成为许多研究和实际应用的首选。了解这些格式及其转换方法,可以帮助研究人员和开发者更有效地处理目标检测任务。

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

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

相关文章

RabbitMQ, DelayQueue, Redis的介绍以及IDEA的实现

RabbitMQ RabbitMQ是一个开源的消息队列中间件&#xff0c;它实现了高效、可靠的消息传递机制。它支持多种消息传递模式&#xff0c;如发布/订阅、点对点、请求/回应等。RabbitMQ以其可靠性、灵活性和易用性受到广泛的关注和应用。 RabbitMQ基于AMQP&#xff08;Advanced Mess…

【禅道客户案例】专访鸿泉物联研发副总监徐小倩,感受上市公司研发项目管理“知与行”

杭州鸿泉物联网技术股份有限公司&#xff08;以下简称“鸿泉物联”、“公司”&#xff09;成立于2009年6月11日&#xff0c;2019年11月6日登陆上海证券交易所科创板&#xff08;股票代码&#xff1a;688288&#xff09;&#xff0c;注册资本10034.392万元&#xff0c;目前员工6…

「案例分享」DevExpress XAF (WinForms UI)赋能医疗管理系统,让操作更自动化!

DevExpress XAF是一款强大的现代应用程序框架&#xff0c;它采用模块化设计&#xff0c;开发人员可以选择内建模块&#xff0c;也可以自行创建&#xff0c;从而以更快的速度和比开发人员当前更强有力的方式创建应用程序。 获取DevExpress 新版正式版下载(Q技术交流&#xff1a…

CTFshow-PWN-栈溢出(pwn44)

64位的 system(); 但是好像没"/bin/sh" 上面的办法不行了&#xff0c;想想办法 检查&#xff1a; 是 64 位程序 ida 反编译 main 函数&#xff1a; 跟进 ctfshow 函数&#xff1a; 存在栈溢出 offset&#xff1a;0xAh8 在前面经验的基础上&#xff0c;这里我们直…

Redis第14讲——Redis实现分布式锁(Redission源码解析)

在多线程环境下&#xff0c;为了保证数据的线程安全&#xff0c;我们通常用加锁的方式&#xff0c;使同一时刻只有一个线程可以对这个共享资源进行操作&#xff0c;在单服务系统我们常用JVM锁——Synchronized、ReentrantLock等。然而在多台服务系统的情况下&#xff0c;JVM锁就…

数字科技助力垃圾分类展厅,增强内容交互新体验!

如今&#xff0c;许多行业都开始运用数字技术&#xff0c;探索其在展览展示领域中的应用&#xff0c;其中垃圾分类展厅作为现代城市文明建设的重要一环&#xff0c;也通过这些技术的运用&#xff0c;打造出了更加生动且富有科技感的展示空间&#xff0c;它不仅提升公众对垃圾分…

原生微信小程序中案例--仿boss区域树选择列多选功能

1. 需求描述&#xff1a; 区域三级列表&#xff0c; 有添加&#xff0c;编辑&#xff0c;删除功能。 选择父级分类&#xff0c;其下子类全部选中&#xff0c;当前分类后加标志显示全字样取消选中子类&#xff0c;其父类分类后标志显示选中数量若子类全部选中&#xff0c;除当…

神经网络鸢尾花分类

⚠申明&#xff1a; 未经许可&#xff0c;禁止以任何形式转载&#xff0c;若要引用&#xff0c;请标注链接地址。 全文共计3077字&#xff0c;阅读大概需要3分钟 &#x1f308;更多学习内容&#xff0c; 欢迎&#x1f44f;关注&#x1f440;【文末】我的个人微信公众号&#xf…

【超简单实用】Zotero 7 内置pdf背景颜色更改插推荐以及安装

Zotero beta7 pdf 内置颜色更换 zetore 6 很多成熟的插件在 zetore 7都不能用了。版本回退看起来内置文章的注释会被消除&#xff0c;所以又不想退回去。前几个月在找beta 7 的pdf 护眼色的插件一直没有&#xff0c;今天终于发现了&#xff01;&#xff01;&#xff01;&#…

《架构风清扬-Java面试系列第26讲》聊聊的LinkedBlockingQueue的特点及使用场景

LinkedBlockingQueue也是BlockingQueue接口的一个实现类之一 这个属于基础性问题&#xff0c;老规矩&#xff0c;我们将从使用场景和代码示例来进行讲解 来&#xff0c;思考片刻&#xff0c;给出你的答案 1&#xff0c;使用场景 实现&#xff1a;基于链表实现的阻塞队列&#…

Django5框架之多重继承

在Django模型中也支持使用多重继承&#xff0c;这点与Python语法中的继承是一致的。Django模型多重继承就是同时继承多个父类模型&#xff0c;父类中第一个出现的基类&#xff08;如&#xff1a;Meta类&#xff09;是默认被使用的。如果存在多个父类包含Meta类的情况&#xff0…

Redis - Set 集合

前言 集合类型可保存多个字符串类型的元素&#xff0c;但和列表类型不同的是&#xff0c;集合中的元素之间是⽆序的&#xff08;顺序不重要&#xff0c;变换一下集合中的数据顺序&#xff0c;集合不会发生改变&#xff09; 的并且元素不允许重复 ⼀个集合中最多可以存储 2^32-1…

echarts 堆叠柱状图 顶部添加合计

堆叠有3个&#xff0c;后面加了一个对象显示顶部的数据&#xff0c; 其实主要的代码还是在series 的第四项&#xff0c;需要注意的是 series的第四项中的data需要为 data: [0, 0, 0] 顶部的统计才能显示出来 增加的代码如下 {name: 综合,type: bar,stack: total,label: {sh…

基于springboot+vue+Mysql的篮球竞赛预约平台

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

STM32 HAL库F103系列之DAC实验(二)

DAC输出正弦波实验 实验简要 1&#xff0c;功能描述 通过DAC1通道1(PA4)输出正弦波&#xff0c;然后通过DS100示波器查看波形 2&#xff0c;使用定时器7 TRGO事件触发转换 TEN1位置1、TSEL1[2:0]010 3&#xff0c;关闭输出缓冲 BOFF1位置1 4&#xff0c;使用DMA模式 DMAE…

无人机+遥控器:工业级手持地面站(支持安卓系统)功能技术详解

手持地面站是一种专为无人机设计的便携式设备&#xff0c;用于实现飞行控制、任务规划、数据链路通信等功能。由于支持安卓系统&#xff0c;这种地面站设备在软件生态上具有极大的灵活性&#xff0c;能够兼容并运行众多基于安卓平台的无人机控制应用程序。 在硬件方面&#xff…

vue中的mixin(局部混入、全局混入)

一、mixin是什么 Mixin是面向对象程序设计语言中的类&#xff0c;提供了方法的实现。其他类可以访问mixin类的方法而不必成为其子类&#xff1b;Mixin类通常作为功能模块使用&#xff0c;在需要该功能时“混入”&#xff0c;有利于代码复用又避免了多继承的复杂 Vue中的mixin…

如何在 Ubuntu 14.04 上配置 StatsD 以收集 Graphite 的任意统计数据

介绍 Graphite 是一个图形库&#xff0c;允许您以灵活和强大的方式可视化不同类型的数据。它通过其他统计收集应用程序发送给它的数据进行图形化。 在之前的指南中&#xff0c;我们讨论了如何安装和配置 Graphite 本身&#xff0c;以及如何安装和配置 collectd 以编译系统和服…

python实现视频剪辑

即刻关注&#xff0c;获取更多 实现目标 因上传某盘等文件大小限制&#xff0c;无法上传视频&#xff0c;故需要对视频进行压缩 参考资料 ffmpeg文档参考: https://ffmpeg.org/ffmpeg.html 依赖条件 已经安装好python3.11 &#xff0c;原则上更高版本也可以 安装 ffmpeg 依赖 p…

21.3K star!推荐一款可视化自动化测试/爬虫/数据采集神器!功能免费且强大!

大家好&#xff0c;我是狂师&#xff01; 在大数据时代&#xff0c;信息的获取与分析变得尤为重要。对于开发者、数据分析师乃至非技术人员来说&#xff0c;能够高效地采集网络数据并进行分析是一个强有力的工具。今天&#xff0c;我要向大家推荐的是一款功能强大、操作简单且…