最近搜索

pythone 标注工具。人工智能,标注工具,YOLOv8框架

浏览:25
管理员 2025-06-17 10:17





这是针对YOLOv8框架,训练数据的标注,人工智能,标注工具


import os
import re
import cv2
import glob
import numpy as np
from PIL import Image, ImageDraw, ImageFont


# 004这个代码 拉框有点卡,显示中文  能正常标注。
# 005 修改004的问题。  这个代码运行不显示图片。需要标注一下。才会显示。
# 006 显示图片了。  但是标注第2个图片,第1个图片的标注还存在。

class SmoothYOLOAnnotator:
    def __init__(self, image_folder):
        self.image_folder = image_folder

        self.images = sorted(glob.glob(os.path.join(image_folder, "*.jpg")) +
                             glob.glob(os.path.join(image_folder, "*.png")),
                             key=lambda x: int(re.search(r'\d+', os.path.basename(x)).group()))

        # glob.glob() 函数解析 通过通配符匹配文件路径,返回符合条件的文件列表
        # * 任意数量字符 *.jpg匹配所有JPG
        # ?  单个字   pic?.png匹配pic1.png
        # []  指定字符范围[a - z].txt匹配a - z开头的txt
        # sorted 对可迭代对象(列表、元组、字符串等)进行排序 返回一个新的排序后列表,不修改原对象
        print(self.images)

        self.current_index = 0 #从第1个开始标注  0就是从1     1就是从第2个标注
        self.annotations = []
        self.temp_box = None
        self.classes = {1: "小明", 2: "小红", 3: "小刚"}
        self.cache_img = None
        self.font = self.load_font()
        self.window_name = "YOLO标注工具"
        self.crosshair_color = (0, 0, 255)  # 红色十字线
        self.crosshair_thickness = 1



    def load_font(self):
        try:
            return ImageFont.truetype("simhei.ttf", 20)
        except:
            return ImageFont.load_default()

    def init_display(self):
        """初始化显示图片"""
        # 清空上一张图的标注
        self.annotations = []
        self.temp_box = None

        # 从图片列表中读取当前索引对应的图片
        self.current_img = cv2.imread(self.images[self.current_index])
        if self.current_img is None:
            print(f"无法加载图片: {self.images[self.current_index]}")
            return
        # 更新缓存图片(添加标注框和文字)

        # 重置缓存并更新显示
        self.cache_img = None
        self.update_cache()
        # 在指定窗口中显示处理后的图片
        cv2.imshow(self.window_name, self.cache_img)
        cv2.waitKey(1)  # 强制刷新显示

    def update_cache(self):
        # 复制当前图片到缓存(避免修改原图)  所有修改都在缓存图片上进行,不破坏原图
        self.cache_img = self.current_img.copy()
        # 遍历所有标注信息(class_id是类别,x1,y1是左上角,x2,y2是右下角)
        for (class_id, x1, y1, x2, y2) in self.annotations:
            # 画绿色矩形框(线宽2像素)
            cv2.rectangle(self.cache_img, (x1, y1), (x2, y2), (0, 255, 0), 2)
            # 转换到PIL格式(因为OpenCV和PIL处理图片方式不同)
            pil_img = Image.fromarray(cv2.cvtColor(self.cache_img, cv2.COLOR_BGR2RGB))
            # 准备在图片上写字  绿色矩形框(标记物体位置)
            # 绿色文字标签(说明物体类别)
            draw = ImageDraw.Draw(pil_img)
            # 在框上方25像素处写类别名称(绿色文字)
            draw.text((x1, y1 - 25), self.classes[class_id], font=self.font, fill=(0, 255, 0))
            # 转回OpenCV格式
            self.cache_img = cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)

    def mouse_callback(self, event, x, y, flags, param):

        """增强的鼠标回调函数"""
        # 十字线绘制逻辑
        display_img = self.cache_img.copy() if self.cache_img is not None else self.current_img.copy()
        cv2.line(display_img, (0, y), (display_img.shape[1], y),
                 self.crosshair_color, self.crosshair_thickness)
        cv2.line(display_img, (x, 0), (x, display_img.shape[0]),
                 self.crosshair_color, self.crosshair_thickness)
        # 十字线绘制逻辑

        if event == cv2.EVENT_LBUTTONDOWN:
            self.temp_box = [x, y, x, y]
        elif event == cv2.EVENT_MOUSEMOVE:
            if self.temp_box:
                self.temp_box[2:] = [x, y]
                display_img = self.cache_img.copy()
                cv2.rectangle(display_img,
                              (self.temp_box[0], self.temp_box[1]),
                              (self.temp_box[2], self.temp_box[3]),
                              (255, 0, 0), 1)
                cv2.imshow(self.window_name, display_img)
        elif event == cv2.EVENT_LBUTTONUP:
            if self.temp_box:
                x1, y1 = min(self.temp_box[0], x), min(self.temp_box[1], y)
                x2, y2 = max(self.temp_box[0], x), max(self.temp_box[1], y)
                self.select_class(x1, y1, x2, y2)
                self.temp_box = None
        self.update_display(display_img)

    def update_display(self, img=None):
        """更新显示内容"""
        if img is None:
            img = self.current_img.copy()
            self.update_cache()
            if self.cache_img is not None:
                img = self.cache_img.copy()
        cv2.imshow(self.window_name, img)

    def select_class(self, x1, y1, x2, y2):
        # 创建当前图像的副本用于显示选择界面
        selection_img = self.current_img.copy()
        # 将OpenCV格式(BGR)转为PIL格式(RGB)
        pil_img = Image.fromarray(cv2.cvtColor(selection_img, cv2.COLOR_BGR2RGB))
        draw = ImageDraw.Draw(pil_img)
        # 绘制分类选择提示文字(红色)
        draw.text((10, 20), "选择分类:", font=self.font, fill=(0, 0, 255))
        # 遍历classes字典显示所有可选类别(1.小明 2.小红 3.小刚)

        for i, (class_id, name) in enumerate(self.classes.items()):
            draw.text((10, 50 + i * 30), f"{class_id}. {name}", font=self.font, fill=(0, 0, 255))
            # 显示选择窗口(转回OpenCV格式)
        cv2.imshow("选择分类", cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR))

        # 等待键盘输入(0表示无限等待)
        key = cv2.waitKey(0) & 0xFF
        cv2.destroyWindow("选择分类")  # 关闭选择窗口

        # 如果按了1/2/3键(ASCII码49-51)
        if 49 <= key <= 51:
            # 保存标注(key-48将ASCII码转为数字1/2/3)
            self.annotations.append((key - 48, x1, y1, x2, y2))
            self.update_cache()# 更新显示
            cv2.imshow(self.window_name, self.cache_img)

    def run(self):
        # 创建显示窗口并设置鼠标回调函数
        cv2.namedWindow(self.window_name)
        cv2.setMouseCallback(self.window_name, self.mouse_callback)
        self.init_display()  # 初始化显示第一张图片

        # 主循环(遍历所有图片)
        while self.current_index < len(self.images):
            key = cv2.waitKey(10) & 0xFF  # 10ms等待按键
            if key == ord("n"):  # 按n键下一张
                self.save_annotations() # 保存当前标注
                self.current_index += 1
                if self.current_index < len(self.images):
                    self.init_display()  # 显示新图片
            elif key == ord("p"): # 按p键上一张
                self.save_annotations()
                self.current_index = max(0, self.current_index - 1)
                self.init_display()
            elif key == ord("d"):# 按d键删除最后一个标注
                if self.annotations:
                    self.annotations.pop()# 移除最后标注
                    self.update_cache()
                    cv2.imshow(self.window_name, self.cache_img)
            elif key == ord("q"):  # 按q键退出
                break
        cv2.destroyAllWindows()# 关闭所有窗口


    def save_annotations(self): #标注保存
        if not self.annotations: return

        height, width = self.current_img.shape[:2]
        txt_path = os.path.splitext(self.images[self.current_index])[0] + ".txt"

        with open(txt_path, "w") as f:
            for (class_id, x1, y1, x2, y2) in self.annotations:
                x_center = ((x1 + x2) / 2) / width
                y_center = ((y1 + y2) / 2) / height
                box_width = abs(x2 - x1) / width
                box_height = abs(y2 - y1) / height
                f.write(f"{class_id - 1} {x_center:.6f} {y_center:.6f} {box_width:.6f} {box_height:.6f}\n")


if __name__ == "__main__":
    image_folder = "E:/123";
    # image_folder = input("输入图片文件夹路径: ")
    annotator = SmoothYOLOAnnotator(image_folder)
    annotator.run()



输入文件夹路径 。下面的图片名称 必须是 1 到  n    后缀名 jpg  png 都可以。

  

        self.classes = {1: "小明", 2: "小红", 3: "小刚"}  这是分类。请自行修改。



        self.current_index = 0 #从第1个开始标注  0就是从1     1就是从第2个标注
        
        假如我们想从第5张图片标注 请输入4
        
        0是从1第个图片标注。




针对标注窗口 显示分数乱码,安装了一个东西。

pip install opencv-python pillow numpy
显示分类名称乱码的时候,安装了这个库。


联系站长

站长微信:xiaomao0055

站长QQ:14496453