From c92a7ed70fee29dc81b7cd4fe1acc737c40c2add Mon Sep 17 00:00:00 2001 From: Chilicyy Date: Fri, 15 Sep 2023 15:46:22 +0800 Subject: [PATCH] modify config files --- README.md | 10 +- Train_custom_data.md | 164 ++++++++++++++++++++++++++ assets/speed_comparision_seg.png | Bin 0 -> 85175 bytes configs/{solo => exp}/yolov6l_solo.py | 0 configs/{solo => exp}/yolov6m_solo.py | 0 configs/{solo => exp}/yolov6n_solo.py | 0 configs/{solo => exp}/yolov6s_solo.py | 0 configs/{solo => exp}/yolov6x_solo.py | 0 configs/yolov6l_seg_finetune.py | 72 +++++++++++ configs/yolov6m_seg_finetune.py | 70 +++++++++++ configs/yolov6n_seg_finetune.py | 69 +++++++++++ configs/yolov6s_seg_finetune.py | 69 +++++++++++ configs/yolov6x_seg.py | 4 +- configs/yolov6x_seg_finetune.py | 72 +++++++++++ 14 files changed, 523 insertions(+), 7 deletions(-) create mode 100644 Train_custom_data.md create mode 100644 assets/speed_comparision_seg.png rename configs/{solo => exp}/yolov6l_solo.py (100%) rename configs/{solo => exp}/yolov6m_solo.py (100%) rename configs/{solo => exp}/yolov6n_solo.py (100%) rename configs/{solo => exp}/yolov6s_solo.py (100%) rename configs/{solo => exp}/yolov6x_solo.py (100%) create mode 100644 configs/yolov6l_seg_finetune.py create mode 100644 configs/yolov6m_seg_finetune.py create mode 100644 configs/yolov6n_seg_finetune.py create mode 100644 configs/yolov6s_seg_finetune.py create mode 100644 configs/yolov6x_seg_finetune.py diff --git a/README.md b/README.md index 7d0d1848..200e178f 100644 --- a/README.md +++ b/README.md @@ -51,13 +51,13 @@ pip install -r requirements.txt Single GPU ```shell -python tools/train.py --batch 8 --conf configs/yolov6s_finetune.py --data data/coco.yaml --device 0 +python tools/train.py --batch 8 --conf configs/yolov6s_seg_finetune.py --data data/coco.yaml --device 0 ``` Multi GPUs (DDP mode recommended) ```shell -python -m torch.distributed.launch --nproc_per_node 8 tools/train.py --batch 64 --conf configs/yolov6s_finetune.py --data data/coco.yaml --device 0,1,2,3,4,5,6,7 +python -m torch.distributed.launch --nproc_per_node 8 tools/train.py --batch 64 --conf configs/yolov6s_seg_finetune.py --data data/coco.yaml --device 0,1,2,3,4,5,6,7 ``` - fuse_ab: Not supported in current version - conf: select config file to specify network/optimizer/hyperparameters. We recommend to apply yolov6n/s/m/l_finetune.py when training on your custom dataset. @@ -88,7 +88,7 @@ python -m torch.distributed.launch --nproc_per_node 8 tools/train.py --batch 64 Reproduce mAP on COCO val2017 dataset with 640×640 resolution ```shell -python tools/eval.py --data data/coco.yaml --batch 32 --weights yolov6s.pt --task val +python tools/eval.py --data data/coco.yaml --batch 32 --weights yolov6s_seg.pt --task val ``` @@ -102,11 +102,11 @@ First, download a pretrained model from the YOLOv6 [release](https://github.com/ Second, run inference with `tools/infer.py` ```shell -python tools/infer.py --weights yolov6s.pt --source img.jpg / imgdir / video.mp4 +python tools/infer.py --weights yolov6s_seg.pt --source img.jpg / imgdir / video.mp4 ``` If you want to inference on local camera or web camera, you can run: ```shell -python tools/infer.py --weights yolov6s.pt --webcam --webcam-addr 0 +python tools/infer.py --weights yolov6s_seg.pt --webcam --webcam-addr 0 ``` `webcam-addr` can be local camera number id or rtsp address. Maybe you want to eval a solo-head model, remember to add the *--issolo* parameter. diff --git a/Train_custom_data.md b/Train_custom_data.md new file mode 100644 index 00000000..2a281d0b --- /dev/null +++ b/Train_custom_data.md @@ -0,0 +1,164 @@ +# Train Custom Data + +This guidence explains how to train your own custom data with YOLOv6 (take fine-tuning YOLOv6-s model for example). + +## 0. Before you start + +Clone this repo and follow README.md to install requirements in a Python3.8 environment. +```shell +$ git clone https://github.com/meituan/YOLOv6.git +``` + +## 1. Prepare your own dataset + +**Step 1**: Prepare your own dataset with images. For labeling images, you can use tools like [Labelme](https://github.com/wkentaro/labelme) or [Roboflow](https://roboflow.com/). + +**Step 2**: Generate label files in YOLO format. + +One image corresponds to one label file, and the label format example is presented as below. + +```json +# class_id RLE&polygon(n * (x_coord, y_coord)) +0 0.0503437 0.0314644 0.0688125 0.114603 0.0604219 0.247197 0.0654531 0.330335 0.0436406 0.359561 0.0201406 0.361799 0.00335937 0.294372 0.00671875 0.229205 0.0134219 0.112364 0.0251719 0.0359623 0.0268594 0.00449791 +1 0.663672 0.412218 0.603688 0.425167 0.528234 0.42 0.475984 0.399268 0.417938 0.391485 0.348281 0.383724 0.301844 0.414812 0.317328 0.430356 0.3715 0.469205 0.458578 0.508075 0.510813 0.515837 0.574672 0.495126 0.628844 0.476987 0.657875 0.453661 0.690766 0.435544 +``` + + +- Each row represents one object. +- Class id starts from `0`. +- RLE&polygon means n points, there should be 2n numbers which means n pairs of (x_coord, y_coord) + +**Step 3**: Organize directories. + +Organize your directory of custom dataset as follows: + +```shell +custom_dataset +├── images +│   ├── train +│   │   ├── train0.jpg +│   │   └── train1.jpg +│   ├── val +│   │   ├── val0.jpg +│   │   └── val1.jpg +│   └── test +│   ├── test0.jpg +│   └── test1.jpg +└── labels + ├── train + │   ├── train0.txt + │   └── train1.txt + ├── val + │   ├── val0.txt + │   └── val1.txt + └── test + ├── test0.txt + └── test1.txt +``` + +**Step 4**: Create `dataset.yaml` in `$YOLOv6_DIR/data`. + +```yaml +# Please insure that your custom_dataset are put in same parent dir with YOLOv6_DIR +train: ../custom_dataset/images/train # train images +val: ../custom_dataset/images/val # val images +test: ../custom_dataset/images/test # test images (optional) + +# whether it is coco dataset, only coco dataset should be set to True. +is_coco: False + +# Classes +nc: 20 # number of classes +names: ['aeroplane', 'bicycle', 'bird', 'boat', 'bottle', 'bus', 'car', 'cat', 'chair', 'cow', 'diningtable', 'dog', + 'horse', 'motorbike', 'person', 'pottedplant', 'sheep', 'sofa', 'train', 'tvmonitor'] # class names +``` + +## 2. Create a config file + +We use a config file to specify the network structure and training setting, including optimizer and data augmentation hyperparameters. + +If you create a new config file, please put it under the `configs` directory. +Or just use the provided config file in `$YOLOV6_HOME/configs/*_finetune.py`. Download the pretrained model which you want to use from [here](https://github.com/meituan/YOLOv6#benchmark). + +```python +## YOLOv6s Model config file +model = dict( + type='YOLOv6s', + pretrained='./weights/yolov6s.pt', # download the pretrained model from YOLOv6 github if you're going to use the pretrained model + depth_multiple = 0.33, + width_multiple = 0.50, + ... +) +solver=dict( + optim='SGD', + lr_scheduler='Cosine', + ... +) + +data_aug = dict( + hsv_h=0.015, + hsv_s=0.7, + hsv_v=0.4, + ... +) + +One more thing, there are 4 more parameters in seg model, +**isseg** means it's a seg model or not +**issolo** means it's a solo head or a yolact head +**npr** and **nm** mean how many features net should extract and use them to get the mask. +``` + + + +## 3. Train + +Single GPU + +```shell +# Be sure to open use_dfl mode in config file (use_dfl=True, reg_max=16) if you want to do self-distillation training further. +python tools/train.py --batch 32 --conf configs/yolov6s_finetune.py --data data/dataset.yaml --fuse_ab --device 0 +``` + +Multi GPUs (DDP mode recommended) + +```shell +# Be sure to open use_dfl mode in config file (use_dfl=True, reg_max=16) if you want to do self-distillation training further. +python -m torch.distributed.launch --nproc_per_node 8 tools/train.py --batch 256 --conf configs/yolov6s_finetune.py --data data/dataset.yaml --fuse_ab --device 0,1,2,3,4,5,6,7 +``` + +Self-distillation training + +```shell +# Be sure to open use_dfl mode in config file (use_dfl=True, reg_max=16). +python -m torch.distributed.launch --nproc_per_node 8 tools/train.py --batch 256 --conf configs/yolov6s_finetune.py --data data/dataset.yaml --distill --teacher_model_path your_model_path --device 0,1,2,3,4,5,6,7 +``` + + +## 4. Evaluation + +```shell +python tools/eval.py --data data/data.yaml --weights output_dir/name/weights/best_ckpt.pt --task val --device 0 +``` + + + +## 5. Inference + +```shell +python tools/infer.py --weights output_dir/name/weights/best_ckpt.pt --source img.jpg --device 0 +``` + + + +## 6. Deployment + +Export as [ONNX](https://github.com/meituan/YOLOv6/tree/main/deploy/ONNX) Format + +```shell +# Without NMS OP, pure model. +python deploy/ONNX/export_onnx.py --weights output_dir/name/weights/best_ckpt.pt --simplify --device 0 +# If you want to run with ONNX-Runtime (NMS integrated). +python deploy/ONNX/export_onnx.py --weights output_dir/name/weights/best_ckpt.pt --simplify --device 0 --dynamic-batch --end2end --ort +# If you want to run with TensorRT (NMS integrated). +python deploy/ONNX/export_onnx.py --weights output_dir/name/weights/best_ckpt.pt --simplify --device 0 --dynamic-batch --end2end +``` diff --git a/assets/speed_comparision_seg.png b/assets/speed_comparision_seg.png new file mode 100644 index 0000000000000000000000000000000000000000..0a17d4fb540504a80afa96a8155f67082cddef3e GIT binary patch literal 85175 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