fix augmentation - correct bboxes to be within borders+margin

Add classes = 80 as default number in yaml file
This commit is contained in:
zxsanny
2024-09-24 12:56:05 +03:00
parent 42ea4ebc2f
commit c234e8b190
9 changed files with 178 additions and 78 deletions
+1 -2
View File
@@ -36,13 +36,12 @@ Linux
``` ```
**3. Fix possible problems** **3. Fix possible problems**
* Windows:
* cv2.error: OpenCV(4.10.0) ...\window.cpp:1301: error: (-2:Unspecified error) * cv2.error: OpenCV(4.10.0) ...\window.cpp:1301: error: (-2:Unspecified error)
``` ```
pip uninstall opencv-python pip uninstall opencv-python
pip install opencv-python pip install opencv-python
``` ```
* fbgemm.dll error * fbgemm.dll error (Windows specific)
``` ```
copypaste libomp140.x86_64.dll to C:\Windows\System32 copypaste libomp140.x86_64.dll to C:\Windows\System32
``` ```
+10 -10
View File
@@ -1,61 +1,61 @@
[ [
{ {
"Id": 0, "Id": 0,
"Name": "Броньована техніка", "Name": "Armored-Vehicle",
"Color": "#80FF0000", "Color": "#80FF0000",
"ColorBrush": "#80FF0000" "ColorBrush": "#80FF0000"
}, },
{ {
"Id": 1, "Id": 1,
"Name": "Вантажівка", "Name": "Truck",
"Color": "#8000FF00", "Color": "#8000FF00",
"ColorBrush": "#8000FF00" "ColorBrush": "#8000FF00"
}, },
{ {
"Id": 2, "Id": 2,
"Name": "Машина легкова", "Name": "Vehicle",
"Color": "#800000FF", "Color": "#800000FF",
"ColorBrush": "#800000FF" "ColorBrush": "#800000FF"
}, },
{ {
"Id": 3, "Id": 3,
"Name": "Артилерія", "Name": "Artillery",
"Color": "#80FFFF00", "Color": "#80FFFF00",
"ColorBrush": "#80FFFF00" "ColorBrush": "#80FFFF00"
}, },
{ {
"Id": 4, "Id": 4,
"Name": "Тінь від техніки", "Name": "Shadow",
"Color": "#80FF00FF", "Color": "#80FF00FF",
"ColorBrush": "#80FF00FF" "ColorBrush": "#80FF00FF"
}, },
{ {
"Id": 5, "Id": 5,
"Name": "Окопи", "Name": "Trenches",
"Color": "#8000FFFF", "Color": "#8000FFFF",
"ColorBrush": "#8000FFFF" "ColorBrush": "#8000FFFF"
}, },
{ {
"Id": 6, "Id": 6,
"Name": "Військовий", "Name": "Military-men",
"Color": "#80000000", "Color": "#80000000",
"ColorBrush": "#80000000" "ColorBrush": "#80000000"
}, },
{ {
"Id": 7, "Id": 7,
"Name": "Накати", "Name": "Tyre-tracks",
"Color": "#80800000", "Color": "#80800000",
"ColorBrush": "#80800000" "ColorBrush": "#80800000"
}, },
{ {
"Id": 8, "Id": 8,
"Name": "Танк з додатковим захистом", "Name": "Additional-armored-tank",
"Color": "#80008000", "Color": "#80008000",
"ColorBrush": "#80008000" "ColorBrush": "#80008000"
}, },
{ {
"Id": 9, "Id": 9,
"Name": "Дим", "Name": "Smoke",
"Color": "#80000080", "Color": "#80000080",
"ColorBrush": "#80000080" "ColorBrush": "#80000080"
} }
+9 -9
View File
@@ -4,8 +4,6 @@ import xml.etree.cElementTree as et
from pathlib import Path from pathlib import Path
import cv2 import cv2
labels_dir = 'labels'
images_dir = 'images'
tag_size = 'size' tag_size = 'size'
tag_object = 'object' tag_object = 'object'
@@ -17,9 +15,11 @@ default_class = 1
image_extensions = ['jpg', 'png', 'jpeg'] image_extensions = ['jpg', 'png', 'jpeg']
def convert(folder, read_annotations, ann_format): def convert(folder, dest_folder, read_annotations, ann_format):
os.makedirs(images_dir, exist_ok=True) dest_images_dir = os.path.join(dest_folder, 'images')
os.makedirs(labels_dir, exist_ok=True) dest_labels_dir = os.path.join(dest_folder, 'labels')
os.makedirs(dest_images_dir, exist_ok=True)
os.makedirs(dest_labels_dir, exist_ok=True)
for f in os.listdir(folder): for f in os.listdir(folder):
if not f[-3:] in image_extensions: if not f[-3:] in image_extensions:
@@ -39,8 +39,8 @@ def convert(folder, read_annotations, ann_format):
except Exception as e: except Exception as e:
print(f'Error conversion for {f}. Error: {e}') print(f'Error conversion for {f}. Error: {e}')
shutil.copy(os.path.join(folder, f), os.path.join(images_dir, f)) shutil.copy(os.path.join(folder, f), os.path.join(dest_images_dir, f))
with open(os.path.join(labels_dir, f'{Path(label).stem}.txt'), 'w') as new_label_file: with open(os.path.join(dest_labels_dir, f'{Path(label).stem}.txt'), 'w') as new_label_file:
new_label_file.writelines(lines) new_label_file.writelines(lines)
new_label_file.close() new_label_file.close()
print(f'Image {f} has been processed successfully') print(f'Image {f} has been processed successfully')
@@ -115,5 +115,5 @@ def rename_images(folder):
if __name__ == '__main__': if __name__ == '__main__':
convert('datasets/others/UAVHeightImages', read_bbox_oriented, 'txt') convert('/azaion/datasets/others/cars_height', '/azaion/datasets/converted', read_bbox_oriented, 'txt')
convert('datasets/others/UAVimages', read_pascal_voc, 'xml') convert('/azaion/datasets/others/cars', '/azaion/datasets/converted', read_pascal_voc, 'xml')
+41
View File
@@ -0,0 +1,41 @@
# prerequisites
sudo apt install python3.12-venv python-is-python3
if [ ! -f azaion.pt ]; then
echo "----ERROR!----"
echo "Script requires azaion.pt file model in this folder!!!"
echo "----ERROR!----"
exit
fi
# convert PT to ONNX
git clone https://github.com/airockchip/ultralytics_yolov8
cd ultralytics_yolov8
python -m venv env
source env/bin/activate
pip install .
cp ../azaion.pt .
sed -i -E "s/(model:).*$/\1 azaion.pt/" ultralytics/cfg/default.yaml
export PYTHONPATH=./
python ./ultralytics/engine/exporter.py
cp azaion.onnx ../
cd ..
exit
# convert ONNX to RKNN
wget -c https://mirrors.bfsu.edu.cn/anaconda/miniconda/Miniconda3-latest-Linux-x86_64.sh
chmod +x Miniconda3-latest-Linux-x86_64.sh
bash Miniconda3-latest-Linux-x86_64.sh
source ~/miniconda3/bin/activate
conda create -n toolkit2 python=3.11
conda activate toolkit2
git clone https://github.com/rockchip-linux/rknn-toolkit2.git
cd rknn-toolkit2/rknn-toolkit2/packages
pip install -r requirements_cp311-1.6.0.txt
pip install rknn_toolkit2-1.6.0+81f21f4d-cp311-cp311-linux_x86_64.whl
cd ~/opi5-rknn
git clone https://github.com/airockchip/rknn_model_zoo.git
cd rknn_model_zoo/examples/yolov8/python
python convert.py ~/azaion/models/azaion-2024-08-13.onnx rk3588 i8
cp ../model/yolov8.rknn ~/azaion/models/azaion-2024-08-13.rknn # Output file form convert script is hard-coded...
+32 -5
View File
@@ -11,10 +11,35 @@ from constants import (data_images_dir, data_labels_dir, processed_images_dir, p
from dto.imageLabel import ImageLabel from dto.imageLabel import ImageLabel
def correct_bboxes(labels):
margin = 0.0005
min_size = 0.01
res = []
for bboxes in labels:
x = bboxes[0]
y = bboxes[1]
half_width = 0.5*bboxes[2]
half_height = 0.5*bboxes[3]
# calc how much bboxes are outside borders ( +small margin ).
# value should be negative. If it's positive, then put 0, as no correction
w_diff = min( (1 - margin) - (x + half_width), (x - half_width) - margin, 0 )
w = bboxes[2] + 2*w_diff
if w < min_size:
continue
h_diff = min( (1 - margin) - (y + half_height), ((y - half_height) - margin), 0)
h = bboxes[3] + 2 * h_diff
if h < min_size:
continue
res.append([x, y, w, h, bboxes[4]])
return res
pass
def image_processing(img_ann: ImageLabel) -> [ImageLabel]: def image_processing(img_ann: ImageLabel) -> [ImageLabel]:
transforms = [ transforms = [
A.Compose([A.HorizontalFlip(always_apply=True)], A.Compose([A.HorizontalFlip(always_apply=True)],
bbox_params=A.BboxParams(format='yolo')), bbox_params=A.BboxParams(format='yolo', )),
A.Compose([A.RandomBrightnessContrast(always_apply=True)], A.Compose([A.RandomBrightnessContrast(always_apply=True)],
bbox_params=A.BboxParams(format='yolo')), bbox_params=A.BboxParams(format='yolo')),
A.Compose([A.SafeRotate(limit=90, always_apply=True)], A.Compose([A.SafeRotate(limit=90, always_apply=True)],
@@ -33,9 +58,12 @@ def image_processing(img_ann: ImageLabel) -> [ImageLabel]:
] ]
results = [] results = []
labels = correct_bboxes(img_ann.labels)
if len(labels) == 0 and len(img_ann.labels) != 0:
print('no labels but was!!!')
for i, transform in enumerate(transforms): for i, transform in enumerate(transforms):
try: try:
res = transform(image=img_ann.image, bboxes=img_ann.labels) res = transform(image=img_ann.image, bboxes=labels)
path = Path(img_ann.image_path) path = Path(img_ann.image_path)
name = f'{path.stem}_{i + 1}' name = f'{path.stem}_{i + 1}'
img = ImageLabel( img = ImageLabel(
@@ -51,9 +79,6 @@ def image_processing(img_ann: ImageLabel) -> [ImageLabel]:
def write_result(img_ann: ImageLabel): def write_result(img_ann: ImageLabel):
os.makedirs(os.path.dirname(img_ann.image_path), exist_ok=True)
os.makedirs(os.path.dirname(img_ann.labels_path), exist_ok=True)
cv2.imencode('.jpg', img_ann.image)[1].tofile(img_ann.image_path) cv2.imencode('.jpg', img_ann.image)[1].tofile(img_ann.image_path)
print(f'{img_ann.image_path} written') print(f'{img_ann.image_path} written')
@@ -92,6 +117,8 @@ def process_image(img_ann):
def main(): def main():
os.makedirs(processed_images_dir, exist_ok=True)
os.makedirs(processed_labels_dir, exist_ok=True)
while True: while True:
processed_images = set(f.name for f in os.scandir(processed_images_dir)) processed_images = set(f.name for f in os.scandir(processed_images_dir))
images = [] images = []
+26 -6
View File
@@ -3,18 +3,38 @@ apt update
apt upgrade apt upgrade
apt install -y ssh apt install -y ssh
addgroup sftp
adduser azaionsftp adduser azaionsftp
usermod -G sftp azaionsftp
usermod -a -G sftp zxsanny
mkdir /azaion-media/sftphome chown root:root /home/azaionsftp
chown -R azaionsftp:azaionsftp /azaion-media/sftphome/
chmod -R 755 /azaion-media/sftphome/ cd /home/azaionsftp
mkdir datasets
chown -R azaionsftp:azaionsftp datasets
mount -o bind /azaion/datasets datasets
chown -R zxsanny:sftp /azaion/data
mkdir data
chown -R azaionsftp:azaionsftp data
mount -o bind /azaion/data data
chown -R zxsanny:sftp /azaion/data-processed
mkdir data-processed
chown -R azaionsftp:azaionsftp data-processed
mount -o bind /azaion/data-processed data-processed
chmod -R 755 /home/azaionsftp/
cat <<EOT >> /etc/ssh/sshd_config cat <<EOT >> /etc/ssh/sshd_config
Match Group azaionsftp Match Group sftp
ChrootDirectory %h ChrootDirectory %h
PasswordAuthentication yes
AllowTcpForwarding no
X11Forwarding no X11Forwarding no
ForceCommand internal-sftp ForceCommand internal-sftp
AllowTcpForwarding no
PasswordAuthentication yes
EOT EOT
service ssh restart service ssh restart
+11 -4
View File
@@ -18,6 +18,8 @@ train_set = 70
valid_set = 20 valid_set = 20
test_set = 10 test_set = 10
DEFAULT_CLASS_NUM = 80
def form_dataset(from_date: datetime): def form_dataset(from_date: datetime):
makedirs(today_dataset, exist_ok=True) makedirs(today_dataset, exist_ok=True)
@@ -89,7 +91,10 @@ def create_yaml():
lines = ['names:'] lines = ['names:']
for c in annotation_classes: for c in annotation_classes:
lines.append(f'- {annotation_classes[c].name}') lines.append(f'- {annotation_classes[c].name}')
lines.append(f'nc: {len(annotation_classes)}') classes_count = len(annotation_classes)
for c in range(DEFAULT_CLASS_NUM - classes_count):
lines.append(f'- Class-{c + classes_count + 1}')
lines.append(f'nc: {DEFAULT_CLASS_NUM}')
lines.append(f'test: test/images') lines.append(f'test: test/images')
lines.append(f'train: train/images') lines.append(f'train: train/images')
@@ -132,17 +137,19 @@ def get_latest_model():
if __name__ == '__main__': if __name__ == '__main__':
latest_date, latest_model = get_latest_model() latest_date, latest_model = get_latest_model()
# create_yaml()
# form_dataset(latest_date) # form_dataset(latest_date)
model_name = latest_model if latest_model is not None and path.isfile(latest_model) else 'yolov8m.yaml' model_name = latest_model if latest_model is not None and path.isfile(latest_model) else 'yolov8m.yaml'
print(f'Initial model: {model_name}') print(f'Initial model: {model_name}')
model = YOLO(model_name) model = YOLO(model_name)
# cur_folder = path.join(datasets_dir, f'{prefix}2024-06-18') cur_folder = path.join(datasets_dir, f'{prefix}2024-09-19')
cur_folder = today_dataset # cur_folder = today_dataset
yaml = abspath(path.join(cur_folder, 'data.yaml')) yaml = abspath(path.join(cur_folder, 'data.yaml'))
results = model.train(data=yaml, epochs=100, batch=60, imgsz=640) results = model.train(data=yaml, epochs=100, batch=58, imgsz=640)
shutil.copytree(results.save_dir, path.join(models_dir, today_folder)) shutil.copytree(results.save_dir, path.join(models_dir, today_folder))
shutil.rmtree('runs') shutil.rmtree('runs')
+46
View File
@@ -0,0 +1,46 @@
# Ultralytics YOLO 🚀, AGPL-3.0 license
# YOLOv8 object detection model with P3-P5 outputs. For Usage examples see https://docs.ultralytics.com/tasks/detect
# Parameters
nc: 80 # number of classes
scales: # model compound scaling constants, i.e. 'model=yolov8n.yaml' will call yolov8.yaml with scale 'n'
# [depth, width, max_channels]
n: [0.33, 0.25, 1024] # YOLOv8n summary: 225 layers, 3157200 parameters, 3157184 gradients, 8.9 GFLOPs
s: [0.33, 0.50, 1024] # YOLOv8s summary: 225 layers, 11166560 parameters, 11166544 gradients, 28.8 GFLOPs
m: [0.67, 0.75, 768] # YOLOv8m summary: 295 layers, 25902640 parameters, 25902624 gradients, 79.3 GFLOPs
l: [1.00, 1.00, 512] # YOLOv8l summary: 365 layers, 43691520 parameters, 43691504 gradients, 165.7 GFLOPs
x: [1.00, 1.25, 512] # YOLOv8x summary: 365 layers, 68229648 parameters, 68229632 gradients, 258.5 GFLOPs
# YOLOv8.0n backbone
backbone:
# [from, repeats, module, args]
- [-1, 1, Conv, [64, 3, 2]] # 0-P1/2
- [-1, 1, Conv, [128, 3, 2]] # 1-P2/4
- [-1, 3, C2f, [128, True]]
- [-1, 1, Conv, [256, 3, 2]] # 3-P3/8
- [-1, 6, C2f, [256, True]]
- [-1, 1, Conv, [512, 3, 2]] # 5-P4/16
- [-1, 6, C2f, [512, True]]
- [-1, 1, Conv, [1024, 3, 2]] # 7-P5/32
- [-1, 3, C2f, [1024, True]]
- [-1, 1, SPPF, [1024, 5]] # 9
# YOLOv8.0n head
head:
- [-1, 1, nn.Upsample, [None, 2, 'nearest']]
- [[-1, 6], 1, Concat, [1]] # cat backbone P4
- [-1, 3, C2f, [512]] # 12
- [-1, 1, nn.Upsample, [None, 2, 'nearest']]
- [[-1, 4], 1, Concat, [1]] # cat backbone P3
- [-1, 3, C2f, [256]] # 15 (P3/8-small)
- [-1, 1, Conv, [256, 3, 2]]
- [[-1, 12], 1, Concat, [1]] # cat head P4
- [-1, 3, C2f, [512]] # 18 (P4/16-medium)
- [-1, 1, Conv, [512, 3, 2]]
- [[-1, 9], 1, Concat, [1]] # cat head P5
- [-1, 3, C2f, [1024]] # 21 (P5/32-large)
- [[15, 18, 21], 1, Detect, [nc]] # Detect(P3, P4, P5)
-40
View File
@@ -1,40 +0,0 @@
# Ultralytics YOLO 🚀, GPL-3.0 license
# Parameters
nc: 50 # number of classes
depth_multiple: 0.67 # scales module repeats
width_multiple: 0.75 # scales convolution channels
# YOLOv8.0m backbone
backbone:
# [from, repeats, module, args]
- [-1, 1, Conv, [64, 3, 2]] # 0-P1/2
- [-1, 1, Conv, [128, 3, 2]] # 1-P2/4
- [-1, 3, C2f, [128, True]]
- [-1, 1, Conv, [256, 3, 2]] # 3-P3/8
- [-1, 6, C2f, [256, True]]
- [-1, 1, Conv, [512, 3, 2]] # 5-P4/16
- [-1, 6, C2f, [512, True]]
- [-1, 1, Conv, [768, 3, 2]] # 7-P5/32
- [-1, 3, C2f, [768, True]]
- [-1, 1, SPPF, [768, 5]] # 9
# YOLOv8.0m head
head:
- [-1, 1, nn.Upsample, [None, 2, 'nearest']]
- [[-1, 6], 1, Concat, [1]] # cat backbone P4
- [-1, 3, C2f, [512]] # 13
- [-1, 1, nn.Upsample, [None, 2, 'nearest']]
- [[-1, 4], 1, Concat, [1]] # cat backbone P3
- [-1, 3, C2f, [256]] # 17 (P3/8-small)
- [-1, 1, Conv, [256, 3, 2]]
- [[-1, 12], 1, Concat, [1]] # cat head P4
- [-1, 3, C2f, [512]] # 20 (P4/16-medium)
- [-1, 1, Conv, [512, 3, 2]]
- [[-1, 9], 1, Concat, [1]] # cat head P5
- [-1, 3, C2f, [768]] # 23 (P5/32-large)
- [[15, 18, 21], 1, Detect, [nc]] # Detect(P3, P4, P5)