补充:
由于labelimg版本更新,只需要下载最新版本就可以了。自带复制上一张图片bbox。
####################分割符###################
前言
在有些标注中,前后图像信息非常相似,又有所不同,想要让前一张的标注信息保留到下一张,方便在下一张进行微调。
解决
xml保存目录必须与图片同目录
这里只能修改源码:
step1:安装labelimg
拉取labelimg代码
git clone https://github.com/heartexlabs/labelImg.git
安装labelimg(这里跟随github的步骤就好,参考)
cd .\labelImg\
pyrcc5 -o libs/resources.py resources.qr
step2:修改代码
在 ./labelImg.py 文件中修改
修改__init__代码,为其添加###中的一段
def __init__(self, default_filename=None, default_prefdef_class_file=None, default_save_dir=None):
################################以下代码为自添加代码######################################
self.load_last_bbox=True #设置为True,即可加载上一张标注信息,否则不加载标注信息
self.last_xml_path=""
################################以上代码为自添加代码######################################
super(MainWindow, self).__init__()
self.setWindowTitle(__appname__)
修改 其中的 load_file代码,为其添加###中的一段
def load_file(self, file_path=None):
"""Load the specified file, or the last opened file if None."""
self.reset_state()
self.canvas.setEnabled(False)
if file_path is None:
file_path = self.settings.get(SETTING_FILENAME)
# Make sure that filePath is a regular python string, rather than QString
file_path = ustr(file_path)
################################以下代码为自添加代码######################################
print(f"load_file:{file_path}",f"是否存在xml{os.path.exists(file_path[:-4]+'.xml')}")
if self.load_last_bbox:
#shanmh
print("加载上一张",self.last_xml_path)
if os.path.exists(file_path[:-4]+'.xml'):
pass
else:
if self.last_xml_path !="" and os.path.exists(self.last_xml_path):
shutil.copy(self.last_xml_path,file_path[:-4]+'.xml')
print("完成重写")
else:
print("不符合",self.last_xml_path !="" , os.path.exists(self.last_xml_path))
self.last_xml_path=(file_path[:-4]+'.xml')
################################以上代码为自添加代码######################################
# Fix bug: An index error after select a directory when open a new file.
unicode_file_path = ustr(file_path)
unicode_file_path = os.path.abspath(unicode_file_path)
step3:运行
再次运行就可以了
注:不想改的,直接复制整个labelimg.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import argparse
import codecs
import os.path
import platform
import shutil
import sys
import webbrowser as wb
from functools import partial
try:
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
except ImportError:
# needed for py3+qt4
# Ref:
# http://pyqt.sourceforge.net/Docs/PyQt4/incompatible_apis.html
# http://stackoverflow.com/questions/21217399/pyqt4-qtcore-qvariant-object-instead-of-a-string
if sys.version_info.major >= 3:
import sip
sip.setapi('QVariant', 2)
from PyQt4.QtGui import *
from PyQt4.QtCore import *
from libs.combobox import ComboBox
from libs.default_label_combobox import DefaultLabelComboBox
from libs.resources import *
from libs.constants import *
from libs.utils import *
from libs.settings import Settings
from libs.shape import Shape, DEFAULT_LINE_COLOR, DEFAULT_FILL_COLOR
from libs.stringBundle import StringBundle
from libs.canvas import Canvas
from libs.zoomWidget import ZoomWidget
from libs.lightWidget import LightWidget
from libs.labelDialog import LabelDialog
from libs.colorDialog import ColorDialog
from libs.labelFile import LabelFile, LabelFileError, LabelFileFormat
from libs.toolBar import ToolBar
from libs.pascal_voc_io import PascalVocReader
from libs.pascal_voc_io import XML_EXT
from libs.yolo_io import YoloReader
from libs.yolo_io import TXT_EXT
from libs.create_ml_io import CreateMLReader
from libs.create_ml_io import JSON_EXT
from libs.ustr import ustr
from libs.hashableQListWidgetItem import HashableQListWidgetItem
__appname__ = 'labelImg'
class WindowMixin(object):
def menu(self, title, actions=None):
menu = self.menuBar().addMenu(title)
if actions:
add_actions(menu, actions)
return menu
def toolbar(self, title, actions=None):
toolbar = ToolBar(title)
toolbar.setObjectName(u'%sToolBar' % title)
# toolbar.setOrientation(Qt.Vertical)
toolbar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
if actions:
add_actions(toolbar, actions)
self.addToolBar(Qt.LeftToolBarArea, toolbar)
return toolbar
class MainWindow(QMainWindow, WindowMixin):
FIT_WINDOW, FIT_WIDTH, MANUAL_ZOOM = list(range(3))
def __init__(self, default_filename=None, default_prefdef_class_file=None, default_save_dir=None):
################################以下代码为自添加代码######################################
self.load_last_bbox=True
self.last_xml_path=""
################################以上代码为自添加代码######################################
super(MainWindow, self).__init__()
self.setWindowTitle(__appname__)
# Load setting in the main thread
self.settings = Settings()
self.settings.load()
settings = self.settings
self.os_name = platform.system()
# Load string bundle for i18n
self.string_bundle = StringBundle.get_bundle()
get_str = lambda str_id: self.string_bundle.get_string(str_id)
# Save as Pascal voc xml
self.default_save_dir = default_save_dir
self.label_file_format = settings.get(SETTING_LABEL_FILE_FORMAT, LabelFileFormat.PASCAL_VOC)
# For loading all image under a directory
self.m_img_list = []
self.dir_name = None
self.label_hist = []
self.last_open_dir = None
self.cur_img_idx = 0
self.img_count = len(self.m_img_list)
# Whether we need to save or not.
self.dirty = False
self._no_selection_slot = False
self._beginner = True
self.screencast = "https://youtu.be/p0nR2YsCY_U"
# Load predefined classes to the list
self.load_predefined_classes(default_prefdef_class_file)
if self.label_hist:
self.default_label = self.label_hist[0]
else:
print("Not find:/data/predefined_classes.txt (optional)")
# Main widgets and related state.
self.label_dialog = LabelDialog(parent=self, list_item=self.label_hist)
self.items_to_shapes = {}
self.shapes_to_items = {}
self.prev_label_text = ''
list_layout = QVBoxLayout()
list_layout.setContentsMargins(0, 0, 0, 0)
# Create a widget for using default label
self.use_default_label_checkbox = QCheckBox(get_str('useDefaultLabel'))
self.use_default_label_checkbox.setChecked(False)
self.default_label_combo_box = DefaultLabelComboBox(self,items=self.label_hist)
use_default_label_qhbox_layout = QHBoxLayout()
use_default_label_qhbox_layout.addWidget(self.use_default_label_checkbox)
use_default_label_qhbox_layout.addWidget(self.default_label_combo_box)
use_default_label_container = QWidget()
use_default_label_container.setLayout(use_default_label_qhbox_layout)
# Create a widget for edit and diffc button
self.diffc_button = QCheckBox(get_str('useDifficult'))
self.diffc_button.setChecked(False)
self.diffc_button.stateChanged.connect(self.button_state)
self.edit_button = QToolButton()
self.edit_button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
# Add some of widgets to list_layout
list_layout.addWidget(self.edit_button)
list_layout.addWidget(self.diffc_button)
list_layout.addWidget(use_default_label_container)
# Create and add combobox for showing unique labels in group
self.combo_box = ComboBox(self)
list_layout.addWidget(self.combo_box)
# Create and add a widget for showing current label items
self.label_list = QListWidget()
label_list_container = QWidget()
label_list_container.setLayout(list_layout)
self.label_list.itemActivated.connect(self.label_selection_changed)
self.label_list.itemSelectionChanged.connect(self.label_selection_changed)
self.label_list.itemDoubleClicked.connect(self.edit_label)
# Connect to itemChanged to detect checkbox changes.
self.label_list.itemChanged.connect(self.label_item_changed)
list_layout.addWidget(self.label_list)
self.dock = QDockWidget(get_str('boxLabelText'), self)
self.dock.setObjectName(get_str('labels'))
self.dock.setWidget(label_list_container)
self.file_list_widget = QListWidget()
self.file_list_widget.itemDoubleClicked.connect(self.file_item_double_clicked)
file_list_layout = QVBoxLayout()
file_list_layout.setContentsMargins(0, 0, 0, 0)
file_list_layout.addWidget(self.file_list_widget)
file_list_container = QWidget()
file_list_container.setLayout(file_list_layout)
self.file_dock = QDockWidget(get_str('fileList'), self)
self.file_dock.setObjectName(get_str('files'))
self.file_dock.setWidget(file_list_container)
self.zoom_widget = ZoomWidget()
self.light_widget = LightWidget(get_str('lightWidgetTitle'))
self.color_dialog = ColorDialog(parent=self)
self.canvas = Canvas(parent=self)
self.canvas.zoomRequest.connect(self.zoom_request)
self.canvas.lightRequest.connect(self.light_request)
self.canvas.set_drawing_shape_to_square(settings.get(SETTING_DRAW_SQUARE, False))
scroll = QScrollArea()
scroll.setWidget(self.canvas)
scroll.setWidgetResizable(True)
self.scroll_bars = {
Qt.Vertical: scroll.verticalScrollBar(),
Qt.Horizontal: scroll.horizontalScrollBar()
}
self.scroll_area = scroll
self.canvas.scrollRequest.connect(self.scroll_request)
self.canvas.newShape.connect(self.new_shape)
self.canvas.shapeMoved.connect(self.set_dirty)
self.canvas.selectionChanged.connect(self.shape_selection_changed)
self.canvas.drawingPolygon.connect(self.toggle_drawing_sensitive)
self.setCentralWidget(scroll)
self.addDockWidget(Qt.RightDockWidgetArea, self.dock)
self.addDockWidget(Qt.RightDockWidgetArea, self.file_dock)
self.file_dock.setFeatures(QDockWidget.DockWidgetFloatable)
self.dock_features = QDockWidget.DockWidgetClosable | QDockWidget.DockWidgetFloatable
self.dock.setFeatures(self.dock.features() ^ self.dock_features)
# Actions
action = partial(new_action, self)
quit = action(get_str('quit'), self.close,
'Ctrl+Q', 'quit', get_str('quitApp'))
open = action(get_str('openFile'), self.open_file,
'Ctrl+O', 'open', get_str('openFileDetail'))
open_dir = action(get_str('openDir'), self.open_dir_dialog,
'Ctrl+u', 'open', get_str('openDir'))
change_save_dir = action(get_str('changeSaveDir'), self.change_save_dir_dialog,
'Ctrl+r', 'open', get_str('changeSavedAnnotationDir'))
open_annotation = action(get_str('openAnnotation'), self.open_annotation_dialog,
'Ctrl+Shift+O', 'open', get_str('openAnnotationDetail'))
copy_prev_bounding = action(get_str('copyPrevBounding'), self.copy_previous_bounding_boxes, 'Ctrl+v', 'copy', get_str('copyPrevBounding'))
open_next_image = action(get_str('nextImg'), self.open_next_image,
'd', 'next', get_str('nextImgDetail'))
open_prev_image = action(get_str('prevImg'), self.open_prev_image,
'a', 'prev', get_str('prevImgDetail'))
verify = action(get_str('verifyImg'), self.verify_image,
'space', 'verify', get_str('verifyImgDetail'))
save = action(get_str('save'), self.save_file,
'Ctrl+S', 'save', get_str('saveDetail'), enabled=False)
def get_format_meta(format):
"""
returns a tuple containing (title, icon_name) of the selected format
"""
if format == LabelFileFormat.PASCAL_VOC:
return '&PascalVOC', 'format_voc'
elif format == LabelFileFormat.YOLO:
return '&YOLO', 'format_yolo'
elif format == LabelFileFormat.CREATE_ML:
return '&CreateML', 'format_createml'
save_format = action(get_format_meta(self.label_file_format)[0],
self.change_format, 'Ctrl+Y',
get_format_meta(self.label_file_format)[1],
get_str('changeSaveFormat'), enabled=True)
save_as = action(get_str('saveAs'), self.save_file_as,
'Ctrl+Shift+S', 'save-as', get_str('saveAsDetail'), enabled=False)
close = action(get_str('closeCur'), self.close_file, 'Ctrl+W', 'close', get_str('closeCurDetail'))
delete_image = action(get_str('deleteImg'), self.delete_image, 'Ctrl+Shift+D', 'close', get_str('deleteImgDetail'))
reset_all = action(get_str('resetAll'), self.reset_all, None, 'resetall', get_str('resetAllDetail'))
color1 = action(get_str('boxLineColor'), self.choose_color1,
'Ctrl+L', 'color_line', get_str('boxLineColorDetail'))
create_mode = action(get_str('crtBox'), self.set_create_mode,
'w', 'new', get_str('crtBoxDetail'), enabled=False)
edit_mode = action(get_str('editBox'), self.set_edit_mode,
'Ctrl+J', 'edit', get_str('editBoxDetail'), enabled=False)
c