├── .gitignore ├── .travis.yml ├── LICENSE ├── MANIFEST.in ├── Makefile ├── __init__.py ├── build-tools ├── .gitignore ├── README.md ├── build-for-macos.sh ├── build-for-pypi.sh ├── build-ubuntu-binary.sh ├── build-windows-binary.sh ├── envsetup.sh └── run-in-container.sh ├── data ├── origin.xml ├── predefined_classes.txt └── template.xml ├── demo └── demo.png ├── labelImg.py ├── libs ├── __init__.py ├── canvas.py ├── colorDialog.py ├── combobox.py ├── constants.py ├── hashableQListWidgetItem.py ├── labelDialog.py ├── labelFile.py ├── pascal_voc_io.py ├── resources.py ├── settings.py ├── shape.py ├── stringBundle.py ├── toolBar.py ├── ustr.py ├── utils.py ├── yolo_io.py └── zoomWidget.py ├── pytorch_yolov5 ├── models │ ├── __init__.py │ ├── common.py │ ├── experimental.py │ ├── export.py │ ├── hub │ │ ├── yolov3-spp.yaml │ │ ├── yolov5-fpn.yaml │ │ └── yolov5-panet.yaml │ ├── yolo.py │ ├── yolov5l.yaml │ ├── yolov5m.yaml │ ├── yolov5s.yaml │ └── yolov5x.yaml ├── utils │ ├── __init__.py │ ├── activations.py │ ├── datasets.py │ ├── google_utils.py │ ├── post_process.py │ ├── torch_utils.py │ └── utils.py └── weights │ └── download_weights.sh ├── readme.md ├── readme_CN.md ├── requirements.txt ├── resources.qrc ├── resources ├── icons │ ├── app.icns │ ├── app.png │ ├── app.svg │ ├── cancel.png │ ├── close.png │ ├── color.png │ ├── color_line.png │ ├── copy.png │ ├── delete.png │ ├── done.png │ ├── done.svg │ ├── edit.png │ ├── expert1.png │ ├── expert2.png │ ├── eye.png │ ├── feBlend-icon.png │ ├── file.png │ ├── fit-width.png │ ├── fit-window.png │ ├── fit.png │ ├── format_voc.png │ ├── format_yolo.png │ ├── help.png │ ├── labels.png │ ├── labels.svg │ ├── new.png │ ├── next.png │ ├── objects.png │ ├── open.png │ ├── open.svg │ ├── prev.png │ ├── quit.png │ ├── resetall.png │ ├── save-as.png │ ├── save-as.svg │ ├── save.png │ ├── save.svg │ ├── undo-cross.png │ ├── undo.png │ ├── verify.png │ ├── zoom-in.png │ ├── zoom-out.png │ └── zoom.png └── strings │ ├── strings-zh-CN.properties │ ├── strings-zh-TW.properties │ └── strings.properties ├── setup.cfg ├── setup.py └── tests ├── .gitignore ├── test.512.512.bmp ├── test_io.py ├── test_qt.py ├── test_settings.py ├── test_stringBundle.py ├── test_utils.py └── 臉書.jpg /.gitignore: -------------------------------------------------------------------------------- 1 | resources/icons/.DS_Store 2 | 3 | .idea* 4 | labelImg.egg-info* 5 | 6 | *.pyc 7 | .*.swp 8 | 9 | build/ 10 | dist/ 11 | 12 | tags 13 | cscope* 14 | .ycm_extra_conf.py 15 | .subvimrc 16 | .vscode 17 | *.pkl 18 | *.bat 19 | *.h5 20 | *.pt 21 | *.pth 22 | *.git 23 | temp* 24 | 25 | *.mp4 26 | *.jpg -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # vim: set ts=2 et: 2 | 3 | # run xvfb with 32-bit color 4 | # xvfb-run -s '-screen 0 1600x1200x24+32' command_goes_here 5 | 6 | matrix: 7 | include: 8 | 9 | 10 | # Python 2.7 + QT4 11 | - os: linux 12 | dist: trusty 13 | sudo: required 14 | language: generic 15 | python: "2.7" 16 | env: 17 | - QT=4 18 | - CONDA=4.2.0 19 | addons: 20 | apt: 21 | packages: 22 | - cmake 23 | #- python-qt4 24 | #- pyqt4-dev-tools 25 | - xvfb 26 | before_install: 27 | # ref: https://www.continuum.io/downloads 28 | - curl -O https://repo.continuum.io/archive/Anaconda2-4.2.0-Linux-x86_64.sh 29 | # ref: http://conda.pydata.org/docs/help/silent.html 30 | - /bin/bash Anaconda2-4.2.0-Linux-x86_64.sh -b -p $HOME/anaconda2 31 | - export PATH="$HOME/anaconda2/bin:$PATH" 32 | # ref: http://stackoverflow.com/questions/21637922/how-to-install-pyqt4-in-anaconda 33 | - conda create -y -n labelImg-py2qt4 python=2.7 34 | - source activate labelImg-py2qt4 35 | - conda install -y pyqt=4 36 | - conda install -y lxml 37 | - make qt4py2 38 | - xvfb-run make testpy2 39 | 40 | 41 | # Python 3 + QT5 42 | - os: linux 43 | dist: trusty 44 | sudo: required 45 | language: generic 46 | python: "3.5" 47 | env: 48 | - QT=5 49 | - CONDA=4.2.0 50 | addons: 51 | apt: 52 | packages: 53 | - cmake 54 | - xvfb 55 | before_install: 56 | # ref: https://www.continuum.io/downloads 57 | - curl -O https://repo.continuum.io/archive/Anaconda3-4.2.0-Linux-x86_64.sh 58 | # ref: http://conda.pydata.org/docs/help/silent.html 59 | - /bin/bash Anaconda3-4.2.0-Linux-x86_64.sh -b -p $HOME/anaconda3 60 | - export PATH="$HOME/anaconda3/bin:$PATH" 61 | # ref: http://stackoverflow.com/questions/21637922/how-to-install-pyqt4-in-anaconda 62 | - conda create -y -n labelImg-py3qt5 python=3.5 63 | - source activate labelImg-py3qt5 64 | - conda install -y pyqt=5 65 | - conda install -y lxml 66 | - make qt5py3 67 | - xvfb-run make testpy3 68 | 69 | # Pipenv Python 3 + QT5 - Build .app 70 | - os: osx 71 | language: generic 72 | python: "3.7" 73 | env: 74 | - PIPENV_VENV_IN_PROJECT=1 75 | - PIPENV_IGNORE_VIRTUALENVS=1 76 | install: 77 | - pip3 install pipenv 78 | - pipenv install pyqt5 lxml 79 | - pipenv run pip install pyqt5==5.13.2 lxml 80 | - pipenv run make qt5py3 81 | - rm -rf build dist 82 | - pipenv run python setup.py py2app 83 | - open dist/labelImg.app 84 | 85 | script: 86 | - exit 0 87 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) <2015-Present> Tzutalin 2 | 3 | Copyright (C) 2013 MIT, Computer Science and Artificial Intelligence Laboratory. Bryan Russell, Antonio Torralba, William T. Freeman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include CONTRIBUTING.rst 2 | include HISTORY.rst 3 | include LICENSE 4 | include README.rst 5 | 6 | include resources.qrc 7 | 8 | recursive-include data * 9 | recursive-include icons * 10 | recursive-include libs * 11 | 12 | recursive-exclude build-tools * 13 | recursive-exclude tests * 14 | recursive-exclude * __pycache__ 15 | recursive-exclude * *.py[co] 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # ex: set ts=8 noet: 2 | 3 | all: qt5 test 4 | 5 | test: testpy3 6 | 7 | testpy2: 8 | python -m unittest discover tests 9 | 10 | testpy3: 11 | python3 -m unittest discover tests 12 | 13 | qt4: qt4py2 14 | 15 | qt5: qt5py3 16 | 17 | qt4py2: 18 | pyrcc4 -py2 -o libs/resources.py resources.qrc 19 | 20 | qt4py3: 21 | pyrcc4 -py3 -o libs/resources.py resources.qrc 22 | 23 | qt5py3: 24 | pyrcc5 -o libs/resources.py resources.qrc 25 | 26 | clean: 27 | rm -rf ~/.labelImgSettings.pkl *.pyc dist labelImg.egg-info __pycache__ build 28 | 29 | pip_upload: 30 | python3 setup.py upload 31 | 32 | long_description: 33 | restview --long-description 34 | 35 | .PHONY: all 36 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wufan-tb/AutoLabelImg/6bc97b6c19e9a471b427d3f442f45c594ce45ff6/__init__.py -------------------------------------------------------------------------------- /build-tools/.gitignore: -------------------------------------------------------------------------------- 1 | *.spec 2 | build 3 | dist 4 | pyinstaller 5 | python-2.* 6 | pywin32* 7 | virtual-wine 8 | venv_wine 9 | PyQt4-* 10 | lxml-* 11 | windows_v* 12 | linux_v* 13 | -------------------------------------------------------------------------------- /build-tools/README.md: -------------------------------------------------------------------------------- 1 | ### Deploy to PyPI 2 | 3 | ``` 4 | cd [ROOT] 5 | sh build-tools/build-for-pypi.sh 6 | ``` 7 | 8 | ### Build for Ubuntu 9 | 10 | ``` 11 | cd build-tools 12 | sh run-in-container.sh 13 | sh envsetup.sh 14 | sh build-ubuntu-binary.sh 15 | ``` 16 | 17 | ### Build for Windows 18 | 19 | ``` 20 | cd build-tools 21 | sh run-in-container.sh 22 | sh envsetup.sh 23 | sh build-windows-binary.sh 24 | ``` 25 | 26 | ### Build for macOS High Sierra 27 | ``` 28 | cd build-tools 29 | ./build-for-macos.sh 30 | ``` 31 | 32 | Note: If there are some problems, try to 33 | ``` 34 | sudo rm -rf virtual-wne venv_wine 35 | ``` 36 | -------------------------------------------------------------------------------- /build-tools/build-for-macos.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | brew install python@2 4 | pip install --upgrade virtualenv 5 | 6 | # clone labelimg source 7 | rm -rf /tmp/labelImgSetup 8 | mkdir /tmp/labelImgSetup 9 | cd /tmp/labelImgSetup 10 | curl https://codeload.github.com/tzutalin/labelImg/zip/master --output labelImg.zip 11 | unzip labelImg.zip 12 | rm labelImg.zip 13 | 14 | # setup python3 space 15 | virtualenv --system-site-packages -p python3 /tmp/labelImgSetup/labelImg-py3 16 | source /tmp/labelImgSetup/labelImg-py3/bin/activate 17 | cd labelImg-master 18 | 19 | # build labelImg app 20 | pip install py2app 21 | pip install PyQt5 lxml 22 | make qt5py3 23 | rm -rf build dist 24 | python setup.py py2app -A 25 | mv "/tmp/labelImgSetup/labelImg-master/dist/labelImg.app" /Applications 26 | # deactivate python3 27 | deactivate 28 | cd ../ 29 | rm -rf /tmp/labelImgSetup 30 | echo 'DONE' 31 | -------------------------------------------------------------------------------- /build-tools/build-for-pypi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Packaging and Release 3 | docker run --workdir=$(pwd)/ --volume="/home/$USER:/home/$USER" tzutalin/py2qt4 /bin/sh -c 'make qt4py2; make test;sudo python setup.py sdist;sudo python setup.py install' 4 | 5 | while true; do 6 | read -p "Do you wish to deploy this to PyPI(twine upload dist/* or pip install dist/*)?" yn 7 | case $yn in 8 | [Yy]* ) docker run -it --rm --workdir=$(pwd)/ --volume="/home/$USER:/home/$USER" tzutalin/py2qt4; break;; 9 | [Nn]* ) exit;; 10 | * ) echo "Please answer yes or no.";; 11 | esac 12 | done 13 | # python setup.py register 14 | # python setup.py sdist upload 15 | # Net pypi: twine upload dist/* 16 | 17 | # Test before upladoing: pip install dist/labelImg.tar.gz 18 | -------------------------------------------------------------------------------- /build-tools/build-ubuntu-binary.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ### Ubuntu use pyinstall v3.0 3 | THIS_SCRIPT_PATH=`readlink -f $0` 4 | THIS_SCRIPT_DIR=`dirname ${THIS_SCRIPT_PATH}` 5 | cd pyinstaller 6 | git checkout v3.2 7 | cd ${THIS_SCRIPT_DIR} 8 | 9 | rm -r build 10 | rm -r dist 11 | rm labelImg.spec 12 | python pyinstaller/pyinstaller.py --hidden-import=xml \ 13 | --hidden-import=xml.etree \ 14 | --hidden-import=xml.etree.ElementTree \ 15 | --hidden-import=lxml.etree \ 16 | -D -F -n labelImg -c "../labelImg.py" -p ../libs -p ../ 17 | 18 | FOLDER=$(git describe --abbrev=0 --tags) 19 | FOLDER="linux_"$FOLDER 20 | rm -rf "$FOLDER" 21 | mkdir "$FOLDER" 22 | cp dist/labelImg $FOLDER 23 | cp -rf ../data $FOLDER/data 24 | zip "$FOLDER.zip" -r $FOLDER 25 | -------------------------------------------------------------------------------- /build-tools/build-windows-binary.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ### Window requires pyinstall v2.1 3 | wine msiexec -i python-2.7.8.msi 4 | wine pywin32-218.win32-py2.7.exe 5 | wine PyQt4-4.11.4-gpl-Py2.7-Qt4.8.7-x32.exe 6 | wine lxml-3.7.3.win32-py2.7.exe 7 | 8 | THIS_SCRIPT_PATH=`readlink -f $0` 9 | THIS_SCRIPT_DIR=`dirname ${THIS_SCRIPT_PATH}` 10 | cd pyinstaller 11 | git checkout v2.1 12 | cd ${THIS_SCRIPT_DIR} 13 | echo ${THIS_SCRIPT_DIR} 14 | 15 | #. venv_wine/bin/activate 16 | rm -r build 17 | rm -r dist 18 | rm labelImg.spec 19 | 20 | wine c:/Python27/python.exe pyinstaller/pyinstaller.py --hidden-import=xml \ 21 | --hidden-import=xml.etree \ 22 | --hidden-import=xml.etree.ElementTree \ 23 | --hidden-import=lxml.etree \ 24 | -D -F -n labelImg -c "../labelImg.py" -p ../libs -p ../ 25 | 26 | FOLDER=$(git describe --abbrev=0 --tags) 27 | FOLDER="windows_"$FOLDER 28 | rm -rf "$FOLDER" 29 | mkdir "$FOLDER" 30 | cp dist/labelImg.exe $FOLDER 31 | cp -rf ../data $FOLDER/data 32 | zip "$FOLDER.zip" -r $FOLDER 33 | -------------------------------------------------------------------------------- /build-tools/envsetup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | THIS_SCRIPT_PATH=`readlink -f $0` 4 | THIS_SCRIPT_DIR=`dirname ${THIS_SCRIPT_PATH}` 5 | #OS Ubuntu 14.04 6 | ### Common packages for linux/windows 7 | if [ ! -e "pyinstaller" ]; then 8 | git clone https://github.com/pyinstaller/pyinstaller 9 | cd pyinstaller 10 | git checkout v2.1 -b v2.1 11 | cd ${THIS_SCRIPT_DIR} 12 | fi 13 | 14 | echo "Going to clone and download packages for building windows" 15 | #Pacakges 16 | #> pyinstaller (2.1) 17 | #> wine (1.6.2) 18 | #> virtual-wine (0.1) 19 | #> python-2.7.8.msi 20 | #> pywin32-218.win32-py2.7.exe 21 | 22 | ## tool to install on Ubuntu 23 | #$ sudo apt-get install wine 24 | 25 | ### Clone a repo to create virtual wine env 26 | if [ ! -e "virtual-wine" ]; then 27 | git clone https://github.com/htgoebel/virtual-wine.git 28 | fi 29 | 30 | apt-get install scons 31 | ### Create virtual env 32 | rm -rf venv_wine 33 | ./virtual-wine/vwine-setup venv_wine 34 | #### Active virutal env 35 | . venv_wine/bin/activate 36 | 37 | ### Use wine to install packages to virtual env 38 | if [ ! -e "python-2.7.8.msi" ]; then 39 | wget "https://www.python.org/ftp/python/2.7.8/python-2.7.8.msi" 40 | fi 41 | 42 | if [ ! -e "pywin32-218.win32-py2.7.exe" ]; then 43 | wget "http://nchc.dl.sourceforge.net/project/pywin32/pywin32/Build%20218/pywin32-218.win32-py2.7.exe" 44 | fi 45 | 46 | if [ ! -e "PyQt4-4.11.4-gpl-Py2.7-Qt4.8.7-x32.exe" ]; then 47 | wget "http://nchc.dl.sourceforge.net/project/pyqt/PyQt4/PyQt-4.11.4/PyQt4-4.11.4-gpl-Py2.7-Qt4.8.7-x32.exe" 48 | fi 49 | 50 | if [ ! -e "lxml-3.7.3.win32-py2.7.exe" ]; then 51 | wget "https://pypi.python.org/packages/a3/f6/a28c5cf63873f6c55a3eb7857b736379229b85ba918261d2e88cf886905e/lxml-3.7.3.win32-py2.7.exe#md5=a0f746355876aca4ca5371cb0f1d13ce" 52 | fi 53 | 54 | -------------------------------------------------------------------------------- /build-tools/run-in-container.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | docker run -it \ 3 | --user $(id -u) \ 4 | -e DISPLAY=unix$DISPLAY \ 5 | --workdir=$(pwd) \ 6 | --volume="/home/$USER:/home/$USER" \ 7 | --volume="/etc/group:/etc/group:ro" \ 8 | --volume="/etc/passwd:/etc/passwd:ro" \ 9 | --volume="/etc/shadow:/etc/shadow:ro" \ 10 | --volume="/etc/sudoers.d:/etc/sudoers.d:ro" \ 11 | -v /tmp/.X11-unix:/tmp/.X11-unix \ 12 | tzutalin/py2qt4 13 | 14 | -------------------------------------------------------------------------------- /data/origin.xml: -------------------------------------------------------------------------------- 1 | 2 | img 3 | bclj_137.jpg 4 | D:\_Learning\_mycode\VOC_Data\img\bclj_137.jpg 5 | 6 | Unknown 7 | 8 | 9 | 2560 10 | 1440 11 | 3 12 | 13 | 0 14 | 15 | person 16 | Unspecified 17 | 0 18 | 0 19 | 20 | 780 21 | 266 22 | 898 23 | 620 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /data/predefined_classes.txt: -------------------------------------------------------------------------------- 1 | person 2 | car 3 | motorbike 4 | bicycle 5 | bus 6 | truck 7 | -------------------------------------------------------------------------------- /data/template.xml: -------------------------------------------------------------------------------- 1 | 2 | img 3 | bclj_137.jpg 4 | D:\_Learning\_mycode\VOC_Data\img\bclj_137.jpg 5 | 6 | Unknown 7 | 8 | 9 | 2560 10 | 1440 11 | 3 12 | 13 | 0 14 | -------------------------------------------------------------------------------- /demo/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wufan-tb/AutoLabelImg/6bc97b6c19e9a471b427d3f442f45c594ce45ff6/demo/demo.png -------------------------------------------------------------------------------- /libs/__init__.py: -------------------------------------------------------------------------------- 1 | __version_info__ = ('1', '8', '2') 2 | __version__ = '.'.join(__version_info__) 3 | -------------------------------------------------------------------------------- /libs/colorDialog.py: -------------------------------------------------------------------------------- 1 | try: 2 | from PyQt5.QtGui import * 3 | from PyQt5.QtCore import * 4 | from PyQt5.QtWidgets import QColorDialog, QDialogButtonBox 5 | except ImportError: 6 | from PyQt4.QtGui import * 7 | from PyQt4.QtCore import * 8 | 9 | BB = QDialogButtonBox 10 | 11 | 12 | class ColorDialog(QColorDialog): 13 | 14 | def __init__(self, parent=None): 15 | super(ColorDialog, self).__init__(parent) 16 | self.setOption(QColorDialog.ShowAlphaChannel) 17 | # The Mac native dialog does not support our restore button. 18 | self.setOption(QColorDialog.DontUseNativeDialog) 19 | # Add a restore defaults button. 20 | # The default is set at invocation time, so that it 21 | # works across dialogs for different elements. 22 | self.default = None 23 | self.bb = self.layout().itemAt(1).widget() 24 | self.bb.addButton(BB.RestoreDefaults) 25 | self.bb.clicked.connect(self.checkRestore) 26 | 27 | def getColor(self, value=None, title=None, default=None): 28 | self.default = default 29 | if title: 30 | self.setWindowTitle(title) 31 | if value: 32 | self.setCurrentColor(value) 33 | return self.currentColor() if self.exec_() else None 34 | 35 | def checkRestore(self, button): 36 | if self.bb.buttonRole(button) & BB.ResetRole and self.default: 37 | self.setCurrentColor(self.default) 38 | -------------------------------------------------------------------------------- /libs/combobox.py: -------------------------------------------------------------------------------- 1 | import sys 2 | try: 3 | from PyQt5.QtWidgets import QWidget, QHBoxLayout, QComboBox 4 | except ImportError: 5 | # needed for py3+qt4 6 | # Ref: 7 | # http://pyqt.sourceforge.net/Docs/PyQt4/incompatible_apis.html 8 | # http://stackoverflow.com/questions/21217399/pyqt4-qtcore-qvariant-object-instead-of-a-string 9 | if sys.version_info.major >= 3: 10 | import sip 11 | sip.setapi('QVariant', 2) 12 | from PyQt4.QtGui import QWidget, QHBoxLayout, QComboBox 13 | 14 | 15 | class ComboBox(QWidget): 16 | def __init__(self, parent=None, items=['']): 17 | super(ComboBox, self).__init__(parent) 18 | 19 | layout = QHBoxLayout() 20 | self.cb = QComboBox() 21 | self.items = items 22 | self.cb.addItems(self.items) 23 | 24 | self.cb.currentIndexChanged.connect(parent.comboSelectionChanged) 25 | 26 | layout.addWidget(self.cb) 27 | self.setLayout(layout) 28 | 29 | def update_items(self, items): 30 | self.items = items 31 | 32 | self.cb.clear() 33 | self.cb.addItems(self.items) 34 | -------------------------------------------------------------------------------- /libs/constants.py: -------------------------------------------------------------------------------- 1 | SETTING_FILENAME = 'filename' 2 | SETTING_RECENT_FILES = 'recentFiles' 3 | SETTING_WIN_SIZE = 'window/size' 4 | SETTING_WIN_POSE = 'window/position' 5 | SETTING_WIN_GEOMETRY = 'window/geometry' 6 | SETTING_LINE_COLOR = 'line/color' 7 | SETTING_FILL_COLOR = 'fill/color' 8 | SETTING_ADVANCE_MODE = 'advanced' 9 | SETTING_WIN_STATE = 'window/state' 10 | SETTING_SAVE_DIR = 'savedir' 11 | SETTING_PAINT_LABEL = 'paintlabel' 12 | SETTING_LAST_OPEN_DIR = 'lastOpenDir' 13 | SETTING_AUTO_SAVE = 'autosave' 14 | SETTING_SINGLE_CLASS = 'singleclass' 15 | SETTING_Magnifying_Lens = "magnifyinglens" 16 | FORMAT_PASCALVOC = 'PascalVOC' 17 | FORMAT_YOLO = 'YOLO' 18 | SETTING_DRAW_SQUARE = 'draw/square' 19 | DEFAULT_ENCODING = 'utf-8' 20 | -------------------------------------------------------------------------------- /libs/hashableQListWidgetItem.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import sys 4 | try: 5 | from PyQt5.QtGui import * 6 | from PyQt5.QtCore import * 7 | from PyQt5.QtWidgets import * 8 | except ImportError: 9 | # needed for py3+qt4 10 | # Ref: 11 | # http://pyqt.sourceforge.net/Docs/PyQt4/incompatible_apis.html 12 | # http://stackoverflow.com/questions/21217399/pyqt4-qtcore-qvariant-object-instead-of-a-string 13 | if sys.version_info.major >= 3: 14 | import sip 15 | sip.setapi('QVariant', 2) 16 | from PyQt4.QtGui import * 17 | from PyQt4.QtCore import * 18 | 19 | # PyQt5: TypeError: unhashable type: 'QListWidgetItem' 20 | 21 | 22 | class HashableQListWidgetItem(QListWidgetItem): 23 | 24 | def __init__(self, *args): 25 | super(HashableQListWidgetItem, self).__init__(*args) 26 | 27 | def __hash__(self): 28 | return hash(id(self)) 29 | -------------------------------------------------------------------------------- /libs/labelDialog.py: -------------------------------------------------------------------------------- 1 | try: 2 | from PyQt5.QtGui import * 3 | from PyQt5.QtCore import * 4 | from PyQt5.QtWidgets import * 5 | except ImportError: 6 | from PyQt4.QtGui import * 7 | from PyQt4.QtCore import * 8 | 9 | from libs.utils import newIcon, labelValidator 10 | 11 | BB = QDialogButtonBox 12 | 13 | 14 | class LabelDialog(QDialog): 15 | 16 | def __init__(self, text="Enter object label", parent=None, listItem=None): 17 | super(LabelDialog, self).__init__(parent) 18 | 19 | self.edit = QLineEdit() 20 | self.edit.setText(text) 21 | self.edit.setValidator(labelValidator()) 22 | self.edit.editingFinished.connect(self.postProcess) 23 | 24 | model = QStringListModel() 25 | model.setStringList(listItem) 26 | completer = QCompleter() 27 | completer.setModel(model) 28 | self.edit.setCompleter(completer) 29 | 30 | layout = QVBoxLayout() 31 | layout.addWidget(self.edit) 32 | self.buttonBox = bb = BB(BB.Ok | BB.Cancel, Qt.Horizontal, self) 33 | bb.button(BB.Ok).setIcon(newIcon('done')) 34 | bb.button(BB.Cancel).setIcon(newIcon('undo')) 35 | bb.accepted.connect(self.validate) 36 | bb.rejected.connect(self.reject) 37 | layout.addWidget(bb) 38 | 39 | if listItem is not None and len(listItem) > 0: 40 | self.listWidget = QListWidget(self) 41 | for item in listItem: 42 | self.listWidget.addItem(item) 43 | self.listWidget.itemClicked.connect(self.listItemClick) 44 | self.listWidget.itemDoubleClicked.connect(self.listItemDoubleClick) 45 | layout.addWidget(self.listWidget) 46 | 47 | self.setLayout(layout) 48 | 49 | def validate(self): 50 | try: 51 | if self.edit.text().trimmed(): 52 | self.accept() 53 | except AttributeError: 54 | # PyQt5: AttributeError: 'str' object has no attribute 'trimmed' 55 | if self.edit.text().strip(): 56 | self.accept() 57 | 58 | def postProcess(self): 59 | try: 60 | self.edit.setText(self.edit.text().trimmed()) 61 | except AttributeError: 62 | # PyQt5: AttributeError: 'str' object has no attribute 'trimmed' 63 | self.edit.setText(self.edit.text()) 64 | 65 | def popUp(self, text='', move=True): 66 | self.edit.setText(text) 67 | self.edit.setSelection(0, len(text)) 68 | self.edit.setFocus(Qt.PopupFocusReason) 69 | if move: 70 | self.move(QCursor.pos()) 71 | return self.edit.text() if self.exec_() else None 72 | 73 | def listItemClick(self, tQListWidgetItem): 74 | try: 75 | text = tQListWidgetItem.text().trimmed() 76 | except AttributeError: 77 | # PyQt5: AttributeError: 'str' object has no attribute 'trimmed' 78 | text = tQListWidgetItem.text().strip() 79 | self.edit.setText(text) 80 | 81 | def listItemDoubleClick(self, tQListWidgetItem): 82 | self.listItemClick(tQListWidgetItem) 83 | self.validate() 84 | -------------------------------------------------------------------------------- /libs/labelFile.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Tzutalin 2 | # Create by TzuTaLin 3 | 4 | try: 5 | from PyQt5.QtGui import QImage 6 | except ImportError: 7 | from PyQt4.QtGui import QImage 8 | 9 | from base64 import b64encode, b64decode 10 | from libs.pascal_voc_io import PascalVocWriter 11 | from libs.yolo_io import YOLOWriter 12 | from libs.pascal_voc_io import XML_EXT 13 | import os.path 14 | import sys 15 | 16 | 17 | class LabelFileError(Exception): 18 | pass 19 | 20 | 21 | class LabelFile(object): 22 | # It might be changed as window creates. By default, using XML ext 23 | # suffix = '.lif' 24 | suffix = XML_EXT 25 | 26 | def __init__(self, filename=None): 27 | self.shapes = () 28 | self.imagePath = None 29 | self.imageData = None 30 | self.verified = False 31 | 32 | def savePascalVocFormat(self, filename, shapes, imagePath, imageData, 33 | lineColor=None, fillColor=None, databaseSrc=None): 34 | imgFolderPath = os.path.dirname(imagePath) 35 | imgFolderName = os.path.split(imgFolderPath)[-1] 36 | imgFileName = os.path.basename(imagePath) 37 | #imgFileNameWithoutExt = os.path.splitext(imgFileName)[0] 38 | # Read from file path because self.imageData might be empty if saving to 39 | # Pascal format 40 | image = QImage() 41 | image.load(imagePath) 42 | imageShape = [image.height(), image.width(), 43 | 1 if image.isGrayscale() else 3] 44 | writer = PascalVocWriter(imgFolderName, imgFileName, 45 | imageShape, localImgPath=imagePath) 46 | writer.verified = self.verified 47 | 48 | for shape in shapes: 49 | points = shape['points'] 50 | label = shape['label'] 51 | # Add Chris 52 | difficult = int(shape['difficult']) 53 | bndbox = LabelFile.convertPoints2BndBox(points) 54 | writer.addBndBox(bndbox[0], bndbox[1], bndbox[2], bndbox[3], label, difficult) 55 | 56 | writer.save(targetFile=filename) 57 | return 58 | 59 | def saveYoloFormat(self, filename, shapes, imagePath, imageData, classList, 60 | lineColor=None, fillColor=None, databaseSrc=None): 61 | imgFolderPath = os.path.dirname(imagePath) 62 | imgFolderName = os.path.split(imgFolderPath)[-1] 63 | imgFileName = os.path.basename(imagePath) 64 | #imgFileNameWithoutExt = os.path.splitext(imgFileName)[0] 65 | # Read from file path because self.imageData might be empty if saving to 66 | # Pascal format 67 | image = QImage() 68 | image.load(imagePath) 69 | imageShape = [image.height(), image.width(), 70 | 1 if image.isGrayscale() else 3] 71 | writer = YOLOWriter(imgFolderName, imgFileName, 72 | imageShape, localImgPath=imagePath) 73 | writer.verified = self.verified 74 | 75 | for shape in shapes: 76 | points = shape['points'] 77 | label = shape['label'] 78 | # Add Chris 79 | difficult = int(shape['difficult']) 80 | bndbox = LabelFile.convertPoints2BndBox(points) 81 | writer.addBndBox(bndbox[0], bndbox[1], bndbox[2], bndbox[3], label, difficult) 82 | 83 | writer.save(targetFile=filename, classList=classList) 84 | return 85 | 86 | def toggleVerify(self): 87 | self.verified = not self.verified 88 | 89 | ''' ttf is disable 90 | def load(self, filename): 91 | import json 92 | with open(filename, 'rb') as f: 93 | data = json.load(f) 94 | imagePath = data['imagePath'] 95 | imageData = b64decode(data['imageData']) 96 | lineColor = data['lineColor'] 97 | fillColor = data['fillColor'] 98 | shapes = ((s['label'], s['points'], s['line_color'], s['fill_color'])\ 99 | for s in data['shapes']) 100 | # Only replace data after everything is loaded. 101 | self.shapes = shapes 102 | self.imagePath = imagePath 103 | self.imageData = imageData 104 | self.lineColor = lineColor 105 | self.fillColor = fillColor 106 | 107 | def save(self, filename, shapes, imagePath, imageData, lineColor=None, fillColor=None): 108 | import json 109 | with open(filename, 'wb') as f: 110 | json.dump(dict( 111 | shapes=shapes, 112 | lineColor=lineColor, fillColor=fillColor, 113 | imagePath=imagePath, 114 | imageData=b64encode(imageData)), 115 | f, ensure_ascii=True, indent=2) 116 | ''' 117 | 118 | @staticmethod 119 | def isLabelFile(filename): 120 | fileSuffix = os.path.splitext(filename)[1].lower() 121 | return fileSuffix == LabelFile.suffix 122 | 123 | @staticmethod 124 | def convertPoints2BndBox(points): 125 | xmin = float('inf') 126 | ymin = float('inf') 127 | xmax = float('-inf') 128 | ymax = float('-inf') 129 | for p in points: 130 | x = p[0] 131 | y = p[1] 132 | xmin = min(x, xmin) 133 | ymin = min(y, ymin) 134 | xmax = max(x, xmax) 135 | ymax = max(y, ymax) 136 | 137 | # Martin Kersner, 2015/11/12 138 | # 0-valued coordinates of BB caused an error while 139 | # training faster-rcnn object detector. 140 | if xmin < 1: 141 | xmin = 1 142 | 143 | if ymin < 1: 144 | ymin = 1 145 | 146 | return (int(xmin), int(ymin), int(xmax), int(ymax)) 147 | -------------------------------------------------------------------------------- /libs/pascal_voc_io.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf8 -*- 3 | import sys 4 | from xml.etree import ElementTree 5 | from xml.etree.ElementTree import Element, SubElement 6 | from lxml import etree 7 | import codecs 8 | from libs.constants import DEFAULT_ENCODING 9 | from libs.ustr import ustr 10 | 11 | 12 | XML_EXT = '.xml' 13 | ENCODE_METHOD = DEFAULT_ENCODING 14 | 15 | class PascalVocWriter: 16 | 17 | def __init__(self, foldername, filename, imgSize,databaseSrc='Unknown', localImgPath=None): 18 | self.foldername = foldername 19 | self.filename = filename 20 | self.databaseSrc = databaseSrc 21 | self.imgSize = imgSize 22 | self.boxlist = [] 23 | self.localImgPath = localImgPath 24 | self.verified = False 25 | 26 | def prettify(self, elem): 27 | """ 28 | Return a pretty-printed XML string for the Element. 29 | """ 30 | rough_string = ElementTree.tostring(elem, 'utf8') 31 | root = etree.fromstring(rough_string) 32 | return etree.tostring(root, pretty_print=True, encoding=ENCODE_METHOD).replace(" ".encode(), "\t".encode()) 33 | # minidom does not support UTF-8 34 | '''reparsed = minidom.parseString(rough_string) 35 | return reparsed.toprettyxml(indent="\t", encoding=ENCODE_METHOD)''' 36 | 37 | def genXML(self): 38 | """ 39 | Return XML root 40 | """ 41 | # Check conditions 42 | if self.filename is None or \ 43 | self.foldername is None or \ 44 | self.imgSize is None: 45 | return None 46 | 47 | top = Element('annotation') 48 | if self.verified: 49 | top.set('verified', 'yes') 50 | 51 | folder = SubElement(top, 'folder') 52 | folder.text = self.foldername 53 | 54 | filename = SubElement(top, 'filename') 55 | filename.text = self.filename 56 | 57 | if self.localImgPath is not None: 58 | localImgPath = SubElement(top, 'path') 59 | localImgPath.text = self.localImgPath 60 | 61 | source = SubElement(top, 'source') 62 | database = SubElement(source, 'database') 63 | database.text = self.databaseSrc 64 | 65 | size_part = SubElement(top, 'size') 66 | width = SubElement(size_part, 'width') 67 | height = SubElement(size_part, 'height') 68 | depth = SubElement(size_part, 'depth') 69 | width.text = str(self.imgSize[1]) 70 | height.text = str(self.imgSize[0]) 71 | if len(self.imgSize) == 3: 72 | depth.text = str(self.imgSize[2]) 73 | else: 74 | depth.text = '1' 75 | 76 | segmented = SubElement(top, 'segmented') 77 | segmented.text = '0' 78 | return top 79 | 80 | def addBndBox(self, xmin, ymin, xmax, ymax, name, difficult): 81 | bndbox = {'xmin': xmin, 'ymin': ymin, 'xmax': xmax, 'ymax': ymax} 82 | bndbox['name'] = name 83 | bndbox['difficult'] = difficult 84 | self.boxlist.append(bndbox) 85 | 86 | def appendObjects(self, top): 87 | for each_object in self.boxlist: 88 | object_item = SubElement(top, 'object') 89 | name = SubElement(object_item, 'name') 90 | name.text = ustr(each_object['name']) 91 | pose = SubElement(object_item, 'pose') 92 | pose.text = "Unspecified" 93 | truncated = SubElement(object_item, 'truncated') 94 | if int(float(each_object['ymax'])) == int(float(self.imgSize[0])) or (int(float(each_object['ymin']))== 1): 95 | truncated.text = "1" # max == height or min 96 | elif (int(float(each_object['xmax']))==int(float(self.imgSize[1]))) or (int(float(each_object['xmin']))== 1): 97 | truncated.text = "1" # max == width or min 98 | else: 99 | truncated.text = "0" 100 | difficult = SubElement(object_item, 'difficult') 101 | difficult.text = str( bool(each_object['difficult']) & 1 ) 102 | bndbox = SubElement(object_item, 'bndbox') 103 | xmin = SubElement(bndbox, 'xmin') 104 | xmin.text = str(each_object['xmin']) 105 | ymin = SubElement(bndbox, 'ymin') 106 | ymin.text = str(each_object['ymin']) 107 | xmax = SubElement(bndbox, 'xmax') 108 | xmax.text = str(each_object['xmax']) 109 | ymax = SubElement(bndbox, 'ymax') 110 | ymax.text = str(each_object['ymax']) 111 | 112 | def save(self, targetFile=None): 113 | root = self.genXML() 114 | self.appendObjects(root) 115 | out_file = None 116 | if targetFile is None: 117 | out_file = codecs.open( 118 | self.filename + XML_EXT, 'w', encoding=ENCODE_METHOD) 119 | else: 120 | out_file = codecs.open(targetFile, 'w', encoding=ENCODE_METHOD) 121 | 122 | prettifyResult = self.prettify(root) 123 | out_file.write(prettifyResult.decode('utf8')) 124 | out_file.close() 125 | 126 | 127 | class PascalVocReader: 128 | 129 | def __init__(self, filepath): 130 | # shapes type: 131 | # [labbel, [(x1,y1), (x2,y2), (x3,y3), (x4,y4)], color, color, difficult] 132 | self.shapes = [] 133 | self.filepath = filepath 134 | self.verified = False 135 | try: 136 | self.parseXML() 137 | except: 138 | pass 139 | 140 | def getShapes(self): 141 | return self.shapes 142 | 143 | def addShape(self, label, bndbox, difficult): 144 | xmin = int(float(bndbox.find('xmin').text)) 145 | ymin = int(float(bndbox.find('ymin').text)) 146 | xmax = int(float(bndbox.find('xmax').text)) 147 | ymax = int(float(bndbox.find('ymax').text)) 148 | points = [(xmin, ymin), (xmax, ymin), (xmax, ymax), (xmin, ymax)] 149 | self.shapes.append((label, points, None, None, difficult)) 150 | 151 | def parseXML(self): 152 | assert self.filepath.endswith(XML_EXT), "Unsupport file format" 153 | parser = etree.XMLParser(encoding=ENCODE_METHOD) 154 | xmltree = ElementTree.parse(self.filepath, parser=parser).getroot() 155 | filename = xmltree.find('filename').text 156 | try: 157 | verified = xmltree.attrib['verified'] 158 | if verified == 'yes': 159 | self.verified = True 160 | except KeyError: 161 | self.verified = False 162 | 163 | for object_iter in xmltree.findall('object'): 164 | bndbox = object_iter.find("bndbox") 165 | label = object_iter.find('name').text 166 | # Add chris 167 | difficult = False 168 | if object_iter.find('difficult') is not None: 169 | difficult = bool(int(object_iter.find('difficult').text)) 170 | self.addShape(label, bndbox, difficult) 171 | return True 172 | -------------------------------------------------------------------------------- /libs/settings.py: -------------------------------------------------------------------------------- 1 | import pickle 2 | import os 3 | import sys 4 | 5 | 6 | class Settings(object): 7 | def __init__(self): 8 | # Be default, the home will be in the same folder as labelImg 9 | home = os.path.expanduser("~") 10 | self.data = {} 11 | self.path = os.path.join(home, '.labelImgSettings.pkl') 12 | 13 | def __setitem__(self, key, value): 14 | self.data[key] = value 15 | 16 | def __getitem__(self, key): 17 | return self.data[key] 18 | 19 | def get(self, key, default=None): 20 | if key in self.data: 21 | return self.data[key] 22 | return default 23 | 24 | def save(self): 25 | if self.path: 26 | with open(self.path, 'wb') as f: 27 | pickle.dump(self.data, f, pickle.HIGHEST_PROTOCOL) 28 | return True 29 | return False 30 | 31 | def load(self): 32 | try: 33 | if os.path.exists(self.path): 34 | with open(self.path, 'rb') as f: 35 | self.data = pickle.load(f) 36 | return True 37 | except: 38 | print('Loading setting failed') 39 | return False 40 | 41 | def reset(self): 42 | if os.path.exists(self.path): 43 | os.remove(self.path) 44 | print('Remove setting pkl file ${0}'.format(self.path)) 45 | self.data = {} 46 | self.path = None 47 | -------------------------------------------------------------------------------- /libs/shape.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | try: 6 | from PyQt5.QtGui import * 7 | from PyQt5.QtCore import * 8 | except ImportError: 9 | from PyQt4.QtGui import * 10 | from PyQt4.QtCore import * 11 | 12 | from libs.utils import distance 13 | import sys 14 | 15 | DEFAULT_LINE_COLOR = QColor(0, 255, 0, 128) 16 | DEFAULT_FILL_COLOR = QColor(255, 0, 0, 128) 17 | DEFAULT_SELECT_LINE_COLOR = QColor(255, 255, 255) 18 | DEFAULT_SELECT_FILL_COLOR = QColor(0, 128, 255, 155) 19 | DEFAULT_VERTEX_FILL_COLOR = QColor(0, 255, 0, 255) 20 | DEFAULT_HVERTEX_FILL_COLOR = QColor(255, 0, 0) 21 | MIN_Y_LABEL = 10 22 | 23 | 24 | class Shape(object): 25 | P_SQUARE, P_ROUND = range(2) 26 | 27 | MOVE_VERTEX, NEAR_VERTEX = range(2) 28 | 29 | # The following class variables influence the drawing 30 | # of _all_ shape objects. 31 | line_color = DEFAULT_LINE_COLOR 32 | fill_color = DEFAULT_FILL_COLOR 33 | select_line_color = DEFAULT_SELECT_LINE_COLOR 34 | select_fill_color = DEFAULT_SELECT_FILL_COLOR 35 | vertex_fill_color = DEFAULT_VERTEX_FILL_COLOR 36 | hvertex_fill_color = DEFAULT_HVERTEX_FILL_COLOR 37 | point_type = P_ROUND 38 | point_size = 8 39 | scale = 1.0 40 | 41 | def __init__(self, label=None, line_color=None, difficult=False, paintLabel=False): 42 | self.label = label 43 | self.points = [] 44 | self.fill = False 45 | self.selected = False 46 | self.difficult = difficult 47 | self.paintLabel = paintLabel 48 | 49 | self._highlightIndex = None 50 | self._highlightMode = self.NEAR_VERTEX 51 | self._highlightSettings = { 52 | self.NEAR_VERTEX: (4, self.P_ROUND), 53 | self.MOVE_VERTEX: (1.5, self.P_SQUARE), 54 | } 55 | 56 | self._closed = False 57 | 58 | if line_color is not None: 59 | # Override the class line_color attribute 60 | # with an object attribute. Currently this 61 | # is used for drawing the pending line a different color. 62 | self.line_color = line_color 63 | 64 | def close(self): 65 | self._closed = True 66 | 67 | def reachMaxPoints(self): 68 | if len(self.points) >= 4: 69 | return True 70 | return False 71 | 72 | def addPoint(self, point): 73 | if not self.reachMaxPoints(): 74 | self.points.append(point) 75 | 76 | def popPoint(self): 77 | if self.points: 78 | return self.points.pop() 79 | return None 80 | 81 | def isClosed(self): 82 | return self._closed 83 | 84 | def setOpen(self): 85 | self._closed = False 86 | 87 | def paint(self, painter): 88 | if self.points: 89 | color = self.select_line_color if self.selected else self.line_color 90 | pen = QPen(color) 91 | # Try using integer sizes for smoother drawing(?) 92 | pen.setWidth(max(1, int(round(2.0 / self.scale)))) 93 | painter.setPen(pen) 94 | 95 | line_path = QPainterPath() 96 | vrtx_path = QPainterPath() 97 | 98 | line_path.moveTo(self.points[0]) 99 | # Uncommenting the following line will draw 2 paths 100 | # for the 1st vertex, and make it non-filled, which 101 | # may be desirable. 102 | #self.drawVertex(vrtx_path, 0) 103 | 104 | for i, p in enumerate(self.points): 105 | line_path.lineTo(p) 106 | self.drawVertex(vrtx_path, i) 107 | if self.isClosed(): 108 | line_path.lineTo(self.points[0]) 109 | 110 | painter.drawPath(line_path) 111 | painter.drawPath(vrtx_path) 112 | painter.fillPath(vrtx_path, self.vertex_fill_color) 113 | 114 | # Draw text at the top-left 115 | if self.paintLabel: 116 | min_x = sys.maxsize 117 | min_y = sys.maxsize 118 | for point in self.points: 119 | min_x = min(min_x, point.x()) 120 | min_y = min(min_y, point.y()) 121 | if min_x != sys.maxsize and min_y != sys.maxsize: 122 | font = QFont() 123 | font.setPointSize(8) 124 | font.setBold(True) 125 | painter.setFont(font) 126 | if(self.label == None): 127 | self.label = "" 128 | if(min_y < MIN_Y_LABEL): 129 | min_y += MIN_Y_LABEL 130 | painter.drawText(min_x, min_y, self.label) 131 | 132 | if self.fill: 133 | color = self.select_fill_color if self.selected else self.fill_color 134 | painter.fillPath(line_path, color) 135 | 136 | def drawVertex(self, path, i): 137 | d = self.point_size / self.scale 138 | shape = self.point_type 139 | point = self.points[i] 140 | if i == self._highlightIndex: 141 | size, shape = self._highlightSettings[self._highlightMode] 142 | d *= size 143 | if self._highlightIndex is not None: 144 | self.vertex_fill_color = self.hvertex_fill_color 145 | else: 146 | self.vertex_fill_color = Shape.vertex_fill_color 147 | if shape == self.P_SQUARE: 148 | path.addRect(point.x() - d / 2, point.y() - d / 2, d, d) 149 | elif shape == self.P_ROUND: 150 | path.addEllipse(point, d / 2.0, d / 2.0) 151 | else: 152 | assert False, "unsupported vertex shape" 153 | 154 | def nearestVertex(self, point, epsilon): 155 | for i, p in enumerate(self.points): 156 | if distance(p - point) <= epsilon: 157 | return i 158 | return None 159 | 160 | def containsPoint(self, point): 161 | return self.makePath().contains(point) 162 | 163 | def makePath(self): 164 | path = QPainterPath(self.points[0]) 165 | for p in self.points[1:]: 166 | path.lineTo(p) 167 | return path 168 | 169 | def boundingRect(self): 170 | return self.makePath().boundingRect() 171 | 172 | def moveBy(self, offset): 173 | self.points = [p + offset for p in self.points] 174 | 175 | def moveVertexBy(self, i, offset): 176 | self.points[i] = self.points[i] + offset 177 | 178 | def highlightVertex(self, i, action): 179 | self._highlightIndex = i 180 | self._highlightMode = action 181 | 182 | def highlightClear(self): 183 | self._highlightIndex = None 184 | 185 | def copy(self): 186 | shape = Shape("%s" % self.label) 187 | shape.points = [p for p in self.points] 188 | shape.fill = self.fill 189 | shape.selected = self.selected 190 | shape._closed = self._closed 191 | if self.line_color != Shape.line_color: 192 | shape.line_color = self.line_color 193 | if self.fill_color != Shape.fill_color: 194 | shape.fill_color = self.fill_color 195 | shape.difficult = self.difficult 196 | return shape 197 | 198 | def __len__(self): 199 | return len(self.points) 200 | 201 | def __getitem__(self, key): 202 | return self.points[key] 203 | 204 | def __setitem__(self, key, value): 205 | self.points[key] = value 206 | -------------------------------------------------------------------------------- /libs/stringBundle.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import re 4 | import os 5 | import sys 6 | import locale 7 | from libs.ustr import ustr 8 | 9 | try: 10 | from PyQt5.QtCore import * 11 | except ImportError: 12 | if sys.version_info.major >= 3: 13 | import sip 14 | sip.setapi('QVariant', 2) 15 | from PyQt4.QtCore import * 16 | 17 | 18 | class StringBundle: 19 | 20 | __create_key = object() 21 | 22 | def __init__(self, create_key, localeStr): 23 | assert(create_key == StringBundle.__create_key), "StringBundle must be created using StringBundle.getBundle" 24 | self.idToMessage = {} 25 | paths = self.__createLookupFallbackList(localeStr) 26 | for path in paths: 27 | self.__loadBundle(path) 28 | 29 | @classmethod 30 | def getBundle(cls, localeStr=None): 31 | if localeStr is None: 32 | try: 33 | localeStr = locale.getlocale()[0] if locale.getlocale() and len( 34 | locale.getlocale()) > 0 else os.getenv('LANG') 35 | except: 36 | print('Invalid locale') 37 | localeStr = 'en' 38 | 39 | return StringBundle(cls.__create_key, localeStr) 40 | 41 | def getString(self, stringId): 42 | assert(stringId in self.idToMessage), "Missing string id : " + stringId 43 | return self.idToMessage[stringId] 44 | 45 | def __createLookupFallbackList(self, localeStr): 46 | resultPaths = [] 47 | basePath = ":/strings" 48 | resultPaths.append(basePath) 49 | if localeStr is not None: 50 | # Don't follow standard BCP47. Simple fallback 51 | tags = re.split('[^a-zA-Z]', localeStr) 52 | for tag in tags: 53 | lastPath = resultPaths[-1] 54 | resultPaths.append(lastPath + '-' + tag) 55 | 56 | return resultPaths 57 | 58 | def __loadBundle(self, path): 59 | PROP_SEPERATOR = '=' 60 | f = QFile(path) 61 | if f.exists(): 62 | if f.open(QIODevice.ReadOnly | QFile.Text): 63 | text = QTextStream(f) 64 | text.setCodec("UTF-8") 65 | 66 | while not text.atEnd(): 67 | line = ustr(text.readLine()) 68 | key_value = line.split(PROP_SEPERATOR) 69 | key = key_value[0].strip() 70 | value = PROP_SEPERATOR.join(key_value[1:]).strip().strip('"') 71 | self.idToMessage[key] = value 72 | 73 | f.close() 74 | -------------------------------------------------------------------------------- /libs/toolBar.py: -------------------------------------------------------------------------------- 1 | try: 2 | from PyQt5.QtGui import * 3 | from PyQt5.QtCore import * 4 | from PyQt5.QtWidgets import * 5 | except ImportError: 6 | from PyQt4.QtGui import * 7 | from PyQt4.QtCore import * 8 | 9 | 10 | class ToolBar(QToolBar): 11 | 12 | def __init__(self, title): 13 | super(ToolBar, self).__init__(title) 14 | layout = self.layout() 15 | m = (0, 0, 0, 0) 16 | layout.setSpacing(0) 17 | layout.setContentsMargins(*m) 18 | self.setContentsMargins(*m) 19 | self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint) 20 | 21 | def addAction(self, action): 22 | if isinstance(action, QWidgetAction): 23 | return super(ToolBar, self).addAction(action) 24 | btn = ToolButton() 25 | btn.setDefaultAction(action) 26 | btn.setToolButtonStyle(self.toolButtonStyle()) 27 | self.addWidget(btn) 28 | 29 | 30 | class ToolButton(QToolButton): 31 | """ToolBar companion class which ensures all buttons have the same size.""" 32 | minSize = (60, 60) 33 | 34 | def minimumSizeHint(self): 35 | ms = super(ToolButton, self).minimumSizeHint() 36 | w1, h1 = ms.width(), ms.height() 37 | w2, h2 = self.minSize 38 | ToolButton.minSize = max(w1, w2), max(h1, h2) 39 | return QSize(*ToolButton.minSize) 40 | -------------------------------------------------------------------------------- /libs/ustr.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from libs.constants import DEFAULT_ENCODING 3 | 4 | def ustr(x): 5 | '''py2/py3 unicode helper''' 6 | 7 | if sys.version_info < (3, 0, 0): 8 | from PyQt4.QtCore import QString 9 | if type(x) == str: 10 | return x.decode(DEFAULT_ENCODING) 11 | if type(x) == QString: 12 | #https://blog.csdn.net/friendan/article/details/51088476 13 | #https://blog.csdn.net/xxm524/article/details/74937308 14 | return unicode(x.toUtf8(), DEFAULT_ENCODING, 'ignore') 15 | return x 16 | else: 17 | return x 18 | -------------------------------------------------------------------------------- /libs/utils.py: -------------------------------------------------------------------------------- 1 | from math import sqrt 2 | from libs.ustr import ustr 3 | import hashlib 4 | import re 5 | import sys 6 | 7 | try: 8 | from PyQt5.QtGui import * 9 | from PyQt5.QtCore import * 10 | from PyQt5.QtWidgets import * 11 | except ImportError: 12 | from PyQt4.QtGui import * 13 | from PyQt4.QtCore import * 14 | 15 | 16 | def newIcon(icon): 17 | return QIcon(':/' + icon) 18 | 19 | 20 | def newButton(text, icon=None, slot=None): 21 | b = QPushButton(text) 22 | if icon is not None: 23 | b.setIcon(newIcon(icon)) 24 | if slot is not None: 25 | b.clicked.connect(slot) 26 | return b 27 | 28 | 29 | def newAction(parent, text, slot=None, shortcut=None, icon=None, 30 | tip=None, checkable=False, enabled=True): 31 | """Create a new action and assign callbacks, shortcuts, etc.""" 32 | a = QAction(text, parent) 33 | if icon is not None: 34 | a.setIcon(newIcon(icon)) 35 | if shortcut is not None: 36 | if isinstance(shortcut, (list, tuple)): 37 | a.setShortcuts(shortcut) 38 | else: 39 | a.setShortcut(shortcut) 40 | if tip is not None: 41 | a.setToolTip(tip) 42 | a.setStatusTip(tip) 43 | if slot is not None: 44 | a.triggered.connect(slot) 45 | if checkable: 46 | a.setCheckable(True) 47 | a.setEnabled(enabled) 48 | return a 49 | 50 | 51 | def addActions(widget, actions): 52 | for action in actions: 53 | if action is None: 54 | widget.addSeparator() 55 | elif isinstance(action, QMenu): 56 | widget.addMenu(action) 57 | else: 58 | widget.addAction(action) 59 | 60 | 61 | def labelValidator(): 62 | return QRegExpValidator(QRegExp(r'^[^ \t].+'), None) 63 | 64 | 65 | class struct(object): 66 | 67 | def __init__(self, **kwargs): 68 | self.__dict__.update(kwargs) 69 | 70 | 71 | def distance(p): 72 | return sqrt(p.x() * p.x() + p.y() * p.y()) 73 | 74 | 75 | def fmtShortcut(text): 76 | mod, key = text.split('+', 1) 77 | return '%s+%s' % (mod, key) 78 | 79 | 80 | def generateColorByText(text): 81 | s = ustr(text) 82 | hashCode = int(hashlib.sha256(s.encode('utf-8')).hexdigest(), 16) 83 | r = int((hashCode / 255) % 255) 84 | g = int((hashCode / 65025) % 255) 85 | b = int((hashCode / 16581375) % 255) 86 | return QColor(r, g, b, 100) 87 | 88 | def have_qstring(): 89 | '''p3/qt5 get rid of QString wrapper as py3 has native unicode str type''' 90 | return not (sys.version_info.major >= 3 or QT_VERSION_STR.startswith('5.')) 91 | 92 | def util_qt_strlistclass(): 93 | return QStringList if have_qstring() else list 94 | 95 | def natural_sort(list, key=lambda s:s): 96 | """ 97 | Sort the list into natural alphanumeric order. 98 | """ 99 | def get_alphanum_key_func(key): 100 | convert = lambda text: int(text) if text.isdigit() else text 101 | return lambda s: [convert(c) for c in re.split('([0-9]+)', key(s))] 102 | sort_key = get_alphanum_key_func(key) 103 | list.sort(key=sort_key) 104 | -------------------------------------------------------------------------------- /libs/yolo_io.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf8 -*- 3 | import sys 4 | import os 5 | from xml.etree import ElementTree 6 | from xml.etree.ElementTree import Element, SubElement 7 | from lxml import etree 8 | import codecs 9 | from libs.constants import DEFAULT_ENCODING 10 | 11 | TXT_EXT = '.txt' 12 | ENCODE_METHOD = DEFAULT_ENCODING 13 | 14 | class YOLOWriter: 15 | 16 | def __init__(self, foldername, filename, imgSize, databaseSrc='Unknown', localImgPath=None): 17 | self.foldername = foldername 18 | self.filename = filename 19 | self.databaseSrc = databaseSrc 20 | self.imgSize = imgSize 21 | self.boxlist = [] 22 | self.localImgPath = localImgPath 23 | self.verified = False 24 | 25 | def addBndBox(self, xmin, ymin, xmax, ymax, name, difficult): 26 | bndbox = {'xmin': xmin, 'ymin': ymin, 'xmax': xmax, 'ymax': ymax} 27 | bndbox['name'] = name 28 | bndbox['difficult'] = difficult 29 | self.boxlist.append(bndbox) 30 | 31 | def BndBox2YoloLine(self, box, classList=[]): 32 | xmin = box['xmin'] 33 | xmax = box['xmax'] 34 | ymin = box['ymin'] 35 | ymax = box['ymax'] 36 | 37 | xcen = float((xmin + xmax)) / 2 / self.imgSize[1] 38 | ycen = float((ymin + ymax)) / 2 / self.imgSize[0] 39 | 40 | w = float((xmax - xmin)) / self.imgSize[1] 41 | h = float((ymax - ymin)) / self.imgSize[0] 42 | 43 | # PR387 44 | boxName = box['name'] 45 | if boxName not in classList: 46 | classList.append(boxName) 47 | 48 | classIndex = classList.index(boxName) 49 | 50 | return classIndex, xcen, ycen, w, h 51 | 52 | def save(self, classList=[], targetFile=None): 53 | 54 | out_file = None #Update yolo .txt 55 | out_class_file = None #Update class list .txt 56 | 57 | if targetFile is None: 58 | out_file = open( 59 | self.filename + TXT_EXT, 'w', encoding=ENCODE_METHOD) 60 | classesFile = os.path.join(os.path.dirname(os.path.abspath(self.filename)), "classes.txt") 61 | out_class_file = open(classesFile, 'w') 62 | 63 | else: 64 | out_file = codecs.open(targetFile, 'w', encoding=ENCODE_METHOD) 65 | classesFile = os.path.join(os.path.dirname(os.path.abspath(targetFile)), "classes.txt") 66 | out_class_file = open(classesFile, 'w') 67 | 68 | 69 | for box in self.boxlist: 70 | classIndex, xcen, ycen, w, h = self.BndBox2YoloLine(box, classList) 71 | # print (classIndex, xcen, ycen, w, h) 72 | out_file.write("%d %.6f %.6f %.6f %.6f\n" % (classIndex, xcen, ycen, w, h)) 73 | 74 | # print (classList) 75 | # print (out_class_file) 76 | for c in classList: 77 | out_class_file.write(c+'\n') 78 | 79 | out_class_file.close() 80 | out_file.close() 81 | 82 | 83 | 84 | class YoloReader: 85 | 86 | def __init__(self, filepath, image, classListPath=None): 87 | # shapes type: 88 | # [labbel, [(x1,y1), (x2,y2), (x3,y3), (x4,y4)], color, color, difficult] 89 | self.shapes = [] 90 | self.filepath = filepath 91 | 92 | if classListPath is None: 93 | dir_path = os.path.dirname(os.path.realpath(self.filepath)) 94 | self.classListPath = os.path.join(dir_path, "classes.txt") 95 | else: 96 | self.classListPath = classListPath 97 | 98 | # print (filepath, self.classListPath) 99 | 100 | classesFile = open(self.classListPath, 'r') 101 | self.classes = classesFile.read().strip('\n').split('\n') 102 | 103 | # print (self.classes) 104 | 105 | imgSize = [image.height(), image.width(), 106 | 1 if image.isGrayscale() else 3] 107 | 108 | self.imgSize = imgSize 109 | 110 | self.verified = False 111 | # try: 112 | self.parseYoloFormat() 113 | # except: 114 | # pass 115 | 116 | def getShapes(self): 117 | return self.shapes 118 | 119 | def addShape(self, label, xmin, ymin, xmax, ymax, difficult): 120 | 121 | points = [(xmin, ymin), (xmax, ymin), (xmax, ymax), (xmin, ymax)] 122 | self.shapes.append((label, points, None, None, difficult)) 123 | 124 | def yoloLine2Shape(self, classIndex, xcen, ycen, w, h): 125 | label = self.classes[int(classIndex)] 126 | 127 | xmin = max(float(xcen) - float(w) / 2, 0) 128 | xmax = min(float(xcen) + float(w) / 2, 1) 129 | ymin = max(float(ycen) - float(h) / 2, 0) 130 | ymax = min(float(ycen) + float(h) / 2, 1) 131 | 132 | xmin = int(self.imgSize[1] * xmin) 133 | xmax = int(self.imgSize[1] * xmax) 134 | ymin = int(self.imgSize[0] * ymin) 135 | ymax = int(self.imgSize[0] * ymax) 136 | 137 | return label, xmin, ymin, xmax, ymax 138 | 139 | def parseYoloFormat(self): 140 | bndBoxFile = open(self.filepath, 'r') 141 | for bndBox in bndBoxFile: 142 | classIndex, xcen, ycen, w, h = bndBox.split(' ') 143 | label, xmin, ymin, xmax, ymax = self.yoloLine2Shape(classIndex, xcen, ycen, w, h) 144 | 145 | # Caveat: difficult flag is discarded when saved as yolo format. 146 | self.addShape(label, xmin, ymin, xmax, ymax, False) 147 | -------------------------------------------------------------------------------- /libs/zoomWidget.py: -------------------------------------------------------------------------------- 1 | try: 2 | from PyQt5.QtGui import * 3 | from PyQt5.QtCore import * 4 | from PyQt5.QtWidgets import * 5 | except ImportError: 6 | from PyQt4.QtGui import * 7 | from PyQt4.QtCore import * 8 | 9 | 10 | class ZoomWidget(QSpinBox): 11 | 12 | def __init__(self, value=100): 13 | super(ZoomWidget, self).__init__() 14 | self.setButtonSymbols(QAbstractSpinBox.NoButtons) 15 | self.setRange(1, 500) 16 | self.setSuffix(' %') 17 | self.setValue(value) 18 | self.setToolTip(u'Zoom Level') 19 | self.setStatusTip(self.toolTip()) 20 | self.setAlignment(Qt.AlignCenter) 21 | 22 | def minimumSizeHint(self): 23 | height = super(ZoomWidget, self).minimumSizeHint().height() 24 | fm = QFontMetrics(self.font()) 25 | width = fm.width(str(self.maximum())) 26 | return QSize(width, height) 27 | -------------------------------------------------------------------------------- /pytorch_yolov5/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wufan-tb/AutoLabelImg/6bc97b6c19e9a471b427d3f442f45c594ce45ff6/pytorch_yolov5/models/__init__.py -------------------------------------------------------------------------------- /pytorch_yolov5/models/common.py: -------------------------------------------------------------------------------- 1 | # This file contains modules common to various models 2 | 3 | from utils.utils import * 4 | 5 | 6 | def autopad(k, p=None): # kernel, padding 7 | # Pad to 'same' 8 | if p is None: 9 | p = k // 2 if isinstance(k, int) else [x // 2 for x in k] # auto-pad 10 | return p 11 | 12 | 13 | def DWConv(c1, c2, k=1, s=1, act=True): 14 | # Depthwise convolution 15 | return Conv(c1, c2, k, s, g=math.gcd(c1, c2), act=act) 16 | 17 | 18 | class Conv(nn.Module): 19 | # Standard convolution 20 | def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True): # ch_in, ch_out, kernel, stride, padding, groups 21 | super(Conv, self).__init__() 22 | self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p), groups=g, bias=False) 23 | self.bn = nn.BatchNorm2d(c2) 24 | self.act = nn.LeakyReLU(0.1, inplace=True) if act else nn.Identity() 25 | 26 | def forward(self, x): 27 | return self.act(self.bn(self.conv(x))) 28 | 29 | def fuseforward(self, x): 30 | return self.act(self.conv(x)) 31 | 32 | 33 | class Bottleneck(nn.Module): 34 | # Standard bottleneck 35 | def __init__(self, c1, c2, shortcut=True, g=1, e=0.5): # ch_in, ch_out, shortcut, groups, expansion 36 | super(Bottleneck, self).__init__() 37 | c_ = int(c2 * e) # hidden channels 38 | self.cv1 = Conv(c1, c_, 1, 1) 39 | self.cv2 = Conv(c_, c2, 3, 1, g=g) 40 | self.add = shortcut and c1 == c2 41 | 42 | def forward(self, x): 43 | return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x)) 44 | 45 | 46 | class BottleneckCSP(nn.Module): 47 | # CSP Bottleneck https://github.com/WongKinYiu/CrossStagePartialNetworks 48 | def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5): # ch_in, ch_out, number, shortcut, groups, expansion 49 | super(BottleneckCSP, self).__init__() 50 | c_ = int(c2 * e) # hidden channels 51 | self.cv1 = Conv(c1, c_, 1, 1) 52 | self.cv2 = nn.Conv2d(c1, c_, 1, 1, bias=False) 53 | self.cv3 = nn.Conv2d(c_, c_, 1, 1, bias=False) 54 | self.cv4 = Conv(2 * c_, c2, 1, 1) 55 | self.bn = nn.BatchNorm2d(2 * c_) # applied to cat(cv2, cv3) 56 | self.act = nn.LeakyReLU(0.1, inplace=True) 57 | self.m = nn.Sequential(*[Bottleneck(c_, c_, shortcut, g, e=1.0) for _ in range(n)]) 58 | 59 | def forward(self, x): 60 | y1 = self.cv3(self.m(self.cv1(x))) 61 | y2 = self.cv2(x) 62 | return self.cv4(self.act(self.bn(torch.cat((y1, y2), dim=1)))) 63 | 64 | 65 | class SPP(nn.Module): 66 | # Spatial pyramid pooling layer used in YOLOv3-SPP 67 | def __init__(self, c1, c2, k=(5, 9, 13)): 68 | super(SPP, self).__init__() 69 | c_ = c1 // 2 # hidden channels 70 | self.cv1 = Conv(c1, c_, 1, 1) 71 | self.cv2 = Conv(c_ * (len(k) + 1), c2, 1, 1) 72 | self.m = nn.ModuleList([nn.MaxPool2d(kernel_size=x, stride=1, padding=x // 2) for x in k]) 73 | 74 | def forward(self, x): 75 | x = self.cv1(x) 76 | return self.cv2(torch.cat([x] + [m(x) for m in self.m], 1)) 77 | 78 | 79 | class Focus(nn.Module): 80 | # Focus wh information into c-space 81 | def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True): # ch_in, ch_out, kernel, stride, padding, groups 82 | super(Focus, self).__init__() 83 | self.conv = Conv(c1 * 4, c2, k, s, p, g, act) 84 | 85 | def forward(self, x): # x(b,c,w,h) -> y(b,4c,w/2,h/2) 86 | return self.conv(torch.cat([x[..., ::2, ::2], x[..., 1::2, ::2], x[..., ::2, 1::2], x[..., 1::2, 1::2]], 1)) 87 | 88 | 89 | class Concat(nn.Module): 90 | # Concatenate a list of tensors along dimension 91 | def __init__(self, dimension=1): 92 | super(Concat, self).__init__() 93 | self.d = dimension 94 | 95 | def forward(self, x): 96 | return torch.cat(x, self.d) 97 | 98 | 99 | class Flatten(nn.Module): 100 | # Use after nn.AdaptiveAvgPool2d(1) to remove last 2 dimensions 101 | @staticmethod 102 | def forward(x): 103 | return x.view(x.size(0), -1) 104 | 105 | 106 | class Classify(nn.Module): 107 | # Classification head, i.e. x(b,c1,20,20) to x(b,c2) 108 | def __init__(self, c1, c2, k=1, s=1, p=None, g=1): # ch_in, ch_out, kernel, stride, padding, groups 109 | super(Classify, self).__init__() 110 | self.aap = nn.AdaptiveAvgPool2d(1) # to x(b,c1,1,1) 111 | self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p), groups=g, bias=False) # to x(b,c2,1,1) 112 | self.flat = Flatten() 113 | 114 | def forward(self, x): 115 | z = torch.cat([self.aap(y) for y in (x if isinstance(x, list) else [x])], 1) # cat if list 116 | return self.flat(self.conv(z)) # flatten to x(b,c2) 117 | -------------------------------------------------------------------------------- /pytorch_yolov5/models/experimental.py: -------------------------------------------------------------------------------- 1 | # This file contains experimental modules 2 | 3 | from models.common import * 4 | from utils import google_utils 5 | 6 | 7 | class CrossConv(nn.Module): 8 | # Cross Convolution Downsample 9 | def __init__(self, c1, c2, k=3, s=1, g=1, e=1.0, shortcut=False): 10 | # ch_in, ch_out, kernel, stride, groups, expansion, shortcut 11 | super(CrossConv, self).__init__() 12 | c_ = int(c2 * e) # hidden channels 13 | self.cv1 = Conv(c1, c_, (1, k), (1, s)) 14 | self.cv2 = Conv(c_, c2, (k, 1), (s, 1), g=g) 15 | self.add = shortcut and c1 == c2 16 | 17 | def forward(self, x): 18 | return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x)) 19 | 20 | 21 | class C3(nn.Module): 22 | # Cross Convolution CSP 23 | def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5): # ch_in, ch_out, number, shortcut, groups, expansion 24 | super(C3, self).__init__() 25 | c_ = int(c2 * e) # hidden channels 26 | self.cv1 = Conv(c1, c_, 1, 1) 27 | self.cv2 = nn.Conv2d(c1, c_, 1, 1, bias=False) 28 | self.cv3 = nn.Conv2d(c_, c_, 1, 1, bias=False) 29 | self.cv4 = Conv(2 * c_, c2, 1, 1) 30 | self.bn = nn.BatchNorm2d(2 * c_) # applied to cat(cv2, cv3) 31 | self.act = nn.LeakyReLU(0.1, inplace=True) 32 | self.m = nn.Sequential(*[CrossConv(c_, c_, 3, 1, g, 1.0, shortcut) for _ in range(n)]) 33 | 34 | def forward(self, x): 35 | y1 = self.cv3(self.m(self.cv1(x))) 36 | y2 = self.cv2(x) 37 | return self.cv4(self.act(self.bn(torch.cat((y1, y2), dim=1)))) 38 | 39 | 40 | class Sum(nn.Module): 41 | # Weighted sum of 2 or more layers https://arxiv.org/abs/1911.09070 42 | def __init__(self, n, weight=False): # n: number of inputs 43 | super(Sum, self).__init__() 44 | self.weight = weight # apply weights boolean 45 | self.iter = range(n - 1) # iter object 46 | if weight: 47 | self.w = nn.Parameter(-torch.arange(1., n) / 2, requires_grad=True) # layer weights 48 | 49 | def forward(self, x): 50 | y = x[0] # no weight 51 | if self.weight: 52 | w = torch.sigmoid(self.w) * 2 53 | for i in self.iter: 54 | y = y + x[i + 1] * w[i] 55 | else: 56 | for i in self.iter: 57 | y = y + x[i + 1] 58 | return y 59 | 60 | 61 | class GhostConv(nn.Module): 62 | # Ghost Convolution https://github.com/huawei-noah/ghostnet 63 | def __init__(self, c1, c2, k=1, s=1, g=1, act=True): # ch_in, ch_out, kernel, stride, groups 64 | super(GhostConv, self).__init__() 65 | c_ = c2 // 2 # hidden channels 66 | self.cv1 = Conv(c1, c_, k, s, g, act) 67 | self.cv2 = Conv(c_, c_, 5, 1, c_, act) 68 | 69 | def forward(self, x): 70 | y = self.cv1(x) 71 | return torch.cat([y, self.cv2(y)], 1) 72 | 73 | 74 | class GhostBottleneck(nn.Module): 75 | # Ghost Bottleneck https://github.com/huawei-noah/ghostnet 76 | def __init__(self, c1, c2, k, s): 77 | super(GhostBottleneck, self).__init__() 78 | c_ = c2 // 2 79 | self.conv = nn.Sequential(GhostConv(c1, c_, 1, 1), # pw 80 | DWConv(c_, c_, k, s, act=False) if s == 2 else nn.Identity(), # dw 81 | GhostConv(c_, c2, 1, 1, act=False)) # pw-linear 82 | self.shortcut = nn.Sequential(DWConv(c1, c1, k, s, act=False), 83 | Conv(c1, c2, 1, 1, act=False)) if s == 2 else nn.Identity() 84 | 85 | def forward(self, x): 86 | return self.conv(x) + self.shortcut(x) 87 | 88 | 89 | class MixConv2d(nn.Module): 90 | # Mixed Depthwise Conv https://arxiv.org/abs/1907.09595 91 | def __init__(self, c1, c2, k=(1, 3), s=1, equal_ch=True): 92 | super(MixConv2d, self).__init__() 93 | groups = len(k) 94 | if equal_ch: # equal c_ per group 95 | i = torch.linspace(0, groups - 1E-6, c2).floor() # c2 indices 96 | c_ = [(i == g).sum() for g in range(groups)] # intermediate channels 97 | else: # equal weight.numel() per group 98 | b = [c2] + [0] * groups 99 | a = np.eye(groups + 1, groups, k=-1) 100 | a -= np.roll(a, 1, axis=1) 101 | a *= np.array(k) ** 2 102 | a[0] = 1 103 | c_ = np.linalg.lstsq(a, b, rcond=None)[0].round() # solve for equal weight indices, ax = b 104 | 105 | self.m = nn.ModuleList([nn.Conv2d(c1, int(c_[g]), k[g], s, k[g] // 2, bias=False) for g in range(groups)]) 106 | self.bn = nn.BatchNorm2d(c2) 107 | self.act = nn.LeakyReLU(0.1, inplace=True) 108 | 109 | def forward(self, x): 110 | return x + self.act(self.bn(torch.cat([m(x) for m in self.m], 1))) 111 | 112 | 113 | class Ensemble(nn.ModuleList): 114 | # Ensemble of models 115 | def __init__(self): 116 | super(Ensemble, self).__init__() 117 | 118 | def forward(self, x, augment=False): 119 | y = [] 120 | for module in self: 121 | y.append(module(x, augment)[0]) 122 | # y = torch.stack(y).max(0)[0] # max ensemble 123 | # y = torch.cat(y, 1) # nms ensemble 124 | y = torch.stack(y).mean(0) # mean ensemble 125 | return y, None # inference, train output 126 | 127 | 128 | def attempt_load(weights, map_location=None): 129 | # Loads an ensemble of models weights=[a,b,c] or a single model weights=[a] or weights=a 130 | model = Ensemble() 131 | for w in weights if isinstance(weights, list) else [weights]: 132 | google_utils.attempt_download(w) 133 | model.append(torch.load(w, map_location=map_location)['model'].float().fuse().eval()) # load FP32 model 134 | 135 | if len(model) == 1: 136 | return model[-1] # return model 137 | else: 138 | print('Ensemble created with %s\n' % weights) 139 | for k in ['names', 'stride']: 140 | setattr(model, k, getattr(model[-1], k)) 141 | return model # return ensemble 142 | -------------------------------------------------------------------------------- /pytorch_yolov5/models/export.py: -------------------------------------------------------------------------------- 1 | """Exports a YOLOv5 *.pt model to ONNX and TorchScript formats 2 | 3 | Usage: 4 | $ export PYTHONPATH="$PWD" && python models/export.py --weights ./weights/yolov5s.pt --img 640 --batch 1 5 | """ 6 | 7 | import argparse 8 | 9 | from models.common import * 10 | from utils import google_utils 11 | 12 | if __name__ == '__main__': 13 | parser = argparse.ArgumentParser() 14 | parser.add_argument('--weights', type=str, default='./yolov5s.pt', help='weights path') 15 | parser.add_argument('--img-size', nargs='+', type=int, default=[640, 640], help='image size') 16 | parser.add_argument('--batch-size', type=int, default=1, help='batch size') 17 | opt = parser.parse_args() 18 | opt.img_size *= 2 if len(opt.img_size) == 1 else 1 # expand 19 | print(opt) 20 | 21 | # Input 22 | img = torch.zeros((opt.batch_size, 3, *opt.img_size)) # image size(1,3,320,192) iDetection 23 | 24 | # Load PyTorch model 25 | google_utils.attempt_download(opt.weights) 26 | model = torch.load(opt.weights, map_location=torch.device('cpu'))['model'].float() 27 | model.eval() 28 | model.model[-1].export = True # set Detect() layer export=True 29 | y = model(img) # dry run 30 | 31 | # TorchScript export 32 | try: 33 | print('\nStarting TorchScript export with torch %s...' % torch.__version__) 34 | f = opt.weights.replace('.pt', '.torchscript.pt') # filename 35 | ts = torch.jit.trace(model, img) 36 | ts.save(f) 37 | print('TorchScript export success, saved as %s' % f) 38 | except Exception as e: 39 | print('TorchScript export failure: %s' % e) 40 | 41 | # ONNX export 42 | try: 43 | import onnx 44 | 45 | print('\nStarting ONNX export with onnx %s...' % onnx.__version__) 46 | f = opt.weights.replace('.pt', '.onnx') # filename 47 | model.fuse() # only for ONNX 48 | torch.onnx.export(model, img, f, verbose=False, opset_version=12, input_names=['images'], 49 | output_names=['classes', 'boxes'] if y is None else ['output']) 50 | 51 | # Checks 52 | onnx_model = onnx.load(f) # load onnx model 53 | onnx.checker.check_model(onnx_model) # check onnx model 54 | print(onnx.helper.printable_graph(onnx_model.graph)) # print a human readable model 55 | print('ONNX export success, saved as %s' % f) 56 | except Exception as e: 57 | print('ONNX export failure: %s' % e) 58 | 59 | # CoreML export 60 | try: 61 | import coremltools as ct 62 | 63 | print('\nStarting CoreML export with coremltools %s...' % ct.__version__) 64 | # convert model from torchscript and apply pixel scaling as per detect.py 65 | model = ct.convert(ts, inputs=[ct.ImageType(name='images', shape=img.shape, scale=1 / 255.0, bias=[0, 0, 0])]) 66 | f = opt.weights.replace('.pt', '.mlmodel') # filename 67 | model.save(f) 68 | print('CoreML export success, saved as %s' % f) 69 | except Exception as e: 70 | print('CoreML export failure: %s' % e) 71 | 72 | # Finish 73 | print('\nExport complete. Visualize with https://github.com/lutzroeder/netron.') 74 | -------------------------------------------------------------------------------- /pytorch_yolov5/models/hub/yolov3-spp.yaml: -------------------------------------------------------------------------------- 1 | # parameters 2 | nc: 80 # number of classes 3 | depth_multiple: 1.0 # model depth multiple 4 | width_multiple: 1.0 # layer channel multiple 5 | 6 | # anchors 7 | anchors: 8 | - [10,13, 16,30, 33,23] # P3/8 9 | - [30,61, 62,45, 59,119] # P4/16 10 | - [116,90, 156,198, 373,326] # P5/32 11 | 12 | # darknet53 backbone 13 | backbone: 14 | # [from, number, module, args] 15 | [[-1, 1, Conv, [32, 3, 1]], # 0 16 | [-1, 1, Conv, [64, 3, 2]], # 1-P1/2 17 | [-1, 1, Bottleneck, [64]], 18 | [-1, 1, Conv, [128, 3, 2]], # 3-P2/4 19 | [-1, 2, Bottleneck, [128]], 20 | [-1, 1, Conv, [256, 3, 2]], # 5-P3/8 21 | [-1, 8, Bottleneck, [256]], 22 | [-1, 1, Conv, [512, 3, 2]], # 7-P4/16 23 | [-1, 8, Bottleneck, [512]], 24 | [-1, 1, Conv, [1024, 3, 2]], # 9-P5/32 25 | [-1, 4, Bottleneck, [1024]], # 10 26 | ] 27 | 28 | # YOLOv3-SPP head 29 | head: 30 | [[-1, 1, Bottleneck, [1024, False]], 31 | [-1, 1, SPP, [512, [5, 9, 13]]], 32 | [-1, 1, Conv, [1024, 3, 1]], 33 | [-1, 1, Conv, [512, 1, 1]], 34 | [-1, 1, Conv, [1024, 3, 1]], # 15 (P5/32-large) 35 | 36 | [-2, 1, Conv, [256, 1, 1]], 37 | [-1, 1, nn.Upsample, [None, 2, 'nearest']], 38 | [[-1, 8], 1, Concat, [1]], # cat backbone P4 39 | [-1, 1, Bottleneck, [512, False]], 40 | [-1, 1, Bottleneck, [512, False]], 41 | [-1, 1, Conv, [256, 1, 1]], 42 | [-1, 1, Conv, [512, 3, 1]], # 22 (P4/16-medium) 43 | 44 | [-2, 1, Conv, [128, 1, 1]], 45 | [-1, 1, nn.Upsample, [None, 2, 'nearest']], 46 | [[-1, 6], 1, Concat, [1]], # cat backbone P3 47 | [-1, 1, Bottleneck, [256, False]], 48 | [-1, 2, Bottleneck, [256, False]], # 27 (P3/8-small) 49 | 50 | [[27, 22, 15], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5) 51 | ] 52 | -------------------------------------------------------------------------------- /pytorch_yolov5/models/hub/yolov5-fpn.yaml: -------------------------------------------------------------------------------- 1 | # parameters 2 | nc: 80 # number of classes 3 | depth_multiple: 1.0 # model depth multiple 4 | width_multiple: 1.0 # layer channel multiple 5 | 6 | # anchors 7 | anchors: 8 | - [10,13, 16,30, 33,23] # P3/8 9 | - [30,61, 62,45, 59,119] # P4/16 10 | - [116,90, 156,198, 373,326] # P5/32 11 | 12 | # YOLOv5 backbone 13 | backbone: 14 | # [from, number, module, args] 15 | [[-1, 1, Focus, [64, 3]], # 0-P1/2 16 | [-1, 1, Conv, [128, 3, 2]], # 1-P2/4 17 | [-1, 3, Bottleneck, [128]], 18 | [-1, 1, Conv, [256, 3, 2]], # 3-P3/8 19 | [-1, 9, BottleneckCSP, [256]], 20 | [-1, 1, Conv, [512, 3, 2]], # 5-P4/16 21 | [-1, 9, BottleneckCSP, [512]], 22 | [-1, 1, Conv, [1024, 3, 2]], # 7-P5/32 23 | [-1, 1, SPP, [1024, [5, 9, 13]]], 24 | [-1, 6, BottleneckCSP, [1024]], # 9 25 | ] 26 | 27 | # YOLOv5 FPN head 28 | head: 29 | [[-1, 3, BottleneckCSP, [1024, False]], # 10 (P5/32-large) 30 | 31 | [-1, 1, nn.Upsample, [None, 2, 'nearest']], 32 | [[-1, 6], 1, Concat, [1]], # cat backbone P4 33 | [-1, 1, Conv, [512, 1, 1]], 34 | [-1, 3, BottleneckCSP, [512, False]], # 14 (P4/16-medium) 35 | 36 | [-1, 1, nn.Upsample, [None, 2, 'nearest']], 37 | [[-1, 4], 1, Concat, [1]], # cat backbone P3 38 | [-1, 1, Conv, [256, 1, 1]], 39 | [-1, 3, BottleneckCSP, [256, False]], # 18 (P3/8-small) 40 | 41 | [[18, 14, 10], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5) 42 | ] 43 | -------------------------------------------------------------------------------- /pytorch_yolov5/models/hub/yolov5-panet.yaml: -------------------------------------------------------------------------------- 1 | # parameters 2 | nc: 80 # number of classes 3 | depth_multiple: 1.0 # model depth multiple 4 | width_multiple: 1.0 # layer channel multiple 5 | 6 | # anchors 7 | anchors: 8 | - [116,90, 156,198, 373,326] # P5/32 9 | - [30,61, 62,45, 59,119] # P4/16 10 | - [10,13, 16,30, 33,23] # P3/8 11 | 12 | # YOLOv5 backbone 13 | backbone: 14 | # [from, number, module, args] 15 | [[-1, 1, Focus, [64, 3]], # 0-P1/2 16 | [-1, 1, Conv, [128, 3, 2]], # 1-P2/4 17 | [-1, 3, BottleneckCSP, [128]], 18 | [-1, 1, Conv, [256, 3, 2]], # 3-P3/8 19 | [-1, 9, BottleneckCSP, [256]], 20 | [-1, 1, Conv, [512, 3, 2]], # 5-P4/16 21 | [-1, 9, BottleneckCSP, [512]], 22 | [-1, 1, Conv, [1024, 3, 2]], # 7-P5/32 23 | [-1, 1, SPP, [1024, [5, 9, 13]]], 24 | [-1, 3, BottleneckCSP, [1024, False]], # 9 25 | ] 26 | 27 | # YOLOv5 PANet head 28 | head: 29 | [[-1, 1, Conv, [512, 1, 1]], 30 | [-1, 1, nn.Upsample, [None, 2, 'nearest']], 31 | [[-1, 6], 1, Concat, [1]], # cat backbone P4 32 | [-1, 3, BottleneckCSP, [512, False]], # 13 33 | 34 | [-1, 1, Conv, [256, 1, 1]], 35 | [-1, 1, nn.Upsample, [None, 2, 'nearest']], 36 | [[-1, 4], 1, Concat, [1]], # cat backbone P3 37 | [-1, 3, BottleneckCSP, [256, False]], # 17 (P3/8-small) 38 | 39 | [-1, 1, Conv, [256, 3, 2]], 40 | [[-1, 14], 1, Concat, [1]], # cat head P4 41 | [-1, 3, BottleneckCSP, [512, False]], # 20 (P4/16-medium) 42 | 43 | [-1, 1, Conv, [512, 3, 2]], 44 | [[-1, 10], 1, Concat, [1]], # cat head P5 45 | [-1, 3, BottleneckCSP, [1024, False]], # 23 (P5/32-large) 46 | 47 | [[17, 20, 23], 1, Detect, [nc, anchors]], # Detect(P5, P4, P3) 48 | ] 49 | -------------------------------------------------------------------------------- /pytorch_yolov5/models/yolo.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | from copy import deepcopy 3 | 4 | from models.experimental import * 5 | 6 | 7 | class Detect(nn.Module): 8 | def __init__(self, nc=80, anchors=(), ch=()): # detection layer 9 | super(Detect, self).__init__() 10 | self.stride = None # strides computed during build 11 | self.nc = nc # number of classes 12 | self.no = nc + 5 # number of outputs per anchor 13 | self.nl = len(anchors) # number of detection layers 14 | self.na = len(anchors[0]) // 2 # number of anchors 15 | self.grid = [torch.zeros(1)] * self.nl # init grid 16 | a = torch.tensor(anchors).float().view(self.nl, -1, 2) 17 | self.register_buffer('anchors', a) # shape(nl,na,2) 18 | self.register_buffer('anchor_grid', a.clone().view(self.nl, 1, -1, 1, 1, 2)) # shape(nl,1,na,1,1,2) 19 | self.m = nn.ModuleList(nn.Conv2d(x, self.no * self.na, 1) for x in ch) # output conv 20 | self.export = False # onnx export 21 | 22 | def forward(self, x): 23 | # x = x.copy() # for profiling 24 | z = [] # inference output 25 | 26 | if not hasattr(self, 'export'): 27 | self.export = False 28 | 29 | self.training |= self.export 30 | for i in range(self.nl): 31 | x[i] = self.m[i](x[i]) # conv 32 | bs, _, ny, nx = x[i].shape # x(bs,255,20,20) to x(bs,3,20,20,85) 33 | x[i] = x[i].view(bs, self.na, self.no, ny, nx).permute(0, 1, 3, 4, 2).contiguous() 34 | 35 | if not self.training: # inference 36 | if self.grid[i].shape[2:4] != x[i].shape[2:4]: 37 | self.grid[i] = self._make_grid(nx, ny).to(x[i].device) 38 | 39 | y = x[i].sigmoid() 40 | y[..., 0:2] = (y[..., 0:2] * 2. - 0.5 + self.grid[i].to(x[i].device)) * self.stride[i] # xy 41 | y[..., 2:4] = (y[..., 2:4] * 2) ** 2 * self.anchor_grid[i] # wh 42 | z.append(y.view(bs, -1, self.no)) 43 | 44 | return x if self.training else (torch.cat(z, 1), x) 45 | 46 | @staticmethod 47 | def _make_grid(nx=20, ny=20): 48 | yv, xv = torch.meshgrid([torch.arange(ny), torch.arange(nx)]) 49 | return torch.stack((xv, yv), 2).view((1, 1, ny, nx, 2)).float() 50 | 51 | 52 | class Model(nn.Module): 53 | def __init__(self, cfg='yolov5s.yaml', ch=3, nc=None): # model, input channels, number of classes 54 | super(Model, self).__init__() 55 | if isinstance(cfg, dict): 56 | self.yaml = cfg # model dict 57 | else: # is *.yaml 58 | import yaml # for torch hub 59 | self.yaml_file = Path(cfg).name 60 | with open(cfg) as f: 61 | self.yaml = yaml.load(f, Loader=yaml.FullLoader) # model dict 62 | 63 | # Define model 64 | if nc and nc != self.yaml['nc']: 65 | print('Overriding %s nc=%g with nc=%g' % (cfg, self.yaml['nc'], nc)) 66 | self.yaml['nc'] = nc # override yaml value 67 | self.model, self.save = parse_model(deepcopy(self.yaml), ch=[ch]) # model, savelist, ch_out 68 | # print([x.shape for x in self.forward(torch.zeros(1, ch, 64, 64))]) 69 | 70 | # Build strides, anchors 71 | m = self.model[-1] # Detect() 72 | if isinstance(m, Detect): 73 | s = 128 # 2x min stride 74 | m.stride = torch.tensor([s / x.shape[-2] for x in self.forward(torch.zeros(1, ch, s, s))]) # forward 75 | m.anchors /= m.stride.view(-1, 1, 1) 76 | check_anchor_order(m) 77 | self.stride = m.stride 78 | self._initialize_biases() # only run once 79 | # print('Strides: %s' % m.stride.tolist()) 80 | 81 | # Init weights, biases 82 | torch_utils.initialize_weights(self) 83 | self.info() 84 | print('') 85 | 86 | def forward(self, x, augment=False, profile=False): 87 | if augment: 88 | img_size = x.shape[-2:] # height, width 89 | s = [1, 0.83, 0.67] # scales 90 | f = [None, 3, None] # flips (2-ud, 3-lr) 91 | y = [] # outputs 92 | for si, fi in zip(s, f): 93 | xi = torch_utils.scale_img(x.flip(fi) if fi else x, si) 94 | yi = self.forward_once(xi)[0] # forward 95 | # cv2.imwrite('img%g.jpg' % s, 255 * xi[0].numpy().transpose((1, 2, 0))[:, :, ::-1]) # save 96 | yi[..., :4] /= si # de-scale 97 | if fi == 2: 98 | yi[..., 1] = img_size[0] - yi[..., 1] # de-flip ud 99 | elif fi == 3: 100 | yi[..., 0] = img_size[1] - yi[..., 0] # de-flip lr 101 | y.append(yi) 102 | return torch.cat(y, 1), None # augmented inference, train 103 | else: 104 | return self.forward_once(x, profile) # single-scale inference, train 105 | 106 | def forward_once(self, x, profile=False): 107 | y, dt = [], [] # outputs 108 | for m in self.model: 109 | if m.f != -1: # if not from previous layer 110 | x = y[m.f] if isinstance(m.f, int) else [x if j == -1 else y[j] for j in m.f] # from earlier layers 111 | 112 | if profile: 113 | try: 114 | import thop 115 | o = thop.profile(m, inputs=(x,), verbose=False)[0] / 1E9 * 2 # FLOPS 116 | except: 117 | o = 0 118 | t = torch_utils.time_synchronized() 119 | for _ in range(10): 120 | _ = m(x) 121 | dt.append((torch_utils.time_synchronized() - t) * 100) 122 | print('%10.1f%10.0f%10.1fms %-40s' % (o, m.np, dt[-1], m.type)) 123 | 124 | x = m(x) # run 125 | y.append(x if m.i in self.save else None) # save output 126 | 127 | if profile: 128 | print('%.1fms total' % sum(dt)) 129 | return x 130 | 131 | def _initialize_biases(self, cf=None): # initialize biases into Detect(), cf is class frequency 132 | # cf = torch.bincount(torch.tensor(np.concatenate(dataset.labels, 0)[:, 0]).long(), minlength=nc) + 1. 133 | m = self.model[-1] # Detect() module 134 | for mi, s in zip(m.m, m.stride): #  from 135 | b = mi.bias.view(m.na, -1) # conv.bias(255) to (3,85) 136 | b[:, 4] += math.log(8 / (640 / s) ** 2) # obj (8 objects per 640 image) 137 | b[:, 5:] += math.log(0.6 / (m.nc - 0.99)) if cf is None else torch.log(cf / cf.sum()) # cls 138 | mi.bias = torch.nn.Parameter(b.view(-1), requires_grad=True) 139 | 140 | def _print_biases(self): 141 | m = self.model[-1] # Detect() module 142 | for mi in m.m: #  from 143 | b = mi.bias.detach().view(m.na, -1).T # conv.bias(255) to (3,85) 144 | print(('%6g Conv2d.bias:' + '%10.3g' * 6) % (mi.weight.shape[1], *b[:5].mean(1).tolist(), b[5:].mean())) 145 | 146 | # def _print_weights(self): 147 | # for m in self.model.modules(): 148 | # if type(m) is Bottleneck: 149 | # print('%10.3g' % (m.w.detach().sigmoid() * 2)) # shortcut weights 150 | 151 | def fuse(self): # fuse model Conv2d() + BatchNorm2d() layers 152 | print('Fusing layers... ', end='') 153 | for m in self.model.modules(): 154 | if type(m) is Conv: 155 | m._non_persistent_buffers_set = set() # pytorch 1.6.0 compatability 156 | m.conv = torch_utils.fuse_conv_and_bn(m.conv, m.bn) # update conv 157 | m.bn = None # remove batchnorm 158 | m.forward = m.fuseforward # update forward 159 | self.info() 160 | return self 161 | 162 | def info(self): # print model information 163 | torch_utils.model_info(self) 164 | 165 | 166 | def parse_model(d, ch): # model_dict, input_channels(3) 167 | print('\n%3s%18s%3s%10s %-40s%-30s' % ('', 'from', 'n', 'params', 'module', 'arguments')) 168 | anchors, nc, gd, gw = d['anchors'], d['nc'], d['depth_multiple'], d['width_multiple'] 169 | na = (len(anchors[0]) // 2) if isinstance(anchors, list) else anchors # number of anchors 170 | no = na * (nc + 5) # number of outputs = anchors * (classes + 5) 171 | 172 | layers, save, c2 = [], [], ch[-1] # layers, savelist, ch out 173 | for i, (f, n, m, args) in enumerate(d['backbone'] + d['head']): # from, number, module, args 174 | m = eval(m) if isinstance(m, str) else m # eval strings 175 | for j, a in enumerate(args): 176 | try: 177 | args[j] = eval(a) if isinstance(a, str) else a # eval strings 178 | except: 179 | pass 180 | 181 | n = max(round(n * gd), 1) if n > 1 else n # depth gain 182 | if m in [nn.Conv2d, Conv, Bottleneck, SPP, DWConv, MixConv2d, Focus, CrossConv, BottleneckCSP, C3]: 183 | c1, c2 = ch[f], args[0] 184 | 185 | # Normal 186 | # if i > 0 and args[0] != no: # channel expansion factor 187 | # ex = 1.75 # exponential (default 2.0) 188 | # e = math.log(c2 / ch[1]) / math.log(2) 189 | # c2 = int(ch[1] * ex ** e) 190 | # if m != Focus: 191 | 192 | c2 = make_divisible(c2 * gw, 8) if c2 != no else c2 193 | 194 | # Experimental 195 | # if i > 0 and args[0] != no: # channel expansion factor 196 | # ex = 1 + gw # exponential (default 2.0) 197 | # ch1 = 32 # ch[1] 198 | # e = math.log(c2 / ch1) / math.log(2) # level 1-n 199 | # c2 = int(ch1 * ex ** e) 200 | # if m != Focus: 201 | # c2 = make_divisible(c2, 8) if c2 != no else c2 202 | 203 | args = [c1, c2, *args[1:]] 204 | if m in [BottleneckCSP, C3]: 205 | args.insert(2, n) 206 | n = 1 207 | elif m is nn.BatchNorm2d: 208 | args = [ch[f]] 209 | elif m is Concat: 210 | c2 = sum([ch[-1 if x == -1 else x + 1] for x in f]) 211 | elif m is Detect: 212 | args.append([ch[x + 1] for x in f]) 213 | if isinstance(args[1], int): # number of anchors 214 | args[1] = [list(range(args[1] * 2))] * len(f) 215 | else: 216 | c2 = ch[f] 217 | 218 | m_ = nn.Sequential(*[m(*args) for _ in range(n)]) if n > 1 else m(*args) # module 219 | t = str(m)[8:-2].replace('__main__.', '') # module type 220 | np = sum([x.numel() for x in m_.parameters()]) # number params 221 | m_.i, m_.f, m_.type, m_.np = i, f, t, np # attach index, 'from' index, type, number params 222 | print('%3s%18s%3s%10.0f %-40s%-30s' % (i, f, n, np, t, args)) # print 223 | save.extend(x % i for x in ([f] if isinstance(f, int) else f) if x != -1) # append to savelist 224 | layers.append(m_) 225 | ch.append(c2) 226 | return nn.Sequential(*layers), sorted(save) 227 | 228 | 229 | if __name__ == '__main__': 230 | parser = argparse.ArgumentParser() 231 | parser.add_argument('--cfg', type=str, default='yolov5s.yaml', help='model.yaml') 232 | parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu') 233 | opt = parser.parse_args() 234 | opt.cfg = check_file(opt.cfg) # check file 235 | device = torch_utils.select_device(opt.device) 236 | 237 | # Create model 238 | model = Model(opt.cfg).to(device) 239 | model.train() 240 | 241 | # Profile 242 | # img = torch.rand(8 if torch.cuda.is_available() else 1, 3, 640, 640).to(device) 243 | # y = model(img, profile=True) 244 | 245 | # ONNX export 246 | # model.model[-1].export = True 247 | # torch.onnx.export(model, img, opt.cfg.replace('.yaml', '.onnx'), verbose=True, opset_version=11) 248 | 249 | # Tensorboard 250 | # from torch.utils.tensorboard import SummaryWriter 251 | # tb_writer = SummaryWriter() 252 | # print("Run 'tensorboard --logdir=models/runs' to view tensorboard at http://localhost:6006/") 253 | # tb_writer.add_graph(model.model, img) # add model to tensorboard 254 | # tb_writer.add_image('test', img[0], dataformats='CWH') # add model to tensorboard 255 | -------------------------------------------------------------------------------- /pytorch_yolov5/models/yolov5l.yaml: -------------------------------------------------------------------------------- 1 | # parameters 2 | nc: 80 # number of classes 3 | depth_multiple: 1.0 # model depth multiple 4 | width_multiple: 1.0 # layer channel multiple 5 | 6 | # anchors 7 | anchors: 8 | - [10,13, 16,30, 33,23] # P3/8 9 | - [30,61, 62,45, 59,119] # P4/16 10 | - [116,90, 156,198, 373,326] # P5/32 11 | 12 | # YOLOv5 backbone 13 | backbone: 14 | # [from, number, module, args] 15 | [[-1, 1, Focus, [64, 3]], # 0-P1/2 16 | [-1, 1, Conv, [128, 3, 2]], # 1-P2/4 17 | [-1, 3, BottleneckCSP, [128]], 18 | [-1, 1, Conv, [256, 3, 2]], # 3-P3/8 19 | [-1, 9, BottleneckCSP, [256]], 20 | [-1, 1, Conv, [512, 3, 2]], # 5-P4/16 21 | [-1, 9, BottleneckCSP, [512]], 22 | [-1, 1, Conv, [1024, 3, 2]], # 7-P5/32 23 | [-1, 1, SPP, [1024, [5, 9, 13]]], 24 | [-1, 3, BottleneckCSP, [1024, False]], # 9 25 | ] 26 | 27 | # YOLOv5 head 28 | head: 29 | [[-1, 1, Conv, [512, 1, 1]], 30 | [-1, 1, nn.Upsample, [None, 2, 'nearest']], 31 | [[-1, 6], 1, Concat, [1]], # cat backbone P4 32 | [-1, 3, BottleneckCSP, [512, False]], # 13 33 | 34 | [-1, 1, Conv, [256, 1, 1]], 35 | [-1, 1, nn.Upsample, [None, 2, 'nearest']], 36 | [[-1, 4], 1, Concat, [1]], # cat backbone P3 37 | [-1, 3, BottleneckCSP, [256, False]], # 17 (P3/8-small) 38 | 39 | [-1, 1, Conv, [256, 3, 2]], 40 | [[-1, 14], 1, Concat, [1]], # cat head P4 41 | [-1, 3, BottleneckCSP, [512, False]], # 20 (P4/16-medium) 42 | 43 | [-1, 1, Conv, [512, 3, 2]], 44 | [[-1, 10], 1, Concat, [1]], # cat head P5 45 | [-1, 3, BottleneckCSP, [1024, False]], # 23 (P5/32-large) 46 | 47 | [[17, 20, 23], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5) 48 | ] 49 | -------------------------------------------------------------------------------- /pytorch_yolov5/models/yolov5m.yaml: -------------------------------------------------------------------------------- 1 | # parameters 2 | nc: 80 # number of classes 3 | depth_multiple: 0.67 # model depth multiple 4 | width_multiple: 0.75 # layer channel multiple 5 | 6 | # anchors 7 | anchors: 8 | - [10,13, 16,30, 33,23] # P3/8 9 | - [30,61, 62,45, 59,119] # P4/16 10 | - [116,90, 156,198, 373,326] # P5/32 11 | 12 | # YOLOv5 backbone 13 | backbone: 14 | # [from, number, module, args] 15 | [[-1, 1, Focus, [64, 3]], # 0-P1/2 16 | [-1, 1, Conv, [128, 3, 2]], # 1-P2/4 17 | [-1, 3, BottleneckCSP, [128]], 18 | [-1, 1, Conv, [256, 3, 2]], # 3-P3/8 19 | [-1, 9, BottleneckCSP, [256]], 20 | [-1, 1, Conv, [512, 3, 2]], # 5-P4/16 21 | [-1, 9, BottleneckCSP, [512]], 22 | [-1, 1, Conv, [1024, 3, 2]], # 7-P5/32 23 | [-1, 1, SPP, [1024, [5, 9, 13]]], 24 | [-1, 3, BottleneckCSP, [1024, False]], # 9 25 | ] 26 | 27 | # YOLOv5 head 28 | head: 29 | [[-1, 1, Conv, [512, 1, 1]], 30 | [-1, 1, nn.Upsample, [None, 2, 'nearest']], 31 | [[-1, 6], 1, Concat, [1]], # cat backbone P4 32 | [-1, 3, BottleneckCSP, [512, False]], # 13 33 | 34 | [-1, 1, Conv, [256, 1, 1]], 35 | [-1, 1, nn.Upsample, [None, 2, 'nearest']], 36 | [[-1, 4], 1, Concat, [1]], # cat backbone P3 37 | [-1, 3, BottleneckCSP, [256, False]], # 17 (P3/8-small) 38 | 39 | [-1, 1, Conv, [256, 3, 2]], 40 | [[-1, 14], 1, Concat, [1]], # cat head P4 41 | [-1, 3, BottleneckCSP, [512, False]], # 20 (P4/16-medium) 42 | 43 | [-1, 1, Conv, [512, 3, 2]], 44 | [[-1, 10], 1, Concat, [1]], # cat head P5 45 | [-1, 3, BottleneckCSP, [1024, False]], # 23 (P5/32-large) 46 | 47 | [[17, 20, 23], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5) 48 | ] 49 | -------------------------------------------------------------------------------- /pytorch_yolov5/models/yolov5s.yaml: -------------------------------------------------------------------------------- 1 | # parameters 2 | nc: 80 # number of classes 3 | depth_multiple: 0.33 # model depth multiple 4 | width_multiple: 0.50 # layer channel multiple 5 | 6 | # anchors 7 | anchors: 8 | - [10,13, 16,30, 33,23] # P3/8 9 | - [30,61, 62,45, 59,119] # P4/16 10 | - [116,90, 156,198, 373,326] # P5/32 11 | 12 | # YOLOv5 backbone 13 | backbone: 14 | # [from, number, module, args] 15 | [[-1, 1, Focus, [64, 3]], # 0-P1/2 16 | [-1, 1, Conv, [128, 3, 2]], # 1-P2/4 17 | [-1, 3, BottleneckCSP, [128]], 18 | [-1, 1, Conv, [256, 3, 2]], # 3-P3/8 19 | [-1, 9, BottleneckCSP, [256]], 20 | [-1, 1, Conv, [512, 3, 2]], # 5-P4/16 21 | [-1, 9, BottleneckCSP, [512]], 22 | [-1, 1, Conv, [1024, 3, 2]], # 7-P5/32 23 | [-1, 1, SPP, [1024, [5, 9, 13]]], 24 | [-1, 3, BottleneckCSP, [1024, False]], # 9 25 | ] 26 | 27 | # YOLOv5 head 28 | head: 29 | [[-1, 1, Conv, [512, 1, 1]], 30 | [-1, 1, nn.Upsample, [None, 2, 'nearest']], 31 | [[-1, 6], 1, Concat, [1]], # cat backbone P4 32 | [-1, 3, BottleneckCSP, [512, False]], # 13 33 | 34 | [-1, 1, Conv, [256, 1, 1]], 35 | [-1, 1, nn.Upsample, [None, 2, 'nearest']], 36 | [[-1, 4], 1, Concat, [1]], # cat backbone P3 37 | [-1, 3, BottleneckCSP, [256, False]], # 17 (P3/8-small) 38 | 39 | [-1, 1, Conv, [256, 3, 2]], 40 | [[-1, 14], 1, Concat, [1]], # cat head P4 41 | [-1, 3, BottleneckCSP, [512, False]], # 20 (P4/16-medium) 42 | 43 | [-1, 1, Conv, [512, 3, 2]], 44 | [[-1, 10], 1, Concat, [1]], # cat head P5 45 | [-1, 3, BottleneckCSP, [1024, False]], # 23 (P5/32-large) 46 | 47 | [[17, 20, 23], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5) 48 | ] 49 | -------------------------------------------------------------------------------- /pytorch_yolov5/models/yolov5x.yaml: -------------------------------------------------------------------------------- 1 | # parameters 2 | nc: 80 # number of classes 3 | depth_multiple: 1.33 # model depth multiple 4 | width_multiple: 1.25 # layer channel multiple 5 | 6 | # anchors 7 | anchors: 8 | - [10,13, 16,30, 33,23] # P3/8 9 | - [30,61, 62,45, 59,119] # P4/16 10 | - [116,90, 156,198, 373,326] # P5/32 11 | 12 | # YOLOv5 backbone 13 | backbone: 14 | # [from, number, module, args] 15 | [[-1, 1, Focus, [64, 3]], # 0-P1/2 16 | [-1, 1, Conv, [128, 3, 2]], # 1-P2/4 17 | [-1, 3, BottleneckCSP, [128]], 18 | [-1, 1, Conv, [256, 3, 2]], # 3-P3/8 19 | [-1, 9, BottleneckCSP, [256]], 20 | [-1, 1, Conv, [512, 3, 2]], # 5-P4/16 21 | [-1, 9, BottleneckCSP, [512]], 22 | [-1, 1, Conv, [1024, 3, 2]], # 7-P5/32 23 | [-1, 1, SPP, [1024, [5, 9, 13]]], 24 | [-1, 3, BottleneckCSP, [1024, False]], # 9 25 | ] 26 | 27 | # YOLOv5 head 28 | head: 29 | [[-1, 1, Conv, [512, 1, 1]], 30 | [-1, 1, nn.Upsample, [None, 2, 'nearest']], 31 | [[-1, 6], 1, Concat, [1]], # cat backbone P4 32 | [-1, 3, BottleneckCSP, [512, False]], # 13 33 | 34 | [-1, 1, Conv, [256, 1, 1]], 35 | [-1, 1, nn.Upsample, [None, 2, 'nearest']], 36 | [[-1, 4], 1, Concat, [1]], # cat backbone P3 37 | [-1, 3, BottleneckCSP, [256, False]], # 17 (P3/8-small) 38 | 39 | [-1, 1, Conv, [256, 3, 2]], 40 | [[-1, 14], 1, Concat, [1]], # cat head P4 41 | [-1, 3, BottleneckCSP, [512, False]], # 20 (P4/16-medium) 42 | 43 | [-1, 1, Conv, [512, 3, 2]], 44 | [[-1, 10], 1, Concat, [1]], # cat head P5 45 | [-1, 3, BottleneckCSP, [1024, False]], # 23 (P5/32-large) 46 | 47 | [[17, 20, 23], 1, Detect, [nc, anchors]], # Detect(P3, P4, P5) 48 | ] 49 | -------------------------------------------------------------------------------- /pytorch_yolov5/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wufan-tb/AutoLabelImg/6bc97b6c19e9a471b427d3f442f45c594ce45ff6/pytorch_yolov5/utils/__init__.py -------------------------------------------------------------------------------- /pytorch_yolov5/utils/activations.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | import torch.nn.functional as F 4 | 5 | 6 | # Swish ------------------------------------------------------------------------ 7 | class SwishImplementation(torch.autograd.Function): 8 | @staticmethod 9 | def forward(ctx, x): 10 | ctx.save_for_backward(x) 11 | return x * torch.sigmoid(x) 12 | 13 | @staticmethod 14 | def backward(ctx, grad_output): 15 | x = ctx.saved_tensors[0] 16 | sx = torch.sigmoid(x) 17 | return grad_output * (sx * (1 + x * (1 - sx))) 18 | 19 | 20 | class MemoryEfficientSwish(nn.Module): 21 | @staticmethod 22 | def forward(x): 23 | return SwishImplementation.apply(x) 24 | 25 | 26 | class HardSwish(nn.Module): # https://arxiv.org/pdf/1905.02244.pdf 27 | @staticmethod 28 | def forward(x): 29 | return x * F.hardtanh(x + 3, 0., 6., True) / 6. 30 | 31 | 32 | class Swish(nn.Module): 33 | @staticmethod 34 | def forward(x): 35 | return x * torch.sigmoid(x) 36 | 37 | 38 | # Mish ------------------------------------------------------------------------ 39 | class MishImplementation(torch.autograd.Function): 40 | @staticmethod 41 | def forward(ctx, x): 42 | ctx.save_for_backward(x) 43 | return x.mul(torch.tanh(F.softplus(x))) # x * tanh(ln(1 + exp(x))) 44 | 45 | @staticmethod 46 | def backward(ctx, grad_output): 47 | x = ctx.saved_tensors[0] 48 | sx = torch.sigmoid(x) 49 | fx = F.softplus(x).tanh() 50 | return grad_output * (fx + x * sx * (1 - fx * fx)) 51 | 52 | 53 | class MemoryEfficientMish(nn.Module): 54 | @staticmethod 55 | def forward(x): 56 | return MishImplementation.apply(x) 57 | 58 | 59 | class Mish(nn.Module): # https://github.com/digantamisra98/Mish 60 | @staticmethod 61 | def forward(x): 62 | return x * F.softplus(x).tanh() 63 | 64 | 65 | # FReLU https://arxiv.org/abs/2007.11824 -------------------------------------- 66 | class FReLU(nn.Module): 67 | def __init__(self, c1, k=3): # ch_in, kernel 68 | super(FReLU, self).__init__() 69 | self.conv = nn.Conv2d(c1, c1, k, 1, 1, groups=c1) 70 | self.bn = nn.BatchNorm2d(c1) 71 | 72 | def forward(self, x): 73 | return torch.max(x, self.bn(self.conv(x))) 74 | -------------------------------------------------------------------------------- /pytorch_yolov5/utils/google_utils.py: -------------------------------------------------------------------------------- 1 | # This file contains google utils: https://cloud.google.com/storage/docs/reference/libraries 2 | # pip install --upgrade google-cloud-storage 3 | # from google.cloud import storage 4 | 5 | import os 6 | import time 7 | from pathlib import Path 8 | 9 | 10 | def attempt_download(weights): 11 | # Attempt to download pretrained weights if not found locally 12 | weights = weights.strip().replace("'", '') 13 | msg = weights + ' missing, try downloading from https://drive.google.com/drive/folders/1Drs_Aiu7xx6S-ix95f9kNsA6ueKRpN2J' 14 | 15 | r = 1 # return 16 | if len(weights) > 0 and not os.path.isfile(weights): 17 | d = {'yolov3-spp.pt': '1mM67oNw4fZoIOL1c8M3hHmj66d8e-ni_', # yolov3-spp.yaml 18 | 'yolov5s.pt': '1R5T6rIyy3lLwgFXNms8whc-387H0tMQO', # yolov5s.yaml 19 | 'yolov5m.pt': '1vobuEExpWQVpXExsJ2w-Mbf3HJjWkQJr', # yolov5m.yaml 20 | 'yolov5l.pt': '1hrlqD1Wdei7UT4OgT785BEk1JwnSvNEV', # yolov5l.yaml 21 | 'yolov5x.pt': '1mM8aZJlWTxOg7BZJvNUMrTnA2AbeCVzS', # yolov5x.yaml 22 | } 23 | 24 | file = Path(weights).name 25 | if file in d: 26 | r = gdrive_download(id=d[file], name=weights) 27 | 28 | if not (r == 0 and os.path.exists(weights) and os.path.getsize(weights) > 1E6): # weights exist and > 1MB 29 | os.remove(weights) if os.path.exists(weights) else None # remove partial downloads 30 | s = "curl -L -o %s 'storage.googleapis.com/ultralytics/yolov5/ckpt/%s'" % (weights, file) 31 | r = os.system(s) # execute, capture return values 32 | 33 | # Error check 34 | if not (r == 0 and os.path.exists(weights) and os.path.getsize(weights) > 1E6): # weights exist and > 1MB 35 | os.remove(weights) if os.path.exists(weights) else None # remove partial downloads 36 | raise Exception(msg) 37 | 38 | 39 | def gdrive_download(id='1n_oKgR81BJtqk75b00eAjdv03qVCQn2f', name='coco128.zip'): 40 | # Downloads a file from Google Drive, accepting presented query 41 | # from utils.google_utils import *; gdrive_download() 42 | t = time.time() 43 | 44 | print('Downloading https://drive.google.com/uc?export=download&id=%s as %s... ' % (id, name), end='') 45 | os.remove(name) if os.path.exists(name) else None # remove existing 46 | os.remove('cookie') if os.path.exists('cookie') else None 47 | 48 | # Attempt file download 49 | os.system("curl -c ./cookie -s -L \"drive.google.com/uc?export=download&id=%s\" > /dev/null" % id) 50 | if os.path.exists('cookie'): # large file 51 | s = "curl -Lb ./cookie \"drive.google.com/uc?export=download&confirm=`awk '/download/ {print $NF}' ./cookie`&id=%s\" -o %s" % ( 52 | id, name) 53 | else: # small file 54 | s = 'curl -s -L -o %s "drive.google.com/uc?export=download&id=%s"' % (name, id) 55 | r = os.system(s) # execute, capture return values 56 | os.remove('cookie') if os.path.exists('cookie') else None 57 | 58 | # Error check 59 | if r != 0: 60 | os.remove(name) if os.path.exists(name) else None # remove partial 61 | print('Download error ') # raise Exception('Download error') 62 | return r 63 | 64 | # Unzip if archive 65 | if name.endswith('.zip'): 66 | print('unzipping... ', end='') 67 | os.system('unzip -q %s' % name) # unzip 68 | os.remove(name) # remove zip to free space 69 | 70 | print('Done (%.1fs)' % (time.time() - t)) 71 | return r 72 | 73 | 74 | # def upload_blob(bucket_name, source_file_name, destination_blob_name): 75 | # # Uploads a file to a bucket 76 | # # https://cloud.google.com/storage/docs/uploading-objects#storage-upload-object-python 77 | # 78 | # storage_client = storage.Client() 79 | # bucket = storage_client.get_bucket(bucket_name) 80 | # blob = bucket.blob(destination_blob_name) 81 | # 82 | # blob.upload_from_filename(source_file_name) 83 | # 84 | # print('File {} uploaded to {}.'.format( 85 | # source_file_name, 86 | # destination_blob_name)) 87 | # 88 | # 89 | # def download_blob(bucket_name, source_blob_name, destination_file_name): 90 | # # Uploads a blob from a bucket 91 | # storage_client = storage.Client() 92 | # bucket = storage_client.get_bucket(bucket_name) 93 | # blob = bucket.blob(source_blob_name) 94 | # 95 | # blob.download_to_filename(destination_file_name) 96 | # 97 | # print('Blob {} downloaded to {}.'.format( 98 | # source_blob_name, 99 | # destination_file_name)) 100 | -------------------------------------------------------------------------------- /pytorch_yolov5/utils/post_process.py: -------------------------------------------------------------------------------- 1 | #================== def some functions ================== 2 | import cv2,time,os,json,datetime,csv,matplotlib 3 | import numpy as np 4 | matplotlib.use('Agg') 5 | import matplotlib.pyplot as plt 6 | 7 | def draw_box_info(draw_info, img, labels, scores, boxes, **kwds): 8 | for i in range(len(boxes)): 9 | label=int(labels[i]) 10 | score=round(scores[i],2) 11 | box=boxes[i] 12 | mean_score=round(kwds['Mscores'][i],2) if 'Mscores' in kwds.keys() else score 13 | obj_id='ID:'+str(int(kwds['ID'][i])) if 'ID' in kwds.keys() else '' 14 | 15 | if min(score,mean_score)>=draw_info['draw_threshold'][label]: 16 | ymin = min(img.shape[0]-5,max(5,int(box[draw_info['box_type'].index('ymin')]))) 17 | xmin = min(img.shape[1]-5,max(5,int(box[draw_info['box_type'].index('xmin')]))) 18 | ymax = max(5,min(img.shape[0]-5,int(box[draw_info['box_type'].index('ymax')]))) 19 | xmax = max(5,min(img.shape[1]-5,int(box[draw_info['box_type'].index('xmax')]))) 20 | class_name = str(draw_info['label_name'][label]) 21 | class_color=draw_info['label_color'][label] 22 | info_txt='{:s}|{}'.format(class_name,obj_id) 23 | t_size=cv2.getTextSize(info_txt, cv2.FONT_HERSHEY_TRIPLEX, 0.8 , 2)[0] 24 | cv2.rectangle(img, (xmin, ymin), (xmax, ymax), class_color, 2) 25 | cv2.rectangle(img, (xmin, ymin), (xmin + t_size[0]+4, ymin + t_size[1]+10), class_color, -1) 26 | cv2.putText(img, info_txt, (xmin+2, ymin+t_size[1]+2), cv2.FONT_HERSHEY_TRIPLEX, 0.8, [255,255,255], 2) 27 | return img 28 | 29 | def common_draw_img(draw_info, img, labels, scores, boxes, save_path, **kwds): 30 | 31 | plt.rcParams['figure.figsize'] = (19.2, 10.8) 32 | plt.rcParams['savefig.dpi'] = 100 33 | linewidth=3 34 | fontsize=15 35 | plt.imshow(img) 36 | 37 | for i in range(labels.shape[0]): 38 | label=int(labels[i]) 39 | score=scores[i] 40 | box=boxes[i] 41 | try: 42 | condition = draw_info['if_draw_box'][i] 43 | except: 44 | try: 45 | condition= (kwds['exist'][i]==True) 46 | except: 47 | condition = False 48 | 49 | if score>=0 : 50 | ymin = min(img.shape[0]-5,max(5,int(box[draw_info['box_type'].index('ymin')]))) 51 | xmin = min(img.shape[1]-5,max(5,int(box[draw_info['box_type'].index('xmin')]))) 52 | ymax = max(5,min(img.shape[0]-5,int(box[draw_info['box_type'].index('ymax')]))) 53 | xmax = max(5,min(img.shape[1]-5,int(box[draw_info['box_type'].index('xmax')]))) 54 | class_name = str(draw_info['label_name'][label]) 55 | class_color=draw_info['label_color'][label] 56 | rect = plt.Rectangle((xmin, ymin), xmax - xmin, 57 | ymax - ymin, fill=False, 58 | edgecolor=class_color, 59 | linewidth=linewidth) 60 | plt.gca().add_patch(rect) 61 | info_txt='{:s} | {:.3f} | '.format(class_name, score) 62 | info_length=2 63 | for key in kwds: 64 | if key != 'Counter': 65 | if info_length%3==0: 66 | info_txt+='\n' 67 | info_txt=info_txt+'{:s}: {:s} | '.format(key, str(kwds[key][i])) 68 | info_length+=1 69 | plt.gca().text(min(img.shape[1]-15,max(xmin+1,0)), min(img.shape[0]-10,max(ymin-8,0)), 70 | info_txt, 71 | bbox=dict(facecolor=class_color, alpha=0.5), 72 | fontsize=fontsize, color='white') 73 | else: 74 | pass 75 | 76 | plt.axis('off') 77 | plt.gca().xaxis.set_major_locator(plt.NullLocator()) 78 | plt.gca().yaxis.set_major_locator(plt.NullLocator()) 79 | plt.subplots_adjust(top=1, bottom=0, right=1, left=0, hspace=0, wspace=0) 80 | plt.margins(0, 0) 81 | plt.savefig(save_path) 82 | plt.close() 83 | 84 | def box_pre_select(labels,scores,boxes,label_list,thres_dict): 85 | label_list=np.array(label_list) 86 | temp=[] 87 | temp.append(labels) 88 | temp.append(scores) 89 | temp.append(boxes) 90 | for item in label_list: 91 | the_label=int(item) 92 | temp[2] = temp[2][temp[0]!=the_label] 93 | temp[1] = temp[1][temp[0]!=the_label] 94 | temp[0] = temp[0][temp[0]!=the_label] 95 | result=temp.copy() 96 | result[2] = temp[2][temp[1]>[thres_dict[temp[0][i]] for i in range(len(temp[0]))]] 97 | result[1] = temp[1][temp[1]>[thres_dict[temp[0][i]] for i in range(len(temp[0]))]] 98 | result[0] = temp[0][temp[1]>[thres_dict[temp[0][i]] for i in range(len(temp[0]))]] 99 | return result[0],result[1],result[2] 100 | 101 | def add_bounding(img_path,quyu_path): #add bounding on img file 102 | origin = cv2.imread(img_path) 103 | quyu = cv2.imread(quyu_path) 104 | quyu = cv2.resize(quyu, (origin.shape[1], origin.shape[0])) 105 | _, th = cv2.threshold(cv2.cvtColor(quyu.copy(), cv2.COLOR_BGR2GRAY), 200, 255, cv2.THRESH_BINARY) 106 | th = 255 - th 107 | contours, _ = cv2.findContours(th, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) 108 | final_img = cv2.drawContours(origin.copy(), contours, -1, (0, 0, 255), 3) 109 | b, g, r = cv2.split(final_img) 110 | final_img = cv2.merge([r, g, b]) 111 | plt.figure(figsize=(19.2, 10.8)) 112 | plt.subplots_adjust(top=1, bottom=0, right=1, left=0, hspace=0, wspace=0) 113 | plt.imshow(final_img) 114 | plt.savefig(img_path) 115 | plt.close() 116 | 117 | def draw_bounding(img,quyu_path): #draw bounding on img, diffierent from above 118 | quyu = cv2.imread(quyu_path) 119 | quyu = cv2.resize(quyu, (img.shape[1], img.shape[0])) 120 | _, th = cv2.threshold(cv2.cvtColor(quyu.copy(), cv2.COLOR_BGR2GRAY), 200, 255, cv2.THRESH_BINARY) 121 | th = 255 - th 122 | contours, _ = cv2.findContours(th, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE) 123 | final_img = cv2.drawContours(img.copy(), contours, -1, (0, 0, 255), 3) 124 | return final_img 125 | 126 | def find_max_ios_box(test_box,box_list): 127 | index=None 128 | max_ios=0.2 129 | for i in range(len(box_list)): 130 | box1=test_box 131 | box2=box_list[i] 132 | local_ios=retina_IOS(box1,box2) 133 | if local_ios>max_ios: 134 | max_ios=local_ios 135 | index=i 136 | return index,max_ios 137 | 138 | def retina_IOS(rectA,rectB): 139 | W = min(rectA[2], rectB[2]) - max(rectA[0], rectB[0]) 140 | H = min(rectA[3], rectB[3]) - max(rectA[1], rectB[1]) 141 | if W <= 0 or H <= 0: 142 | return 0; 143 | SA = (rectA[2] - rectA[0]) * (rectA[3] - rectA[1]) 144 | SB = (rectB[2] - rectB[0]) * (rectB[3] - rectB[1]) 145 | min_S=min(SA,SB) 146 | cross = W * H 147 | return cross/min_S 148 | 149 | def transform_box(boxes,detect_window=None): 150 | if detect_window==None: 151 | pass 152 | else: 153 | for i in range(len(boxes)): 154 | boxes[i][0]=boxes[i][0]+detect_window[0] 155 | boxes[i][1]=boxes[i][1]+detect_window[1] 156 | boxes[i][2]=boxes[i][2]+detect_window[0] 157 | boxes[i][3]=boxes[i][3]+detect_window[1] 158 | return boxes 159 | 160 | -------------------------------------------------------------------------------- /pytorch_yolov5/utils/torch_utils.py: -------------------------------------------------------------------------------- 1 | import math 2 | import os 3 | import time 4 | from copy import deepcopy 5 | 6 | import torch 7 | import torch.backends.cudnn as cudnn 8 | import torch.nn as nn 9 | import torch.nn.functional as F 10 | import torchvision.models as models 11 | 12 | 13 | def init_seeds(seed=0): 14 | torch.manual_seed(seed) 15 | 16 | # Speed-reproducibility tradeoff https://pytorch.org/docs/stable/notes/randomness.html 17 | if seed == 0: # slower, more reproducible 18 | cudnn.deterministic = True 19 | cudnn.benchmark = False 20 | else: # faster, less reproducible 21 | cudnn.deterministic = False 22 | cudnn.benchmark = True 23 | 24 | 25 | def select_device(device='', apex=False, batch_size=None): 26 | # device = 'cpu' or '0' or '0,1,2,3' 27 | cpu_request = device.lower() == 'cpu' 28 | if device and not cpu_request: # if device requested other than 'cpu' 29 | os.environ['CUDA_VISIBLE_DEVICES'] = device # set environment variable 30 | assert torch.cuda.is_available(), 'CUDA unavailable, invalid device %s requested' % device # check availablity 31 | 32 | cuda = False if cpu_request else torch.cuda.is_available() 33 | if cuda: 34 | c = 1024 ** 2 # bytes to MB 35 | ng = torch.cuda.device_count() 36 | if ng > 1 and batch_size: # check that batch_size is compatible with device_count 37 | assert batch_size % ng == 0, 'batch-size %g not multiple of GPU count %g' % (batch_size, ng) 38 | x = [torch.cuda.get_device_properties(i) for i in range(ng)] 39 | s = 'Using CUDA ' + ('Apex ' if apex else '') # apex for mixed precision https://github.com/NVIDIA/apex 40 | for i in range(0, ng): 41 | if i == 1: 42 | s = ' ' * len(s) 43 | print("%sdevice%g _CudaDeviceProperties(name='%s', total_memory=%dMB)" % 44 | (s, i, x[i].name, x[i].total_memory / c)) 45 | else: 46 | print('Using CPU') 47 | 48 | print('') # skip a line 49 | return torch.device('cuda:0' if cuda else 'cpu') 50 | 51 | 52 | def time_synchronized(): 53 | torch.cuda.synchronize() if torch.cuda.is_available() else None 54 | return time.time() 55 | 56 | 57 | def is_parallel(model): 58 | # is model is parallel with DP or DDP 59 | return type(model) in (nn.parallel.DataParallel, nn.parallel.DistributedDataParallel) 60 | 61 | 62 | def initialize_weights(model): 63 | for m in model.modules(): 64 | t = type(m) 65 | if t is nn.Conv2d: 66 | pass # nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu') 67 | elif t is nn.BatchNorm2d: 68 | m.eps = 1e-3 69 | m.momentum = 0.03 70 | elif t in [nn.LeakyReLU, nn.ReLU, nn.ReLU6]: 71 | m.inplace = True 72 | 73 | 74 | def find_modules(model, mclass=nn.Conv2d): 75 | # finds layer indices matching module class 'mclass' 76 | return [i for i, m in enumerate(model.module_list) if isinstance(m, mclass)] 77 | 78 | 79 | def sparsity(model): 80 | # Return global model sparsity 81 | a, b = 0., 0. 82 | for p in model.parameters(): 83 | a += p.numel() 84 | b += (p == 0).sum() 85 | return b / a 86 | 87 | 88 | def prune(model, amount=0.3): 89 | # Prune model to requested global sparsity 90 | import torch.nn.utils.prune as prune 91 | print('Pruning model... ', end='') 92 | for name, m in model.named_modules(): 93 | if isinstance(m, nn.Conv2d): 94 | prune.l1_unstructured(m, name='weight', amount=amount) # prune 95 | prune.remove(m, 'weight') # make permanent 96 | print(' %.3g global sparsity' % sparsity(model)) 97 | 98 | 99 | def fuse_conv_and_bn(conv, bn): 100 | # https://tehnokv.com/posts/fusing-batchnorm-and-conv/ 101 | with torch.no_grad(): 102 | # init 103 | fusedconv = nn.Conv2d(conv.in_channels, 104 | conv.out_channels, 105 | kernel_size=conv.kernel_size, 106 | stride=conv.stride, 107 | padding=conv.padding, 108 | bias=True).to(conv.weight.device) 109 | 110 | # prepare filters 111 | w_conv = conv.weight.clone().view(conv.out_channels, -1) 112 | w_bn = torch.diag(bn.weight.div(torch.sqrt(bn.eps + bn.running_var))) 113 | fusedconv.weight.copy_(torch.mm(w_bn, w_conv).view(fusedconv.weight.size())) 114 | 115 | # prepare spatial bias 116 | b_conv = torch.zeros(conv.weight.size(0), device=conv.weight.device) if conv.bias is None else conv.bias 117 | b_bn = bn.bias - bn.weight.mul(bn.running_mean).div(torch.sqrt(bn.running_var + bn.eps)) 118 | fusedconv.bias.copy_(torch.mm(w_bn, b_conv.reshape(-1, 1)).reshape(-1) + b_bn) 119 | 120 | return fusedconv 121 | 122 | 123 | def model_info(model, verbose=False): 124 | # Plots a line-by-line description of a PyTorch model 125 | n_p = sum(x.numel() for x in model.parameters()) # number parameters 126 | n_g = sum(x.numel() for x in model.parameters() if x.requires_grad) # number gradients 127 | if verbose: 128 | print('%5s %40s %9s %12s %20s %10s %10s' % ('layer', 'name', 'gradient', 'parameters', 'shape', 'mu', 'sigma')) 129 | for i, (name, p) in enumerate(model.named_parameters()): 130 | name = name.replace('module_list.', '') 131 | print('%5g %40s %9s %12g %20s %10.3g %10.3g' % 132 | (i, name, p.requires_grad, p.numel(), list(p.shape), p.mean(), p.std())) 133 | 134 | try: # FLOPS 135 | from thop import profile 136 | flops = profile(deepcopy(model), inputs=(torch.zeros(1, 3, 64, 64),), verbose=False)[0] / 1E9 * 2 137 | fs = ', %.1f GFLOPS' % (flops * 100) # 640x640 FLOPS 138 | except: 139 | fs = '' 140 | 141 | print('Model Summary: %g layers, %g parameters, %g gradients%s' % (len(list(model.parameters())), n_p, n_g, fs)) 142 | 143 | 144 | def load_classifier(name='resnet101', n=2): 145 | # Loads a pretrained model reshaped to n-class output 146 | model = models.__dict__[name](pretrained=True) 147 | 148 | # Display model properties 149 | input_size = [3, 224, 224] 150 | input_space = 'RGB' 151 | input_range = [0, 1] 152 | mean = [0.485, 0.456, 0.406] 153 | std = [0.229, 0.224, 0.225] 154 | for x in [input_size, input_space, input_range, mean, std]: 155 | print(x + ' =', eval(x)) 156 | 157 | # Reshape output to n classes 158 | filters = model.fc.weight.shape[1] 159 | model.fc.bias = nn.Parameter(torch.zeros(n), requires_grad=True) 160 | model.fc.weight = nn.Parameter(torch.zeros(n, filters), requires_grad=True) 161 | model.fc.out_features = n 162 | return model 163 | 164 | 165 | def scale_img(img, ratio=1.0, same_shape=False): # img(16,3,256,416), r=ratio 166 | # scales img(bs,3,y,x) by ratio 167 | if ratio == 1.0: 168 | return img 169 | else: 170 | h, w = img.shape[2:] 171 | s = (int(h * ratio), int(w * ratio)) # new size 172 | img = F.interpolate(img, size=s, mode='bilinear', align_corners=False) # resize 173 | if not same_shape: # pad/crop img 174 | gs = 32 # (pixels) grid size 175 | h, w = [math.ceil(x * ratio / gs) * gs for x in (h, w)] 176 | return F.pad(img, [0, w - s[1], 0, h - s[0]], value=0.447) # value = imagenet mean 177 | 178 | 179 | def copy_attr(a, b, include=(), exclude=()): 180 | # Copy attributes from b to a, options to only include [...] and to exclude [...] 181 | for k, v in b.__dict__.items(): 182 | if (len(include) and k not in include) or k.startswith('_') or k in exclude: 183 | continue 184 | else: 185 | setattr(a, k, v) 186 | 187 | 188 | class ModelEMA: 189 | """ Model Exponential Moving Average from https://github.com/rwightman/pytorch-image-models 190 | Keep a moving average of everything in the model state_dict (parameters and buffers). 191 | This is intended to allow functionality like 192 | https://www.tensorflow.org/api_docs/python/tf/train/ExponentialMovingAverage 193 | A smoothed version of the weights is necessary for some training schemes to perform well. 194 | This class is sensitive where it is initialized in the sequence of model init, 195 | GPU assignment and distributed training wrappers. 196 | """ 197 | 198 | def __init__(self, model, decay=0.9999, updates=0): 199 | # Create EMA 200 | self.ema = deepcopy(model.module if is_parallel(model) else model).eval() # FP32 EMA 201 | # if next(model.parameters()).device.type != 'cpu': 202 | # self.ema.half() # FP16 EMA 203 | self.updates = updates # number of EMA updates 204 | self.decay = lambda x: decay * (1 - math.exp(-x / 2000)) # decay exponential ramp (to help early epochs) 205 | for p in self.ema.parameters(): 206 | p.requires_grad_(False) 207 | 208 | def update(self, model): 209 | # Update EMA parameters 210 | with torch.no_grad(): 211 | self.updates += 1 212 | d = self.decay(self.updates) 213 | 214 | msd = model.module.state_dict() if is_parallel(model) else model.state_dict() # model state_dict 215 | for k, v in self.ema.state_dict().items(): 216 | if v.dtype.is_floating_point: 217 | v *= d 218 | v += (1. - d) * msd[k].detach() 219 | 220 | def update_attr(self, model, include=(), exclude=('process_group', 'reducer')): 221 | # Update EMA attributes 222 | copy_attr(self.ema, model, include, exclude) 223 | -------------------------------------------------------------------------------- /pytorch_yolov5/weights/download_weights.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Download common models 3 | 4 | python -c " 5 | from utils.google_utils import *; 6 | attempt_download('weights/yolov5s.pt'); 7 | attempt_download('weights/yolov5m.pt'); 8 | attempt_download('weights/yolov5l.pt'); 9 | attempt_download('weights/yolov5x.pt') 10 | " 11 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # AutoLabelImg:MultiFunction AutoAnnotate Tools 2 | 3 | ![AutoLabelImg](./demo/demo.png) 4 | 5 | ### [English](./readme.md) | [中文](./readme_CN.md) 6 | 7 | ### Introduction: 8 | 9 | Based on [labelImg](https://github.com/tzutalin/labelImg), we add many useful annotate tools, in **Annoatate-tools** and **Video-tools** menu, including: 10 | 11 | - **`TOOL LIST`**: 12 | - [x] **Auto Annotate**:anto annotate images using yolov5 detector 13 | - [x] **Tracking Annotate**:using tracking method in opencv, annotate video data 14 | - [x] **Magnifing Lens**:helpful when annotating small objects, optional function 15 | - [x] **Data Agument**:data agument 16 | - [x] **Search System**:search details info based on your input 17 | - [x] other tools:label selecting/rename/counting, fix annotation, video merge/extract, welcome to try 18 | 19 | ### Demo: 20 | 21 | seen in Vtuber: 22 | 23 | [Auto Annotate](https://www.bilibili.com/video/BV1Uu411Q7WW/) 24 | 25 | [Tracking Annotate](https://www.bilibili.com/video/BV1XT4y1X7At/) 26 | 27 | [Magnifing Lens](https://www.bilibili.com/video/BV1nL4y1G7qm/) 28 | 29 | [Data Augment](https://www.bilibili.com/video/BV1Vu411R7Km/) 30 | 31 | [Search System](https://www.bilibili.com/video/BV1ZL4y137ar/) 32 | 33 | ### Update log: 34 | 35 | 2022.01.14:remove Retinanet( matain yolov5 only), and add label selecting when autolabeling 36 | 37 | 2022.01.11:imporve magnifing lens, more fluent and can be shut 38 | 39 | 2020.12.28:add video tracking annotate 40 | 41 | 2020.12.10:autolabelimg,version 1.0 42 | 43 | ## Installation: 44 | 45 | 1. clone this repo: 46 | 47 | ```bash 48 | git clone https://github.com/wufan-tb/AutoLabelImg 49 | cd AutoLabelImg 50 | ``` 51 | 52 | 2. install requirments: 53 | 54 | ```bash 55 | conda create -n {your_env_name} python=3.7.6 56 | conda activate {your_env_name} 57 | pip install -r requirements.txt 58 | ``` 59 | 60 | 3. compile source code: 61 | 62 | **Ubuntu User:** 63 | 64 | ``` 65 | sudo apt-get install pyqt5-dev-tools 66 | make qt5py3 67 | ``` 68 | 69 | **Windows User:** 70 | 71 | ``` 72 | pyrcc5 -o libs/resources.py resources.qrc 73 | ``` 74 | 75 | 4. prepare yolov5 weights file and move them to here: [official model zoo:[Yolov5](https://github.com/ultralytics/yolov5)] 76 | 77 | ```bash 78 | mv {your_model_weight.pt} pytorch_yolov5/weights/ 79 | ``` 80 | 81 | 5. open labelimg software 82 | 83 | ``` 84 | python labelImg.py 85 | ``` 86 | 87 | ## Set shortcut to open software[optional] 88 | 89 | **Windows User:** 90 | 91 | create a file:labelImg.bat, open it and type these text(D disk as an example): 92 | 93 | ```bash 94 | D: 95 | cd D:{path to your labelImg folder} 96 | start python labelImg.py 97 | exit 98 | ``` 99 | 100 | double click labelImg.bat to open the software. 101 | 102 | **Ubuntu User:** 103 | 104 | open environment setting file: 105 | 106 | ```bash 107 | vim ~/.bashrc 108 | ``` 109 | 110 | add this command: 111 | 112 | ```bash 113 | alias labelimg='cd {path to your labelImg folder} && python labelImg.py 114 | ``` 115 | 116 | source it: 117 | 118 | ```bash 119 | source ~/.bashrc 120 | ``` 121 | 122 | typing 'labeling' in terminal to open the software. 123 | 124 | ## Citation 125 | 126 | ``` 127 | { AutoLabelImg, 128 | author = {Wu Fan}, 129 | year = {2020}, 130 | url = {\url{https://https://github.com/wufan-tb/AutoLabelImg}} 131 | } 132 | ``` 133 | 134 | -------------------------------------------------------------------------------- /readme_CN.md: -------------------------------------------------------------------------------- 1 | # AutoLabelImg 多功能自动标注工具 2 | 3 | ![AutoLabelImg](./demo/demo.png) 4 | 5 | ### [English](./readme.md) | [中文](./readme_CN.md) 6 | 7 | ### 简介: 8 | 9 | 在[labelImg](https://github.com/tzutalin/labelImg)的基础上,增加了多种标注工具,放在**Annoatate-tools**和**Video-tools**两个菜单栏下面。具体功能包含如下: 10 | 11 | - **`TOOL LIST`**: 12 | - [x] **自动标注**:基于yolov5的模型自动标注 13 | - [x] **追踪标注**:利用opencv的追踪功能,自动标注视频数据 14 | - [x] **放大镜**:局部放大,对小目标的标注有帮助,可以关闭 15 | - [x] **数据增强**:随机使用平移,翻转,缩放,亮度,gama,模糊等手段增强图片 16 | - [x] **查询系统**:输入关键字获得详细说明信息 17 | - [x] 其他辅助工具:类别筛选/重命名/统计、标注文件属性校正、视频提取/合成、图片重命名等,可以利用查询系统查看详细信息,欢迎体验 18 | 19 | ### Demo: 20 | 21 | 因文件较大,视频已上传至B站 22 | 23 | [自动标注](https://www.bilibili.com/video/BV1Uu411Q7WW/) 24 | 25 | [视频追踪标注](https://www.bilibili.com/video/BV1XT4y1X7At/) 26 | 27 | [放大镜](https://www.bilibili.com/video/BV1nL4y1G7qm/) 28 | 29 | [数据增强](https://www.bilibili.com/video/BV1Vu411R7Km/) 30 | 31 | [查询系统](https://www.bilibili.com/video/BV1ZL4y137ar/) 32 | 33 | ### 更新日志: 34 | 35 | 2022.01.14:自动标注去掉Retinanet,仅保留yolov5,并增加标签选择 36 | 37 | 2022.01.11:优化放大镜卡顿现象,增加放大镜可关闭选项 38 | 39 | 2020.12.28:增加视频追踪标注工具 40 | 41 | 2020.12.10:初步把所有工具加进labelimg,版本1.0 42 | 43 | ## 安装步骤: 44 | 45 | 1. 复制仓库: 46 | 47 | ```bash 48 | git clone https://github.com/wufan-tb/AutoLabelImg 49 | cd AutoLabelImg 50 | ``` 51 | 52 | 2. 安装依赖: 53 | 54 | ```bash 55 | conda create -n {your_env_name} python=3.7.6 56 | conda activate {your_env_name} 57 | pip install -r requirements.txt 58 | ``` 59 | 60 | 3. 源码编译: 61 | 62 | **Ubuntu用户:** 63 | 64 | ``` 65 | sudo apt-get install pyqt5-dev-tools 66 | make qt5py3 67 | ``` 68 | 69 | **Windows用户:** 70 | 71 | ``` 72 | pyrcc5 -o libs/resources.py resources.qrc 73 | ``` 74 | 75 | 4. 准备yolov5模型并放置在如下位置,官方模型获取参考[Yolov5](https://github.com/ultralytics/yolov5) 76 | 77 | ```bash 78 | mv {your_model_weight.pt} pytorch_yolov5/weights/ 79 | ``` 80 | 81 | 5. 打开软件,开始标注 82 | 83 | ``` 84 | python labelImg.py 85 | ``` 86 | 87 | ## 设置快捷方式[非必须] 88 | 89 | **Windows用户:** 90 | 91 | 桌面创建labelImg.bat(可以新建文本文件,然后把后缀.txt改成.bat),右键用文本编辑器打开,键入下面内容(不一定是D盘,根据实际输入): 92 | 93 | ```bash 94 | D: 95 | cd D:{path to your labelImg folder} 96 | start python labelImg.py 97 | exit 98 | ``` 99 | 100 | 下面是一个实际案例,根据自己的实际路径修改第一二行即可: 101 | 102 | ``` 103 | D: 104 | cd D:\_project\AutoLabelImg 105 | start python labelImg.py 106 | exit 107 | ``` 108 | 109 | 双击labelImg.bat即可打开标注软件。 110 | 111 | **Ubuntu用户:** 112 | 113 | 打开环境变量文件: 114 | 115 | ```bash 116 | vim ~/.bashrc 117 | ``` 118 | 119 | 然后增加下面内容: 120 | 121 | ```bash 122 | alias labelimg='cd {path to your labelImg folder} && python labelImg.py 123 | ``` 124 | 125 | 使环境变量生效: 126 | 127 | ```bash 128 | source ~/.bashrc 129 | ``` 130 | 131 | 然后在终端输入指令'labelimg'即可打开标注软件。 132 | 133 | ## 引用 134 | 135 | ``` 136 | { AutoLabelImg, 137 | author = {Wu Fan}, 138 | year = {2020}, 139 | url = {\url{https://https://github.com/wufan-tb/AutoLabelImg}} 140 | } 141 | ``` 142 | 143 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | natsort 2 | strsimpy 3 | easygui 4 | pyautogui 5 | pyqt5>=5.14.1 6 | lxml>=4.6.5 7 | opencv-python>=4.1.1.26 8 | opencv-contrib-python>=4.1.1.26 9 | torch==1.13.1 10 | torchvision==0.7.0 11 | 12 | -------------------------------------------------------------------------------- /resources.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | resources/icons/help.png 5 | resources/icons/app.png 6 | resources/icons/expert2.png 7 | resources/icons/done.png 8 | resources/icons/file.png 9 | resources/icons/labels.png 10 | resources/icons/objects.png 11 | resources/icons/close.png 12 | resources/icons/fit-width.png 13 | resources/icons/fit-window.png 14 | resources/icons/undo.png 15 | resources/icons/eye.png 16 | resources/icons/quit.png 17 | resources/icons/copy.png 18 | resources/icons/edit.png 19 | resources/icons/open.png 20 | resources/icons/save.png 21 | resources/icons/format_voc.png 22 | resources/icons/format_yolo.png 23 | resources/icons/save-as.png 24 | resources/icons/color.png 25 | resources/icons/color_line.png 26 | resources/icons/zoom.png 27 | resources/icons/zoom-in.png 28 | resources/icons/zoom-out.png 29 | resources/icons/cancel.png 30 | resources/icons/next.png 31 | resources/icons/prev.png 32 | resources/icons/resetall.png 33 | resources/icons/verify.png 34 | resources/strings/strings.properties 35 | resources/strings/strings-zh-TW.properties 36 | resources/strings/strings-zh-CN.properties 37 | 38 | 39 | -------------------------------------------------------------------------------- /resources/icons/app.icns: -------------------------------------------------------------------------------- 1 | icns -------------------------------------------------------------------------------- /resources/icons/app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wufan-tb/AutoLabelImg/6bc97b6c19e9a471b427d3f442f45c594ce45ff6/resources/icons/app.png -------------------------------------------------------------------------------- /resources/icons/app.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.15, written by Peter Selinger 2001-2017 9 | 10 | 12 | 22 | 24 | 26 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /resources/icons/cancel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wufan-tb/AutoLabelImg/6bc97b6c19e9a471b427d3f442f45c594ce45ff6/resources/icons/cancel.png -------------------------------------------------------------------------------- /resources/icons/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wufan-tb/AutoLabelImg/6bc97b6c19e9a471b427d3f442f45c594ce45ff6/resources/icons/close.png -------------------------------------------------------------------------------- /resources/icons/color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wufan-tb/AutoLabelImg/6bc97b6c19e9a471b427d3f442f45c594ce45ff6/resources/icons/color.png -------------------------------------------------------------------------------- /resources/icons/color_line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wufan-tb/AutoLabelImg/6bc97b6c19e9a471b427d3f442f45c594ce45ff6/resources/icons/color_line.png -------------------------------------------------------------------------------- /resources/icons/copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wufan-tb/AutoLabelImg/6bc97b6c19e9a471b427d3f442f45c594ce45ff6/resources/icons/copy.png -------------------------------------------------------------------------------- /resources/icons/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wufan-tb/AutoLabelImg/6bc97b6c19e9a471b427d3f442f45c594ce45ff6/resources/icons/delete.png -------------------------------------------------------------------------------- /resources/icons/done.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wufan-tb/AutoLabelImg/6bc97b6c19e9a471b427d3f442f45c594ce45ff6/resources/icons/done.png -------------------------------------------------------------------------------- /resources/icons/done.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 27 | 29 | 36 | 40 | 44 | 48 | 52 | 56 | 57 | 64 | 68 | 72 | 76 | 80 | 84 | 85 | 92 | 96 | 100 | 104 | 108 | 112 | 113 | 121 | 125 | 129 | 133 | 137 | 141 | 142 | 143 | 145 | 147 | begin='' id='W5M0MpCehiHzreSzNTczkc9d' 148 | 150 | 151 | 153 | 154 | Adobe PDF library 5.00 155 | 156 | 158 | 160 | 162 | 163 | 2003-12-22T22:34:35+02:00 164 | 165 | 2004-04-17T21:25:50Z 166 | 167 | Adobe Illustrator 10.0 168 | 169 | 2004-01-19T17:51:02+01:00 170 | 171 | 172 | 174 | 175 | JPEG 176 | 177 | 256 178 | 179 | 256 180 | 181 | /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA 182 | AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK 183 | DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f 184 | Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgBAAEAAwER 185 | AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA 186 | AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB 187 | UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE 188 | 1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ 189 | qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy 190 | obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp 191 | 0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo 192 | +DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9U4q7FXYq7FXYq7FXYq7 193 | FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F 194 | XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX 195 | Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY 196 | 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 342 | 343 | 344 | 345 | 346 | 348 | 349 | uuid:4b4d592f-95b8-4bcd-a892-74a536c5e52f 350 | 351 | 353 | 354 | image/svg+xml 355 | 356 | 357 | 359 | test.ai 360 | 361 | 362 | 363 | 364 | 365 | end='w' 366 | 367 | 372 | 376 | 380 | 384 | 388 | 392 | 396 | 400 | 401 | -------------------------------------------------------------------------------- /resources/icons/edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wufan-tb/AutoLabelImg/6bc97b6c19e9a471b427d3f442f45c594ce45ff6/resources/icons/edit.png -------------------------------------------------------------------------------- /resources/icons/expert1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wufan-tb/AutoLabelImg/6bc97b6c19e9a471b427d3f442f45c594ce45ff6/resources/icons/expert1.png -------------------------------------------------------------------------------- /resources/icons/expert2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wufan-tb/AutoLabelImg/6bc97b6c19e9a471b427d3f442f45c594ce45ff6/resources/icons/expert2.png -------------------------------------------------------------------------------- /resources/icons/eye.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wufan-tb/AutoLabelImg/6bc97b6c19e9a471b427d3f442f45c594ce45ff6/resources/icons/eye.png -------------------------------------------------------------------------------- /resources/icons/feBlend-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wufan-tb/AutoLabelImg/6bc97b6c19e9a471b427d3f442f45c594ce45ff6/resources/icons/feBlend-icon.png -------------------------------------------------------------------------------- /resources/icons/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wufan-tb/AutoLabelImg/6bc97b6c19e9a471b427d3f442f45c594ce45ff6/resources/icons/file.png -------------------------------------------------------------------------------- /resources/icons/fit-width.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wufan-tb/AutoLabelImg/6bc97b6c19e9a471b427d3f442f45c594ce45ff6/resources/icons/fit-width.png -------------------------------------------------------------------------------- /resources/icons/fit-window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wufan-tb/AutoLabelImg/6bc97b6c19e9a471b427d3f442f45c594ce45ff6/resources/icons/fit-window.png -------------------------------------------------------------------------------- /resources/icons/fit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wufan-tb/AutoLabelImg/6bc97b6c19e9a471b427d3f442f45c594ce45ff6/resources/icons/fit.png -------------------------------------------------------------------------------- /resources/icons/format_voc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wufan-tb/AutoLabelImg/6bc97b6c19e9a471b427d3f442f45c594ce45ff6/resources/icons/format_voc.png -------------------------------------------------------------------------------- /resources/icons/format_yolo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wufan-tb/AutoLabelImg/6bc97b6c19e9a471b427d3f442f45c594ce45ff6/resources/icons/format_yolo.png -------------------------------------------------------------------------------- /resources/icons/help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wufan-tb/AutoLabelImg/6bc97b6c19e9a471b427d3f442f45c594ce45ff6/resources/icons/help.png -------------------------------------------------------------------------------- /resources/icons/labels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wufan-tb/AutoLabelImg/6bc97b6c19e9a471b427d3f442f45c594ce45ff6/resources/icons/labels.png -------------------------------------------------------------------------------- /resources/icons/new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wufan-tb/AutoLabelImg/6bc97b6c19e9a471b427d3f442f45c594ce45ff6/resources/icons/new.png -------------------------------------------------------------------------------- /resources/icons/next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wufan-tb/AutoLabelImg/6bc97b6c19e9a471b427d3f442f45c594ce45ff6/resources/icons/next.png -------------------------------------------------------------------------------- /resources/icons/objects.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wufan-tb/AutoLabelImg/6bc97b6c19e9a471b427d3f442f45c594ce45ff6/resources/icons/objects.png -------------------------------------------------------------------------------- /resources/icons/open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wufan-tb/AutoLabelImg/6bc97b6c19e9a471b427d3f442f45c594ce45ff6/resources/icons/open.png -------------------------------------------------------------------------------- /resources/icons/open.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | image/svg+xml 42 | 43 | 45 | 52 | 54 | 55 | 56 | 58 | 60 | 67 | 68 | 70 | 77 | 81 | 85 | 89 | 92 | 95 | 98 | 101 | 104 | 105 | 109 | 116 | 120 | 124 | 128 | 132 | 135 | 138 | 141 | 144 | 147 | 150 | 153 | 154 | 158 | 165 | 169 | 173 | 177 | 181 | 185 | 188 | 191 | 194 | 195 | 199 | 206 | 210 | 214 | 218 | 222 | 226 | 229 | 232 | 235 | 236 | 240 | 247 | 251 | 255 | 258 | 261 | 264 | 265 | 269 | 276 | 280 | 284 | 288 | 291 | 294 | 297 | 300 | 303 | 304 | 308 | 315 | 319 | 323 | 327 | 330 | 333 | 336 | 339 | 342 | 343 | 347 | 354 | 358 | 362 | 365 | 368 | 371 | 372 | 376 | 383 | 387 | 391 | 395 | 399 | 403 | 407 | 411 | 415 | 418 | 421 | 424 | 425 | 429 | 433 | 440 | 444 | 448 | 451 | 454 | 457 | 458 | 462 | 466 | 473 | 477 | 481 | 484 | 487 | 490 | 491 | 495 | 502 | 506 | 510 | 514 | 517 | 520 | 523 | 526 | 529 | 530 | 534 | 541 | 545 | 549 | 552 | 555 | 558 | 559 | 563 | 567 | 574 | 575 | 576 | 577 | -------------------------------------------------------------------------------- /resources/icons/prev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wufan-tb/AutoLabelImg/6bc97b6c19e9a471b427d3f442f45c594ce45ff6/resources/icons/prev.png -------------------------------------------------------------------------------- /resources/icons/quit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wufan-tb/AutoLabelImg/6bc97b6c19e9a471b427d3f442f45c594ce45ff6/resources/icons/quit.png -------------------------------------------------------------------------------- /resources/icons/resetall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wufan-tb/AutoLabelImg/6bc97b6c19e9a471b427d3f442f45c594ce45ff6/resources/icons/resetall.png -------------------------------------------------------------------------------- /resources/icons/save-as.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wufan-tb/AutoLabelImg/6bc97b6c19e9a471b427d3f442f45c594ce45ff6/resources/icons/save-as.png -------------------------------------------------------------------------------- /resources/icons/save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wufan-tb/AutoLabelImg/6bc97b6c19e9a471b427d3f442f45c594ce45ff6/resources/icons/save.png -------------------------------------------------------------------------------- /resources/icons/undo-cross.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wufan-tb/AutoLabelImg/6bc97b6c19e9a471b427d3f442f45c594ce45ff6/resources/icons/undo-cross.png -------------------------------------------------------------------------------- /resources/icons/undo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wufan-tb/AutoLabelImg/6bc97b6c19e9a471b427d3f442f45c594ce45ff6/resources/icons/undo.png -------------------------------------------------------------------------------- /resources/icons/verify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wufan-tb/AutoLabelImg/6bc97b6c19e9a471b427d3f442f45c594ce45ff6/resources/icons/verify.png -------------------------------------------------------------------------------- /resources/icons/zoom-in.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wufan-tb/AutoLabelImg/6bc97b6c19e9a471b427d3f442f45c594ce45ff6/resources/icons/zoom-in.png -------------------------------------------------------------------------------- /resources/icons/zoom-out.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wufan-tb/AutoLabelImg/6bc97b6c19e9a471b427d3f442f45c594ce45ff6/resources/icons/zoom-out.png -------------------------------------------------------------------------------- /resources/icons/zoom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wufan-tb/AutoLabelImg/6bc97b6c19e9a471b427d3f442f45c594ce45ff6/resources/icons/zoom.png -------------------------------------------------------------------------------- /resources/strings/strings-zh-CN.properties: -------------------------------------------------------------------------------- 1 | saveAsDetail=將标签保存到其他文件 2 | changeSaveDir=改变存放目录 3 | openFile=打开文件 4 | shapeLineColorDetail=更改线条颜色 5 | resetAll=全部重置 6 | crtBox=创建区块 7 | crtBoxDetail=创建一个新的区块 8 | dupBoxDetail=复制区块 9 | verifyImg=验证图像 10 | zoominDetail=放大 11 | verifyImgDetail=验证图像 12 | saveDetail=保存标签文件 13 | openFileDetail=打开图像文件 14 | fitWidthDetail=调整宽度适应到窗口宽度 15 | tutorial=YouTube教学 16 | editLabel=编辑标签 17 | openAnnotationDetail=打开标签文件 18 | quit=退出 19 | shapeFillColorDetail=更改填充颜色 20 | closeCurDetail=关闭当前文件 21 | closeCur=关闭文件 22 | fitWin=调整到窗口大小 23 | delBox=删除选择的区块 24 | boxLineColorDetail=选择线框颜色 25 | originalsize=原始大小 26 | resetAllDetail=重置所有设定 27 | zoomoutDetail=放大画面 28 | save=保存 29 | saveAs=另存为 30 | fitWinDetail=缩放到当前窗口大小 31 | openDir=打开目录 32 | showHide=显示/隐藏标签 33 | changeSaveFormat=更改存储格式 34 | shapeFillColor=填充颜色 35 | quitApp=退出程序 36 | dupBox=复制区块 37 | delBoxDetail=删除区块 38 | zoomin=放大画面 39 | info=信息 40 | openAnnotation=开启标签 41 | prevImgDetail=上一个图像 42 | fitWidth=缩放到跟当前画面一样宽 43 | zoomout=缩小画面 44 | changeSavedAnnotationDir=更改保存标签文件的预设目录 45 | nextImgDetail=下一个图像 46 | originalsizeDetail=放大到原始大小 47 | prevImg=上一个图像 48 | tutorialDetail=显示示范内容 49 | shapeLineColor=形状线条颜色 50 | boxLineColor=区块线条颜色 51 | editLabelDetail=修改当前所选的区块颜色 52 | nextImg=下一个图片 53 | useDefaultLabel=使用预设标签 54 | useDifficult=有难度的 55 | boxLabelText=区块的标签 56 | labels=标签 57 | autoSaveMode=自动保存模式 58 | singleClsMode=单一类别模式 59 | displayLabel=显示类别 60 | fileList=文件列表 61 | files=文件 62 | advancedMode=专家模式 63 | advancedModeDetail=切换到专家模式 64 | showAllBoxDetail=显示所有区块 65 | hideAllBoxDetail=隐藏所有区块 66 | -------------------------------------------------------------------------------- /resources/strings/strings-zh-TW.properties: -------------------------------------------------------------------------------- 1 | saveAsDetail=將標籤保存到其他文件 2 | changeSaveDir=改變存放目錄 3 | openFile=開啟檔案 4 | shapeLineColorDetail=更改線條顏色 5 | resetAll=重置 6 | crtBox=創建區塊 7 | crtBoxDetail=畫一個區塊 8 | dupBoxDetail=複製區塊 9 | verifyImg=驗證圖像 10 | zoominDetail=放大 11 | verifyImgDetail=驗證圖像 12 | saveDetail=將標籤存到 13 | openFileDetail=打開圖像 14 | fitWidthDetail=調整到窗口寬度 15 | tutorial=YouTube教學 16 | editLabel=編輯標籤 17 | openAnnotationDetail=打開標籤文件 18 | quit=結束 19 | shapeFillColorDetail=更改填充顏色 20 | closeCurDetail=關閉目前檔案 21 | closeCur=關閉 22 | fitWin=調整到跟窗口一樣大小 23 | delBox=刪除選取區塊 24 | boxLineColorDetail=選擇框線顏色 25 | originalsize=原始大小 26 | resetAllDetail=重設所有設定 27 | zoomoutDetail=畫面放大 28 | save=儲存 29 | saveAs=另存為 30 | fitWinDetail=縮放到窗口一樣 31 | openDir=開啟目錄 32 | showHide=顯示/隱藏標籤 33 | changeSaveFormat=更改儲存格式 34 | shapeFillColor=填充顏色 35 | quitApp=離開本程式 36 | dupBox=複製區塊 37 | delBoxDetail=刪除區塊 38 | zoomin=放大畫面 39 | info=資訊 40 | openAnnotation=開啟標籤 41 | prevImgDetail=上一個圖像 42 | fitWidth=縮放到跟畫面一樣寬 43 | zoomout=縮小畫面 44 | changeSavedAnnotationDir=更改預設標籤存的目錄 45 | nextImgDetail=下一個圖像 46 | originalsizeDetail=放大到原始大小 47 | prevImg=上一個圖像 48 | tutorialDetail=顯示示範內容 49 | shapeLineColor=形狀線條顏色 50 | boxLineColor=日期分隔線顏色 51 | editLabelDetail=修改所選區塊的標籤 52 | nextImg=下一張圖片 53 | useDefaultLabel=使用預設標籤 54 | useDifficult=有難度的 55 | boxLabelText=區塊的標籤 56 | labels=標籤 57 | autoSaveMode=自動儲存模式 58 | singleClsMode=單一類別模式 59 | displayLabel=顯示類別 60 | fileList=檔案清單 61 | files=檔案 62 | advancedMode=進階模式 63 | advancedModeDetail=切到進階模式 64 | showAllBoxDetail=顯示所有區塊 65 | hideAllBoxDetail=隱藏所有區塊 -------------------------------------------------------------------------------- /resources/strings/strings.properties: -------------------------------------------------------------------------------- 1 | openFile=Open 2 | openFileDetail=Open image or label file 3 | quit=Quit 4 | quitApp=Quit application 5 | openDir=Open Dir 6 | changeSavedAnnotationDir=Change default saved Annotation dir 7 | openAnnotation=Open Annotation 8 | openAnnotationDetail=Open an annotation file 9 | changeSaveDir=Change Save Dir 10 | nextImg=Next Image 11 | nextImgDetail=Open the next Image 12 | prevImg=Prev Image 13 | prevImgDetail=Open the previous Image 14 | verifyImg=Verify Image 15 | verifyImgDetail=Verify Image 16 | save=Save 17 | saveDetail=Save the labels to a file 18 | changeSaveFormat=Change save format 19 | saveAs=Save As 20 | saveAsDetail=Save the labels to a different file 21 | closeCur=Close 22 | closeCurDetail=Close the current file 23 | resetAll=Reset All 24 | resetAllDetail=Reset All 25 | boxLineColor=Box Line Color 26 | boxLineColorDetail=Choose Box line color 27 | crtBox=Create\nRectBox 28 | crtBoxDetail=Draw a new box 29 | delBox=Delete\nRectBox 30 | delBoxDetail=Remove the box 31 | dupBox=Duplicate\nRectBox 32 | dupBoxDetail=Create a duplicate of the selected box 33 | tutorial=Tutorial 34 | tutorialDetail=Show demo 35 | info=Information 36 | zoomin=Zoom In 37 | zoominDetail=Increase zoom level 38 | zoomout=Zoom Out 39 | zoomoutDetail=Decrease zoom level 40 | originalsize=Original size 41 | originalsizeDetail=Zoom to original size 42 | fitWin=Fit Window 43 | fitWinDetail=Zoom follows window size 44 | fitWidth=Fit Width 45 | fitWidthDetail=Zoom follows window width 46 | editLabel=Edit Label 47 | editLabelDetail=Modify the label of the selected Box 48 | shapeLineColor=Shape Line Color 49 | shapeLineColorDetail=Change the line color for this specific shape 50 | shapeFillColor=Shape Fill Color 51 | shapeFillColorDetail=Change the fill color for this specific shape 52 | showHide=Show/Hide Label Panel 53 | useDefaultLabel=Use default label 54 | useDifficult=difficult 55 | boxLabelText=Box Labels 56 | labels=Labels 57 | autoSaveMode=Auto Save mode 58 | singleClsMode=Single Class Mode 59 | displayLabel=Display Labels 60 | fileList=File List 61 | files=Files 62 | advancedMode=Advanced Mode 63 | advancedModeDetail=Swtich to advanced mode 64 | showAllBoxDetail=Show all bounding boxes 65 | hideAllBoxDetail=Hide all bounding boxes 66 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | commit = True 3 | tag = True 4 | 5 | [bumpversion:file:setup.py] 6 | 7 | [bdist_wheel] 8 | universal = 1 9 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from setuptools import setup, find_packages, Command 5 | from sys import platform as _platform 6 | from shutil import rmtree 7 | import sys 8 | import os 9 | 10 | here = os.path.abspath(os.path.dirname(__file__)) 11 | NAME = 'labelImg' 12 | REQUIRES_PYTHON = '>=3.0.0' 13 | REQUIRED_DEP = ['pyqt5', 'lxml'] 14 | about = {} 15 | 16 | with open(os.path.join(here, 'libs', '__init__.py')) as f: 17 | exec(f.read(), about) 18 | 19 | with open('README.rst') as readme_file: 20 | readme = readme_file.read() 21 | 22 | with open('HISTORY.rst') as history_file: 23 | history = history_file.read() 24 | 25 | 26 | # OS specific settings 27 | SET_REQUIRES = [] 28 | if _platform == "linux" or _platform == "linux2": 29 | # linux 30 | print('linux') 31 | elif _platform == "darwin": 32 | # MAC OS X 33 | SET_REQUIRES.append('py2app') 34 | 35 | required_packages = find_packages() 36 | required_packages.append('labelImg') 37 | 38 | APP = [NAME + '.py'] 39 | OPTIONS = { 40 | 'argv_emulation': True, 41 | 'iconfile': 'resources/icons/app.icns' 42 | } 43 | 44 | class UploadCommand(Command): 45 | """Support setup.py upload.""" 46 | 47 | description=readme + '\n\n' + history, 48 | 49 | user_options = [] 50 | 51 | @staticmethod 52 | def status(s): 53 | """Prints things in bold.""" 54 | print('\033[1m{0}\033[0m'.format(s)) 55 | 56 | def initialize_options(self): 57 | pass 58 | 59 | def finalize_options(self): 60 | pass 61 | 62 | def run(self): 63 | try: 64 | self.status('Removing previous builds…') 65 | rmtree(os.path.join(here, 'dist')) 66 | except OSError: 67 | self.status('Fail to remove previous builds..') 68 | pass 69 | 70 | self.status('Building Source and Wheel (universal) distribution…') 71 | os.system( 72 | '{0} setup.py sdist bdist_wheel --universal'.format(sys.executable)) 73 | 74 | self.status('Uploading the package to PyPI via Twine…') 75 | os.system('twine upload dist/*') 76 | 77 | self.status('Pushing git tags…') 78 | os.system('git tag -d v{0}'.format(about['__version__'])) 79 | os.system('git tag v{0}'.format(about['__version__'])) 80 | # os.system('git push --tags') 81 | 82 | sys.exit() 83 | 84 | 85 | setup( 86 | app=APP, 87 | name=NAME, 88 | version=about['__version__'], 89 | description="LabelImg is a graphical image annotation tool and label object bounding boxes in images", 90 | long_description=readme + '\n\n' + history, 91 | author="TzuTa Lin", 92 | author_email='tzu.ta.lin@gmail.com', 93 | url='https://github.com/tzutalin/labelImg', 94 | python_requires=REQUIRES_PYTHON, 95 | package_dir={'labelImg': '.'}, 96 | packages=required_packages, 97 | entry_points={ 98 | 'console_scripts': [ 99 | 'labelImg=labelImg.labelImg:main' 100 | ] 101 | }, 102 | include_package_data=True, 103 | install_requires=REQUIRED_DEP, 104 | license="MIT license", 105 | zip_safe=False, 106 | keywords='labelImg labelTool development annotation deeplearning', 107 | classifiers=[ 108 | 'Development Status :: 5 - Production/Stable', 109 | 'Intended Audience :: Developers', 110 | 'License :: OSI Approved :: MIT License', 111 | 'Natural Language :: English', 112 | 'Programming Language :: Python :: 3', 113 | 'Programming Language :: Python :: 3.3', 114 | 'Programming Language :: Python :: 3.4', 115 | 'Programming Language :: Python :: 3.5', 116 | 'Programming Language :: Python :: 3.6', 117 | 'Programming Language :: Python :: 3.7', 118 | ], 119 | package_data={'data/predefined_classes.txt': ['data/predefined_classes.txt']}, 120 | options={'py2app': OPTIONS}, 121 | setup_requires=SET_REQUIRES, 122 | # $ setup.py publish support. 123 | cmdclass={ 124 | 'upload': UploadCommand, 125 | } 126 | ) 127 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | test.xml 2 | -------------------------------------------------------------------------------- /tests/test.512.512.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wufan-tb/AutoLabelImg/6bc97b6c19e9a471b427d3f442f45c594ce45ff6/tests/test.512.512.bmp -------------------------------------------------------------------------------- /tests/test_io.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import unittest 4 | 5 | class TestPascalVocRW(unittest.TestCase): 6 | 7 | def test_upper(self): 8 | dir_name = os.path.abspath(os.path.dirname(__file__)) 9 | libs_path = os.path.join(dir_name, '..', 'libs') 10 | sys.path.insert(0, libs_path) 11 | from pascal_voc_io import PascalVocWriter 12 | from pascal_voc_io import PascalVocReader 13 | 14 | # Test Write/Read 15 | writer = PascalVocWriter('tests', 'test', (512, 512, 1), localImgPath='tests/test.512.512.bmp') 16 | difficult = 1 17 | writer.addBndBox(60, 40, 430, 504, 'person', difficult) 18 | writer.addBndBox(113, 40, 450, 403, 'face', difficult) 19 | writer.save('tests/test.xml') 20 | 21 | reader = PascalVocReader('tests/test.xml') 22 | shapes = reader.getShapes() 23 | 24 | personBndBox = shapes[0] 25 | face = shapes[1] 26 | self.assertEqual(personBndBox[0], 'person') 27 | self.assertEqual(personBndBox[1], [(60, 40), (430, 40), (430, 504), (60, 504)]) 28 | self.assertEqual(face[0], 'face') 29 | self.assertEqual(face[1], [(113, 40), (450, 40), (450, 403), (113, 403)]) 30 | 31 | if __name__ == '__main__': 32 | unittest.main() 33 | -------------------------------------------------------------------------------- /tests/test_qt.py: -------------------------------------------------------------------------------- 1 | 2 | from unittest import TestCase 3 | 4 | from labelImg import get_main_app 5 | 6 | 7 | class TestMainWindow(TestCase): 8 | 9 | app = None 10 | win = None 11 | 12 | def setUp(self): 13 | self.app, self.win = get_main_app() 14 | 15 | def tearDown(self): 16 | self.win.close() 17 | self.app.quit() 18 | 19 | def test_noop(self): 20 | pass 21 | -------------------------------------------------------------------------------- /tests/test_settings.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | import time 5 | import unittest 6 | 7 | __author__ = 'TzuTaLin' 8 | 9 | dir_name = os.path.abspath(os.path.dirname(__file__)) 10 | libs_path = os.path.join(dir_name, '..', 'libs') 11 | sys.path.insert(0, libs_path) 12 | from settings import Settings 13 | 14 | class TestSettings(unittest.TestCase): 15 | 16 | def test_basic(self): 17 | settings = Settings() 18 | settings['test0'] = 'hello' 19 | settings['test1'] = 10 20 | settings['test2'] = [0, 2, 3] 21 | self.assertEqual(settings.get('test3', 3), 3) 22 | self.assertEqual(settings.save(), True) 23 | 24 | settings.load() 25 | self.assertEqual(settings.get('test0'), 'hello') 26 | self.assertEqual(settings.get('test1'), 10) 27 | 28 | settings.reset() 29 | 30 | 31 | 32 | if __name__ == '__main__': 33 | unittest.main() 34 | -------------------------------------------------------------------------------- /tests/test_stringBundle.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import unittest 4 | import resources 5 | from stringBundle import StringBundle 6 | 7 | class TestStringBundle(unittest.TestCase): 8 | 9 | def test_loadDefaultBundle_withoutError(self): 10 | strBundle = StringBundle.getBundle('en') 11 | self.assertEqual(strBundle.getString("openDir"), 'Open Dir', 'Fail to load the default bundle') 12 | 13 | def test_fallback_withoutError(self): 14 | strBundle = StringBundle.getBundle('zh-TW') 15 | self.assertEqual(strBundle.getString("openDir"), u'\u958B\u555F\u76EE\u9304', 'Fail to load the zh-TW bundle') 16 | 17 | def test_setInvaleLocaleToEnv_printErrorMsg(self): 18 | prev_lc = os.environ['LC_ALL'] 19 | prev_lang = os.environ['LANG'] 20 | os.environ['LC_ALL'] = 'UTF-8' 21 | os.environ['LANG'] = 'UTF-8' 22 | strBundle = StringBundle.getBundle() 23 | self.assertEqual(strBundle.getString("openDir"), 'Open Dir', 'Fail to load the default bundle') 24 | os.environ['LC_ALL'] = prev_lc 25 | os.environ['LANG'] = prev_lang 26 | 27 | 28 | if __name__ == '__main__': 29 | unittest.main() 30 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import unittest 4 | from libs.utils import struct, newAction, newIcon, addActions, fmtShortcut, generateColorByText, natural_sort 5 | 6 | class TestUtils(unittest.TestCase): 7 | 8 | def test_generateColorByGivingUniceText_noError(self): 9 | res = generateColorByText(u'\u958B\u555F\u76EE\u9304') 10 | self.assertTrue(res.green() >= 0) 11 | self.assertTrue(res.red() >= 0) 12 | self.assertTrue(res.blue() >= 0) 13 | 14 | def test_nautalSort_noError(self): 15 | l1 = ['f1', 'f11', 'f3' ] 16 | exptected_l1 = ['f1', 'f3', 'f11'] 17 | natural_sort(l1) 18 | for idx, val in enumerate(l1): 19 | self.assertTrue(val == exptected_l1[idx]) 20 | 21 | if __name__ == '__main__': 22 | unittest.main() 23 | -------------------------------------------------------------------------------- /tests/臉書.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wufan-tb/AutoLabelImg/6bc97b6c19e9a471b427d3f442f45c594ce45ff6/tests/臉書.jpg --------------------------------------------------------------------------------