From c234e8b19069e01ab99cd4672943788b2c3c4a0c Mon Sep 17 00:00:00 2001 From: zxsanny Date: Tue, 24 Sep 2024 12:56:05 +0300 Subject: [PATCH] fix augmentation - correct bboxes to be within borders+margin Add classes = 80 as default number in yaml file --- README.md | 3 +- classes.json | 22 ++++++------ convert-annotations.py | 18 +++++----- convert-rknn/install_converter_yolov8.sh | 41 +++++++++++++++++++++ preprocessing.py | 37 ++++++++++++++++--- scripts/init-sftp.sh | 34 ++++++++++++++---- train.py | 15 +++++--- yolov8.yaml | 46 ++++++++++++++++++++++++ yolov8m.yaml | 40 --------------------- 9 files changed, 178 insertions(+), 78 deletions(-) create mode 100644 convert-rknn/install_converter_yolov8.sh create mode 100644 yolov8.yaml delete mode 100644 yolov8m.yaml diff --git a/README.md b/README.md index 1c0706b..29c4af3 100644 --- a/README.md +++ b/README.md @@ -36,13 +36,12 @@ Linux ``` **3. Fix possible problems** -* Windows: * cv2.error: OpenCV(4.10.0) ...\window.cpp:1301: error: (-2:Unspecified error) ``` pip uninstall opencv-python pip install opencv-python ``` - * fbgemm.dll error + * fbgemm.dll error (Windows specific) ``` copypaste libomp140.x86_64.dll to C:\Windows\System32 ``` diff --git a/classes.json b/classes.json index eda7918..f127ffb 100644 --- a/classes.json +++ b/classes.json @@ -1,62 +1,62 @@ [ { "Id": 0, - "Name": "Броньована техніка", + "Name": "Armored-Vehicle", "Color": "#80FF0000", "ColorBrush": "#80FF0000" }, { "Id": 1, - "Name": "Вантажівка", + "Name": "Truck", "Color": "#8000FF00", "ColorBrush": "#8000FF00" }, { "Id": 2, - "Name": "Машина легкова", + "Name": "Vehicle", "Color": "#800000FF", "ColorBrush": "#800000FF" }, { "Id": 3, - "Name": "Артилерія", + "Name": "Artillery", "Color": "#80FFFF00", "ColorBrush": "#80FFFF00" }, { "Id": 4, - "Name": "Тінь від техніки", + "Name": "Shadow", "Color": "#80FF00FF", "ColorBrush": "#80FF00FF" }, { "Id": 5, - "Name": "Окопи", + "Name": "Trenches", "Color": "#8000FFFF", "ColorBrush": "#8000FFFF" }, { "Id": 6, - "Name": "Військовий", + "Name": "Military-men", "Color": "#80000000", "ColorBrush": "#80000000" }, { "Id": 7, - "Name": "Накати", + "Name": "Tyre-tracks", "Color": "#80800000", "ColorBrush": "#80800000" }, { "Id": 8, - "Name": "Танк з додатковим захистом", + "Name": "Additional-armored-tank", "Color": "#80008000", "ColorBrush": "#80008000" }, { "Id": 9, - "Name": "Дим", + "Name": "Smoke", "Color": "#80000080", "ColorBrush": "#80000080" } - ] \ No newline at end of file + ] diff --git a/convert-annotations.py b/convert-annotations.py index 488f580..91c3ed6 100644 --- a/convert-annotations.py +++ b/convert-annotations.py @@ -4,8 +4,6 @@ import xml.etree.cElementTree as et from pathlib import Path import cv2 -labels_dir = 'labels' -images_dir = 'images' tag_size = 'size' tag_object = 'object' @@ -17,9 +15,11 @@ default_class = 1 image_extensions = ['jpg', 'png', 'jpeg'] -def convert(folder, read_annotations, ann_format): - os.makedirs(images_dir, exist_ok=True) - os.makedirs(labels_dir, exist_ok=True) +def convert(folder, dest_folder, read_annotations, ann_format): + dest_images_dir = os.path.join(dest_folder, 'images') + 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): if not f[-3:] in image_extensions: @@ -39,8 +39,8 @@ def convert(folder, read_annotations, ann_format): except Exception as e: print(f'Error conversion for {f}. Error: {e}') - shutil.copy(os.path.join(folder, f), os.path.join(images_dir, f)) - with open(os.path.join(labels_dir, f'{Path(label).stem}.txt'), 'w') as new_label_file: + shutil.copy(os.path.join(folder, f), os.path.join(dest_images_dir, f)) + 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.close() print(f'Image {f} has been processed successfully') @@ -115,5 +115,5 @@ def rename_images(folder): if __name__ == '__main__': - convert('datasets/others/UAVHeightImages', read_bbox_oriented, 'txt') - convert('datasets/others/UAVimages', read_pascal_voc, 'xml') + convert('/azaion/datasets/others/cars_height', '/azaion/datasets/converted', read_bbox_oriented, 'txt') + convert('/azaion/datasets/others/cars', '/azaion/datasets/converted', read_pascal_voc, 'xml') diff --git a/convert-rknn/install_converter_yolov8.sh b/convert-rknn/install_converter_yolov8.sh new file mode 100644 index 0000000..9999218 --- /dev/null +++ b/convert-rknn/install_converter_yolov8.sh @@ -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... diff --git a/preprocessing.py b/preprocessing.py index 672c9fd..4b17694 100644 --- a/preprocessing.py +++ b/preprocessing.py @@ -11,10 +11,35 @@ from constants import (data_images_dir, data_labels_dir, processed_images_dir, p 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]: transforms = [ 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)], bbox_params=A.BboxParams(format='yolo')), A.Compose([A.SafeRotate(limit=90, always_apply=True)], @@ -33,9 +58,12 @@ def image_processing(img_ann: ImageLabel) -> [ImageLabel]: ] 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): 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) name = f'{path.stem}_{i + 1}' img = ImageLabel( @@ -51,9 +79,6 @@ def image_processing(img_ann: ImageLabel) -> [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) print(f'{img_ann.image_path} written') @@ -92,6 +117,8 @@ def process_image(img_ann): def main(): + os.makedirs(processed_images_dir, exist_ok=True) + os.makedirs(processed_labels_dir, exist_ok=True) while True: processed_images = set(f.name for f in os.scandir(processed_images_dir)) images = [] diff --git a/scripts/init-sftp.sh b/scripts/init-sftp.sh index 41e79de..13f6a11 100644 --- a/scripts/init-sftp.sh +++ b/scripts/init-sftp.sh @@ -3,18 +3,38 @@ apt update apt upgrade apt install -y ssh +addgroup sftp adduser azaionsftp +usermod -G sftp azaionsftp +usermod -a -G sftp zxsanny -mkdir /azaion-media/sftphome -chown -R azaionsftp:azaionsftp /azaion-media/sftphome/ -chmod -R 755 /azaion-media/sftphome/ +chown root:root /home/azaionsftp + +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 <> /etc/ssh/sshd_config -Match Group azaionsftp +Match Group sftp ChrootDirectory %h + PasswordAuthentication yes + AllowTcpForwarding no X11Forwarding no ForceCommand internal-sftp - AllowTcpForwarding no - PasswordAuthentication yes EOT -service ssh restart \ No newline at end of file +service ssh restart + diff --git a/train.py b/train.py index 9a30991..04cb157 100644 --- a/train.py +++ b/train.py @@ -18,6 +18,8 @@ train_set = 70 valid_set = 20 test_set = 10 +DEFAULT_CLASS_NUM = 80 + def form_dataset(from_date: datetime): makedirs(today_dataset, exist_ok=True) @@ -89,7 +91,10 @@ def create_yaml(): lines = ['names:'] for c in annotation_classes: 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'train: train/images') @@ -132,17 +137,19 @@ def get_latest_model(): if __name__ == '__main__': latest_date, latest_model = get_latest_model() + # create_yaml() # form_dataset(latest_date) + model_name = latest_model if latest_model is not None and path.isfile(latest_model) else 'yolov8m.yaml' print(f'Initial model: {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')) - 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.rmtree('runs') diff --git a/yolov8.yaml b/yolov8.yaml new file mode 100644 index 0000000..2255450 --- /dev/null +++ b/yolov8.yaml @@ -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) diff --git a/yolov8m.yaml b/yolov8m.yaml deleted file mode 100644 index df8fdcf..0000000 --- a/yolov8m.yaml +++ /dev/null @@ -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)