├── .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 |
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 | 
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 | 
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 |
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 |
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 |
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------