基于OpenCV实现动态绘制矩形与多边形并自动保存坐标

阿麦
阿麦
发布时间:2026-10-12 | 3人浏览过
原创

本文详解如何利用Python与OpenCV在图像窗口中动态绘制矩形和多边形。通过自定义鼠标回调与键盘指令切换标注模式,脚本自动按屏幕分辨率适配窗口大小,实时记录顶点位置并导出至TXT文件,同时内置坐标清洗逻辑,适用于定制化图像数据标注场景。

环境与依赖准备

运行该脚本需确保本地已安装以下组件:

  • Python 3.7+
  • 核心库:opencv-python、imutils
  • Windows API支持:pywin32(用于获取桌面物理分辨率以实现图像自适应缩放)

配置文件设置

程序启动前需准备一个JSON文件指定待处理图像的绝对路径,例如命名为drawPath.json:

{"path": "D:\\ALLBuffers\\Pycharm\\OpencvRun\\DataSet\\smpj.jpg"}

核心功能实现逻辑

代码主要包含图像分辨率自适应检测、鼠标事件监听绑定、几何图形实时绘制与坐标持久化四大模块。优化后的交互逻辑默认直接进入多边形标注状态,支持运行时切换矩形模式,并在退出时统一清理无效数据并保存结果。

import json
import cv2
import os
import imutils
import win32gui, win32print, win32con

WIN_NAME = 'draw_rect'

def get_list0(path):
    if not os.path.exists(path):
        print("记录该型号标准位置的文件缺失或输入文件名不一致")
        return []
    with open(path, 'r', encoding='utf-8') as f:
        lines = f.readlines()
    zb0 = []
    for line in lines:
        line = line.strip()
        if line != '(pt1,pt2):':
            zb0.append(int(line))

    list0 = []
    for i in range(0, len(zb0), 4):
        x0, y0, x1, y1 = zb0[i:i + 4]
        if y1 <= y0:
            y0, y1 = y1, y0
        list0.append([x0, y0, x1, y1])
    return list0

def set_ratio(image):
    if image is None:
        return 0, 0
    img_h, img_w = image.shape[:2]
    hDC = win32gui.GetDC(0)
    screen_w = win32print.GetDeviceCaps(hDC, win32con.DESKTOPHORZRES)
    screen_h = win32print.GetDeviceCaps(hDC, win32con.DESKTOPVERTRES)

    num_wh = 1
    if img_w * img_h > 1.9e7:
        num_wh = 4
    elif img_w * img_h > 1.0e7:
        num_wh = 3
    elif min(img_w, img_h) >= min(screen_w, screen_h) or \\
            max(img_w, img_h) >= max(screen_w, screen_h):
        num_wh = 2
    return int(img_h / num_wh), int(img_w / num_wh)

POLY_MODE = False

def define_start(img_name, img_path):
    pts = []

    def draw_roi(event, x, y, flags, param):
        nonlocal POLY_MODE
        img2 = img.copy()
        if event == cv2.EVENT_LBUTTONDOWN:
            pts.append((x, y))
            cv2.circle(img2, pts[-1], 3, (0, 255, 0), -1)
        elif event == cv2.EVENT_RBUTTONDOWN:
            if len(pts) > 0:
                pts.pop()
        elif event == cv2.EVENT_MBUTTONDOWN:
            POLY_MODE = True

        if len(pts) > 0:
            cv2.circle(img2, pts[-1], 3, (0, 255, 0), -1)
        if len(pts) > 1:
            for i in range(len(pts) - 1):
                cv2.circle(img2, pts[i], 5, (0, 255, 0), -1)
                cv2.line(img2, pts[i], pts[i + 1], color=(0, 0, 255), thickness=2)
            cv2.line(img2, pts[0], pts[-1], color=(0, 0, 255), thickness=2)
        cv2.imshow(WIN_NAME, img2)

    if not img_path.lower().endswith(('.jpg', '.png')):
        print("输入图片类型错误!请输入JPG/PNG格式的图片!")
        return

    image = cv2.imread(img_path)
    ratio_h, ratio_w = set_ratio(image)
    if ratio_h == 0 or ratio_w == 0:
        print("无法获取有效图像或分辨率数据")
        return

    txt_path = f"./DrawRect/biaozhun/labels/{img_name}.txt"
    os.makedirs(os.path.dirname(txt_path), exist_ok=True)
    f = open(txt_path, mode='w', encoding='utf-8')
    txt_save = []

    img = imutils.resize(image, width=ratio_w)
    cv2.namedWindow(WIN_NAME, cv2.WINDOW_NORMAL)
    cv2.resizeWindow(WIN_NAME, ratio_w, ratio_h)
    cv2.imshow(WIN_NAME, img)
    cv2.setMouseCallback(WIN_NAME, draw_roi)

    while True:
        w_key = cv2.waitKey(1)
        if w_key == 13 or cv2.getWindowProperty(WIN_NAME, 0) == -1:
            for i, pt in enumerate(pts):
                if i == 0:
                    txt_save.append("(pt1,pt2):")
                txt_save.append(str(pt[0]))
                txt_save.append(str(pt[1]))

            while '(pt1,pt2):' in txt_save:
                idx = txt_save.index('(pt1,pt2):')
                del txt_save[idx:idx+6]

            for val in txt_save:
                f.write(str(val) + '\n')
            print("坐标数据已输出:", txt_save)
            f.close()
            break

        if POLY_MODE or w_key == 32:
            roi = cv2.selectROI(windowName=WIN_NAME, img=img, showCrosshair=False, fromCenter=False)
            x, y, w, h = roi
            cv2.rectangle(img, (x, y), (x + w, y + h), color=(0, 0, 255), thickness=2)
            cv2.imshow(WIN_NAME, img)

            txt_save.extend(["(pt1,pt2):", str(x), str(y), str(x + w), str(y + h)])
            points_list = get_list0(txt_path)
            new_points_list = []
            for coords in points_list:
                x0, y0, x1, y1 = coords
                if abs(x1 - x0) > 5 and abs(y1 - y0) > 5:
                    new_points_list.extend(["(pt1,pt2):", x0, y0, x1, y1])

            file2 = open(txt_path, 'w', encoding='utf-8')
            for item in new_points_list:
                file2.write(str(item) + '\n')
            file2.close()
            break
    cv2.destroyAllWindows()

if __name__ == '__main__':
    path_file = open('./DataSet/drawPath.json', 'r', encoding='utf-8')
    path_dic = json.load(path_file)
    path_file.close()
    img_path = path_dic['path']
    img_name = os.path.splitext(os.path.basename(img_path))[0]
    define_start(img_name, img_path)

交互操作说明

第1步:初始化窗口与图像加载

运行主程序后,脚本依据set_ratio函数对比当前图像像素数与系统桌面分辨率。当图像尺寸较大时自动计算缩放因子(通常为1/2或1/4),调用imutils.resize保持宽高比,防止高维图像在普通屏幕上显示不全。

第2步:多边形点位绘制

窗口弹出后默认进入多边形模式。鼠标左键单击图像任意位置追加顶点,右键点击撤销最近一个点。算法会实时绘制红绿双色连线预览,绿点表示已确认节点,红线勾勒闭合轨迹。

第3步:矩形框选与数据导出

按下空格键或鼠标中键可临时激活矩形模式,此时界面将接管控制权并弹出cv2.selectROI拖拽框。选定形状后按下Enter键结束程序。框架会在退出前遍历坐标数组,剔除水平或垂直跨度小于5像素的噪点边界,最终按行追加写入对应名称的.txt标注文件。

关键细节与排错提示

输出的TXT文件采用纯文本逐行列出数值,每四列一组对应矩形左上角与右下角坐标,多边形则连续存放各顶点XY值。若运行时报错No module named 'win32',请执行pip install pywin32补全系统API依赖。原草稿中存在f.close()置于break之后导致语句不可达的问题,本版已修正资源释放顺序。若发现标注坐标与实际画面错位,请确认操作系统未开启超清模式下的Display Scaling(缩放比例),或直接在原生分辨率显示器下运行。

以上就是基于OpenCV实现动态绘制矩形与多边形并自动保存坐标的详细内容,更多关于Python图像处理与自定义标注工具的资料请关注本站其它相关文章!

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表