侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

python实现简单图片物体标注工具

Python  /  管理员 发布于 7年前   305

本文实例为大家分享了python实现简单图片物体标注工具的具体代码,供大家参考,具体内容如下

# coding: utf-8 """物体检测标注小工具基本思路:对要标注的图像建立一个窗口循环,然后每次循环的时候对图像进行一次复制,鼠标在画面上画框的操作、画好的框的相关信息在全局变量中保存,并且在每个循环中根据这些信息,在复制的图像上重新画一遍,然后显示这份复制的图像。简化的设计过程:1、输入是一个文件夹的路径,包含了所需标注物体框的图片。如果图片中标注了物体,则生成一个相同名称加额外后缀_bbox的文件,来保存标注信息。2、标注的方式:按下鼠标左键选择物体框的左上角,松开鼠标左键选择物体框的右下角,按下鼠标右键删除上一个标注好的物体框。所有待标注物体的类别和标注框颜色由用户自定义。如果没有定义则默认只标注一种物体,定义该物体名称为Object。3、方向键 ← 和 → 键用来遍历图片, ↑ 和 ↓ 键用来选择当前要标注的物体,Delete键删除一种脏图片和对应的标注信息。自定义标注物体和颜色的信息用一个元组表示第一个元素表示物体名字第二个元素表示BGR颜色的tuple或者代表标注框坐标的元祖利用repr()保存和eval()读取""" """一些说明:1. 标注相关的物体标签文件即 .labels 结尾的文件,需要与所选文件夹添加到同一个根目录下一定要注意这一点,否则无法更新标注物体的类型标签,致使从始至终都只有一个默认物体出现我就是这个原因,拖了两三天才整好,当然也顺便仔细的读了这篇代码。同时也学习了@staticmethod以及相应Python的decorator的知识。可以说,在曲折中前进才是棒的。2. .labels文件为预设物体标签文件,其内容具体格式为:'object1', (B, G, R)'object2', (B, G, R)'object3', (B, G, R)……具体见文后图片。3. 最后生成的标注文件,在文后会有,到时再进行解释。""" import osimport cv2# tkinter是Python内置的简单GUI库,实现打开文件夹、确认删除等操作十分方便from tkMessageBox import askyesno# 定义标注窗口的默认名称WINDOW_NAME = 'Simple Bounding Box Labeling Tool'# 定义画面刷新帧率FPS = 24# 定义支持的图像格式SUPPORTED_FORMATS = ['jpg', 'jpeg', 'png']# 定义默认物体框的名字为Object,颜色为蓝色,当没有用户自定义物体时,使用该物体DEFAULT_COLOR = {'Object': (255, 0, 0)}# 定义灰色,用于信息显示的背景和未定义物体框的显示COLOR_GRAY = (192, 192, 192)# 在图像下方多处BAR_HEIGHT的区域,用于显示信息BAR_HEIGHT = 16# 上下左右,DELETE键对应的cv2.waitKey()函数的返回值KEY_UP = 2490368KEY_DOWN = 2621440KEY_LEFT = 2424832KEY_RIGHT = 2555904KEY_DELETE = 3014656# 空键用于默认循环KEY_EMPTY = 0get_bbox_name = '{}.bbox'.format  # 定义物体框标注工具类class SimpleBBoxLabeling: def __init__(self, data_dir, fps=FPS, windown_name=WINDOW_NAME):  self._data_dir = data_dir  self.fps = fps  self.window_name = windown_name if windown_name else WINDOW_NAME   # pt0 是正在画的左上角坐标, pt1 是鼠标所在坐标  self._pt0 = None  self._pt1 = None  # 表明当前是否正在画框的状态标记  self._drawing = False  # 当前标注物体的名称  self._cur_label = None  # 当前图像对应的所有已标注框  self._bboxes = []  # 如果有用户自己定义的标注信息则读取,否则使用默认的物体和颜色  label_path = '{}.labels'.format(self._data_dir)  self.label_colors = DEFAULT_COLOR if not os.path.exists(label_path) else self.load_labels(label_path)  # self.label_colors = self.load_labels(label_path)  # 获取已经标注的文件列表和未标注的文件列表  imagefiles = [x for x in os.listdir(self._data_dir) if x[x.rfind('.') + 1:].lower() in SUPPORTED_FORMATS]  labeled = [x for x in imagefiles if os.path.exists(get_bbox_name(x))]  to_be_labeled = [x for x in imagefiles if x not in labeled]   # 每次打开一个文件夹,都自动从还未标注的第一张开始  self._filelist = labeled + to_be_labeled  self._index = len(labeled)  if self._index > len(self._filelist) - 1:   self._index = len(self._filelist) - 1  # 鼠标回调函数 def _mouse_ops(self, event, x, y, flags, param):  # 按下左键,坐标为左上角,同时表示开始画框,改变drawing,标记为True  if event == cv2.EVENT_LBUTTONDOWN:   self._drawing = True   self._pt0 = (x, y)  # 松开左键,表明画框结束,坐标为有效较并保存,同时改变drawing,标记为False  elif event == cv2.EVENT_LBUTTONUP:   self._drawing = False   self._pt1 = (x, y)   self._bboxes.append((self._cur_label, (self._pt0, self._pt1)))  # 实时更新右下角坐标  elif event == cv2.EVENT_MOUSEMOVE:   self._pt1 = (x, y)  # 按下鼠标右键删除最近画好的框  elif event == cv2.EVENT_RBUTTONUP:   if self._bboxes:    self._bboxes.pop()  # 清除所有标注框和当前状态 def _clean_bbox(self):  self._pt0 = None  self._pt1 = None  self._drawing = False  self._bboxes = []  # 画标注框和当前信息的函数 def _draw_bbox(self, img):  # 在图像下方多出BAR_HEIGHT的区域,显示物体信息  h, w = img.shape[:2]  canvas = cv2.copyMakeBorder(img, 0, BAR_HEIGHT, 0, 0, cv2.BORDER_CONSTANT, value=COLOR_GRAY)  # 正在标注的物体信息,如果鼠标左键已经按下,则像是两个点坐标,否则显示当前待标注物体的名  label_msg = '{}: {}, {}'.format(self._cur_label, self._pt0, self._pt1) \   if self._drawing \   else 'Current label: {}'.format(self._cur_label)  # 显示当前文件名,文件个数信息  msg = '{}/{}: {} | {}'.format(self._index + 1, len(self._filelist), self._filelist[self._index], label_msg)  cv2.putText(canvas, msg, (1, h+12), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0), 1)  # 画出已经标好的框和对应名字  for label, (bpt0, bpt1) in self._bboxes:   label_color = self.label_colors[label] if label in self.label_colors else COLOR_GRAY   cv2.rectangle(canvas, bpt0, bpt1, label_color, thickness=2)   cv2.putText(canvas, label, (bpt0[0]+3, bpt0[1]+15), cv2.FONT_HERSHEY_SIMPLEX, 0.5, label_color, 2)  # 画正在标注的框和对应名字  if self._drawing:   label_color = self.label_colors[self._cur_label] if self._cur_label in self.label_colors else COLOR_GRAY   if (self._pt1[0] >= self._pt0[0]) and (self._pt1[1] >= self._pt1[0]):    cv2.rectangle(canvas, self._pt0, self._pt1, label_color, thickness=2)   cv2.putText(canvas, self._cur_label, (self._pt0[0] + 3, self._pt0[1] + 15),      cv2.FONT_HERSHEY_SIMPLEX, 0.5, label_color, 2)  return canvas  # 利用repr()函数导出标注框数据到文件 @staticmethod def export_bbox(filepath, bboxes):  if bboxes:   with open(filepath, 'w') as f:    for bbox in bboxes:     line = repr(bbox) + '\n'     f.write(line)  elif os.path.exists(filepath):   os.remove(filepath)  # 利用eval()函数读取标注框字符串到数据 @staticmethod def load_bbox(filepath):  bboxes = []  with open(filepath, 'r') as f:   line = f.readline().rstrip()   while line:    bboxes.append(eval(line))    line = f.readline().rstrip()  return bboxes  # 利用eval()函数读取物体及对应颜色信息到数据 @staticmethod def load_labels(filepath):  label_colors = {}  with open(filepath, 'r') as f:   line = f.readline().rstrip()   while line:    label, color = eval(line)    label_colors[label] = color    line = f.readline().rstrip()  print label_colors  return label_colors  # 读取图像文件和对应标注框信息(如果有的话) @staticmethod def load_sample(filepath):  img = cv2.imread(filepath)  bbox_filepath = get_bbox_name(filepath)  bboxes = []  if os.path.exists(bbox_filepath):   bboxes = SimpleBBoxLabeling.load_bbox(bbox_filepath)  return img, bboxes  # 导出当前标注框信息并清空 def _export_n_clean_bbox(self):  bbox_filepath = os.sep.join([self._data_dir, get_bbox_name(self._filelist[self._index])])  self.export_bbox(bbox_filepath, self._bboxes)  self._clean_bbox()  # 删除当前样本和对应的标注框信息 def _delete_current_sample(self):  filename = self._filelist[self._index]  filepath = os.sep.join([self._data_dir, filename])  if os.path.exists(filepath):    os.remove(filepath)  filepath = get_bbox_name(filepath)  if os.path.exists(filepath):    os.remove(filepath)  self._filelist.pop(self._index)  print('{} is deleted!'.format(filename))  # 开始OpenCV窗口循环的方法,程序的主逻辑 def start(self):  # 之前标注的文件名,用于程序判断是否需要执行一次图像读取  last_filename = ''   # 标注物体在列表中的下标  label_index = 0   # 所有标注物体名称的列表  labels = self.label_colors.keys()   # 带标注物体的种类数  n_labels = len(labels)   # 定义窗口和鼠标回调  cv2.namedWindow(self.window_name)  cv2.setMouseCallback(self.window_name, self._mouse_ops)  key = KEY_EMPTY   # 定义每次循环的持续时间  delay = int(1000 / FPS)   # 只要没有按下Delete键,就持续循环  while key != KEY_DELETE:   # 上下方向键选择当前标注物体   if key == KEY_UP:    if label_index == 0:     pass    else:     label_index -= 1   elif key == KEY_DOWN:    if label_index == n_labels - 1:     pass    else:     label_index += 1   # 左右方向键选择标注图片   elif key == KEY_LEFT:    # 已经到了第一张图片的话就不需要清空上一张    if self._index > 0:     self._export_n_clean_bbox()    self._index -= 1    if self._index < 0:     self._index = 0   elif key == KEY_RIGHT:    # 已经到了最后一张图片的就不需要清空上一张    if self._index < len(self._filelist) - 1:     self._export_n_clean_bbox()    self._index += 1    if self._index > len(self._filelist) - 1:     self._index = len(self._filelist) - 1   # 删除当前图片和对应标注的信息   elif key == KEY_DELETE:    if askyesno('Delete Sample', 'Are you sure?'):     self._delete_current_sample()     key = KEY_EMPTY     continue   # 如果键盘操作执行了换图片, 则重新读取, 更新图片   filename = self._filelist[self._index]   if filename != last_filename:    filepath = os.sep.join([self._data_dir, filename])    img, self._bboxes = self.load_sample(filepath)   # 更新当前标注物体名称   self._cur_label = labels[label_index]   # 把标注和相关信息画在图片上并显示指定的时间   canvas = self._draw_bbox(img)   cv2.imshow(self.window_name, canvas)   key = cv2.waitKey(delay)   # 当前文件名就是下次循环的老文件名   last_filename = filename  print 'Finished!'  cv2.destroyAllWindows()  #如果退出程序,需要对当前文件进行保存  self.export_bbox(os.sep.join([self._data_dir, get_bbox_name(filename)]), self._bboxes)  print 'Labels updated!'

以上实现了工具类,当然需要一个入口函数,将工具类保存为SimpleBBoxLabeling.py,新建Run_Detect.py,写以下内容:

# coding:utf-8 # tkinter是Python内置的简单GUI库,实现打开文件夹、确认删除等操作十分方便from tkFileDialog import askdirectory# 导入创建的工具类from SimpleBBoxLabeling import SimpleBBoxLabeling if __name__ == '__main__': dir_with_images = askdirectory(title='Where is the images?') labeling_task = SimpleBBoxLabeling(dir_with_images) labeling_task.start()

 以下是实现后的效果:

需要的文件


.labels文件内容格式


选择文件夹


进行标注


生成相应标签内容


标注结果
标注后的文件格式为:物体,左上角(起点)和右下角(终点)的坐标。

参考资料: 《深度学习与计算机视觉――算法原理、框架应用与代码实现》 叶韵(编著)

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


  • 上一条:
    Python简单I/O操作示例
    下一条:
    Python面向对象程序设计之类的定义与继承简单示例
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • 在python语言中Flask框架的学习及简单功能示例(0个评论)
    • 在Python语言中实现GUI全屏倒计时代码示例(0个评论)
    • Python + zipfile库实现zip文件解压自动化脚本示例(0个评论)
    • python爬虫BeautifulSoup快速抓取网站图片(1个评论)
    • vscode 配置 python3开发环境的方法(0个评论)
    • 近期文章
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • 在go + gin中gorm实现指定搜索/区间搜索分页列表功能接口实例(0个评论)
    • 在go语言中实现IP/CIDR的ip和netmask互转及IP段形式互转及ip是否存在IP/CIDR(0个评论)
    • PHP 8.4 Alpha 1现已发布!(0个评论)
    • Laravel 11.15版本发布 - Eloquent Builder中添加的泛型(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-10
    • 2016-11
    • 2018-04
    • 2020-03
    • 2020-04
    • 2020-05
    • 2020-06
    • 2022-01
    • 2023-07
    • 2023-10
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客