🐐 Flask를 사용하여 Yolo v5를 이용한 Object Detection

프로잭트 배경

Flask를 사용하여 Python Web서비스를 구축을 하고
웹상에서 사진을 첨부하여 Yolo v5를 이용한 릴Chip의 Object Detection과
TensorFlow를 이용하여 LED정상 및 비정상을 판단하는 프로젝트 였습니다.

해당글은 Yolo v5를 이용한 내용만 담습니다.


구성 환경

필자는 아래와 같은 환경에서 작성하였습니다.

CPU : i7 - 10750H
그래픽카드 : RTX2060
Ram : 32GB
OS : Windows 10
Python : 3.9.7


Yolo v5 설치

Yolo V4는 C++로 작성되어 있어서 Python에서 사용하기 위해서는
C++로 작성된 코드를 Python에서 사용할 수 있도록 변환해야 합니다.
하지만 Yolo V5는 Python으로 작성되어 있어서 Python에서 사용하기 쉽습니다.
Yolo V5를 설치하기 위해서는 아래와 같은 명령어를 입력합니다.

https://github.com/ultralytics/yolov5

git clone https://github.com/ultralytics/yolov5  # clone

또한 환경에따라 설치가 다르다 그러하니 아래의 링크를 참고하시기 바랍니다.
ex) GPU를 사용하거나 CPU를 사용하거나
https://pytorch.org/get-started/locally/


Yolo v5는 Pytorch를 기반으로 작성되어 있습니다.

requirements.txt

구현한 환경은 이렇습니다.

// requirements.txt
absl-py==1.3.0
aniso8601==9.0.1
asttokens==2.1.0
astunparse==1.6.3
attrs==22.1.0
awscli==1.26.5
backcall==0.2.0
boto3==1.25.4
botocore==1.28.5
cachetools==5.2.0
certifi==2022.9.24
charset-normalizer==2.1.1
click==8.1.3
colorama==0.4.4
contourpy==1.0.5
cycler==0.11.0
decorator==5.1.1
docker==6.0.0
docutils==0.16
executing==1.2.0
Flask==2.2.2
Flask-Cors==3.0.10
flask-marshmallow==0.14.0
Flask-RESTful==0.3.9
Flask-SQLAlchemy==3.0.0
flatbuffers==22.9.24
fonttools==4.37.4
gast==0.4.0
google-auth==2.12.0
google-auth-oauthlib==0.4.6
google-pasta==0.2.0
greenlet==1.1.3
grpcio==1.49.1
h5py==3.7.0
idna==3.4
importlib-metadata==5.0.0
iniconfig==1.1.1
ipython==8.6.0
itsdangerous==2.1.2
jaraco.classes==3.2.3
jedi==0.18.1
Jinja2==3.1.2
jmespath==1.0.1
keras==2.10.0
Keras-Preprocessing==1.1.2
keyring==8.7
keyrings.alt==4.2.0
kiwisolver==1.4.4
libclang==14.0.6
Markdown==3.4.1
MarkupSafe==2.1.1
marshmallow==3.18.0
matplotlib==3.6.0
matplotlib-inline==0.1.6
more-itertools==9.0.0
mysql-connector-python==8.0.30
numpy==1.23.4
oauthlib==3.2.1
opencv-python==4.6.0.66
opt-einsum==3.3.0
packaging==21.3
pandas==1.5.1
parso==0.8.3
pickleshare==0.7.5
Pillow==9.3.0
pip==22.3
pluggy==1.0.0
prompt-toolkit==3.0.31
protobuf==3.19.6
psutil==5.9.3
psycopg2-binary==2.9.3
pure-eval==0.2.2
py==1.11.0
pyasn1==0.4.8
pyasn1-modules==0.2.8
Pygments==2.13.0
pymssql==2.2.5
PyMySQL==1.0.2
pyparsing==3.0.9
pytest==7.1.3
python-dateutil==2.8.2
pytz==2022.4
pywin32==304
pywin32-ctypes==0.2.0
PyYAML==5.4.1
requests==2.28.1
requests-oauthlib==1.3.1
rsa==4.7.2
ruamel.yaml.clib==0.2.7
ruamel.yaml==0.17.21
s3transfer==0.6.0
scipy==1.9.3
seaborn==0.12.1
setuptools==57.4.0
six==1.16.0
SQLAlchemy==1.4.41
stack-data==0.6.0
tabulate==0.9.0
tensorboard==2.10.1
tensorboard-data-server==0.6.1
tensorboard-plugin-wit==1.8.1
tensorflow==2.10.0
tensorflow-estimator==2.10.0
tensorflow-io-gcs-filesystem==0.27.0
termcolor==2.0.1
thop
tomli==2.0.1
torch==1.13.0
torchaudio==0.13.0
torchvision==0.14.0
tqdm==4.64.1
traitlets==5.5.0
typing_extensions==4.4.0
urllib3==1.26.12
voluptuous==0.13.1
wcwidth==0.2.5
websocket-client==1.4.1
Werkzeug==2.2.2
wheel==0.37.1
wrapt==1.14.1
yolo==0.3.1
zipp==3.10.0

Image에 라벨 개수 글씨추가

detect.py 코드의 하단에 보면
count = 0 을 선언한다.
그리고 for문을 돌면서 count를 1씩 증가시킨다.
그리고 count를 이미지에 추가한다.

count = 0
# Write results
for *xyxy, conf, cls in reversed(det):
    if save_txt:  # Write to file
        xywh = (xyxy2xywh(torch.tensor(xyxy).view(1, 4)) / gn).view(-1).tolist()  # normalized xywh
        line = (cls, *xywh, conf) if save_conf else (cls, *xywh)  # label format
        with open(txt_path + '.txt', 'a') as f:
            f.write(('%g ' * len(line)).rstrip() % line + '\n')

    if save_img or save_crop or view_img:  # Add bbox to image
        c = int(cls)  # integer class
        label = None if hide_labels else (names[c] if hide_conf else f'{names[c]} {conf:.2f}')
        #label = str(count)
        annotator.box_label(xyxy, label, color=colors(c, True))
        count += 1
        if save_crop:
            save_one_box(xyxy, imc, file=save_dir / 'crops' / names[c] / f'{p.stem}.jpg', BGR=True)

# Stream results
im0 = annotator.result()

cv2.putText(
  im0, # 이미지
  str(count), # 표시할 문자열
  (200, 400),  # 문자열을 표시할 위치
  cv2.FONT_HERSHEY_SIMPLEX, # 폰트
  15, # 폰트 크기
  (99,111,237), # 폰트 색상 (BGR)이다.
  5 # 폰트 두께
)


train, detect 하기

python 파일에서 detect.py와 train을 import시켜준 후
main() 함수를 호출한다.

import yolov5.detect as detect
import yolov5.train as train

def yolov5_detect_start():
    opt = detect.parse_opt()
    detect.main(opt)

def yolov5_train_start():
    opt = train.parse_opt()
    train.main(opt)

만약 바로 실행시키려면

detect.py의 run 매계변수를,
train.py는 parse_opt 함수에서 수정해준다.

#detect.py
def run(weights='yolov5s.pt',  # 가중치파일 위치,  학습한 객체위치
        source='data/images',  # detect할 이미지 위치
        imgsz=640,  # 이미지 크기
        conf_thres=0.25,  # confidence threshold
        iou_thres=0.45,  # NMS IoU threshold
        max_det=1000,  # 최대 detect 개수
        device='',  # cuda device, i.e. 0 or 0,1,2,3 or cpu
        view_img=False,  # show results
        save_txt=False,  # save results to *.txt
        save_conf=False,  # save confidences in --save-txt labels
        save_crop=False,  # save cropped prediction boxes
        nosave=False,  # do not save images/videos
        classes=None,  # filter by class: --class 0, or --class 0 2 3
        agnostic_nms=False,  # class-agnostic NMS
        augment=False,  # augmented inference
        visualize=False,  # visualize features
        update=False,  # update all models
        project='runs/detect',  # save results to project/name
        name='exp',  # save results to project/name
        exist_ok=False,  # existing project/name ok, do not increment
        line_thickness=3,  # bounding box thickness (pixels)
        hide_labels=False,  # hide labels
        hide_conf=False,  # hide confidences
        half=False,  # use FP16 half-precision inference
        ):
#train.py
def parse_opt(known=False):
    parser = argparse.ArgumentParser()
    
    # weights : 학습한 객체위치
    parser.add_argument('--weights', type=str, default=ROOT / 'yolov5s.pt', help='initial weights path')
    # cfg : 모델 구조
    parser.add_argument('--cfg', type=str, default='', help='model.yaml path')
    # 데이터셋 관련
    parser.add_argument('--data', type=str, default=ROOT / 'data/coco128.yaml', help='dataset.yaml path')
    # hyp : hyperparameter
    parser.add_argument('--hyp', type=str, default=ROOT / 'data/hyps/hyp.scratch-low.yaml', help='hyperparameters path')
    # epochs : 학습 횟수
    parser.add_argument('--epochs', type=int, default=300, help='total training epochs')
    # 배치사이즈
    parser.add_argument('--batch-size', type=int, default=16, help='total batch size for all GPUs, -1 for autobatch')
    # imgsz : 이미지 크기
    parser.add_argument('--imgsz', '--img', '--img-size', type=int, default=640, help='train, val image size (pixels)')
    # rect : 이미지 비율을 유지하면서 이미지 크기를 조절
    parser.add_argument('--rect', action='store_true', help='rectangular training')
    # resume : 학습을 이어서 할지 여부
    parser.add_argument('--resume', nargs='?', const=True, default=False, help='resume most recent training')
    parser.add_argument('--nosave', action='store_true', help='only save final checkpoint')
    # noval : validation을 하지 않는다.
    parser.add_argument('--noval', action='store_true', help='only validate final epoch')
    # noautoanchor : anchor를 자동으로 설정하지 않는다.
    parser.add_argument('--noautoanchor', action='store_true', help='disable AutoAnchor')
    # noplots : 학습과정을 그래프로 보여주지 않는다.
    parser.add_argument('--noplots', action='store_true', help='save no plot files')
    # evolve : hyperparameter를 자동으로 설정한다.
    parser.add_argument('--evolve', type=int, nargs='?', const=300, help='evolve hyerparameters for x generations')
    # bucket : google cloud storage bucket
    parser.add_argument('--bucket', type=str, default='', help='gsutil bucket')
    # cache : 캐시 이미지를 사용한다.
    parser.add_argument('--cache', type=str, nargs='?', const='ram', help='--cache images in "ram" (default) or "disk"')
    # image-weight : 이미지별 가중치를 설정한다.
    parser.add_argument('--image-weights', action='store_true', help='use weighted image selection for training')
    # device : cuda device, i.e. 0 or 0,1,2,3 or cpu
    parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')
    # multi-scale : 다양한 크기의 이미지를 사용한다.
    parser.add_argument('--multi-scale', action='store_true', help='vary img-size +/- 50%%')
    # single-cls : 단일 클래스로 학습한다.
    parser.add_argument('--single-cls', action='store_true', help='train multi-class data as single-class')
    # optimizer : 최적화 알고리즘
    parser.add_argument('--optimizer', type=str, choices=['SGD', 'Adam', 'AdamW'], default='SGD', help='optimizer')
    # sync-bn : batch normalization을 동기화한다.
    parser.add_argument('--sync-bn', action='store_true', help='use SyncBatchNorm, only available in DDP mode')
    # workers : 데이터 로더의 worker 수
    parser.add_argument('--workers', type=int, default=8, help='max dataloader workers (per RANK in DDP mode)')
    # project : 저장할 폴더
    parser.add_argument('--project', default=ROOT / 'runs/train', help='save to project/name')
    # name : 저장할 이름
    parser.add_argument('--name', default='exp', help='save to project/name')
    # exist-ok : 기존에 저장된 파일을 덮어쓴다.
    parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment')
    # quad : 사각형을 4개로 나눈다.
    parser.add_argument('--quad', action='store_true', help='quad dataloader')
    # cos-lr : cosine learning rate scheduler
    parser.add_argument('--cos-lr', action='store_true', help='cosine LR scheduler')
    # sync-lr : learning rate를 동기화한다.
    parser.add_argument('--sync-lr', action='store_true', help='sync learning rates between workers at start of epoch')
    # log-imgs : 학습과정을 이미지로 저장한다.
    parser.add_argument('--log-imgs', type=int, default=16, help='number of images for W&B logging, max 100')
    # log-artifacts : 학습과정을 artifact로 저장한다.
    parser.add_argument('--log-artifacts', action='store_true', help='log artifacts, i.e. final trained model')
    # log-weights : 학습과정을 weight로 저장한다.
    parser.add_argument('--log-weights', action='store_true', help='log training weights')
    # log-hist : 학습과정을 histogram으로 저장한다.
    parser.add_argument('--log-hist', action='store_true', help='log layer histograms')
    # log-alias : alias for wandb run
    parser.add_argument('--log-alias', type=str, default='exp', help='alias for wandb run')
    # log-model : 학습과정을 model로 저장한다.
    parser.add_argument('--log-model', action='store_true', help='log model topology')
    # no-wandb : wandb를 사용하지 않는다.
    parser.add_argument('--no-wandb', action='store_true', help='do not log training to Weights & Biases')
    # label-smoothing : 라벨 스무딩을 사용한다.
    parser.add_argument('--label-smoothing', type=float, default=0.0, help='Label smoothing epsilon')
    # bbox-att : bounding box attention
    parser.add_argument('--bbox-att', action='store_true', help='use bounding box attention in YOLOv5m/n')
    # bbox-att-n : bounding box attention
    parser.add_argument('--bbox-att-n', type=int, default=9, help='number of attention layers in YOLOv5m/n')
    # model-ema : model exponential moving average
    parser.add_argument('--model-ema', action='store_true', help='use Exponential Moving Average of model weights')
    # model-ema-decay : model exponential moving average decay
    parser.add_argument('--model-ema-decay', type=float, default=0.9998, help='model EMA decay rate')
    # update-bn : batch normalization을 업데이트한다.
    parser.add_argument('--update-bn', action='store_true', help='update BatchNorm statistics for model EMA')
    # reid : reid를 사용한다.
    parser.add_argument('--reid', action='store_true', help='use ReID model')
    # reid-dim : reid의 차원
    parser.add_argument('--reid-dim', type=int, default=128, help='ReID embedding dimension')
    # reid-conf : reid의 confidence
    parser.add_argument('--reid-conf', type=float, default=0.4, help='ReID confidence threshold')
    # reid-fp16 : reid를 fp16로 학습한다.
    parser.add_argument('--reid-fp16', action='store_true', help='use FP16 for ReID model')
    # reid-soft-nms : reid를 soft-nms로 학습한다.
    parser.add_argument('--reid-soft-nms', action='store_true', help='use soft-nms for ReID model')
    # reid-aug : reid를 augmentation으로 학습한다.
    parser.add_argument('--reid-aug', action='store_true', help='use augmentation for ReID model')

    parser.add_argument('--patience', type=int, default=100, help='EarlyStopping patience (epochs without improvement)')
    parser.add_argument('--freeze', nargs='+', type=int, default=[0], help='Freeze layers: backbone=10, first3=0 1 2')
    parser.add_argument('--save-period', type=int, default=-1, help='Save checkpoint every x epochs (disabled if < 1)')
    parser.add_argument('--seed', type=int, default=0, help='Global training seed')
    parser.add_argument('--local_rank', type=int, default=-1, help='Automatic DDP Multi-GPU argument, do not modify')

    # Logger arguments
    parser.add_argument('--entity', default=None, help='Entity')
    parser.add_argument('--upload_dataset', nargs='?', const=True, default=False, help='Upload data, "val" option')
    parser.add_argument('--bbox_interval', type=int, default=-1, help='Set bounding-box image logging interval')
    parser.add_argument('--artifact_alias', type=str, default='latest', help='Version of dataset artifact to use')

    return parser.parse_known_args()[0] if known else parser.parse_args()


parse_opt 수정

detect.py의 parse_opt() 함수는 아래와 같다.

def parse_opt(dirName, sdirName, dirDate):
    parser = argparse.ArgumentParser()
    # 가중치파일 위치 ROOT는 맨~위에 선언되어있다.
    # ROOT는 해당 패키지의 위치이다.
    parser.add_argument('--weights', nargs='+', type=str, default=ROOT / 'yolov5/runs/train/exp21/weights/last.pt', help='model path(s)')
    # 디텍팅할 이미지 경로
    parser.add_argument('--source', type=str, default=ROOT / Path('../../'+dirName), help='file/dir/URL/glob, 0 for webcam')
    # 디텍팅할 이미지 사이즈
    parser.add_argument('--imgsz', '--img', '--img-size', nargs='+', type=int, default=[3072], help='inference size h,w')
    # conf 값
    parser.add_argument('--conf-thres', type=float, default=0.37, help='confidence threshold')
    # iou 값
    parser.add_argument('--iou-thres', type=float, default=0.4, help='NMS IoU threshold')
    # 최대 디텍팅 개수
    parser.add_argument('--max-det', type=int, default=5000, help='maximum detections per image')
    # device 설정 (cpu, gpu)
    parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')
    # view_img 설정 (True면 이미지를 보여준다.)
    parser.add_argument('--view-img', action='store_true', help='show results')
    # save_txt 설정 (디텍팅된 결과를 txt로 저장할지 여부)
    parser.add_argument('--save-txt', action='store_true', help='save results to *.txt')
    # save_conf 설정 (conf 값도 저장할지)
    parser.add_argument('--save-conf', action='store_true', help='save confidences in --save-txt labels')
    parser.add_argument('--save-crop', action='store_true', help='save cropped prediction boxes')
    # nosave 설정 (이미지 저장 안함)
    parser.add_argument('--nosave', action='store_true', help='do not save images/videos')
    # classes 설정
    parser.add_argument('--classes', nargs='+', type=int, help='filter by class: --classes 0, or --classes 0 2 3')
    # agnostic 설정 (여러 클래스를 디텍팅할때 사용)
    parser.add_argument('--agnostic-nms', action='store_true', help='class-agnostic NMS')
    # augment 설정 (이미지 증강)
    parser.add_argument('--augment', action='store_true', help='augmented inference')
    # visualize 설정 
    parser.add_argument('--visualize', action='store_true', help='visualize features')
    # update 설정
    parser.add_argument('--update', action='store_true', help='update all models')
    # project 폴더 경로 설정 (default는 yolov5/runs/detect)
    parser.add_argument('--project', default=ROOT / Path('../../'+dirName), help='save results to project/name')
    # 폴더 이름 설정 (default는 yolov5)
    parser.add_argument('--name', default='result' , help='save results to project/name')
    # exist_ok 설정 (폴더가 없을 경우 생성)
    parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment')
    # line_thickness 설정 (디텍팅된 박스 두께) 1로주면 이쁘다
    parser.add_argument('--line-thickness', default=1, type=int, help='bounding box thickness (pixels)')
    # hide_labels 설정 (디텍팅된 박스 안에 텍스트 표시 여부)
    parser.add_argument('--hide-labels', default=True, action='store_true', help='hide labels')
    # hide_conf 설정 (디텍팅된 박스 안에 conf 표시 여부)
    parser.add_argument('--hide-conf', default=True, action='store_true', help='hide confidences')
    # half 설정 (fp16 half-precision)
    parser.add_argument('--half', action='store_true', help='use FP16 half-precision inference')
    # dnn 설정 (opencv dnn)
    parser.add_argument('--dnn', action='store_true', help='use OpenCV DNN for ONNX inference')
    opt = parser.parse_args()
    opt.imgsz *= 2 if len(opt.imgsz) == 1 else 1  # expand
    print_args(FILE.stem, opt)
    return opt

라벨 Class 이름 미출력

parser.add_argument('--hide-labels', default=True, action='store_true', help='hide labels')

라벨 detect 수치 미출력

parser.add_argument('--hide-conf', default=True, action='store_true', help='hide confidences')

결과 이미지 저장 유무

# nosave 설정 (이미지 저장 한다)
parser.add_argument('--nosave', action='store_false', help='do not save images/videos')

# nosave 설정 (이미지 저장 안한다)
parser.add_argument('--nosave', action='store_true', help='do not save images/videos')

Note: 만들고나니 내것이 아니었다.

Leave a comment