前言
- 以前都是纯手动进行标注的,样本估计也就百来张,这回要训练的目标复杂了点,所以采集了有2000张左右的样本
- 每张图片要标注的类别差不多6类,总不能直接手动标注2000张吧
- 一个客户要的,我当时看到这么大工作量的时候内心是拒绝的,随口补了句:除非你自己标注,数据的训练以及后续工作可以交给我,他一口答应了,于是我就给了他100张样本,并在他电脑上给他部署了相关环境教他标注,第二天他就把标注好的数据集给我了
- 于是我就用这100张样本数据训练出了一代模型,测试了下效果,竟然比预估的要好,每张图片的查全率基本有60%以上,正确率90%以上
- 一般来说这样就基本可以达到投入使用的标准了,但是为了追求完美,我选择继续捣鼓下去,当然,并不是继续手动标注,而是研究自动标注
总体实现思路
- 标注少量数据集,30-50张样本数据,并训练出一代模型
- 写个一代模型批量推理预测的代码,如果条件允许的话,将一代模型部署成API接口(下面给出的代码就是基于API接口的)
- 将每张推理的类和类坐标范围提取出来,并按照一定的格式写入对应图片的txt文本
标注文件(*.txt)解析
只有了解标注文件写入的原理后,才能够正确地写入labelimg能够识别的格式~
每次标注完一张图片后,lables目录下就会自动生成一个对应图片名的txt文件,这就是标注的信息文件。一个类的大体格式如下:
0 0.222420 0.500735 0.188612 0.227941
第一位是类别索引,后面四位是边框界,都是以比例的形式
-
类别索引(Class Index):
第一个数字 0 代表被标注对象的类别索引。这个索引是对应于classes.txt中第一行的类别名,比如classes.txt第一行是猫,那么就代表0就代表猫 -
边框界(Bounding Box)
归一化的边界框坐标,格为 (中心点x坐标, 中心点y坐标, 宽度, 高度)。这些值是相对于图像尺寸的比例值,范围在 0 到 1 之间:- 中心点x坐标(0.222420):边界框中心的 x 坐标(相对于图像宽度的比例)。
- 中心点y坐标(0.500735):边界框中心的 y 坐标(相对于图像高度的比例)。
- 宽度(0.188612):边界框的宽度(相对于图像宽度的比例)。
- 高度(0.227941):边界框的高度(相对于图像高度的比例)。
半自动标注实现代码
注意下面的detect函数就是推理预测的代码,需要自行根据自身情况修改!
import requests
from typing import List
import json
import base64
import os
from PIL import Image
def detect(image_path, model_name, img_size=640, conf=0.5, classes="all", download_image=True):
url = "http://127.0.0.1:5678/detect"
files = {'file_list': open(image_path, 'rb')}
data = {
'modelName': model_name,
'img_size': img_size,
'conf': conf,
'classes': classes,
'download_image': download_image
}
response = requests.post(url, files=files, data=data)
return response.text
def 自动标注():
自动标注目标文件夹 = "C:\\Users\\daowuya\Desktop\\自动标注\\images\\train"
自动标注结果文件夹 = "C:\\Users\\daowuya\Desktop\\自动标注\\labels\\train"
模型名称 = "一代模型名称"
n = 1
all = os.listdir(自动标注目标文件夹)
for i in all:
print(f"正在标注第{n}/{len(all)}张图片 {i}")
# 获取图像尺寸
img_path = os.path.join(自动标注目标文件夹, i)
with Image.open(img_path) as img:
img_width, img_height = img.size
result = detect(img_path, 模型名称)
# 去除最外层的引号和方括号,并去除转义字符
trimmed_str = result[2:-2].replace("\\", "")
jsons = json.loads(trimmed_str)
length = len(jsons)
write_content = ""
for j in range(length-1):
if jsons[j]["confidence"] >= 0.9:
# 获取类别索引和边界框信息
class_index = jsons[j]["class"]
bbox = jsons[j]["bbox"] # [左上角x, 左上角y, 右下角x, 右下角y]
# 计算中心点坐标及宽度和高度(归一化)
x_center = ((bbox[0] + bbox[2]) / 2) / img_width
y_center = ((bbox[1] + bbox[3]) / 2) / img_height
width = (bbox[2] - bbox[0]) / img_width
height = (bbox[3] - bbox[1]) / img_height
# 构造写入内容
write_content += f"{class_index} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}\n"
# 写入文件
txt_filename = os.path.splitext(i)[0] + ".txt"
with open(os.path.join(自动标注结果文件夹, txt_filename), "w", encoding='utf-8') as file:
file.write(write_content)
n += 1
if __name__ == "__main__":
自动标注()
lableimg打开自动标注的训练集报错解决
打开自动标注后的数据,出现下面的报错是必然的。经过摸索和亲身实践,都完美解决了,对应的报错我都给出了原因以及解决办法。
-
报错1:
Traceback (most recent call last): File "C:\Users\daowuya\.conda\envs\labelimg\lib\site-packages\labelImg\labelImg.py", line 738, in file_item_double_clicked self.load_file(filename) File "C:\Users\daowuya\.conda\envs\labelimg\lib\site-packages\labelImg\labelImg.py", line 1111, in load_file self.show_bounding_box_from_annotation_file(file_path) File "C:\Users\daowuya\.conda\envs\labelimg\lib\site-packages\labelImg\labelImg.py", line 1144, in show_bounding_box_from_annotation_file self.load_yolo_txt_by_filename(txt_path) File "C:\Users\daowuya\.conda\envs\labelimg\lib\site-packages\labelImg\labelImg.py", line 1557, in load_yolo_txt_by_filename t_yolo_parse_reader = YoloReader(txt_path, self.image) File "C:\Users\daowuya\.conda\envs\labelimg\lib\site-packages\libs\yolo_io.py", line 101, in __init__ self.classes = classes_file.read().strip('\n').split('\n') UnicodeDecodeError: 'gbk(或者utf-8)' codec can't decode byte 0x9e in position 18: illegal multibyte sequence
原因:
labelimg在每次更新classes后,我注意观察了下,即使你刚开始的classes.txt是utf-8编码的,但是在更新后都会变成ANSI编码的。
解决方式:
①打开现有的classes.txt,并另存为ANSI编码的。
②进入labelimg的源代码目录,定位到报错的文件位置
C:\Users\daowuya\.conda\envs\labelimg\lib\site-packages\libs\yolo_io.py
报错的代码:classes_file = open(self.class_list_path, 'r') self.classes = classes_file.read().strip('\n').split('\n')
修改成以ANSI编码打开文件
with open(self.class_list_path, 'r', encoding='ANSI') as classes_file: self.classes = classes_file.read().strip('\n').split('\n')
-
报错2
Traceback (most recent call last): File "C:\Users\daowuya\.conda\envs\labelimg\lib\site-packages\labelImg\labelImg.py", line 1376, in open_next_image self.load_file(filename) File "C:\Users\daowuya\.conda\envs\labelimg\lib\site-packages\labelImg\labelImg.py", line 1111, in load_file self.show_bounding_box_from_annotation_file(file_path) File "C:\Users\daowuya\.conda\envs\labelimg\lib\site-packages\labelImg\labelImg.py", line 1144, in show_bounding_box_from_annotation_file self.load_yolo_txt_by_filename(txt_path) File "C:\Users\daowuya\.conda\envs\labelimg\lib\site-packages\labelImg\labelImg.py", line 1557, in load_yolo_txt_by_filename t_yolo_parse_reader = YoloReader(txt_path, self.image) File "C:\Users\daowuya\.conda\envs\labelimg\lib\site-packages\libs\yolo_io.py", line 115, in __init__ self.parse_yolo_format() File "C:\Users\daowuya\.conda\envs\labelimg\lib\site-packages\libs\yolo_io.py", line 146, in parse_yolo_format label, x_min, y_min, x_max, y_max = self.yolo_line_to_shape(class_index, x_center, y_center, w, h) File "C:\Users\daowuya\.conda\envs\labelimg\lib\site-packages\libs\yolo_io.py", line 128, in yolo_line_to_shape label = self.classes[int(class_index)] IndexError: list index out of range
原因:
这种情况正常打开查看标注情况是没有任何问题的,但是只要是修改或者增加了标注,此时再点击查看下一张图的标注情况就会发生闪退。这是因为在你更新数据后,classes.txt的数据会被重写,重写为上一张图里的所有class,所以一旦你的下一张图里原本标注的class不存在于classes.txt内,就会报“list index out of range”这种错误。
解决方式:
①定位到labelimg.py源码目录
C:\Users\daowuya\.conda\envs\labelimg\Lib\site-packages\labelImg
②在源码目录新建data文件夹,再在data文件夹里新建 predefined_classes.txt,注:这个txt文件的编码需要保存为utf-8。
再将classes.txt里的所有内容都黏贴进去,保存即可!