diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 251e51d..7b5ded7 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,5 +1,4 @@ stages: - - download-models - test-and-build - build-docker - deploy @@ -10,24 +9,6 @@ cache: - emtest/target - venv -download-models: - stage: download-models - tags: - - derby - before_script: - - source scripts/dev-setup - script: - - make download-models - artifacts: - paths: - - detection/model - except: - - main - cache: - key: models - paths: - - detection/model - test: stage: test-and-build except: @@ -40,8 +21,6 @@ test: - make opencv -j$(nproc --ignore=2) - make -C alg qrtool -j$(nproc --ignore=2) - make test - dependencies: - - download-models build-alg: stage: test-and-build @@ -83,7 +62,6 @@ build-docker: dependencies: - build-web - build-alg - - download-models except: - main diff --git a/Dockerfile b/Dockerfile index a1d52dc..ec1d3c0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,6 @@ RUN apt-get update -y && DEBIAN_FRONTEND=noninteractive apt-get install -y $(cat RUN pip3 install --no-cache-dir torch==1.13.0 torchvision==0.14.0 torchaudio==0.13.0 ADD requirements.txt requirements.txt RUN pip3 install --no-cache-dir -r requirements.txt -ADD detection /emblem/detection ADD alg /emblem/alg ADD api /emblem/api ADD web /emblem/web diff --git a/Makefile b/Makefile index d407263..1b85500 100644 --- a/Makefile +++ b/Makefile @@ -21,15 +21,6 @@ WEB_FILES = \ -type f)\ ) -DETECTION_FILES = \ - $(addprefix build/, \ - $(shell find -L \ - detection \ - -type f \ - -not -name '*.pyc' \ - ) \ - ) - SCRIPTS_FILES = \ $(addprefix build/, \ $(shell find -L \ @@ -57,8 +48,8 @@ ALG_FILES = \ ) \ ) -docker-build: build/Dockerfile build/packages.txt build/requirements.txt download-models \ - build/nginx.conf $(WEB_FILES) $(API_FILES) $(ALG_FILES) $(DETECTION_FILES) $(SCRIPTS_FILES) $(DATASET_FILES) +docker-build: build/Dockerfile build/packages.txt build/requirements.txt \ + build/nginx.conf $(WEB_FILES) $(API_FILES) $(ALG_FILES) $(SCRIPTS_FILES) $(DATASET_FILES) find build docker build --network=host -t $(IMAGE) build @@ -101,7 +92,6 @@ deploy-roi-worker: test: FORCE cd emtest && cargo test -- --nocapture cd api; ./manage.py migrate && ./manage.py test tests - make -C detection test OPENCV_TAG := 4.9.0 opencv/src/LICENSE: @@ -147,8 +137,4 @@ opencv.js: opencv/src/LICENSE FORCE --disable_single_file alg/qrtool: - make -C alg qrtool - - -download-models: FORCE - cd detection && python3 app.py --download-models-only + make -C alg qrtool \ No newline at end of file diff --git a/api/products/migrations/0102_remove_codebatch_detection_service.py b/api/products/migrations/0102_remove_codebatch_detection_service.py new file mode 100644 index 0000000..bfda89a --- /dev/null +++ b/api/products/migrations/0102_remove_codebatch_detection_service.py @@ -0,0 +1,17 @@ +# Generated by Django 3.2.25 on 2025-04-24 08:06 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('products', '0101_auto_20250302_1419'), + ] + + operations = [ + migrations.RemoveField( + model_name='codebatch', + name='detection_service', + ), + ] diff --git a/api/products/models.py b/api/products/models.py index 7d96b06..98e0855 100644 --- a/api/products/models.py +++ b/api/products/models.py @@ -170,8 +170,6 @@ class CodeBatch(models.Model): num_digits = models.IntegerField(default=10, verbose_name="尾号长度") is_active = models.BooleanField(default=True, verbose_name="已激活") name = models.CharField(null=True, max_length=255, db_index=True, verbose_name="名称") - detection_service = models.TextField(null=True, blank=True, - verbose_name="指定检测后端微服务地址(可选) ") scan_redirect_url = models.TextField(null=True, blank=True, verbose_name="自定义扫码重定向URL(可选)") enable_auto_torch = models.BooleanField(default=False, verbose_name="自动打开闪光灯") diff --git a/api/products/views.py b/api/products/views.py index 5531889..683c238 100644 --- a/api/products/views.py +++ b/api/products/views.py @@ -642,88 +642,25 @@ class QrVerifyView(BaseView): if batch.qr_angle_allowed_error < 0.01: messages.append(f"batch.qr_angle_allowed_error {batch.qr_angle_allowed_error} < 0.01, not checking angle") return - if angle is not None: - diff = abs(batch.qr_angle - float(angle)) - if diff > batch.qr_angle_allowed_error: - messages.append(f"Angle check failed, captured {angle} but expecting {batch.qr_angle} with error margin {batch.qr_angle_allowed_error}") - raise Exception("Angle check failed") - return - - ds = batch.detection_service or "http://localhost:6006" - for api in ['/dot_detection_top', '/dot_detection_bottom']: - try: - messages.append(f"trying api {api}") - r = requests.post(ds + api, files={ - 'file': open(filename, 'rb'), - }, - data={ - 'threshold': str(batch.qr_angle_allowed_error), - 'angle': str(batch.qr_angle), - } - ) - r = r.json() - messages.append(f"{api} response: {r}") - data = r['data'] - if data["status"] == "OK": - messages.append("status is OK, angle check succeeded") - return - except Exception as e: - messages.append(f"API {api} error: {e}") - pass - messages.append(f"All angle check api failed") - raise Exception("Angle detection failed") - - def feature_comparison_check(self, sc, batch, img_fn, messages, threshold): - if not threshold: - threshold = batch.feature_comparison_threshold - if threshold < 0.01: - messages.append(f"batch.feature_comparison_threshold {batch.feature_comparison_threshold} < 0.01, not comparing feature") - return - feature_roi = self.get_feature_roi(sc.code) - if not feature_roi: - messages.append(f"feature roi not found for code {sc.code}, skiping") - return - ds = batch.detection_service or "http://localhost:6006" - api_name = "/qr_roi_cloud_comparison" - url = ds + api_name - feature_roi_len = len(feature_roi) - qrtool_path = os.path.abspath("../alg/qrtool") - if not qrtool_path: - raise Exception("Cannot find qrtool") - cwd = os.path.dirname(qrtool_path) - cmd = [qrtool_path, 'topleft', img_fn] - messages.append(" ".join(cmd)) - subprocess.check_call(cmd, cwd=cwd) - messages.append(f"calling: {url}, local file {img_fn}, feature roi size {feature_roi_len}, threshold {threshold}") - r = requests.post(url, files={ - 'ter_file': open(img_fn + ".topleft.jpg", 'rb'), - 'std_file': feature_roi, - }, - data={ - 'threshold': str(threshold), - }) - j = r.json() - messages.append(f"response: {j}") - data = j.get('data') - if data.get("status") != "OK" or data.get('comparison_result').lower() not in ['pass', 'passed']: - messages.append(f"Feature comparison failed") - raise Exception(f"feature comparison check failed: {j}") - messages.append(f"Feature comparison succeeded") + if angle is None: + raise Exception("Angle check failed, angle is missing?") + diff = abs(batch.qr_angle - float(angle)) + if diff > batch.qr_angle_allowed_error: + messages.append(f"Angle check failed, captured {angle} but expecting {batch.qr_angle} with error margin {batch.qr_angle_allowed_error}") + raise Exception("Angle check failed") def do_v5_qr_verify(self, img_fn, messages): - try: - resp = requests.post( - "https://themblem.com/api/v5/qr_verify", - files={ - 'frame': open(img_fn, 'rb'), - }, - ) - rd = resp.json() - messages.append(f"v5 qr_verify response: {rd}") - return rd.get("result", {}).get("predicted_class", 0) == 1 - except Exception as e: - messages.append(f"v5 qr verify failed: {e}") - return False + resp = requests.post( + "https://themblem.com/api/v5/qr_verify", + files={ + 'frame': open(img_fn, 'rb'), + }, + ) + rd = resp.json() + messages.append(f"v5 qr_verify response: {rd}") + ok = rd.get("result", {}).get("predicted_class", 0) == 1 + if not ok: + raise Exception("v5 qr verify failed") def post(self, request): image_name = '' @@ -776,10 +713,7 @@ class QrVerifyView(BaseView): sd.tenant = tenant sd.batch = sc.batch self.dot_angle_check(sc.batch, tf.name, messages, request.data.get("angle")) - if self.do_v5_qr_verify(tf.name, messages): - pass - else: - self.feature_comparison_check(sd, sc.batch, tf.name, messages, threshold) + self.do_v5_qr_verify(tf.name, messages) sd.succeeded = True article_id = None if product.article: diff --git a/detection/.dockerignore b/detection/.dockerignore deleted file mode 100644 index c3fba8c..0000000 --- a/detection/.dockerignore +++ /dev/null @@ -1 +0,0 @@ -/.git diff --git a/detection/.gitignore b/detection/.gitignore deleted file mode 100644 index bba848c..0000000 --- a/detection/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -__pycache__ -.*.swp -.*.swo -.*.swn -*.pyc -/tmp/* -.idea -.DS_Store diff --git a/detection/.gitlab-ci.yml b/detection/.gitlab-ci.yml deleted file mode 100644 index fb633e2..0000000 --- a/detection/.gitlab-ci.yml +++ /dev/null @@ -1,36 +0,0 @@ -stages: - - build - - test - - push - - deploy - -cache: - - key: venv - paths: - - venv - -build: - stage: build - script: - - ./scripts/ci build - -test: - stage: test - script: - - ./scripts/ci test - -push: - stage: push - script: - - ./scripts/ci push - -deploy-dev: - stage: deploy - script: - - ./scripts/ci deploy-dev - -deploy-prod: - stage: deploy - when: manual - script: - - ./scripts/ci deploy-prod diff --git a/detection/Makefile b/detection/Makefile deleted file mode 100644 index d2d2fa0..0000000 --- a/detection/Makefile +++ /dev/null @@ -1,2 +0,0 @@ -test: - ./tests/run.py diff --git a/detection/__init__.py b/detection/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/detection/api_debug.py b/detection/api_debug.py deleted file mode 100644 index 21c6ac5..0000000 --- a/detection/api_debug.py +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- -''' -@File : api_debug.py -@Contact : zpyovo@hotmail.com -@License : (C)Copyright 2018-2019, Lab501-TransferLearning-SCUT -@Description : - -@Modify Time @Author @Version @Desciption ------------- ------- -------- ----------- -2023/8/26 17:55 Pengyu Zhang 1.0 None -''' - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -import requests -import cv2 -import os -import json -import numpy as np -std_feature = os.path.join('/project/emblem_detection/tests/data/std_roi_encoding.json') -with open(std_feature, "r") as input_file: - loaded_data = json.load(input_file) -# 提取 qr_encoding 数据 -std_feature_encoding = loaded_data['data']["roi_encoding"] - -imagePath = "/project/emblem_detection/tests/data/1291182811394_pos_iPhone12Pro.jpg" -# imagePath = "/project/emblem_detection/tests/data/1291182811394_pos_LYA-AL00.jpg" -img = cv2.imread(imagePath) -imgfile = cv2.imencode('.jpg', img)[1] -files = { "file" : ('filename.jpg',imgfile, 'image/jpg')} -# form = {"threshold":'50',"angle":'45'} -form = {"threshold": '50', "std_roi_feature": json.dumps(std_feature_encoding)} - -r = requests.post('http://localhost:6006/qr_roi_cloud_feature_comparison', files=files, - data=form).json() - -print(r) - -# r = requests.post('http://localhost:6006/qr_decode', files=files).json() -# -# print(r) -# -# r = requests.post('http://localhost:6006/dot_detection', files=files, data=form).json() -# -# print(r) -# -# r = requests.post('http://localhost:6006/dot_detection_top', files=files, data=form).json() -# -# print(r) -# -# r = requests.post('http://localhost:6006/dot_detection_bottom', files=files, data=form).json() -# -# print(r) -# -# r = requests.post('http://localhost:6006/qr_roi_cloud_feature_extraction', files=files).json() -# -# print(r['data']['status']) \ No newline at end of file diff --git a/detection/app.py b/detection/app.py deleted file mode 100755 index a7951e4..0000000 --- a/detection/app.py +++ /dev/null @@ -1,266 +0,0 @@ -#!/usr/bin/env python3 -from flask import Flask, request -import os -import argparse -import numpy as np -import qr_detection -from PIL import Image -from dot_detection import dots_angle_measure_dl_sr, dots_angle_measure_tr_sr -from thirdTool import qr_box_detect -from common import cropped_image -from qr_verify_Tool.qr_orb import ORB_detect, roi_siml -import json -from qr_verify_Tool.affinity import roi_affinity_siml -from qr_verify_Tool.roi_img_process import * -from qr_verify_Tool.models import * -from utils import get_model -from thirdTool import qrsrgan - -app = Flask(__name__) -# os.environ["CUDA_DEVICE_ORDER"] = "PCI_BUS_ID" -# os.environ["CUDA_VISIBLE_DEVICES"] = "0" - -class Detection(object): - def get_models(self): - self.qr_cloud_detect_model = get_model('qr_cloud_detect_20230928.pt') - self.qr_roi_cloud_detect_model = get_model('qr_roi_cloud_detect_20230928.pt') - self.qr_net_g_2000_model = get_model('net_g_2000.pth') - self.roi_net_g_model = get_model("roi_net_g_20240306.pth") - - def init_models(self): - device = os.environ.get("CUDA_DEVICE", "cpu") - self.qr_box_detect_now = qr_box_detect.QR_Box_detect(model_path=self.qr_cloud_detect_model, device=device) - self.qr_roi_detect_now = qr_box_detect.QR_Box_detect(model_path=self.qr_roi_cloud_detect_model, device=device) - - self.dot_realesrgan = qrsrgan.RealsrGan(model_path=self.qr_net_g_2000_model, device=device) - self.roi_generator = GeneratorUNet() - self.roi_generator.load_state_dict(torch.load(self.roi_net_g_model, map_location=torch.device('cpu'))) - -detection = Detection() - -@app.route('/upload', methods=['POST', 'GET']) -def upload(): - f = request.files.get('file') - upload_path = os.path.join("tmp/tmp." + f.filename.split(".")[-1]) - # secure_filename(f.filename)) #注意:没有的文件夹一定要先创建,不然会提示没有该路径 - f.save(upload_path) - return upload_path - - -''' -#用于防伪图签特征对比 -入参:std_file---备案特征采集区域文件(输入原始roi带黑框图像) - ter_file---终端特征采集文件(输入仿射扭正后1/4的左上角图像) - threshold---图像对比阈值 -返回: - {"comparison_result": 对比结果,'similarity_value':相似度值, "status":'对比方法调用状态'} -''' -@app.route('/qr_roi_cloud_comparison', methods=['POST', 'GET']) -def qr_roi_cloud_comparison(): - def do_api(): - ''' - #获取图片url,此处的url为request参数 - ''' - std_file = request.files.get('std_file') #备案特征采集区域文件 - ter_file = request.files.get('ter_file') #终端特征采集文件 - threshold = float(request.form.get('threshold')) #对比阈值 - - std_roi_img = Image.open(std_file) - ter_img = Image.open(ter_file) - - '''备案特征区域数据处理''' - std_roi_im_fix = roi_img_processing(std_roi_img) - - #提取特征提取区域 - ter_roi_points, qr_roi_conf = detection.qr_roi_detect_now(ter_img) - ter_roi_img = cropped_image(ter_img,ter_roi_points,shift=-3,size=1) - # 图像还原计算 - ter_roi_im_re = detection.roi_generator(ter_roi_img) - - # roi特征区域处理 - ter_roi_im_re_fix = roi_img_processing(ter_roi_im_re) - - if ter_roi_im_re_fix is None: - return {"comparison_result": None, 'similarity_value': None, "status": 'False', "reason": "cannot find roi region after enhancing"} - - similarity = roi_affinity_siml(ter_roi_im_re_fix,std_roi_im_fix) - - comparison_result = "pass" if similarity > threshold else "failed" - return {"comparison_result": comparison_result,'similarity_value':similarity, "status":'OK'} - - try: - return {"data": do_api()} - except Exception as e: - return {"error": str(e)} - -''' -网点角度顶部位置检测 -''' -@app.route('/dot_detection_top', methods=['POST', 'GET']) -def dot_detection_top(): - - def do_api(): - - image_file = request.files.get('file') - threshold = request.form.get('threshold') - angle = request.form.get('angle') - img = Image.open(image_file) - points, conf = detection.qr_box_detect_now(img) - - def angle_ok(a): - if a is None: - return False - return abs(a - float(angle)) <= float(threshold) - - if points is None : - raise Exception("Qr_Decode error") - else: - rotation_region = qr_detection.rotation_region_crop(img, points) - img_ = qr_detection.rotation_waffine(Image.fromarray(rotation_region), img) - img_ = np.array(img_) - - x1_1 = int(points[0, 0] * 1) - y1_1 = int(points[0, 1] * 1 - (points[1, 1] * 1 - points[0, 1] * 1) * (0.25)) - if y1_1 < 0: - y1_1 = int(points[0, 1] * 1 - (points[1, 1] * 1 - points[0, 1] * 1) * (0.20)) - if y1_1 < 0: - y1_1 = int(points[0, 1] * 1 - (points[1, 1] * 1 - points[0, 1] * 1) * (0.18)) - if y1_1 < 0: - y1_1 = int(points[0, 1] * 1 - (points[1, 1] * 1 - points[0, 1] * 1) * (0.15)) - if y1_1 < 0: - y1_1 = int(points[0, 1] * 1 - (points[1, 1] * 1 - points[0, 1] * 1) * (0.14)) - if y1_1 < 0: - y1_1 = int(points[0, 1] * 1 - (points[1, 1] * 1 - points[0, 1] * 1) * (0.13)) - if y1_1 < 0: - y1_1 = int(points[0, 1] * 1 - (points[1, 1] * 1 - points[0, 1] * 1) * (0.12)) - if y1_1 < 0: - y1_1 = int(points[0, 1] * 1 - (points[1, 1] * 1 - points[0, 1] * 1) * (0.11)) - if y1_1 >= 0: - x1_2 = int(x1_1 + (points[1, 0] * 1 - points[0, 0] * 1) * 0.5) - y1_2 = int(y1_1 + (points[1, 1] * 1 - points[0, 1] * 1) * 0.1) - else: - return {"status":"False", "reason": "coordinate invalid"} - - dots_region_top = img_[y1_1:y1_2, x1_1:x1_2] - - # 采用传统插值方式实现图像增强,进行网线角度检测 - tr_sr_lines_arctan, _ = dots_angle_measure_tr_sr(dots_region_top, save_dots=False, - res_code=None) - if angle_ok(tr_sr_lines_arctan): - return {"dot_angle": tr_sr_lines_arctan, "method": "tr_sr", "status": "OK"} - - #采用模型生成实现图像增强,进行网线角度检测 - dl_sr_lines_arctan, _ = dots_angle_measure_dl_sr(dots_region_top, detection.dot_realesrgan, save_dots=False, - res_code=None) - if dl_sr_lines_arctan is not None: - if angle_ok(dl_sr_lines_arctan): - return {"dot_angle": dl_sr_lines_arctan, "method": "dl_sr","status": "OK"} - else: - reason = f"Angle {dl_sr_lines_arctan} not within threshold {threshold} from reference angle {angle}" - return {"dot_angle": dl_sr_lines_arctan, "method": "dl_sr","status": "False", "reason": reason} - return {"status": "False", "reason": "Failed to find dot angle"} - try: - return {"data": do_api()} - except Exception as e: - return {"error": str(e)} -''' -网点角度底部位置检测 -''' -@app.route('/dot_detection_bottom', methods=['POST', 'GET']) -def dot_detection_bottom(): - - def do_api(): - image_file = request.files.get('file') - threshold = request.form.get('threshold') - angle = request.form.get('angle') - img = Image.open(image_file) - points, conf = detection.qr_box_detect_now(img) - - def angle_ok(a): - if a is None: - return False - return abs(a - float(angle)) <= float(threshold) - - if points is None : - raise Exception("Qr_Decode error") - else: - rotation_region = qr_detection.rotation_region_crop(img, points) - img_ = qr_detection.rotation_waffine(Image.fromarray(rotation_region), img) - img_ = np.array(img_) - x2_2 = int(points[1, 0] * 0.8) - y2_2 = int(points[1, 1] * 1 + (points[1, 1] * 1 - points[0, 1] * 1) * (0.25)) - max_y2 = img_.shape[0] - if y2_2 > max_y2: - y2_2 = int(points[1, 1] * 1 + (points[1, 1] * 1 - points[0, 1] * 1) * (0.20)) - - if y2_2 > max_y2: - y2_2 = int(points[1, 1] * 1 + (points[1, 1] * 1 - points[0, 1] * 1) * (0.18)) - - if y2_2 > max_y2: - y2_2 = int(points[1, 1] * 1 + (points[1, 1] * 1 - points[0, 1] * 1) * (0.15)) - - if y2_2 > max_y2: - y2_2 = int(points[1, 1] * 1 + (points[1, 1] * 1 - points[0, 1] * 1) * (0.14)) - - if y2_2 > max_y2: - y2_2 = int(points[1, 1] * 1 + (points[1, 1] * 1 - points[0, 1] * 1) * (0.13)) - - if y2_2 > max_y2: - y2_2 = int(points[1, 1] * 1 + (points[1, 1] * 1 - points[0, 1] * 1) * (0.12)) - - if y2_2 > max_y2: - y2_2 = int(points[1, 1] * 1 + (points[1, 1] * 1 - points[0, 1] * 1) * (0.11)) - - if y2_2 <= max_y2: - x2_1 = int(x2_2 - (points[1, 0] * 1 - points[0, 0] * 1) * 0.5) - y2_1 = int(y2_2 - (points[1, 1] * 1 - points[0, 1] * 1) * 0.1) - else: - return {"status": "False", "reason": "coordinate invalid"} - - dots_region_bottom = img_[y2_1:y2_2, x2_1:x2_2] - - # 采用传统插值方式实现图像增强,进行网线角度检测 - tr_sr_lines_arctan, _ = dots_angle_measure_tr_sr(dots_region_bottom, save_dots=False, - res_code=None) - if angle_ok(tr_sr_lines_arctan): - return {"dot_angle": tr_sr_lines_arctan, "method": "tr_sr", "status": "OK"} - - #采用模型生成实现图像增强,进行网线角度检测 - dl_sr_lines_arctan, _ = dots_angle_measure_dl_sr(dots_region_bottom, detection.dot_realesrgan, save_dots=False, - res_code=None) - if dl_sr_lines_arctan is not None: - if angle_ok(dl_sr_lines_arctan): - return {"dot_angle": dl_sr_lines_arctan, "method": "dl_sr","status": "OK"} - else: - reason = f"Angle {dl_sr_lines_arctan} not within threshold {threshold} from reference angle {angle}" - return {"dot_angle": dl_sr_lines_arctan, "method": "dl_sr","status": "False", "reason": reason} - return {"status": "False", "reason": "Failed to find dot angle"} - - try: - return {"data": do_api()} - except Exception as e: - return {"error": str(e)} - -@app.route('/', methods=['GET']) -def index(): - return 'emblem detection api' - -def parse_args(): - parser = argparse.ArgumentParser() - parser.add_argument("--host", "-l", default="0.0.0.0") - parser.add_argument("--port", "-p", type=int, default=6006) - parser.add_argument("--download-models-only", action="store_true") - return parser.parse_args() - -def main(): - args = parse_args() - detection.get_models() - if args.download_models_only: - print("Models loaded successfully") - return - detection.init_models() - print(f"Starting server at {args.host}:{args.port}") - app.run(host=args.host, port=args.port, debug=True) - -if __name__ == '__main__': - main() \ No newline at end of file diff --git a/detection/common.py b/detection/common.py deleted file mode 100644 index 22e64f7..0000000 --- a/detection/common.py +++ /dev/null @@ -1,100 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- -''' -@File : common.py -@Contact : zpyovo@hotmail.com -@License : (C)Copyright 2018-2019, Lab501-TransferLearning-SCUT -@Description : - -@Modify Time @Author @Version @Desciption ------------- ------- -------- ----------- -2022/4/21 23:46 Pengyu Zhang 1.0 None -''' - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import os -import time -import matplotlib.pyplot as plt - -import numpy as np -import cv2 -from matplotlib.ticker import NullLocator -from PIL import Image - -'''色彩增益加权的AutoMSRCR算法''' -def singleScaleRetinex(img, sigma): - retinex = np.log10(img) - np.log10(cv2.GaussianBlur(img, (0, 0), sigma)) - - return retinex - -def multiScaleRetinex(img, sigma_list): - retinex = np.zeros_like(img) - for sigma in sigma_list: - retinex += singleScaleRetinex(img, sigma) - - retinex = retinex / len(sigma_list) - - return retinex - -def automatedMSRCR(img, sigma_list): - img = np.float64(img) + 1.0 - - img_retinex = multiScaleRetinex(img, sigma_list) - - for i in range(img_retinex.shape[2]): - unique, count = np.unique(np.int32(img_retinex[:, :, i] * 100), return_counts=True) - for u, c in zip(unique, count): - if u == 0: - zero_count = c - break - - low_val = unique[0] / 100.0 - high_val = unique[-1] / 100.0 - for u, c in zip(unique, count): - if u < 0 and c < zero_count * 0.1: - low_val = u / 100.0 - if u > 0 and c < zero_count * 0.1: - high_val = u / 100.0 - break - - img_retinex[:, :, i] = np.maximum(np.minimum(img_retinex[:, :, i], high_val), low_val) - - img_retinex[:, :, i] = (img_retinex[:, :, i] - np.min(img_retinex[:, :, i])) / \ - (np.max(img_retinex[:, :, i]) - np.min(img_retinex[:, :, i])) \ - * 255 - - img_retinex = np.uint8(img_retinex) - - return img_retinex - -'''图像处理过程保存''' -def save_figure(fig_context,fig_name,res_code): - fig = plt.figure() - ax = fig.subplots(1) - ax.imshow(fig_context, aspect='equal') - result_path = os.path.join("tmp", '{}_{}_{}.jpg'.format(fig_name,res_code,time.time())) - plt.axis("off") - plt.gca().xaxis.set_major_locator(NullLocator()) - plt.gca().yaxis.set_major_locator(NullLocator()) - # filename = result_path.split("/")[-1].split(".")[0] - plt.savefig(result_path, quality=95, bbox_inches="tight", pad_inches=0.0) - plt.close() - return result_path - -def cropped_image(img, points, shift, size): - x_min, y_min = points[0] - x_max, y_max = points[1] - - x_min, y_min, x_max, y_max = int(x_min) - shift, int(y_min) - shift, int(x_max) + shift, int(y_max) + shift - quarter_width = (x_max - x_min) // size - quarter_height = (y_max - y_min) // size - - # 裁剪图像 - img = Image.fromarray(np.uint8(img)) - cropped_im = img.crop((x_min, y_min, x_min + quarter_width, y_min + quarter_height)) - - - return cropped_im \ No newline at end of file diff --git a/detection/component/__init__.py b/detection/component/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/detection/component/utile.py b/detection/component/utile.py deleted file mode 100644 index 0165857..0000000 --- a/detection/component/utile.py +++ /dev/null @@ -1,17 +0,0 @@ - - - -def return_img_stream(img_local_path): - """ - 工具函数: - 获取本地图片流 - :param img_local_path:文件单张图片的本地绝对路径 - :return: 图片流 - """ - import base64 - img_stream = '' - with open(img_local_path, 'rb') as img_f: - img_stream = img_f.read() - img_stream = base64.b64encode(img_stream) - # print('img_stream:{}'.format('data:image/png;base64,' + str(img_stream))) - return str(img_stream).split("\'")[1] \ No newline at end of file diff --git a/detection/dot_detection.py b/detection/dot_detection.py deleted file mode 100644 index 1157e90..0000000 --- a/detection/dot_detection.py +++ /dev/null @@ -1,137 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- -''' -@File : dot_detection.py -@Contact : zpyovo@hotmail.com -@License : (C)Copyright 2018-2019, Lab501-TransferLearning-SCUT -@Description : - -@Modify Time @Author @Version @Desciption ------------- ------- -------- ----------- -2022/4/21 17:53 Pengyu Zhang 1.0 None -''' - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import cv2 -from common import automatedMSRCR, save_figure -from component.utile import return_img_stream -import numpy as np -import math -''' -采用realesrgan模型实现图像增强后进行网线角度检测 -''' -def dots_angle_measure_dl_sr(dots_region,realesrgan,save_dots=False,res_code=None): - - original_img_stream = None - process_dots_img_stream = None - detection_dots_img_stream = None - lines_arctan = None - if save_dots: - result_path = save_figure(dots_region, 'original', res_code) - original_img_stream = return_img_stream(result_path) - # 超分计算处理 - process_dots = realesrgan(dots_region) - - - if save_dots: - b, g, r = cv2.split(process_dots) - process_dots_ = cv2.merge([r, g, b]) - result_path = save_figure(process_dots_, 'process_dots', res_code) - process_dots_img_stream = return_img_stream(result_path) - - # 斑点检测 - found, corners = cv2.findCirclesGrid(process_dots, (3, 3), cv2.CALIB_CB_SYMMETRIC_GRID) - - if corners is None or corners.any() != None and corners.shape[0] < 2: - # print("---------------------------------------------------------------") - # print("corners status,", found) - # print("---------------------------------------------------------------") - return lines_arctan, [original_img_stream, process_dots_img_stream] - - elif corners.shape[0] >= 2: - A1 = corners[0] - A2 = corners[1] - if (A1 == A2).all(): - # 斑点检测 - found, corners = cv2.findCirclesGrid(process_dots, (4, 4), cv2.CALIB_CB_SYMMETRIC_GRID) - A1 = corners[0] - A2 = corners[1] - if (A1 == A2).all(): - return lines_arctan, [original_img_stream, process_dots_img_stream] - B1 = corners[1] - B2 = np.array([[0, corners[1][:, 1]]],dtype=object) - - kLine1 = (A2[:, 1] - A1[:, 1]) / (A2[:, 0] - A1[:, 0]) - kLine2 = (B2[:, 1] - B1[:, 1]) / (B2[:, 0] - B1[:, 0]) - tan_k = (kLine2 - kLine1) / (1 + kLine2 * kLine1) - lines_arctan = math.atan(tan_k) - lines_arctan = float('%.2f' % abs(-lines_arctan * 180.0 / 3.1415926)) - if save_dots: - dbg_image_circles = process_dots.copy() - dbg_image_circles = cv2.drawChessboardCorners(dbg_image_circles, (3, 3), corners, found) - result_path = save_figure(dbg_image_circles, 'detection_dots', res_code) - detection_dots_img_stream = return_img_stream(result_path) - - return lines_arctan, [original_img_stream, process_dots_img_stream, detection_dots_img_stream] -''' -采用传统方式实现图像增强后进行网线角度检测 -''' -def dots_angle_measure_tr_sr(dots_region,save_dots=False,res_code=None): - - original_img_stream = None - process_dots_img_stream = None - detection_dots_img_stream = None - lines_arctan = None - if save_dots: - result_path = save_figure(dots_region, 'original', res_code) - original_img_stream = return_img_stream(result_path) - # 锐化插值处理 - sigma_list = [15, 80, 200] - process_dots = automatedMSRCR( - dots_region, - sigma_list - ) - size = 4 - process_dots = cv2.resize(process_dots, None, fx=size, fy=size, interpolation=cv2.INTER_CUBIC) - - if save_dots: - result_path = save_figure(process_dots, ' process_dots', res_code) - process_dots_img_stream = return_img_stream(result_path) - - # 斑点检测 - found, corners = cv2.findCirclesGrid(process_dots, (3, 3), cv2.CALIB_CB_SYMMETRIC_GRID) - - if corners is None or corners.any() != None and corners.shape[0] < 2: - # print("---------------------------------------------------------------") - # print("corners status,", found) - # print("---------------------------------------------------------------") - return lines_arctan, [original_img_stream, process_dots_img_stream] - - elif corners.shape[0] >= 2: - A1 = corners[0] - A2 = corners[1] - if (A1 == A2).all(): - # 斑点检测 - found, corners = cv2.findCirclesGrid(process_dots, (4, 4), cv2.CALIB_CB_SYMMETRIC_GRID) - A1 = corners[0] - A2 = corners[1] - if (A1 == A2).all(): - return lines_arctan, [original_img_stream, process_dots_img_stream] - B1 = corners[1] - B2 = np.array([[0, corners[1][:, 1]]],dtype=object) - - kLine1 = (A2[:, 1] - A1[:, 1]) / (A2[:, 0] - A1[:, 0]) - kLine2 = (B2[:, 1] - B1[:, 1]) / (B2[:, 0] - B1[:, 0]) - tan_k = (kLine2 - kLine1) / (1 + kLine2 * kLine1) - lines_arctan = math.atan(tan_k) - lines_arctan = float('%.2f' % abs(-lines_arctan * 180.0 / 3.1415926)) - if save_dots: - dbg_image_circles = process_dots.copy() - dbg_image_circles = cv2.drawChessboardCorners(dbg_image_circles, (3, 3), corners, found) - result_path = save_figure(dbg_image_circles, 'detection_dots', res_code) - detection_dots_img_stream = return_img_stream(result_path) - - return lines_arctan, [original_img_stream, process_dots_img_stream, detection_dots_img_stream] diff --git a/detection/img_1_001.jpg b/detection/img_1_001.jpg deleted file mode 100644 index f911471..0000000 Binary files a/detection/img_1_001.jpg and /dev/null differ diff --git a/detection/qr_detection.py b/detection/qr_detection.py deleted file mode 100644 index e3385df..0000000 --- a/detection/qr_detection.py +++ /dev/null @@ -1,447 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- -''' -@File : qr_detection.py -@Contact : zpyovo@hotmail.com -@License : (C)Copyright 2018-2019, Lab501-TransferLearning-SCUT -@Description : - -@Modify Time @Author @Version @Desciption ------------- ------- -------- ----------- -2022/4/22 09:08 Pengyu Zhang 1.0 None -''' - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from PIL import Image, ImageEnhance -import numpy as np -import cv2 - - -def solution_1_1(img, detector): - # 亮度处理 - birght_img = ImageEnhance.Brightness(img) - birght_img = birght_img.enhance(2) - birght_img = np.array(birght_img) - res, points = detector.detectAndDecode(birght_img) - if len(res) == 0: - samll_img = cv2.resize(np.array(birght_img), None, fx=1 / 4, fy=1 / 4, interpolation=cv2.INTER_CUBIC) - input = np.array(samll_img) - res, points = detector.detectAndDecode(input) - if len(res) == 0: - samll_img = cv2.resize(np.array(birght_img), None, fx=1 / 8, fy=1 / 8, interpolation=cv2.INTER_CUBIC) - input = np.array(samll_img) - res, points = detector.detectAndDecode(input) - if len(res) == 0: - return None, None - else: - return res, points[0] * 8 - return res, points[0] * 4 - return res, points[0] * 1 - - -def solution_1_2(img, detector): - # 亮度处理 - birght_img = ImageEnhance.Brightness(img) - birght_img = birght_img.enhance(3) - birght_img = np.array(birght_img) - res, points = detector.detectAndDecode(birght_img) - if len(res) == 0: - samll_img = cv2.resize(np.array(birght_img), None, fx=1 / 4, fy=1 / 4, interpolation=cv2.INTER_CUBIC) - input = np.array(samll_img) - res, points = detector.detectAndDecode(input) - if len(res) == 0: - samll_img = cv2.resize(np.array(birght_img), None, fx=1 / 8, fy=1 / 8, interpolation=cv2.INTER_CUBIC) - input = np.array(samll_img) - res, points = detector.detectAndDecode(input) - if len(res) == 0: - return None, None - else: - return res, points[0] * 8 - return res, points[0] * 4 - return res, points[0] * 1 - - -def solution_2_1(img, detector): - # #对比度增强 + 亮度处理 - contrast_img = ImageEnhance.Contrast(img) - contrast_img = contrast_img.enhance(1.5) - birght_img = ImageEnhance.Brightness(contrast_img) - birght_img = birght_img.enhance(2) - birght_img = np.array(birght_img) - res, points = detector.detectAndDecode(birght_img) - if len(res) == 0: - samll_img = cv2.resize(np.array(birght_img), None, fx=1 / 4, fy=1 / 4, interpolation=cv2.INTER_CUBIC) - input = np.array(samll_img) - res, points = detector.detectAndDecode(input) - if len(res) == 0: - samll_img = cv2.resize(np.array(birght_img), None, fx=1 / 8, fy=1 / 8, interpolation=cv2.INTER_CUBIC) - input = np.array(samll_img) - res, points = detector.detectAndDecode(input) - if len(res) == 0: - return None, None - else: - return res, points[0] * 8 - return res, points[0] * 4 - return res, points[0] * 1 - - -def solution_2_2(img, detector): - # #对比度增强 + 亮度处理 - contrast_img = ImageEnhance.Contrast(img) - contrast_img = contrast_img.enhance(1.5) - birght_img = ImageEnhance.Brightness(contrast_img) - birght_img = birght_img.enhance(3) - birght_img = np.array(birght_img) - res, points = detector.detectAndDecode(birght_img) - if len(res) == 0: - samll_img = cv2.resize(np.array(birght_img), None, fx=1 / 4, fy=1 / 4, interpolation=cv2.INTER_CUBIC) - input = np.array(samll_img) - res, points = detector.detectAndDecode(input) - if len(res) == 0: - samll_img = cv2.resize(np.array(birght_img), None, fx=1 / 8, fy=1 / 8, interpolation=cv2.INTER_CUBIC) - input = np.array(samll_img) - res, points = detector.detectAndDecode(input) - if len(res) == 0: - return None, None - else: - return res, points[0] * 8 - return res, points[0] * 4 - return res, points[0] * 1 - - -def solution_3_1(img, detector): - # # 亮度处理 + 对比度增强 - - birght_img = ImageEnhance.Brightness(img) - birght_img = birght_img.enhance(2) - contrast_img = ImageEnhance.Contrast(birght_img) - contrast_img = contrast_img.enhance(1.5) - contrast_img = np.array(contrast_img) - res, points = detector.detectAndDecode(contrast_img) - if len(res) == 0: - samll_img = cv2.resize(np.array(contrast_img), None, fx=1 / 4, fy=1 / 4, interpolation=cv2.INTER_CUBIC) - input = np.array(samll_img) - res, points = detector.detectAndDecode(input) - if len(res) == 0: - samll_img = cv2.resize(np.array(contrast_img), None, fx=1 / 8, fy=1 / 8, interpolation=cv2.INTER_CUBIC) - input = np.array(samll_img) - res, points = detector.detectAndDecode(input) - if len(res) == 0: - return None, None - else: - return res, points[0] * 8 - return res, points[0] * 4 - return res, points[0] * 1 - - -def solution_3_2(img, detector): - # 亮度处理 + 对比度增强 - - birght_img = ImageEnhance.Brightness(img) - birght_img = birght_img.enhance(3) - contrast_img = ImageEnhance.Contrast(birght_img) - contrast_img = contrast_img.enhance(1.5) - contrast_img = np.array(contrast_img) - res, points = detector.detectAndDecode(contrast_img) - if len(res) == 0: - samll_img = cv2.resize(np.array(contrast_img), None, fx=1 / 4, fy=1 / 4, interpolation=cv2.INTER_CUBIC) - input = np.array(samll_img) - res, points = detector.detectAndDecode(input) - if len(res) == 0: - samll_img = cv2.resize(np.array(contrast_img), None, fx=1 / 8, fy=1 / 8, interpolation=cv2.INTER_CUBIC) - input = np.array(samll_img) - res, points = detector.detectAndDecode(input) - if len(res) == 0: - return None, None - else: - return res, points[0] * 8 - return res, points[0] * 4 - return res, points[0] * 1 - - -def solution_4_1(img, detector): - # 亮度处理 + 对比度增强 + 锐化 - birght_img = ImageEnhance.Brightness(img) - birght_img = birght_img.enhance(2) - contrast_img = ImageEnhance.Contrast(birght_img) - contrast_img = contrast_img.enhance(1.5) - sharpness_img = ImageEnhance.Sharpness(contrast_img) - sharpness_img = sharpness_img.enhance(1.5) - sharpness_img = np.array(sharpness_img) - res, points = detector.detectAndDecode(sharpness_img) - if len(res) == 0: - samll_img = cv2.resize(np.array(sharpness_img), None, fx=1 / 4, fy=1 / 4, interpolation=cv2.INTER_CUBIC) - input = np.array(samll_img) - res, points = detector.detectAndDecode(input) - if len(res) == 0: - samll_img = cv2.resize(np.array(sharpness_img), None, fx=1 / 8, fy=1 / 8, interpolation=cv2.INTER_CUBIC) - input = np.array(samll_img) - res, points = detector.detectAndDecode(input) - if len(res) == 0: - return None, None - else: - return res, points[0] * 8 - return res, points[0] * 4 - return res, points[0] * 1 - - -def solution_4_2(img, detector): - # 亮度处理 + 对比度增强 + 锐化 - birght_img = ImageEnhance.Brightness(img) - birght_img = birght_img.enhance(3) - contrast_img = ImageEnhance.Contrast(birght_img) - contrast_img = contrast_img.enhance(1.5) - sharpness_img = ImageEnhance.Sharpness(contrast_img) - sharpness_img = sharpness_img.enhance(1.5) - sharpness_img = np.array(sharpness_img) - res, points = detector.detectAndDecode(sharpness_img) - if len(res) == 0: - samll_img = cv2.resize(np.array(sharpness_img), None, fx=1 / 4, fy=1 / 4, interpolation=cv2.INTER_CUBIC) - input = np.array(samll_img) - res, points = detector.detectAndDecode(input) - if len(res) == 0: - samll_img = cv2.resize(np.array(sharpness_img), None, fx=1 / 8, fy=1 / 8, interpolation=cv2.INTER_CUBIC) - input = np.array(samll_img) - res, points = detector.detectAndDecode(input) - if len(res) == 0: - return None, None - else: - return res, points[0] * 8 - return res, points[0] * 4 - return res, points[0] * 1 - - -def solution_5(img, detector): - # 缩放X4 - samll_img = cv2.resize(np.array(img), None, fx=1 / 4, fy=1 / 4, interpolation=cv2.INTER_CUBIC) - input = np.array(samll_img) - res, points = detector.detectAndDecode(input) - if len(res) == 0: - return None, None - else: - return res, points[0] * 4 - - -def solution_6(img, detector): - # 缩放X8 - samll_img = cv2.resize(np.array(img), None, fx=1 / 8, fy=1 / 8, interpolation=cv2.INTER_CUBIC) - input = np.array(samll_img) - res, points = detector.detectAndDecode(input) - if len(res) == 0: - return None, None - else: - return res, points[0] * 8 - - -def rotation_waffine(rotation_region, input): - # 亮度处理 - birght_img = ImageEnhance.Brightness(rotation_region) - birght_img = birght_img.enhance(5) - - # 灰度二值化 - img_grey = cv2.cvtColor(np.array(birght_img), cv2.COLOR_BGR2GRAY) - ret2, thresh = cv2.threshold(img_grey, 0, 255, - cv2.THRESH_BINARY + cv2.THRESH_OTSU) - - # 腐蚀 - # OpenCV定义的结构矩形元素 - kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (50, 50)) - eroded = cv2.erode(thresh, kernel) - - # canny边缘检测 - eroded_canny = cv2.Canny(eroded, 100, 300) - contours, hierarchy = cv2.findContours(eroded_canny, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) - # canny_img = np.zeros((np.array(birght_img).shape[0], np.array(birght_img).shape[1], 3), np.uint8) + 255 - # canny_img = cv2.drawContours(canny_img, contours, -1, (0, 255, 0), 3) - - # 寻找最大元素 - k = 0 - index = 0 - if len(contours) != 0: - for i in range(len(contours)): - j = contours[i].size - if j > k: - k = j - index = i - else: - return input - - # 拟合旋转矩形 - cnt = contours[index] - rect = cv2.minAreaRect(cnt) - angle = rect[2] - # box = cv2.boxPoints(rect) - # box = np.int0(box) - # rect_img = cv2.drawContours(canny_img, [box], 0, (0, 0, 255), 2) - - # 根据角度差计算仿射矩阵 - height, width, _ = np.array(input).shape - center = (width // 2, height // 2) - if angle != 0.0: - if angle > 45.0: - angle = angle - 90 - # angle = angle - 90 - # rotate page if not straight relative to QR code - M = cv2.getRotationMatrix2D(center, angle, 1.0) - output = cv2.warpAffine(np.array(input), M, (width, height), flags=cv2.INTER_CUBIC, - borderMode=cv2.BORDER_REPLICATE) - output = Image.fromarray(output) - - output = output.convert("RGB") - else: - output = input - - return output - -def rotation_region_crop(input,points): - input = np.array(input) - x1 = int(points[0, 0]) - y1 = int(points[0, 1]) - x2 = int(points[1, 0]) - y2 = int(points[1, 1]) - x_a = x1 - int((x2 - x1) * 0.1) - x_b = x2 + int((x2 - x1) * 0.1) - y_a = y1 - int((y2 - y1) * 0.1) - y_b = y2 + int((y2 - y1) * 0.1) - if x_a < 0: - x_a = 0 - if y_a < 0: - y_a = 0 - if x_b >= input.shape[1]: - x_b = input.shape[1] - if y_b >= input.shape[0]: - y_b = input.shape[0] - - top_size, bottom_size, left_size, right_size = (50, 50, 50, 50) - rotation_region = cv2.copyMakeBorder(input[y_a:y_b, x_a:x_b], top_size, bottom_size, left_size, right_size, - borderType=cv2.BORDER_REPLICATE, value=0) - return rotation_region - -def qr_detetor(input, detector): - success = False - if not success: - res, points = solution_1_1(input, detector) - - if res == None: - success = False - else: - success = True - rotation_region = rotation_region_crop(input,points) - img = rotation_waffine(Image.fromarray(rotation_region),input) - # img = input - # print('solution_1_1') - - return res, points, img - - if not success: - res, points = solution_1_2(input, detector) - if res == None: - success = False - else: - success = True - rotation_region = rotation_region_crop(input, points) - img = rotation_waffine(Image.fromarray(rotation_region), input) - # img = input - # print('solution_1_2') - return res, points, img - - if not success: - res, points = solution_5(input, detector) - if res == None: - success = False - else: - success = True - rotation_region = rotation_region_crop(input, points) - img = rotation_waffine(Image.fromarray(rotation_region), input) - # img = input - print('solution_5') - return res, points, img - - if not success: - res, points = solution_6(input, detector) - if res == None: - success = False - else: - success = True - rotation_region = rotation_region_crop(input, points) - img = rotation_waffine(Image.fromarray(rotation_region), input) - # img = input - print('solution_6') - return res, points, img - - if not success: - res, points = solution_2_1(input, detector) - if res == None: - success = False - else: - success = True - rotation_region = rotation_region_crop(input, points) - img = rotation_waffine(Image.fromarray(rotation_region), input) - # img = input - print('solution_2_1') - return res, points, img - - if not success: - res, points = solution_2_2(input, detector) - if res == None: - success = False - else: - success = True - rotation_region = rotation_region_crop(input, points) - img = rotation_waffine(Image.fromarray(rotation_region), input) - # img = input - print('solution_2_2') - return res, points, img - - if not success: - res, points = solution_3_1(input, detector) - if res == None: - success = False - else: - success = True - rotation_region = rotation_region_crop(input, points) - img = rotation_waffine(Image.fromarray(rotation_region), input) - # img = input - print('solution_3_1') - return res, points, img - - if not success: - res, points = solution_3_2(input, detector) - if res == None: - success = False - else: - success = True - rotation_region = rotation_region_crop(input, points) - img = rotation_waffine(Image.fromarray(rotation_region), input) - # img = input - print('solution_3_2') - return res, points, img - - if not success: - res, points = solution_4_1(input, detector) - if res == None: - success = False - else: - success = True - rotation_region = rotation_region_crop(input, points) - img = rotation_waffine(Image.fromarray(rotation_region), input) - # img = input - print('solution_4_1') - return res, points, img - - if not success: - res, points = solution_4_2(input, detector) - if res == None: - success = False - else: - success = True - rotation_region = rotation_region_crop(input, points) - img = rotation_waffine(Image.fromarray(rotation_region), input) - # img = input - print('solution_4_2') - return res, points, img - - if success is False: - return None, None, None diff --git a/detection/qr_verify_Tool/__init__.py b/detection/qr_verify_Tool/__init__.py deleted file mode 100644 index 6f0e841..0000000 --- a/detection/qr_verify_Tool/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- -''' -@File : __init__.py.py -@Contact : zpyovo@hotmail.com -@License : (C)Copyright 2018-2019, Lab501-TransferLearning-SCUT -@Description : - -@Modify Time @Author @Version @Desciption ------------- ------- -------- ----------- -2022/3/3 11:41 AM Pengyu Zhang 1.0 None -''' - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function diff --git a/detection/qr_verify_Tool/affinity.py b/detection/qr_verify_Tool/affinity.py deleted file mode 100644 index 31c336f..0000000 --- a/detection/qr_verify_Tool/affinity.py +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- -''' -@File : affinity.py -@Contact : zpyovo@hotmail.com -@License : (C)Copyright 2018-2019, Lab501-TransferLearning-SCUT -@Description : - -@Modify Time @Author @Version @Desciption ------------- ------- -------- ----------- -2022/3/12 9:18 PM Pengyu Zhang 1.0 None -''' - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -import torch -import torch.nn.functional as F -import numpy as np -import cv2 -def eightway_total_diff(arr): - # 对输入数组进行填充,每边填充1个元素 - padded_arr = np.pad(arr, pad_width=1, mode='edge') - - # 获取填充后数组的尺寸 - h, w = padded_arr.shape - - # 初始化输出数组,用于存储总差值 - diff_array = np.zeros((h-2, w-2)) - - # 遍历每个元素(不包括填充的边界) - for y in range(1, h-1): - for x in range(1, w-1): - total_diff = 0 # 初始化当前元素的总差值 - # 计算每个方向的差值 - for dy in range(-1, 2): - for dx in range(-1, 2): - if dy == 0 and dx == 0: - # 排除中心点自身 - continue - # 直接使用填充后的坐标计算差值 - diff = abs(padded_arr[y, x] - padded_arr[y+dy, x+dx]) - # 如果差值小于0,则置为0 - total_diff += max(diff, 0) - # 将总差值存储在输出数组中 - diff_array[y-1, x-1] = total_diff - return diff_array.astype(int) - -def roi_affinity_siml(X_ter,X_str): - X_ter = np.array(X_ter) - X_str = np.array(X_str) - X_ter_diffs = eightway_total_diff(X_ter / X_ter.max()) - X_str_diffs = eightway_total_diff(X_str / X_str.max()) - # 计算差异 - difference = cv2.absdiff(X_ter_diffs, X_str_diffs) - mean_diff = np.mean(difference) - # 计算相似度 - similarity = max((1 - mean_diff) * 100, 0) - return similarity diff --git a/detection/qr_verify_Tool/image_transform.py b/detection/qr_verify_Tool/image_transform.py deleted file mode 100644 index 3b74cda..0000000 --- a/detection/qr_verify_Tool/image_transform.py +++ /dev/null @@ -1,145 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- -''' -@File : image_transform.py -@Contact : zpyovo@hotmail.com -@License : (C)Copyright 2018-2019, Lab501-TransferLearning-SCUT -@Description : - -@Modify Time @Author @Version @Desciption ------------- ------- -------- ----------- -2024/3/5 11:33 Pengyu Zhang 1.0 None -''' - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -from typing import Union, Optional, List, Tuple, Text, BinaryIO -import io -import pathlib -import torch -import math -irange = range - - -def make_grid( - tensor: Union[torch.Tensor, List[torch.Tensor]], - nrow: int = 8, - padding: int = 2, - normalize: bool = False, - range: Optional[Tuple[int, int]] = None, - scale_each: bool = False, - pad_value: int = 0, -) -> torch.Tensor: - """Make a grid of images. - - Args: - tensor (Tensor or list): 4D mini-batch Tensor of shape (B x C x H x W) - or a list of images all of the same size. - nrow (int, optional): Number of images displayed in each row of the grid. - The final grid size is ``(B / nrow, nrow)``. Default: ``8``. - padding (int, optional): amount of padding. Default: ``2``. - normalize (bool, optional): If True, shift the image to the range (0, 1), - by the min and max values specified by :attr:`range`. Default: ``False``. - range (tuple, optional): tuple (min, max) where min and max are numbers, - then these numbers are used to normalize the image. By default, min and max - are computed from the tensor. - scale_each (bool, optional): If ``True``, scale each image in the batch of - images separately rather than the (min, max) over all images. Default: ``False``. - pad_value (float, optional): Value for the padded pixels. Default: ``0``. - - Example: - See this notebook `here `_ - - """ - if not (torch.is_tensor(tensor) or - (isinstance(tensor, list) and all(torch.is_tensor(t) for t in tensor))): - raise TypeError('tensor or list of tensors expected, got {}'.format(type(tensor))) - - # if list of tensors, convert to a 4D mini-batch Tensor - if isinstance(tensor, list): - tensor = torch.stack(tensor, dim=0) - - if tensor.dim() == 2: # single image H x W - tensor = tensor.unsqueeze(0) - if tensor.dim() == 3: # single image - if tensor.size(0) == 1: # if single-channel, convert to 3-channel - tensor = torch.cat((tensor, tensor, tensor), 0) - tensor = tensor.unsqueeze(0) - - if tensor.dim() == 4 and tensor.size(1) == 1: # single-channel images - tensor = torch.cat((tensor, tensor, tensor), 1) - - if normalize is True: - tensor = tensor.clone() # avoid modifying tensor in-place - if range is not None: - assert isinstance(range, tuple), \ - "range has to be a tuple (min, max) if specified. min and max are numbers" - - def norm_ip(img, min, max): - img.clamp_(min=min, max=max) - img.add_(-min).div_(max - min + 1e-5) - - def norm_range(t, range): - if range is not None: - norm_ip(t, range[0], range[1]) - else: - norm_ip(t, float(t.min()), float(t.max())) - - if scale_each is True: - for t in tensor: # loop over mini-batch dimension - norm_range(t, range) - else: - norm_range(tensor, range) - - if tensor.size(0) == 1: - return tensor.squeeze(0) - - # make the mini-batch of images into a grid - nmaps = tensor.size(0) - xmaps = min(nrow, nmaps) - ymaps = int(math.ceil(float(nmaps) / xmaps)) - height, width = int(tensor.size(2) + padding), int(tensor.size(3) + padding) - num_channels = tensor.size(1) - grid = tensor.new_full((num_channels, height * ymaps + padding, width * xmaps + padding), pad_value) - k = 0 - for y in irange(ymaps): - for x in irange(xmaps): - if k >= nmaps: - break - # Tensor.copy_() is a valid method but seems to be missing from the stubs - # https://pytorch.org/docs/stable/tensors.html#torch.Tensor.copy_ - grid.narrow(1, y * height + padding, height - padding).narrow( # type: ignore[attr-defined] - 2, x * width + padding, width - padding - ).copy_(tensor[k]) - k = k + 1 - return grid - - -def image_tf( - tensor: Union[torch.Tensor, List[torch.Tensor]], - nrow: int = 8, - padding: int = 2, - normalize: bool = False, - range: Optional[Tuple[int, int]] = None, - scale_each: bool = False, - pad_value: int = 0, - format: Optional[str] = None, -) -> None: - """Save a given Tensor into an image file. - - Args: - tensor (Tensor or list): Image to be saved. If given a mini-batch tensor, - saves the tensor as a grid of images by calling ``make_grid``. - fp (string or file object): A filename or a file object - format(Optional): If omitted, the format to use is determined from the filename extension. - If a file object was used instead of a filename, this parameter should always be used. - **kwargs: Other arguments are documented in ``make_grid``. - """ - from PIL import Image - grid = make_grid(tensor, nrow=nrow, padding=padding, pad_value=pad_value, - normalize=normalize, range=range, scale_each=scale_each) - # Add 0.5 after unnormalizing to [0, 255] to round to nearest integer - ndarr = grid.mul(255).add_(0.5).clamp_(0, 255).permute(1, 2, 0).to('cpu', torch.uint8).numpy() - im = Image.fromarray(ndarr) - return im diff --git a/detection/qr_verify_Tool/models.py b/detection/qr_verify_Tool/models.py deleted file mode 100755 index 186336a..0000000 --- a/detection/qr_verify_Tool/models.py +++ /dev/null @@ -1,111 +0,0 @@ -import torch.nn as nn -import torch.nn.functional as F -import torch -import torchvision.transforms as transforms -from PIL import Image -from torchvision.utils import save_image -from qr_verify_Tool.image_transform import * - -import numpy as np -def weights_init_normal(m): - classname = m.__class__.__name__ - if classname.find("Conv") != -1: - torch.nn.init.normal_(m.weight.data, 0.0, 0.02) - elif classname.find("BatchNorm2d") != -1: - torch.nn.init.normal_(m.weight.data, 1.0, 0.02) - torch.nn.init.constant_(m.bias.data, 0.0) - - -############################## -# U-NET -############################## - - -class UNetDown(nn.Module): - def __init__(self, in_size, out_size, normalize=True, dropout=0.0): - super(UNetDown, self).__init__() - layers = [nn.Conv2d(in_size, out_size, 4, 2, 1, bias=False)] - if normalize: - layers.append(nn.InstanceNorm2d(out_size)) - layers.append(nn.LeakyReLU(0.2)) - if dropout: - layers.append(nn.Dropout(dropout)) - self.model = nn.Sequential(*layers) - - def forward(self, x): - return self.model(x) - - -class UNetUp(nn.Module): - def __init__(self, in_size, out_size, dropout=0.0): - super(UNetUp, self).__init__() - layers = [ - nn.ConvTranspose2d(in_size, out_size, 4, 2, 1, bias=False), - nn.InstanceNorm2d(out_size), - nn.ReLU(inplace=True), - ] - if dropout: - layers.append(nn.Dropout(dropout)) - - self.model = nn.Sequential(*layers) - - def forward(self, x, skip_input): - x = self.model(x) - x = torch.cat((x, skip_input), 1) - - return x - - -class GeneratorUNet(nn.Module): - def __init__(self, in_channels=3, out_channels=3): - super(GeneratorUNet, self).__init__() - - self.down1 = UNetDown(in_channels, 64, normalize=False) - self.down2 = UNetDown(64, 128) - self.down3 = UNetDown(128, 256) - self.down4 = UNetDown(256, 512, dropout=0.5) - self.down5 = UNetDown(512, 512, dropout=0.5) - self.down6 = UNetDown(512, 512, dropout=0.5) - self.down7 = UNetDown(512, 512, dropout=0.5) - self.down8 = UNetDown(512, 512, normalize=False, dropout=0.5) - - self.up1 = UNetUp(512, 512, dropout=0.5) - self.up2 = UNetUp(1024, 512, dropout=0.5) - self.up3 = UNetUp(1024, 512, dropout=0.5) - self.up4 = UNetUp(1024, 512, dropout=0.5) - self.up5 = UNetUp(1024, 256) - self.up6 = UNetUp(512, 128) - self.up7 = UNetUp(256, 64) - - self.final = nn.Sequential( - nn.Upsample(scale_factor=2), - nn.ZeroPad2d((1, 0, 1, 0)), - nn.Conv2d(128, out_channels, 4, padding=1), - nn.Tanh(), - ) - - def forward(self, x): - - transform_init = transforms.Compose([transforms.Resize((256, 256), Image.NEAREST),transforms.ToTensor(), - transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),]) - x_init = transform_init(x) - x_init = x_init.unsqueeze(0) - d1 = self.down1(x_init) - d2 = self.down2(d1) - d3 = self.down3(d2) - d4 = self.down4(d3) - d5 = self.down5(d4) - d6 = self.down6(d5) - d7 = self.down7(d6) - d8 = self.down8(d7) - u1 = self.up1(d8, d7) - u2 = self.up2(u1, d6) - u3 = self.up3(u2, d5) - u4 = self.up4(u3, d4) - u5 = self.up5(u4, d3) - u6 = self.up6(u5, d2) - u7 = self.up7(u6, d1) - x_final = self.final(u7) - - x_final = image_tf(x_final,normalize=True) - return x_final \ No newline at end of file diff --git a/detection/qr_verify_Tool/network.py b/detection/qr_verify_Tool/network.py deleted file mode 100644 index d7667d2..0000000 --- a/detection/qr_verify_Tool/network.py +++ /dev/null @@ -1,205 +0,0 @@ -import torch -import torch.nn as nn -import torch.nn.functional as F -from torchvision.models import resnet18 - -class DIQA(nn.Module): - def __init__(self): - super(DIQA, self).__init__() - - self.conv1 = nn.Conv2d(in_channels=3, out_channels=48, kernel_size=3, stride=1, padding=1) - self.conv2 = nn.Conv2d(in_channels=48, out_channels=48, kernel_size=3, stride=2, padding=1) - self.conv3 = nn.Conv2d(in_channels=48, out_channels=64, kernel_size=3, stride=1, padding=1) - self.conv4 = nn.Conv2d(in_channels=64, out_channels=64, kernel_size=3, stride=2, padding=1) - self.conv5 = nn.Conv2d(in_channels=64, out_channels=64, kernel_size=3, stride=1, padding=1) - self.conv6 = nn.Conv2d(in_channels=64, out_channels=64, kernel_size=3, stride=1, padding=1) - self.conv7 = nn.Conv2d(in_channels=64, out_channels=128, kernel_size=3, stride=1, padding=1) - self.conv8 = nn.Conv2d(in_channels=128, out_channels=128, kernel_size=3, stride=1, padding=1) - # self.conv9 = nn.Conv2d(in_channels=128, out_channels=1, kernel_size=1, stride=1, padding=0) - - - self.fc1 = nn.Linear(128, 128) - # self.fc1 = nn.Linear(100352, 128) - self.fc2 = nn.Linear(128, 64) - - # 上面定义模型 下面真正意义上的搭建模型 - def forward_once(self, input): - x = input.view(-1, input[0].size(-3), input[0].size(-2), input[0].size(-1)) - - x = F.relu(self.conv1(x)) - x = F.relu(self.conv2(x)) - x = F.relu(self.conv3(x)) - x = F.relu(self.conv4(x)) - x = F.relu(self.conv5(x)) - x = F.relu(self.conv6(x)) - x = F.relu(self.conv7(x)) - conv8 = F.relu(self.conv8(x)) # 12 128 28 28 - - q = torch.nn.functional.adaptive_avg_pool2d(conv8, (1, 1)) # 12 128 1 1 - q = q.squeeze(3).squeeze(2) - # q = conv8.view(conv8.size(0), -1) - q = self.fc1(q) - q = self.fc2(q) - q = F.normalize(q, p=2, dim=1) - - return q - - def forward(self, input1, input2): - output1 = self.forward_once(input1) - output2 = self.forward_once(input2) - return output1, output2 - -class SiameseNetwork(nn.Module): - def __init__(self): - super(SiameseNetwork, self).__init__() - self.cnn1 = nn.Sequential( - nn.Conv2d(1,32,5), - nn.ReLU(True), - nn.MaxPool2d(2,2), - # Currently 45x55x32 - nn.Conv2d(32,64,3), - nn.ReLU(True), - nn.MaxPool2d(2,2), - #Currently 21x26x64 - nn.Conv2d(64,64,3), - nn.ReLU(True), - nn.MaxPool2d(2,2), - # #Currently 9x12x64 - # nn.Conv2d(64, 64, 3), - # nn.BatchNorm2d(64), - # nn.ReLU(True), - # nn.MaxPool2d(2, 2), - # nn.Conv2d(64, 64, 3), - # nn.BatchNorm2d(64), - # nn.ReLU(True) - # nn.MaxPool2d(2, 2), - ) - - self.fc1 = nn.Sequential( - nn.Linear(20736 , 4096), - # nn.Sigmoid(), - # nn.Dropout(0.5,False), - nn.Linear(4096,256)) - # self.out = nn.Linear(4096,1) - - def forward_once(self, x): - output = self.cnn1(x) - output = output.view(-1,20736) - output = self.fc1(output) - output = F.normalize(output, p=2, dim=1) - return output - - def forward(self, input1,input2): - output1 = self.forward_once(input1) - output2 = self.forward_once(input2) - # out = self.out(torch.abs(output1-output2)) - # return out.view(out.size()) - return output1,output2 - -class FaceModel(nn.Module): - def __init__(self,embedding_size,num_classes,pretrained=False): - super(FaceModel, self).__init__() - - self.model = resnet18(pretrained) - - self.embedding_size = embedding_size - - self.model.fc = nn.Linear(512*8*8, self.embedding_size) - - self.model.classifier = nn.Linear(self.embedding_size, num_classes) - - - def l2_norm(self,input): - input_size = input.size() - buffer = torch.pow(input, 2) - - normp = torch.sum(buffer, 1).add_(1e-10) - norm = torch.sqrt(normp) - - _output = torch.div(input, norm.view(-1, 1).expand_as(input)) - - output = _output.view(input_size) - - return output - - def forward_once(self, x): - - x = self.model.conv1(x) - x = self.model.bn1(x) - x = self.model.relu(x) - x = self.model.maxpool(x) - x = self.model.layer1(x) - x = self.model.layer2(x) - x = self.model.layer3(x) - x = self.model.layer4(x) - x = x.view(x.size(0), -1) - x = self.model.fc(x) - self.features = self.l2_norm(x) - # Multiply by alpha = 10 as suggested in https://arxiv.org/pdf/1703.09507.pdf - alpha=10 - self.features = self.features*alpha - - #x = self.model.classifier(self.features) - return self.features - def forward(self, input): - output = self.forward_once(input) - # output2 = self.forward_once(input2) - # out = self.out(torch.abs(output1-output2)) - # return out.view(out.size()) - return output - - def forward_classifier(self, x): - features = self.forward(x) - res = self.model.classifier(features) - return res - -class SignaturesNetwork(nn.Module): - def __init__(self): - super(SignaturesNetwork, self).__init__() - # Setting up the Sequential of CNN Layers - self.cnn1 = nn.Sequential( - nn.Conv2d(3, 96, kernel_size=11,stride=1), - nn.ReLU(inplace=True), - nn.LocalResponseNorm(5,alpha=0.0001,beta=0.75,k=2), - nn.MaxPool2d(3, stride=2), - - nn.Conv2d(96, 256, kernel_size=5,stride=1,padding=2), - nn.ReLU(inplace=True), - nn.LocalResponseNorm(5,alpha=0.0001,beta=0.75,k=2), - nn.MaxPool2d(3, stride=2), - nn.Dropout2d(p=0.3), - - nn.Conv2d(256,384 , kernel_size=3,stride=1,padding=1), - nn.ReLU(inplace=True), - nn.Conv2d(384,256, kernel_size=3,stride=1,padding=1), - nn.ReLU(inplace=True), - nn.MaxPool2d(3, stride=2), - nn.Dropout2d(p=0.3), - ) - - # Defining the fully connected layers - self.fc1 = nn.Sequential( - # First Dense Layer - nn.Linear(952576, 1024), - nn.ReLU(inplace=True), - nn.Dropout2d(p=0.5), - # Second Dense Layer - nn.Linear(1024, 128), - nn.ReLU(inplace=True), - # # Final Dense Layer - nn.Linear(128,128)) - - def forward_once(self, x): - # Forward pass - output = self.cnn1(x) - output = output.view(output.size()[0], -1) - output = self.fc1(output) - return output - - def forward(self, input1, input2): - # forward pass of input 1 - output1 = self.forward_once(input1) - # forward pass of input 2 - output2 = self.forward_once(input2) - # returning the feature vectors of two inputs - return output1, output2 diff --git a/detection/qr_verify_Tool/qr_orb.py b/detection/qr_verify_Tool/qr_orb.py deleted file mode 100644 index 3953ec8..0000000 --- a/detection/qr_verify_Tool/qr_orb.py +++ /dev/null @@ -1,110 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- -''' -@File : qr_orb.py -@Contact : zpyovo@hotmail.com -@License : (C)Copyright 2018-2019, Lab501-TransferLearning-SCUT -@Description : - -@Modify Time @Author @Version @Desciption ------------- ------- -------- ----------- -2023/8/26 18:58 Pengyu Zhang 1.0 None -''' - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import cv2 -import numpy as np - -def preprocess_image(image): - # 将图像调整为固定大小 - image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) - resized_image = cv2.resize(image, (275, 275), interpolation=cv2.INTER_NEAREST) - return resized_image - -def FAST_corner_detection(image, threshold): - keypoints = [] - for i in range(3, image.shape[0] - 3): - for j in range(3, image.shape[1] - 3): - center_pixel = image[i, j] - pixel_difference = [abs(image[i + dx, j + dy] - center_pixel) for dx, dy in - [(0, -3), (0, 3), (-3, 0), (3, 0)]] - if all(diff > threshold for diff in pixel_difference): - keypoints.append(cv2.KeyPoint(j, i, 7)) - return keypoints - - -def BRIEF_descriptor(keypoints, image): - patch_size = 31 - descriptors = [] - for kp in keypoints: - x, y = int(kp.pt[0]), int(kp.pt[1]) - patch = image[y - patch_size // 2:y + patch_size // 2 + 1, x - patch_size // 2:x + patch_size // 2 + 1] - descriptor = "" - for i in range(patch_size * patch_size): - x1, y1 = np.random.randint(0, patch_size, size=2) - x2, y2 = np.random.randint(0, patch_size, size=2) - if patch[y1, x1] < patch[y2, x2]: - descriptor += "0" - else: - descriptor += "1" - descriptors.append(descriptor) - return descriptors - - -def ORB(image, nfeatures=500, fastThreshold=20): - gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) - - keypoints = FAST_corner_detection(gray, fastThreshold) - keypoints = sorted(keypoints, key=lambda x: -x.response)[:nfeatures] - - descriptors = BRIEF_descriptor(keypoints, gray) - - return keypoints, descriptors - -def ORB_detect(img,nfeatures=800,fastThreshold=20): - img = preprocess_image(img) - # 初始化opencv原生ORB检测器 - orb = cv2.ORB_create(nfeatures=nfeatures, scaleFactor=1.2, edgeThreshold=31, patchSize=31, fastThreshold=fastThreshold) - - kp, des = orb.detectAndCompute(img, None) - return des - - -''' -必要传参: - std_roi_feature: 服务器备案特征数据 - ter_roi_feature: 手机终端特征数据 - threshold: 相似度对比阈值 -预留传参: - distance: 距离筛选度量,默认值100 -''' - -def roi_siml(std_roi_feature,ter_roi_feature,distance=100, threshold = None): - - std_roi_feature = std_roi_feature.astype('uint8') - ter_roi_feature = ter_roi_feature.astype('uint8') - threshold = float(threshold) - - # 使用汉明距离对特侦点距离进行计算 - bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=False) - # 使用knn算法进行匹配 - matches = bf.knnMatch(std_roi_feature, trainDescriptors=ter_roi_feature, k=2) - - # 去除无效和模糊的匹配 - good = [] - for match in matches: - if len(match) >= 2: - good = [(m, n) for (m, n) in matches if m.distance < 0.95 * n.distance and m.distance < distance] - - similarity = len(good) / len(matches) *100 - if similarity >=threshold: - return 'passed',similarity - else: - return 'failed ',similarity - - - - diff --git a/detection/qr_verify_Tool/qr_verify.py b/detection/qr_verify_Tool/qr_verify.py deleted file mode 100644 index 5ef9c76..0000000 --- a/detection/qr_verify_Tool/qr_verify.py +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- -''' -@File : qr_verify.py -@Contact : zpyovo@hotmail.com -@License : (C)Copyright 2018-2019, Lab501-TransferLearning-SCUT -@Description : - -@Modify Time @Author @Version @Desciption ------------- ------- -------- ----------- -2022/3/3 11:38 AM Pengyu Zhang 1.0 None -''' - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -import torch -import torch.nn as nn -import numpy as np - -from qr_verify_Tool.network import FaceModel - - -class QR_verify_dev(nn.Module): - def __init__(self): - super(QR_verify_dev, self).__init__() - if torch.cuda.is_available(): - print('cuda available:{}'.format(torch.cuda.is_available())) - self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') - self.checkpoint = torch.load('./model/train_qr_varification_FaceModel_3qp_256_Affine_lr1e-3_far-1_80.pth', - map_location=self.device) - self.model = FaceModel(256, num_classes=640, pretrained=True) - self.model.load_state_dict(self.checkpoint['model']) - self.model.eval() - - def forward(self,input): - # input = torch.from_numpy(np.array(input).transpose((2, 0, 1))).unsqueeze(0) - # input = input.cuda().float() - output = self.model(input) - - return output \ No newline at end of file diff --git a/detection/qr_verify_Tool/roi_img_process.py b/detection/qr_verify_Tool/roi_img_process.py deleted file mode 100644 index 2dba287..0000000 --- a/detection/qr_verify_Tool/roi_img_process.py +++ /dev/null @@ -1,207 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- -''' -@File : roi_img_process.py -@Contact : zpyovo@hotmail.com -@License : (C)Copyright 2018-2019, Lab501-TransferLearning-SCUT -@Description : - -@Modify Time @Author @Version @Desciption ------------- ------- -------- ----------- -2024/1/7 22:57 Pengyu Zhang 1.0 None -''' - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -import cv2 -import numpy as np -from matplotlib import pyplot as plt - -def preprocess_image(image,size,blur): - # 将图像调整为固定大小 - # image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) - if size !=-1: - image = cv2.resize(image, (size, size), interpolation=cv2.INTER_AREA) - if size == -1: - image = cv2.cvtColor(np.array(image), cv2.COLOR_BGR2GRAY) - if blur: - kernel_size = 3 - image = cv2.GaussianBlur(image, (kernel_size, kernel_size), sigmaX=1, sigmaY=0) - return image -def clos_open(input,B_p): - _, binary_image = cv2.threshold(input, B_p, 255, cv2.THRESH_BINARY) - # 定义结构元素(内核) - kernel = np.ones((3, 3), np.uint8) - - # 开运算 - opening = cv2.morphologyEx(binary_image, cv2.MORPH_OPEN, kernel) - - # 定义结构元素(内核) - kernel = np.ones((3, 3), np.uint8) - # 闭运算 - closing = cv2.morphologyEx(opening, cv2.MORPH_CLOSE, kernel) - - return closing - - -def img_angle(input): - # canny边缘检测 - eroded_canny = cv2.Canny(input, 50, 255, apertureSize=3) - contours, hierarchy = cv2.findContours(eroded_canny, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) - - # 寻找最大元素 - k = 0 - index = 0 - if len(contours) != 0: - for i in range(len(contours)): - j = contours[i].size - if j > k: - k = j - index = i - - # 拟合旋转矩形 - cnt = contours[index] - rect = cv2.minAreaRect(cnt) - angle = rect[2] - box = cv2.boxPoints(rect) - - box = np.int0(box) - - # 对点按照y坐标排序(最上和最下的点) - box = box[box[:, 1].argsort()] - - # 上边的两个点按照x坐标排序(决定左上和右上) - top_two = box[:2] - top_two = top_two[top_two[:, 0].argsort()] - - # 下边的两个点按照x坐标排序(决定左下和右下) - bottom_two = box[2:] - bottom_two = bottom_two[bottom_two[:, 0].argsort()] - - # 重新组合为左上、右上、右下、左下的顺序 - ordered_box = np.array([top_two[0], top_two[1], bottom_two[1], bottom_two[0]]) - - return angle,ordered_box - -def angel_affine(input,angle): - height, width = np.array(input).shape - center = (width // 2, height // 2) - if angle is not None and angle != 0.0: - if angle > 45.0: - angle = angle - 90 - # rotate page if not straight relative to QR code - M = cv2.getRotationMatrix2D(center, angle, 1.0) - output = cv2.warpAffine(np.array(input), M, (width, height), flags=cv2.INTER_CUBIC,borderMode=cv2.BORDER_REPLICATE) - - return output - - -def merge_overlapping_rectangles(rectangles): - if len(rectangles) <= 1: - return rectangles - - merged_rectangles = [] - for rect in sorted(rectangles, key=lambda x: x[2]*x[3], reverse=True): - x, y, w, h = rect - if not any((x < x2 + w2 and x + w > x2 and y < y2 + h2 and y + h > y2) for x2, y2, w2, h2 in merged_rectangles): - merged_rectangles.append(rect) - return merged_rectangles - - -def roi2_detector(input,min_area_threshold): - # 使用 Canny 边缘检测 - edges = cv2.Canny(input, 50, 255, apertureSize=3) - - # 寻找轮廓 - contours, _ = cv2.findContours(edges, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) - # 临时存储矩形 - temp_rectangles = [] - - # 识别矩形轮廓 - for contour in contours: - # 轮廓近似 - epsilon = 0.001 * cv2.arcLength(contour, True) - approx = cv2.approxPolyDP(contour, epsilon, True) - - if len(approx) >= 4: - x, y, w, h = cv2.boundingRect(approx) - aspect_ratio = w / float(h) - area_contour = cv2.contourArea(contour) - if aspect_ratio >= 0.75 and aspect_ratio < 1.2 and min_area_threshold < area_contour < 15000: - area_bounding_rect = w * h - area_ratio = area_contour / area_bounding_rect - if 0.75 < area_ratio < 1.05: - temp_rectangles.append((x, y, w, h)) - - # 合并重叠的方框 - merged_rectangles = merge_overlapping_rectangles(temp_rectangles) - - # 选择并绘制最大的方框 - if merged_rectangles: - outermost_rectangle = max(merged_rectangles, key=lambda x: x[2]*x[3]) - rectangle_count = 1 - return outermost_rectangle - else: - return None -from PIL import Image - -def roi_img_processing(input): - input_ray = cv2.cvtColor(np.array(input), cv2.COLOR_BGR2GRAY) - img_resize = preprocess_image(input_ray, 128, blur=False) - img_closing = clos_open(img_resize,B_p=50) # 闭运算 - min_area_threshold=6500 - roi_coordinate = roi2_detector(img_closing,min_area_threshold) - if roi_coordinate is None: - img_closing = clos_open(img_resize, B_p=100) # 闭运算 - roi_coordinate = roi2_detector(img_closing,min_area_threshold) - if roi_coordinate is None: - return None - x, y, w, h = roi_coordinate - # 截取图像 - input_fix = img_resize[y+1:y-1 + h, x+1:x-1 + w] - output = preprocess_image(input_fix, 32, blur=True) - - return output - -def roi_detect(input): - size = 128 - height, width, _ = input.shape - - if height < size: - img_resize = cv2.resize(np.array(input), (size, size), interpolation=cv2.INTER_AREA) - else: - img_resize = cv2.resize(np.array(input), (size, size), interpolation=cv2.INTER_LINEAR) - image_gay = cv2.cvtColor(img_resize, cv2.COLOR_BGR2GRAY) - min_area_threshold = 1000 - img_closing = clos_open(image_gay, B_p=50) # 闭运算 - roi_coordinate = roi2_detector(img_closing,min_area_threshold) - - if roi_coordinate is None : - img_closing = clos_open(img_resize, B_p=100) # 闭运算 - roi_coordinate = roi2_detector(img_closing,min_area_threshold) - # print('150') - if roi_coordinate is None : - img_closing = clos_open(img_resize, B_p=150) # 闭运算 - roi_coordinate = roi2_detector(img_closing,min_area_threshold) - # print('100') - if roi_coordinate is None : - img_closing = clos_open(img_resize, B_p=120) # 闭运算 - roi_coordinate = roi2_detector(img_closing,min_area_threshold) - if roi_coordinate is None: - img_closing = clos_open(img_resize, B_p=75) # 闭运算 - roi_coordinate = roi2_detector(img_closing,min_area_threshold) - if roi_coordinate is None: - return None - x, y, w, h = roi_coordinate - # 截取图像 - input_fix = img_resize[y + 1:y - 1 + h, x + 1:x - 1 + w] - input_fix = Image.fromarray(np.uint8(input_fix)) - return input_fix -def roi_minAreaRect(input): - img_closing = clos_open(input) # 闭运算 - _, box = img_angle(img_closing) # 图像偏差角度 - return box - - - diff --git a/detection/requirements.txt b/detection/requirements.txt deleted file mode 100644 index a304291..0000000 --- a/detection/requirements.txt +++ /dev/null @@ -1,9 +0,0 @@ -Flask==3.0.2 -basicsr==1.3.5 -matplotlib==3.5.1 -opencv-contrib-python==4.5.5.62 -opencv-python==4.5.5.62 -timm==0.9.2 -pandas -seaborn -oss2 diff --git a/detection/roi_fix.py b/detection/roi_fix.py deleted file mode 100644 index 1409bfe..0000000 --- a/detection/roi_fix.py +++ /dev/null @@ -1,145 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- -''' -@File : roi_fix.py -@Contact : zpyovo@hotmail.com -@License : (C)Copyright 2018-2019, Lab501-TransferLearning-SCUT -@Description : - -@Modify Time @Author @Version @Desciption ------------- ------- -------- ----------- -2024/2/27 00:02 Pengyu Zhang 1.0 None -''' - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -'''批量固定尺寸''' -import os -import cv2 -from tqdm import tqdm -import numpy as np - -def merge_overlapping_rectangles(rectangles): - if len(rectangles) <= 1: - return rectangles - - merged_rectangles = [] - for rect in sorted(rectangles, key=lambda x: x[2]*x[3], reverse=True): - x, y, w, h = rect - if not any((x < x2 + w2 and x + w > x2 and y < y2 + h2 and y + h > y2) for x2, y2, w2, h2 in merged_rectangles): - merged_rectangles.append(rect) - return merged_rectangles - -def roi_Inner_detector(input,): - # 定义结构元素(内核) - kernel = np.ones((3, 3), np.uint8) - - # 开运算 - opening = cv2.morphologyEx(input, cv2.MORPH_OPEN, kernel) - - # 定义结构元素(内核) - kernel = np.ones((3, 3), np.uint8) - # 闭运算 - closing = cv2.morphologyEx(opening, cv2.MORPH_CLOSE, kernel) - - # 使用 Canny 边缘检测 - edges = cv2.Canny(closing, 50, 255, apertureSize=3) - - # 寻找轮廓 - contours, _ = cv2.findContours(edges, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) - # 临时存储矩形 - temp_rectangles = [] - - # 识别矩形轮廓 - for contour in contours: - # 轮廓近似 - epsilon = 0.001 * cv2.arcLength(contour, True) - approx = cv2.approxPolyDP(contour, epsilon, True) - - if len(approx) >= 4: - x, y, w, h = cv2.boundingRect(approx) - aspect_ratio = w / float(h) - area_contour = cv2.contourArea(contour) - min_area_threshold = 0 - # print('aspect_ratio',aspect_ratio) - # print('area_contour',area_contour) - if aspect_ratio >= 0.80 and aspect_ratio < 1.2 and 6500 < area_contour < 13000: - area_bounding_rect = w * h - area_ratio = area_contour / area_bounding_rect - # print('aspect_ratio', area_ratio) - # print('area_ratio', area_ratio) - # print('area_contour', area_contour) - if 0.80 < area_ratio < 1.05: - temp_rectangles.append((x, y, w, h)) - - # 合并重叠的方框 - merged_rectangles = merge_overlapping_rectangles(temp_rectangles) - return merged_rectangles - -input_dir = '/project/dataset/QR2024/org100/orange-0-roi-fix' -output_dir = '/project/dataset/QR2024/org100/orange-0-roi-fix2fix' - -size = 128 - -# 创建输出目录 -os.makedirs(output_dir, exist_ok=True) - -# 遍历输入目录中的图片 -for filename in tqdm(os.listdir(input_dir), desc='Processing'): - if filename.endswith('.jpg') or filename.endswith('.png'): - input_path = os.path.join(input_dir, filename) - image = cv2.imread(input_path) - height, width, _ = image.shape - - if height < size: - image = cv2.resize(np.array(image), (size, size), interpolation=cv2.INTER_AREA) - else: - image = cv2.resize(np.array(image), (size, size), interpolation=cv2.INTER_LINEAR) - - image_gay = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) - _, binary_image_A = cv2.threshold(image_gay, 50, 255, cv2.THRESH_BINARY) - merged_rectangles = roi_Inner_detector(binary_image_A) - - if len(merged_rectangles) == 0 : - _, binary_image_A = cv2.threshold(image_gay, 150, 255, cv2.THRESH_BINARY) - merged_rectangles = roi_Inner_detector(binary_image_A) - # print('150') - if len(merged_rectangles) == 0 : - _, binary_image_A = cv2.threshold(image_gay, 100, 255, cv2.THRESH_BINARY) - merged_rectangles = roi_Inner_detector(binary_image_A) - # print('100') - if len(merged_rectangles) == 0 : - _, binary_image_A = cv2.threshold(image_gay, 125, 255, cv2.THRESH_BINARY) - merged_rectangles = roi_Inner_detector(binary_image_A) - # print('120') - if len(merged_rectangles) == 0 : - _, binary_image_A = cv2.threshold(image_gay, 75, 255, cv2.THRESH_BINARY) - merged_rectangles = roi_Inner_detector(binary_image_A) - - if len(merged_rectangles) == 0: - _, binary_image_A = cv2.threshold(image_gay, 85, 255, cv2.THRESH_BINARY) - merged_rectangles = roi_Inner_detector(binary_image_A) - if len(merged_rectangles) == 0: - _, binary_image_A = cv2.threshold(image_gay, 115, 255, cv2.THRESH_BINARY) - merged_rectangles = roi_Inner_detector(binary_image_A) - # print('75') - # if len(merged_rectangles) == 0 : - # _, binary_image_A = cv2.threshold(image_gay, 0, 255, cv2.THRESH_BINARY) - # merged_rectangles = roi_Inner_detector(binary_image_A) - - if merged_rectangles: - outermost_rectangle = max(merged_rectangles, key=lambda x: x[2] * x[3]) - x, y, w, h = outermost_rectangle - image_crope = image[y + 1:y - 1 + h, x + 1:x - 1 + w] - image_crope = cv2.resize(image_crope, (32, 32), interpolation=cv2.INTER_AREA) - output_path = os.path.join(output_dir, filename) - cv2.imwrite(output_path, image_crope) - else: - print("failed",filename) - # output_path = os.path.join(output_dir, filename) - # cv2.imwrite(output_path, image) - # 保存裁剪后的图像 - -print("裁剪完成!") \ No newline at end of file diff --git a/detection/scripts/ci b/detection/scripts/ci deleted file mode 100755 index d1b7497..0000000 --- a/detection/scripts/ci +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env python3 - -import os -import tempfile -import subprocess -import argparse - -BASE_DIR = os.path.abspath(os.path.dirname(__file__) + '/..') - -def parse_args(): - parser = argparse.ArgumentParser() - parser.add_argument("action", help="CI action: build, push") - return parser.parse_args() - -NAME = subprocess.check_output('git rev-parse --short HEAD', shell=True, encoding='utf-8').strip() -IMG_TAG = "registry.cn-shenzhen.aliyuncs.com/euphon-private/emblem-detection:" + NAME - -print(IMG_TAG) - -def do_test(): - subprocess.check_call("docker run --rm --network=host %s ./tests/run.py" % IMG_TAG, shell=True) - -def do_build(): - subprocess.check_call("docker build --network=host -t %s ." % IMG_TAG, shell=True) - -def do_push(): - subprocess.check_call("docker push %s" % IMG_TAG, shell=True) - -def do_deploy(env=""): - kubeconfig = os.path.join(BASE_DIR, 'deploy/kubeconfig.' + env) - src = os.path.join(BASE_DIR, 'deploy/detection.yaml') - tf = tempfile.NamedTemporaryFile(mode='w') - with open(src, 'r') as f: - tmpl = f.read() - tf.write(tmpl.format(image=IMG_TAG)) - tf.flush() - cmd = ['kubectl', '--kubeconfig', kubeconfig, 'apply', '-f', tf.name] - subprocess.check_call(cmd) -def main(): - args = parse_args() - if args.action == "build": - return do_build() - elif args.action == "test": - return do_test() - elif args.action == "push": - return do_push() - elif args.action == "deploy-prod": - return do_deploy('prod') - elif args.action == "deploy-dev": - return do_deploy('dev') - raise Exception("Unrecognized command") - -main() diff --git a/detection/scripts/install-deps.sh b/detection/scripts/install-deps.sh deleted file mode 100755 index 6062902..0000000 --- a/detection/scripts/install-deps.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash -set -e -pip3 install opencv-python==4.5.5.62 -pip3 install opencv-contrib-python==4.5.5.62 -pip3 install -r requirements.txt diff --git a/detection/scripts/start b/detection/scripts/start deleted file mode 100755 index cc34945..0000000 --- a/detection/scripts/start +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -set -e - -python3 app.py diff --git a/detection/tests/data/0074252612205.jpg b/detection/tests/data/0074252612205.jpg deleted file mode 100644 index f0c1d36..0000000 Binary files a/detection/tests/data/0074252612205.jpg and /dev/null differ diff --git a/detection/tests/data/0074252612205_neg_iPhone14Pro_1706940227.9754338.jpg b/detection/tests/data/0074252612205_neg_iPhone14Pro_1706940227.9754338.jpg deleted file mode 100644 index 1226f85..0000000 Binary files a/detection/tests/data/0074252612205_neg_iPhone14Pro_1706940227.9754338.jpg and /dev/null differ diff --git a/detection/tests/data/0078561304875-meta60.jpg b/detection/tests/data/0078561304875-meta60.jpg deleted file mode 100644 index 8e5c8f0..0000000 Binary files a/detection/tests/data/0078561304875-meta60.jpg and /dev/null differ diff --git a/detection/tests/data/0079080983780.jpg b/detection/tests/data/0079080983780.jpg deleted file mode 100644 index cd7fe59..0000000 Binary files a/detection/tests/data/0079080983780.jpg and /dev/null differ diff --git a/detection/tests/data/0079080983780_pos_iPhone14Pro_1706842853.5876188.jpg b/detection/tests/data/0079080983780_pos_iPhone14Pro_1706842853.5876188.jpg deleted file mode 100644 index aa81a44..0000000 Binary files a/detection/tests/data/0079080983780_pos_iPhone14Pro_1706842853.5876188.jpg and /dev/null differ diff --git a/detection/tests/data/11173059793161.jpg b/detection/tests/data/11173059793161.jpg deleted file mode 100644 index 106880b..0000000 Binary files a/detection/tests/data/11173059793161.jpg and /dev/null differ diff --git a/detection/tests/data/11173059793161_pos_M2012K11AC.jpg b/detection/tests/data/11173059793161_pos_M2012K11AC.jpg deleted file mode 100644 index 8cea34e..0000000 Binary files a/detection/tests/data/11173059793161_pos_M2012K11AC.jpg and /dev/null differ diff --git a/detection/tests/data/11173059793161_pos_M2012K11AC_fix.jpg b/detection/tests/data/11173059793161_pos_M2012K11AC_fix.jpg deleted file mode 100644 index 618fa6c..0000000 Binary files a/detection/tests/data/11173059793161_pos_M2012K11AC_fix.jpg and /dev/null differ diff --git a/detection/tests/data/11179652134410.jpg b/detection/tests/data/11179652134410.jpg deleted file mode 100644 index 9aee535..0000000 Binary files a/detection/tests/data/11179652134410.jpg and /dev/null differ diff --git a/detection/tests/data/11179652134410_pos_M2012K11AC_1706586332.6351073.jpg b/detection/tests/data/11179652134410_pos_M2012K11AC_1706586332.6351073.jpg deleted file mode 100644 index 98d203c..0000000 Binary files a/detection/tests/data/11179652134410_pos_M2012K11AC_1706586332.6351073.jpg and /dev/null differ diff --git a/detection/tests/data/1291182811394.jpg b/detection/tests/data/1291182811394.jpg deleted file mode 100644 index 32fd192..0000000 Binary files a/detection/tests/data/1291182811394.jpg and /dev/null differ diff --git a/detection/tests/data/1291182811394_pos_LYA-AL00.jpg b/detection/tests/data/1291182811394_pos_LYA-AL00.jpg deleted file mode 100644 index 3bee680..0000000 Binary files a/detection/tests/data/1291182811394_pos_LYA-AL00.jpg and /dev/null differ diff --git a/detection/tests/data/1291182811394_pos_iPhone12Pro.jpg b/detection/tests/data/1291182811394_pos_iPhone12Pro.jpg deleted file mode 100644 index e89a1c9..0000000 Binary files a/detection/tests/data/1291182811394_pos_iPhone12Pro.jpg and /dev/null differ diff --git a/detection/tests/data/1294642477067_pos_iPhone12Pro.jpg b/detection/tests/data/1294642477067_pos_iPhone12Pro.jpg deleted file mode 100644 index 3c0df8a..0000000 Binary files a/detection/tests/data/1294642477067_pos_iPhone12Pro.jpg and /dev/null differ diff --git a/detection/tests/data/1294642477067_roi.jpg b/detection/tests/data/1294642477067_roi.jpg deleted file mode 100644 index 9b611f9..0000000 Binary files a/detection/tests/data/1294642477067_roi.jpg and /dev/null differ diff --git a/detection/tests/data/qr.jpg b/detection/tests/data/qr.jpg deleted file mode 100644 index e06997f..0000000 Binary files a/detection/tests/data/qr.jpg and /dev/null differ diff --git a/detection/tests/data/qr1.jpg b/detection/tests/data/qr1.jpg deleted file mode 100644 index aae5f38..0000000 Binary files a/detection/tests/data/qr1.jpg and /dev/null differ diff --git a/detection/tests/data/qr2.jpg b/detection/tests/data/qr2.jpg deleted file mode 100644 index 0c0929f..0000000 Binary files a/detection/tests/data/qr2.jpg and /dev/null differ diff --git a/detection/tests/data/roi-11171579861604-32x32.jpg b/detection/tests/data/roi-11171579861604-32x32.jpg deleted file mode 100644 index 33a0c3b..0000000 Binary files a/detection/tests/data/roi-11171579861604-32x32.jpg and /dev/null differ diff --git a/detection/tests/data/roi-11171579861604-trim-32x32.jpg b/detection/tests/data/roi-11171579861604-trim-32x32.jpg deleted file mode 100644 index f1e023a..0000000 Binary files a/detection/tests/data/roi-11171579861604-trim-32x32.jpg and /dev/null differ diff --git a/detection/tests/data/roi-11171579861604.jpg b/detection/tests/data/roi-11171579861604.jpg deleted file mode 100644 index 4076ebf..0000000 Binary files a/detection/tests/data/roi-11171579861604.jpg and /dev/null differ diff --git a/detection/tests/data/std_roi_encoding.json b/detection/tests/data/std_roi_encoding.json deleted file mode 100644 index 0ad30a7..0000000 --- a/detection/tests/data/std_roi_encoding.json +++ /dev/null @@ -1 +0,0 @@ -{"data": {"roi_encoding": [[221, 127, 156, 229, 184, 14, 95, 190, 42, 234, 158, 109, 223, 55, 195, 83, 151, 213, 125, 110, 233, 192, 217, 46, 235, 251, 101, 148, 42, 241, 10, 91], [175, 254, 229, 95, 239, 115, 190, 216, 255, 250, 245, 254, 200, 238, 223, 191, 95, 247, 255, 211, 191, 231, 239, 250, 63, 122, 207, 191, 199, 247, 238, 245], [190, 190, 230, 30, 251, 115, 190, 233, 255, 255, 253, 247, 104, 238, 255, 223, 255, 255, 239, 167, 246, 247, 175, 255, 62, 119, 223, 175, 231, 119, 175, 215], [173, 252, 181, 95, 189, 95, 231, 252, 127, 234, 254, 72, 250, 241, 157, 181, 217, 110, 126, 106, 233, 244, 251, 63, 111, 255, 101, 52, 202, 251, 210, 185], [173, 253, 247, 87, 244, 63, 239, 248, 119, 234, 127, 110, 121, 245, 157, 180, 127, 254, 118, 238, 241, 246, 253, 238, 62, 62, 101, 132, 239, 243, 226, 181], [41, 253, 245, 95, 253, 223, 215, 255, 127, 234, 254, 8, 250, 253, 148, 52, 153, 126, 126, 74, 251, 250, 251, 191, 111, 255, 117, 50, 195, 248, 214, 235], [77, 60, 189, 117, 157, 47, 111, 154, 123, 255, 190, 92, 219, 121, 91, 59, 221, 70, 124, 62, 185, 240, 249, 42, 239, 255, 119, 52, 202, 241, 195, 114], [204, 255, 191, 246, 186, 234, 63, 191, 190, 255, 94, 181, 255, 247, 203, 83, 55, 119, 237, 82, 112, 217, 171, 127, 255, 231, 127, 193, 160, 241, 167, 123], [175, 255, 230, 31, 251, 50, 174, 252, 63, 206, 255, 247, 104, 238, 191, 245, 127, 239, 183, 231, 245, 215, 175, 239, 62, 126, 207, 143, 175, 247, 254, 213], [222, 223, 238, 191, 186, 34, 95, 189, 54, 142, 223, 231, 255, 103, 139, 81, 119, 247, 253, 231, 120, 211, 207, 111, 248, 231, 239, 133, 165, 243, 165, 83], [252, 115, 188, 118, 169, 29, 84, 152, 163, 254, 181, 77, 223, 255, 75, 27, 133, 212, 237, 114, 204, 99, 251, 16, 219, 219, 101, 63, 202, 243, 74, 83], [253, 253, 241, 71, 253, 127, 71, 252, 123, 234, 189, 40, 91, 113, 205, 182, 153, 222, 124, 106, 121, 115, 249, 62, 255, 255, 37, 54, 226, 251, 198, 39], [220, 191, 222, 247, 187, 238, 127, 191, 187, 191, 127, 189, 255, 231, 74, 83, 183, 247, 237, 162, 122, 89, 187, 127, 255, 247, 127, 205, 162, 240, 135, 91], [173, 253, 125, 127, 188, 127, 199, 188, 86, 234, 252, 92, 243, 113, 159, 49, 89, 238, 86, 110, 233, 246, 121, 63, 111, 239, 101, 52, 251, 251, 210, 249], [237, 244, 237, 119, 172, 111, 79, 184, 114, 250, 183, 92, 211, 125, 217, 59, 149, 198, 125, 110, 249, 116, 251, 58, 239, 251, 101, 180, 202, 249, 210, 113], [175, 180, 250, 91, 247, 253, 254, 222, 127, 211, 125, 146, 120, 252, 191, 173, 255, 251, 151, 158, 183, 246, 62, 255, 63, 95, 87, 203, 207, 221, 235, 253], [205, 124, 157, 229, 184, 15, 107, 184, 126, 250, 247, 124, 223, 115, 201, 59, 151, 85, 126, 110, 161, 244, 249, 78, 205, 223, 103, 148, 72, 187, 3, 177], [236, 244, 189, 243, 189, 127, 79, 152, 87, 251, 189, 24, 219, 117, 89, 63, 217, 214, 125, 122, 43, 102, 251, 178, 239, 219, 229, 124, 178, 251, 214, 48], [207, 164, 172, 243, 141, 118, 92, 159, 63, 247, 143, 19, 207, 127, 90, 239, 209, 199, 249, 157, 62, 205, 251, 226, 207, 255, 223, 85, 134, 204, 252, 98], [226, 180, 189, 250, 221, 237, 81, 139, 223, 115, 165, 24, 182, 189, 76, 59, 129, 19, 123, 17, 51, 110, 250, 243, 207, 218, 80, 127, 208, 72, 199, 242], [202, 255, 188, 254, 251, 221, 31, 191, 187, 95, 188, 189, 236, 255, 14, 215, 135, 243, 255, 70, 246, 79, 250, 255, 255, 255, 95, 251, 146, 116, 143, 127], [238, 255, 254, 94, 249, 253, 183, 246, 187, 254, 244, 61, 238, 254, 167, 59, 143, 119, 255, 67, 246, 111, 250, 255, 127, 255, 103, 187, 210, 125, 207, 255], [175, 252, 190, 222, 253, 255, 247, 254, 219, 254, 244, 61, 254, 252, 167, 59, 221, 255, 255, 83, 245, 231, 250, 255, 123, 127, 111, 187, 195, 125, 223, 247], [182, 237, 238, 222, 223, 183, 254, 238, 191, 151, 237, 19, 104, 234, 126, 17, 249, 235, 227, 211, 118, 255, 154, 255, 127, 63, 223, 43, 209, 115, 239, 138], [174, 188, 175, 254, 253, 247, 191, 254, 159, 255, 221, 23, 220, 254, 127, 107, 221, 255, 255, 167, 187, 71, 251, 255, 251, 127, 255, 190, 149, 127, 245, 247], [238, 237, 231, 254, 253, 211, 78, 238, 31, 186, 216, 203, 235, 233, 253, 229, 219, 239, 255, 255, 110, 123, 207, 255, 190, 126, 207, 191, 183, 255, 248, 140], [118, 237, 218, 238, 254, 155, 254, 236, 63, 160, 248, 171, 108, 235, 125, 144, 251, 239, 247, 194, 78, 187, 202, 255, 62, 252, 135, 171, 103, 191, 234, 142], [246, 172, 190, 254, 201, 253, 253, 207, 191, 183, 229, 27, 236, 255, 110, 27, 249, 255, 235, 145, 62, 111, 251, 243, 255, 231, 223, 127, 193, 113, 175, 226], [254, 172, 174, 190, 205, 249, 116, 206, 191, 183, 134, 27, 110, 250, 111, 223, 233, 247, 251, 147, 62, 111, 219, 243, 219, 243, 223, 127, 147, 125, 190, 70], [254, 236, 190, 158, 141, 249, 20, 239, 159, 151, 132, 27, 238, 251, 103, 203, 201, 243, 235, 19, 62, 111, 218, 243, 251, 115, 223, 63, 146, 125, 190, 78], [109, 53, 241, 229, 237, 127, 123, 152, 123, 238, 191, 44, 223, 127, 221, 62, 159, 212, 253, 62, 153, 114, 251, 202, 223, 255, 101, 190, 194, 239, 70, 115], [126, 164, 254, 118, 205, 121, 80, 202, 187, 183, 181, 27, 75, 235, 79, 154, 249, 223, 237, 144, 62, 103, 203, 178, 219, 247, 85, 127, 227, 109, 238, 198], [111, 249, 243, 94, 253, 221, 247, 252, 251, 249, 244, 40, 122, 253, 221, 180, 255, 255, 247, 90, 195, 123, 250, 251, 127, 255, 69, 127, 195, 190, 198, 255], [254, 172, 190, 190, 137, 121, 84, 202, 187, 183, 165, 57, 110, 255, 79, 27, 249, 247, 237, 19, 46, 103, 202, 162, 219, 247, 85, 63, 194, 117, 46, 70], [69, 167, 200, 7, 244, 11, 110, 189, 122, 168, 126, 236, 211, 96, 218, 48, 63, 206, 102, 126, 241, 240, 205, 62, 236, 143, 37, 164, 204, 250, 202, 129], [111, 247, 241, 231, 244, 94, 255, 188, 127, 236, 127, 238, 219, 125, 223, 52, 191, 206, 119, 255, 229, 240, 237, 238, 110, 255, 167, 164, 207, 255, 210, 245], [126, 248, 166, 62, 137, 253, 150, 198, 187, 159, 188, 9, 46, 252, 103, 223, 233, 243, 239, 83, 46, 107, 154, 240, 155, 83, 95, 123, 195, 117, 110, 222], [231, 254, 233, 71, 252, 63, 255, 248, 127, 234, 255, 239, 219, 108, 223, 52, 255, 204, 119, 111, 237, 246, 253, 254, 126, 126, 229, 180, 239, 255, 82, 213], [92, 252, 183, 246, 169, 126, 127, 190, 191, 251, 182, 56, 255, 247, 203, 255, 183, 119, 253, 10, 57, 69, 187, 235, 251, 255, 87, 191, 130, 247, 143, 123], [124, 189, 245, 103, 157, 95, 103, 189, 119, 161, 238, 8, 219, 119, 8, 244, 25, 222, 124, 122, 89, 250, 249, 190, 255, 255, 5, 116, 226, 250, 198, 34], [47, 191, 236, 95, 253, 159, 254, 252, 95, 255, 253, 29, 106, 253, 222, 180, 221, 206, 247, 94, 191, 122, 255, 255, 127, 95, 133, 255, 223, 254, 218, 213], [174, 253, 230, 222, 255, 211, 190, 239, 159, 215, 220, 179, 108, 255, 62, 197, 255, 251, 183, 211, 246, 235, 42, 255, 127, 95, 223, 239, 151, 126, 189, 223], [110, 236, 246, 223, 237, 59, 254, 238, 255, 178, 246, 154, 235, 255, 85, 255, 235, 247, 255, 139, 236, 239, 223, 235, 191, 255, 215, 59, 199, 253, 250, 142], [76, 188, 151, 94, 137, 237, 23, 207, 187, 179, 169, 24, 254, 247, 72, 191, 177, 247, 232, 16, 51, 79, 250, 115, 185, 246, 85, 126, 130, 113, 135, 50], [202, 189, 151, 84, 217, 252, 53, 231, 187, 147, 110, 24, 44, 183, 68, 191, 161, 127, 234, 24, 51, 221, 178, 247, 63, 254, 84, 107, 194, 64, 135, 174], [190, 239, 243, 87, 242, 35, 111, 252, 55, 166, 127, 242, 105, 231, 141, 21, 63, 255, 167, 234, 240, 242, 173, 255, 124, 174, 101, 196, 231, 243, 166, 185], [10, 237, 210, 94, 219, 241, 182, 231, 251, 3, 120, 162, 104, 250, 230, 164, 251, 251, 162, 147, 118, 59, 142, 255, 62, 22, 31, 235, 215, 87, 175, 143], [61, 253, 219, 103, 244, 75, 103, 252, 114, 234, 255, 8, 115, 113, 137, 48, 25, 254, 116, 106, 121, 242, 249, 46, 255, 239, 101, 52, 224, 251, 194, 162], [252, 253, 251, 118, 242, 75, 87, 253, 114, 162, 239, 40, 119, 247, 137, 48, 51, 254, 108, 106, 121, 243, 169, 62, 251, 239, 37, 52, 224, 243, 198, 35], [175, 111, 194, 94, 211, 178, 174, 247, 125, 139, 121, 246, 104, 226, 181, 148, 127, 235, 130, 254, 183, 190, 12, 238, 62, 62, 135, 130, 239, 247, 235, 149], [131, 237, 83, 95, 211, 251, 182, 231, 253, 3, 106, 50, 44, 186, 164, 180, 163, 123, 130, 0, 119, 188, 36, 255, 63, 126, 22, 67, 195, 84, 171, 143], [150, 207, 83, 78, 242, 179, 174, 229, 60, 0, 121, 226, 104, 226, 172, 196, 126, 123, 130, 226, 242, 155, 12, 255, 56, 36, 174, 202, 119, 215, 161, 173], [244, 253, 247, 86, 153, 110, 103, 172, 251, 226, 175, 24, 90, 245, 13, 183, 49, 246, 104, 122, 121, 247, 185, 46, 187, 254, 69, 54, 194, 115, 198, 32], [165, 253, 213, 87, 252, 111, 103, 190, 126, 250, 110, 88, 242, 113, 137, 53, 25, 94, 102, 106, 115, 252, 249, 63, 109, 158, 101, 116, 202, 251, 194, 168], [172, 189, 253, 119, 252, 107, 103, 190, 119, 234, 254, 56, 211, 117, 137, 57, 27, 246, 110, 42, 121, 244, 107, 62, 111, 159, 101, 244, 194, 251, 198, 163], [228, 254, 255, 119, 248, 107, 119, 191, 187, 190, 246, 184, 247, 247, 201, 27, 55, 254, 109, 58, 120, 223, 235, 47, 239, 175, 117, 180, 224, 241, 134, 235], [237, 253, 245, 87, 253, 127, 70, 168, 123, 250, 191, 88, 218, 120, 221, 191, 153, 198, 252, 122, 253, 114, 251, 62, 191, 255, 69, 54, 202, 127, 218, 36], [220, 254, 151, 119, 169, 106, 119, 190, 191, 191, 175, 56, 255, 247, 137, 255, 55, 118, 236, 10, 113, 205, 171, 47, 233, 246, 117, 22, 162, 243, 135, 41], [196, 253, 223, 247, 250, 235, 119, 255, 186, 191, 110, 56, 247, 247, 201, 115, 55, 255, 111, 18, 114, 220, 170, 111, 253, 166, 119, 40, 160, 240, 135, 171], [237, 117, 241, 71, 228, 63, 111, 184, 123, 234, 255, 76, 211, 113, 221, 54, 31, 204, 124, 110, 233, 240, 253, 46, 110, 254, 101, 180, 78, 251, 210, 241], [47, 191, 226, 159, 215, 151, 238, 253, 119, 234, 94, 198, 105, 234, 159, 164, 127, 239, 183, 239, 245, 250, 223, 255, 62, 94, 143, 196, 239, 246, 251, 149], [252, 249, 188, 118, 173, 95, 87, 184, 235, 254, 180, 8, 223, 253, 75, 59, 149, 214, 253, 122, 233, 99, 251, 168, 251, 251, 101, 61, 194, 251, 70, 115], [88, 149, 125, 229, 184, 78, 87, 63, 116, 172, 254, 40, 215, 55, 0, 115, 18, 214, 109, 40, 120, 248, 241, 14, 235, 239, 101, 21, 96, 160, 196, 107], [45, 180, 253, 85, 245, 127, 102, 250, 127, 234, 118, 72, 219, 121, 157, 245, 187, 204, 126, 42, 161, 244, 121, 222, 47, 255, 101, 4, 234, 248, 90, 160], [237, 245, 245, 101, 244, 30, 111, 184, 125, 234, 126, 108, 219, 113, 157, 245, 215, 204, 126, 110, 233, 244, 249, 206, 174, 255, 101, 132, 74, 251, 66, 49], [29, 189, 255, 231, 182, 78, 127, 186, 62, 234, 126, 96, 223, 119, 129, 97, 55, 247, 127, 106, 241, 248, 121, 110, 237, 239, 103, 132, 172, 242, 133, 49], [124, 121, 189, 100, 173, 94, 87, 184, 227, 252, 190, 8, 223, 247, 67, 114, 148, 212, 253, 106, 201, 99, 251, 56, 251, 251, 101, 61, 66, 242, 70, 115], [247, 250, 229, 223, 237, 93, 222, 216, 255, 250, 180, 236, 235, 236, 223, 191, 223, 247, 255, 215, 175, 103, 255, 249, 191, 91, 207, 191, 207, 255, 94, 213], [76, 177, 191, 254, 248, 239, 113, 249, 191, 249, 247, 40, 254, 247, 104, 123, 247, 255, 127, 24, 51, 219, 187, 125, 223, 251, 119, 254, 138, 34, 167, 115], [40, 191, 247, 95, 255, 223, 117, 255, 255, 191, 254, 56, 254, 253, 140, 180, 191, 126, 238, 8, 243, 251, 235, 255, 127, 255, 117, 251, 99, 204, 198, 251], [109, 189, 181, 101, 157, 127, 71, 159, 127, 255, 190, 76, 219, 61, 83, 190, 217, 198, 124, 60, 185, 248, 249, 170, 239, 255, 85, 116, 202, 252, 210, 112], [204, 191, 191, 255, 248, 207, 127, 191, 63, 255, 126, 245, 253, 247, 202, 81, 55, 255, 255, 234, 243, 218, 251, 255, 255, 247, 119, 204, 162, 246, 167, 123], [175, 190, 230, 158, 251, 179, 190, 232, 159, 223, 213, 247, 104, 238, 255, 207, 255, 255, 255, 231, 182, 199, 175, 255, 62, 119, 207, 143, 159, 117, 191, 215], [181, 254, 247, 95, 254, 31, 238, 248, 127, 234, 255, 110, 123, 248, 157, 164, 127, 238, 118, 238, 225, 246, 255, 238, 62, 62, 103, 132, 239, 251, 122, 181], [92, 183, 246, 255, 251, 239, 127, 255, 191, 191, 255, 189, 127, 247, 74, 81, 183, 255, 237, 144, 50, 219, 187, 255, 255, 247, 117, 231, 162, 230, 167, 115], [76, 189, 191, 254, 248, 239, 119, 251, 191, 251, 247, 60, 254, 247, 200, 123, 183, 127, 127, 24, 51, 219, 251, 127, 255, 255, 117, 106, 138, 224, 135, 115], [253, 115, 188, 102, 169, 29, 87, 153, 171, 254, 183, 13, 223, 127, 75, 59, 133, 213, 253, 126, 205, 99, 251, 16, 219, 249, 101, 60, 74, 243, 74, 83], [94, 255, 207, 175, 186, 2, 95, 189, 55, 174, 219, 231, 111, 103, 139, 82, 119, 247, 125, 230, 248, 211, 205, 111, 250, 230, 239, 148, 167, 243, 164, 113], [244, 253, 241, 103, 254, 91, 87, 252, 123, 234, 254, 104, 91, 113, 141, 53, 59, 222, 108, 106, 233, 243, 237, 62, 190, 254, 37, 55, 231, 251, 198, 39], [181, 253, 189, 127, 188, 95, 87, 190, 118, 234, 254, 12, 243, 125, 29, 51, 153, 246, 124, 106, 233, 244, 121, 63, 235, 239, 101, 20, 248, 249, 66, 251], [237, 244, 181, 119, 172, 111, 111, 185, 123, 250, 183, 92, 223, 117, 217, 59, 149, 68, 125, 46, 233, 116, 249, 10, 239, 251, 101, 180, 202, 249, 210, 113], [197, 124, 189, 229, 48, 111, 107, 184, 125, 250, 182, 124, 223, 55, 193, 123, 151, 84, 124, 110, 161, 244, 249, 12, 207, 251, 103, 180, 200, 241, 67, 113], [215, 221, 183, 191, 180, 46, 126, 191, 157, 50, 219, 83, 255, 127, 57, 67, 247, 119, 125, 39, 248, 245, 25, 239, 169, 254, 207, 150, 188, 251, 145, 3], [173, 228, 252, 123, 237, 53, 254, 205, 126, 19, 245, 16, 249, 248, 92, 59, 217, 202, 115, 154, 190, 102, 251, 243, 111, 223, 247, 109, 213, 249, 242, 130], [237, 100, 253, 119, 173, 127, 71, 188, 83, 186, 181, 24, 219, 117, 217, 191, 89, 214, 124, 122, 233, 102, 249, 178, 239, 219, 101, 52, 230, 251, 214, 32], [254, 164, 172, 243, 141, 118, 93, 139, 63, 247, 175, 25, 218, 127, 94, 123, 217, 214, 253, 177, 62, 103, 251, 194, 219, 255, 215, 95, 130, 125, 212, 114], [228, 188, 189, 251, 205, 237, 145, 138, 222, 123, 160, 24, 150, 189, 76, 59, 129, 19, 123, 17, 51, 109, 250, 115, 205, 154, 80, 126, 208, 73, 199, 178], [194, 173, 188, 222, 249, 253, 125, 175, 187, 119, 173, 25, 252, 255, 94, 91, 135, 211, 251, 149, 118, 78, 251, 247, 255, 223, 95, 127, 146, 212, 135, 90], [198, 174, 134, 18, 219, 177, 188, 239, 190, 183, 137, 147, 72, 234, 110, 143, 163, 99, 234, 147, 54, 79, 142, 243, 62, 22, 95, 111, 133, 117, 175, 134], [166, 254, 255, 254, 253, 255, 183, 254, 255, 254, 244, 61, 238, 252, 39, 59, 223, 127, 255, 3, 228, 239, 186, 255, 123, 127, 127, 187, 199, 125, 231, 255], [230, 239, 212, 158, 223, 185, 150, 239, 95, 179, 248, 251, 236, 232, 206, 216, 219, 223, 234, 19, 118, 110, 207, 243, 62, 55, 95, 127, 213, 124, 142, 143], [175, 252, 183, 254, 253, 231, 191, 250, 159, 251, 217, 119, 220, 255, 253, 111, 223, 127, 127, 7, 179, 199, 250, 255, 253, 254, 239, 254, 150, 125, 245, 243], [63, 189, 214, 158, 221, 191, 254, 239, 255, 179, 239, 31, 108, 250, 95, 159, 249, 239, 250, 135, 183, 235, 154, 251, 191, 126, 223, 63, 203, 118, 235, 134], [230, 237, 204, 222, 253, 209, 94, 238, 159, 147, 244, 59, 104, 250, 126, 209, 219, 239, 255, 195, 110, 111, 206, 255, 62, 127, 215, 191, 181, 124, 250, 142], [14, 175, 212, 215, 253, 143, 238, 255, 127, 179, 250, 92, 238, 233, 92, 151, 191, 111, 110, 29, 242, 248, 219, 255, 188, 254, 87, 118, 206, 252, 195, 140], [71, 173, 214, 183, 250, 174, 126, 239, 191, 243, 234, 92, 252, 251, 76, 83, 183, 239, 122, 156, 51, 217, 155, 255, 188, 190, 95, 100, 204, 240, 171, 152], [54, 239, 218, 142, 254, 131, 254, 236, 47, 132, 249, 171, 97, 234, 175, 80, 250, 239, 71, 227, 70, 179, 142, 255, 62, 180, 143, 175, 117, 59, 250, 130], [244, 168, 190, 254, 205, 225, 245, 207, 182, 183, 229, 27, 118, 255, 106, 27, 249, 255, 235, 145, 58, 111, 251, 115, 219, 131, 223, 125, 193, 120, 167, 226], [109, 53, 241, 101, 253, 127, 251, 88, 107, 233, 253, 76, 219, 189, 221, 180, 223, 220, 247, 62, 153, 50, 249, 218, 191, 253, 37, 246, 207, 239, 82, 245], [68, 189, 159, 255, 248, 239, 45, 223, 62, 251, 123, 112, 244, 247, 200, 122, 183, 127, 127, 24, 51, 216, 251, 115, 237, 166, 63, 228, 128, 224, 135, 177], [196, 189, 157, 218, 185, 237, 55, 239, 190, 187, 169, 88, 212, 247, 72, 115, 149, 127, 106, 16, 51, 75, 154, 119, 185, 150, 95, 126, 144, 114, 135, 51], [46, 237, 228, 155, 221, 213, 254, 236, 95, 53, 216, 11, 232, 236, 126, 228, 217, 235, 179, 215, 246, 122, 123, 247, 63, 95, 159, 127, 215, 30, 252, 206], [107, 233, 242, 94, 253, 221, 254, 237, 251, 145, 252, 8, 106, 253, 126, 180, 255, 251, 243, 90, 70, 123, 250, 241, 63, 92, 7, 43, 87, 190, 238, 174], [126, 188, 190, 190, 137, 105, 80, 202, 187, 191, 181, 57, 110, 255, 103, 27, 233, 247, 237, 19, 46, 103, 202, 226, 219, 247, 93, 63, 194, 117, 46, 87], [110, 233, 242, 94, 217, 217, 246, 239, 251, 179, 236, 24, 110, 251, 78, 190, 187, 255, 234, 80, 86, 107, 218, 243, 63, 254, 87, 123, 195, 118, 207, 142], [228, 111, 223, 231, 236, 42, 111, 248, 62, 170, 251, 234, 243, 99, 153, 19, 123, 238, 111, 110, 104, 242, 207, 46, 252, 174, 231, 148, 229, 251, 66, 161], [69, 111, 255, 229, 244, 42, 111, 186, 127, 170, 250, 234, 211, 111, 157, 114, 127, 223, 127, 238, 232, 242, 233, 110, 252, 190, 231, 132, 229, 251, 230, 161], [141, 231, 192, 7, 244, 27, 111, 189, 126, 224, 127, 236, 219, 97, 218, 148, 63, 76, 70, 126, 241, 240, 205, 30, 236, 207, 37, 164, 110, 174, 218, 129], [175, 183, 241, 199, 244, 31, 254, 184, 127, 234, 126, 246, 219, 125, 157, 36, 127, 221, 55, 239, 245, 240, 237, 238, 110, 126, 167, 132, 207, 254, 211, 245], [254, 250, 182, 190, 169, 213, 146, 198, 187, 220, 180, 45, 46, 252, 103, 223, 233, 243, 239, 83, 46, 111, 154, 241, 155, 83, 95, 187, 195, 125, 47, 223], [117, 87, 109, 71, 180, 15, 79, 184, 112, 234, 255, 204, 211, 97, 157, 48, 92, 196, 124, 110, 233, 178, 253, 46, 102, 175, 165, 148, 109, 251, 82, 113], [127, 189, 238, 210, 255, 189, 188, 239, 187, 247, 252, 155, 236, 252, 86, 170, 239, 255, 255, 149, 182, 123, 254, 243, 191, 95, 223, 255, 199, 245, 255, 223], [167, 124, 233, 199, 244, 43, 239, 248, 127, 234, 115, 239, 211, 124, 157, 48, 255, 205, 119, 111, 165, 244, 109, 254, 62, 62, 231, 164, 237, 251, 82, 245], [37, 229, 201, 231, 246, 43, 239, 248, 86, 226, 91, 226, 209, 113, 217, 32, 123, 76, 54, 255, 241, 240, 77, 62, 236, 142, 167, 164, 237, 251, 208, 160], [245, 111, 251, 103, 246, 10, 239, 252, 122, 170, 250, 234, 211, 105, 149, 114, 63, 252, 126, 239, 233, 246, 237, 110, 124, 174, 231, 132, 101, 243, 194, 161], [94, 236, 182, 254, 169, 124, 94, 204, 255, 187, 174, 8, 238, 255, 207, 254, 183, 255, 253, 74, 106, 67, 171, 243, 251, 255, 87, 63, 130, 247, 175, 39], [245, 207, 207, 231, 228, 10, 239, 252, 48, 166, 250, 234, 115, 99, 145, 83, 127, 230, 111, 238, 232, 242, 205, 46, 252, 174, 231, 132, 101, 251, 98, 169], [111, 237, 244, 86, 237, 253, 126, 204, 251, 247, 173, 24, 234, 249, 95, 190, 155, 255, 255, 94, 54, 115, 250, 243, 63, 255, 85, 127, 194, 255, 206, 198], [125, 253, 113, 111, 221, 31, 70, 253, 103, 168, 254, 72, 115, 249, 28, 52, 216, 254, 126, 122, 217, 187, 249, 191, 255, 253, 5, 116, 107, 186, 210, 170], [47, 185, 236, 94, 253, 157, 255, 248, 255, 253, 245, 13, 234, 253, 222, 181, 223, 234, 255, 127, 254, 99, 255, 255, 127, 95, 197, 127, 215, 191, 222, 215], [53, 66, 77, 175, 76, 7, 238, 248, 84, 128, 211, 198, 209, 99, 185, 97, 120, 204, 54, 239, 201, 242, 79, 60, 180, 170, 239, 140, 61, 187, 112, 160], [142, 221, 246, 223, 223, 211, 190, 238, 159, 150, 212, 147, 236, 254, 63, 197, 255, 243, 251, 227, 102, 239, 91, 255, 191, 127, 207, 159, 151, 127, 189, 222], [122, 236, 214, 255, 205, 155, 252, 238, 255, 179, 232, 26, 234, 234, 84, 223, 235, 255, 235, 129, 110, 111, 202, 243, 191, 254, 215, 59, 199, 124, 218, 142], [68, 188, 151, 94, 217, 253, 54, 207, 251, 251, 169, 24, 254, 247, 76, 191, 145, 255, 234, 16, 51, 107, 251, 115, 191, 246, 85, 127, 192, 115, 135, 54], [194, 173, 215, 93, 251, 233, 181, 231, 191, 31, 106, 48, 172, 183, 68, 117, 163, 59, 235, 0, 114, 249, 162, 119, 47, 182, 86, 75, 194, 64, 167, 175], [190, 239, 215, 95, 210, 179, 174, 254, 55, 166, 250, 226, 105, 231, 157, 180, 127, 255, 230, 234, 241, 242, 239, 255, 62, 126, 199, 196, 231, 247, 231, 165], [180, 239, 247, 87, 250, 75, 239, 252, 55, 166, 251, 250, 107, 231, 141, 17, 63, 255, 111, 234, 120, 247, 171, 110, 187, 238, 103, 132, 229, 243, 231, 225], [10, 189, 242, 26, 219, 241, 182, 207, 223, 23, 108, 59, 44, 250, 110, 140, 235, 251, 163, 145, 54, 107, 158, 243, 63, 86, 31, 107, 215, 84, 175, 206], [183, 239, 70, 86, 210, 151, 174, 252, 241, 130, 233, 198, 97, 226, 173, 149, 63, 238, 130, 254, 245, 211, 28, 110, 60, 14, 135, 78, 235, 247, 234, 148], [146, 239, 87, 94, 210, 177, 166, 231, 252, 3, 105, 242, 108, 234, 172, 165, 191, 123, 130, 194, 118, 235, 14, 127, 56, 38, 30, 75, 65, 82, 167, 172], [151, 207, 78, 158, 146, 179, 174, 236, 212, 2, 217, 242, 97, 106, 189, 193, 127, 251, 138, 227, 254, 173, 12, 127, 168, 39, 175, 193, 117, 211, 169, 140], [183, 215, 111, 79, 246, 3, 239, 252, 21, 202, 249, 231, 113, 103, 189, 69, 126, 202, 23, 238, 233, 146, 13, 255, 48, 46, 175, 132, 125, 251, 240, 181], [245, 253, 151, 86, 217, 126, 70, 238, 251, 250, 234, 24, 114, 244, 77, 179, 177, 246, 104, 106, 105, 231, 186, 47, 59, 254, 69, 58, 192, 115, 198, 46], [165, 253, 213, 87, 188, 107, 231, 190, 126, 250, 126, 120, 242, 113, 141, 181, 19, 126, 110, 106, 113, 252, 121, 63, 45, 190, 101, 52, 235, 251, 194, 173], [132, 255, 223, 119, 242, 235, 103, 255, 54, 174, 110, 120, 247, 247, 137, 117, 55, 254, 110, 122, 113, 220, 171, 111, 237, 166, 117, 4, 226, 243, 135, 169], [101, 117, 249, 71, 188, 79, 111, 184, 115, 234, 255, 76, 211, 117, 221, 54, 27, 204, 124, 110, 233, 240, 253, 42, 239, 254, 101, 52, 111, 251, 210, 240], [140, 190, 255, 119, 248, 111, 127, 187, 63, 174, 246, 248, 255, 231, 137, 27, 55, 246, 109, 58, 240, 245, 235, 46, 237, 191, 117, 4, 226, 240, 198, 97], [237, 117, 241, 71, 244, 63, 207, 184, 115, 234, 126, 76, 211, 121, 221, 176, 31, 204, 116, 110, 233, 242, 253, 46, 174, 255, 37, 164, 111, 251, 82, 241], [248, 53, 252, 101, 184, 74, 85, 57, 49, 190, 191, 40, 87, 119, 11, 19, 50, 212, 109, 42, 120, 195, 233, 12, 251, 227, 101, 149, 96, 226, 198, 99], [47, 251, 198, 159, 221, 23, 174, 253, 94, 241, 208, 199, 233, 232, 222, 132, 253, 207, 243, 253, 255, 250, 223, 255, 46, 95, 223, 236, 159, 172, 249, 156], [109, 189, 252, 119, 253, 127, 86, 169, 251, 250, 191, 40, 243, 248, 207, 187, 145, 238, 124, 58, 189, 115, 251, 63, 63, 255, 101, 63, 202, 234, 74, 7], [109, 188, 223, 86, 221, 127, 118, 248, 127, 226, 102, 8, 114, 248, 29, 53, 185, 238, 114, 42, 229, 243, 187, 110, 127, 190, 69, 6, 235, 250, 74, 150], [132, 254, 191, 255, 242, 207, 63, 252, 62, 175, 102, 48, 253, 231, 40, 81, 55, 127, 111, 234, 98, 221, 171, 127, 253, 166, 119, 192, 160, 242, 167, 235], [125, 249, 189, 116, 173, 93, 87, 188, 235, 252, 180, 12, 223, 253, 79, 59, 133, 214, 253, 122, 203, 99, 251, 176, 251, 251, 101, 126, 202, 243, 70, 115], [236, 120, 189, 119, 172, 78, 85, 16, 235, 190, 183, 8, 219, 63, 83, 59, 128, 244, 125, 90, 137, 118, 251, 32, 207, 251, 101, 49, 194, 249, 70, 99], [237, 245, 249, 101, 244, 30, 111, 184, 123, 234, 126, 76, 211, 113, 157, 241, 95, 204, 118, 110, 233, 244, 253, 14, 46, 255, 101, 132, 78, 251, 66, 241], [105, 52, 223, 93, 125, 127, 112, 120, 127, 226, 126, 8, 122, 249, 149, 124, 123, 252, 118, 40, 165, 242, 187, 204, 95, 191, 69, 6, 235, 232, 106, 180], [247, 252, 172, 190, 140, 93, 222, 152, 251, 250, 180, 77, 251, 252, 95, 59, 221, 213, 253, 247, 239, 103, 255, 243, 255, 219, 207, 189, 216, 249, 90, 85], [222, 178, 254, 190, 43, 243, 184, 202, 191, 255, 151, 183, 76, 254, 110, 75, 255, 247, 255, 145, 166, 199, 171, 241, 219, 115, 223, 255, 183, 109, 45, 215], [21, 252, 255, 95, 188, 63, 238, 248, 126, 234, 242, 102, 211, 248, 157, 33, 127, 236, 126, 239, 237, 246, 255, 254, 190, 238, 231, 132, 237, 251, 97, 181], [217, 21, 189, 229, 172, 78, 93, 63, 109, 234, 167, 24, 215, 63, 18, 115, 145, 198, 125, 40, 253, 237, 241, 14, 235, 255, 117, 20, 160, 224, 212, 122], [105, 180, 181, 119, 189, 95, 85, 159, 127, 235, 190, 92, 255, 61, 81, 182, 137, 198, 252, 28, 187, 238, 249, 138, 239, 255, 21, 112, 202, 220, 210, 123], [141, 254, 247, 95, 251, 223, 247, 252, 127, 234, 118, 108, 250, 253, 143, 181, 55, 254, 254, 106, 229, 252, 239, 255, 127, 127, 101, 134, 79, 252, 199, 253], [175, 190, 230, 158, 237, 177, 190, 232, 159, 255, 212, 183, 232, 236, 255, 143, 223, 255, 255, 215, 190, 71, 238, 255, 62, 115, 223, 255, 159, 253, 63, 213], [119, 183, 207, 239, 184, 10, 79, 189, 55, 174, 219, 229, 211, 103, 153, 80, 119, 206, 127, 238, 249, 208, 205, 206, 254, 238, 175, 148, 172, 242, 176, 113], [246, 247, 255, 103, 242, 10, 110, 253, 55, 174, 250, 228, 83, 103, 141, 85, 127, 221, 111, 234, 233, 210, 205, 110, 254, 254, 39, 132, 101, 242, 230, 49], [106, 189, 246, 94, 255, 159, 244, 255, 255, 179, 255, 120, 110, 253, 140, 183, 191, 255, 238, 0, 246, 123, 155, 253, 63, 127, 85, 123, 79, 238, 207, 159], [196, 177, 255, 127, 250, 207, 119, 249, 62, 241, 103, 56, 247, 247, 200, 123, 183, 127, 127, 56, 51, 219, 251, 63, 255, 239, 119, 108, 160, 160, 135, 243], [237, 61, 189, 100, 169, 76, 95, 146, 171, 250, 174, 13, 223, 63, 83, 51, 133, 245, 125, 78, 233, 109, 251, 42, 255, 249, 101, 56, 74, 241, 66, 115], [237, 116, 189, 118, 169, 77, 85, 25, 171, 254, 181, 29, 223, 255, 67, 59, 133, 245, 253, 38, 233, 103, 250, 16, 251, 217, 101, 60, 74, 242, 198, 115], [206, 255, 247, 127, 248, 30, 127, 253, 63, 251, 247, 244, 255, 247, 140, 117, 183, 255, 255, 238, 251, 216, 251, 255, 239, 255, 119, 196, 162, 246, 166, 249], [244, 255, 243, 119, 246, 11, 246, 252, 127, 170, 114, 234, 115, 120, 141, 53, 63, 220, 102, 98, 233, 246, 77, 62, 62, 238, 101, 166, 109, 251, 194, 173], [78, 181, 215, 95, 251, 159, 126, 237, 191, 179, 251, 249, 110, 255, 204, 151, 183, 255, 255, 160, 115, 251, 139, 255, 62, 254, 85, 230, 235, 230, 235, 151], [245, 156, 189, 119, 172, 111, 87, 186, 254, 234, 190, 8, 247, 125, 1, 51, 153, 214, 124, 42, 233, 237, 121, 46, 235, 235, 101, 20, 104, 120, 70, 123], [213, 141, 151, 190, 140, 46, 78, 191, 191, 187, 203, 82, 247, 123, 57, 203, 245, 247, 125, 43, 121, 229, 27, 255, 185, 254, 223, 20, 184, 123, 177, 32], [172, 164, 188, 123, 236, 45, 212, 136, 214, 59, 177, 24, 211, 121, 88, 43, 217, 202, 107, 27, 58, 102, 203, 51, 207, 219, 247, 125, 209, 121, 210, 130], [252, 180, 189, 243, 141, 127, 85, 138, 83, 251, 167, 24, 219, 255, 73, 123, 217, 214, 253, 58, 58, 103, 251, 162, 219, 223, 213, 127, 130, 121, 214, 98], [197, 205, 151, 159, 140, 46, 111, 189, 188, 162, 203, 80, 247, 99, 41, 67, 113, 198, 106, 110, 113, 209, 217, 110, 189, 166, 239, 20, 152, 123, 209, 32], [196, 172, 189, 250, 137, 237, 149, 207, 158, 23, 165, 24, 148, 255, 104, 11, 129, 19, 235, 17, 50, 79, 186, 51, 201, 147, 95, 127, 208, 73, 135, 226], [166, 172, 150, 154, 205, 225, 180, 207, 159, 179, 169, 27, 68, 232, 109, 141, 201, 251, 162, 19, 54, 127, 74, 242, 61, 22, 223, 127, 209, 125, 190, 134], [220, 164, 188, 242, 137, 108, 81, 138, 154, 183, 165, 25, 222, 127, 75, 91, 129, 87, 249, 16, 58, 71, 219, 18, 203, 211, 93, 125, 144, 97, 7, 66], [202, 36, 156, 242, 137, 252, 20, 7, 187, 179, 131, 24, 238, 179, 72, 219, 129, 55, 249, 16, 50, 77, 218, 130, 137, 243, 94, 123, 128, 80, 143, 2], [220, 52, 156, 176, 137, 101, 17, 11, 146, 247, 135, 25, 214, 55, 75, 91, 129, 151, 233, 16, 62, 71, 251, 18, 203, 211, 93, 127, 128, 97, 14, 82], [198, 172, 172, 184, 139, 229, 148, 143, 186, 151, 136, 25, 172, 234, 71, 11, 129, 115, 233, 145, 54, 79, 218, 243, 217, 22, 223, 123, 144, 85, 175, 78], [226, 173, 181, 255, 201, 237, 124, 175, 191, 115, 161, 25, 255, 255, 92, 123, 137, 211, 123, 144, 122, 107, 251, 247, 255, 218, 95, 126, 209, 80, 199, 106], [85, 77, 79, 69, 150, 10, 111, 248, 116, 226, 234, 198, 83, 96, 177, 96, 122, 228, 28, 238, 233, 177, 93, 46, 176, 46, 167, 4, 109, 251, 240, 32], [34, 252, 183, 254, 237, 207, 189, 255, 191, 254, 240, 41, 254, 254, 37, 43, 223, 127, 255, 1, 230, 235, 186, 249, 251, 250, 95, 186, 213, 124, 231, 255], [182, 236, 166, 190, 157, 195, 151, 238, 159, 151, 240, 51, 238, 235, 47, 15, 249, 239, 235, 195, 106, 111, 202, 255, 249, 182, 223, 63, 211, 125, 247, 202], [102, 188, 183, 223, 253, 239, 190, 250, 159, 251, 216, 96, 252, 255, 117, 111, 223, 127, 127, 3, 179, 243, 219, 251, 189, 62, 223, 190, 158, 125, 177, 183], [94, 172, 183, 222, 207, 227, 239, 232, 191, 150, 203, 2, 108, 239, 45, 207, 249, 255, 235, 131, 42, 99, 187, 255, 189, 118, 207, 31, 147, 127, 53, 230], [131, 173, 214, 159, 223, 159, 238, 239, 127, 179, 98, 222, 238, 232, 92, 149, 187, 239, 234, 13, 115, 248, 219, 255, 188, 126, 87, 102, 78, 108, 203, 140], [3, 173, 215, 157, 242, 203, 174, 239, 63, 241, 106, 210, 252, 242, 4, 245, 51, 111, 122, 184, 115, 216, 143, 255, 60, 62, 95, 68, 239, 224, 163, 136], [54, 205, 18, 254, 221, 195, 214, 236, 215, 0, 232, 10, 106, 249, 36, 128, 248, 251, 139, 227, 110, 171, 26, 255, 187, 214, 135, 59, 89, 27, 252, 138], [18, 205, 2, 190, 223, 131, 190, 237, 158, 0, 201, 3, 101, 234, 60, 64, 250, 251, 19, 195, 90, 187, 26, 255, 185, 228, 143, 75, 53, 26, 177, 138], [127, 116, 243, 70, 245, 127, 122, 216, 123, 232, 127, 40, 94, 255, 221, 180, 191, 221, 247, 122, 129, 178, 185, 202, 63, 252, 7, 166, 239, 255, 67, 183], [212, 141, 214, 183, 146, 42, 70, 175, 49, 162, 235, 146, 99, 99, 9, 83, 113, 230, 104, 162, 120, 209, 141, 46, 184, 166, 197, 21, 160, 243, 232, 2], [12, 181, 159, 247, 178, 239, 45, 255, 62, 185, 127, 112, 253, 247, 136, 113, 55, 127, 127, 24, 51, 216, 235, 125, 237, 171, 119, 196, 128, 192, 135, 177], [196, 189, 149, 222, 248, 237, 23, 235, 190, 187, 169, 120, 212, 247, 72, 51, 151, 119, 106, 16, 51, 75, 202, 119, 185, 150, 95, 126, 144, 96, 135, 163], [54, 237, 180, 186, 157, 205, 214, 239, 223, 49, 168, 11, 166, 248, 116, 128, 201, 251, 235, 81, 126, 107, 218, 247, 187, 214, 159, 127, 209, 94, 222, 142], [118, 58, 183, 190, 185, 237, 83, 202, 187, 191, 225, 61, 78, 255, 99, 26, 253, 255, 239, 17, 26, 67, 154, 242, 217, 246, 95, 123, 192, 125, 39, 211], [110, 173, 242, 94, 221, 221, 242, 236, 251, 149, 236, 10, 110, 216, 124, 182, 249, 251, 243, 83, 86, 107, 218, 247, 63, 220, 5, 123, 83, 14, 238, 142], [194, 236, 149, 183, 137, 228, 141, 135, 190, 55, 130, 17, 172, 179, 64, 75, 225, 87, 121, 145, 114, 77, 155, 227, 153, 178, 222, 123, 128, 80, 165, 42], [68, 47, 249, 229, 244, 10, 111, 190, 126, 170, 250, 234, 243, 99, 145, 112, 123, 206, 110, 238, 248, 242, 201, 110, 252, 174, 231, 132, 229, 251, 227, 161], [45, 167, 68, 7, 244, 31, 111, 185, 127, 232, 119, 196, 211, 97, 154, 148, 62, 204, 102, 110, 245, 240, 253, 95, 110, 255, 39, 196, 110, 174, 219, 213], [167, 172, 243, 197, 244, 15, 238, 248, 125, 234, 114, 242, 217, 125, 148, 37, 127, 221, 55, 175, 225, 252, 77, 206, 62, 62, 167, 132, 77, 232, 114, 181], [250, 255, 182, 158, 123, 213, 182, 236, 159, 95, 212, 175, 108, 252, 238, 199, 255, 107, 235, 71, 230, 71, 170, 245, 59, 114, 79, 191, 19, 126, 173, 223], [254, 252, 190, 190, 169, 213, 148, 196, 187, 214, 180, 61, 174, 254, 103, 219, 237, 243, 255, 83, 110, 111, 154, 243, 187, 83, 95, 59, 211, 93, 47, 223], [101, 93, 237, 103, 244, 47, 111, 184, 112, 234, 251, 76, 211, 97, 157, 112, 95, 196, 124, 110, 233, 176, 125, 14, 108, 174, 37, 148, 109, 251, 82, 48], [30, 169, 230, 250, 223, 157, 184, 226, 191, 149, 150, 155, 237, 248, 118, 138, 235, 255, 251, 149, 54, 235, 158, 241, 223, 95, 223, 123, 231, 77, 191, 138], [90, 168, 180, 190, 191, 153, 121, 237, 175, 181, 150, 155, 239, 255, 10, 211, 239, 247, 255, 133, 106, 75, 203, 245, 219, 243, 223, 255, 54, 174, 141, 106], [220, 172, 148, 178, 137, 237, 20, 175, 190, 179, 130, 24, 239, 243, 64, 219, 129, 119, 233, 16, 58, 77, 219, 51, 217, 243, 95, 127, 130, 80, 135, 10], [149, 231, 93, 231, 180, 43, 111, 184, 116, 162, 123, 226, 211, 97, 153, 96, 123, 204, 118, 106, 233, 240, 77, 46, 236, 142, 165, 132, 109, 251, 80, 160], [197, 110, 219, 229, 244, 10, 239, 184, 122, 162, 122, 226, 245, 99, 145, 112, 127, 236, 102, 111, 225, 244, 237, 110, 108, 174, 167, 132, 101, 251, 99, 169], [94, 233, 246, 126, 233, 217, 94, 206, 251, 181, 172, 8, 238, 255, 71, 246, 227, 247, 251, 82, 14, 107, 251, 241, 251, 255, 85, 59, 226, 246, 110, 14], [117, 79, 203, 231, 244, 10, 111, 190, 112, 162, 122, 226, 243, 99, 145, 80, 127, 238, 102, 238, 232, 248, 205, 46, 252, 174, 231, 132, 101, 251, 99, 169], [57, 173, 114, 110, 245, 159, 70, 253, 119, 168, 252, 8, 115, 249, 28, 52, 216, 238, 70, 104, 218, 186, 217, 191, 255, 253, 5, 116, 91, 170, 202, 138], [57, 237, 104, 111, 244, 159, 70, 253, 87, 168, 252, 8, 115, 249, 156, 52, 88, 236, 22, 110, 202, 186, 77, 191, 123, 253, 133, 52, 123, 186, 250, 136], [174, 225, 228, 95, 253, 223, 254, 232, 223, 244, 245, 9, 234, 253, 94, 181, 219, 237, 255, 127, 238, 99, 250, 253, 127, 91, 135, 127, 87, 175, 222, 214], [118, 173, 158, 190, 221, 237, 150, 206, 158, 183, 168, 11, 102, 233, 99, 82, 233, 251, 235, 17, 30, 107, 202, 241, 249, 151, 223, 127, 209, 88, 190, 74], [53, 75, 13, 175, 76, 7, 206, 232, 70, 160, 241, 74, 241, 64, 185, 97, 88, 204, 106, 238, 73, 35, 79, 60, 244, 168, 167, 60, 61, 187, 112, 128], [102, 204, 236, 254, 221, 213, 222, 236, 159, 19, 209, 11, 232, 255, 126, 67, 217, 251, 251, 195, 110, 111, 219, 255, 255, 127, 223, 127, 21, 126, 253, 138], [114, 236, 254, 254, 93, 185, 254, 206, 255, 183, 184, 11, 110, 234, 118, 146, 233, 231, 235, 17, 14, 107, 218, 243, 159, 247, 159, 59, 241, 125, 254, 142], [2, 189, 187, 186, 221, 229, 181, 199, 159, 95, 88, 27, 172, 254, 102, 8, 205, 59, 179, 145, 54, 122, 250, 247, 111, 23, 158, 235, 144, 72, 133, 219], [76, 189, 151, 94, 251, 249, 54, 202, 255, 251, 169, 40, 254, 246, 76, 190, 187, 255, 238, 16, 51, 107, 250, 115, 189, 182, 87, 127, 194, 115, 135, 183], [0, 207, 94, 95, 242, 138, 183, 231, 188, 143, 126, 161, 164, 163, 165, 80, 39, 127, 111, 128, 112, 152, 2, 111, 108, 167, 62, 131, 0, 112, 167, 171], [23, 239, 142, 93, 153, 183, 174, 238, 54, 178, 234, 214, 233, 234, 149, 165, 93, 255, 234, 254, 252, 251, 95, 255, 171, 255, 207, 21, 201, 241, 235, 140], [180, 239, 247, 119, 216, 71, 239, 236, 54, 182, 243, 152, 115, 231, 29, 19, 127, 255, 110, 234, 104, 247, 235, 126, 125, 174, 199, 4, 225, 243, 230, 224], [34, 172, 176, 154, 203, 245, 180, 207, 159, 87, 236, 27, 44, 252, 110, 140, 235, 219, 227, 145, 54, 107, 186, 243, 31, 87, 95, 111, 211, 76, 175, 206], [183, 239, 140, 30, 152, 176, 158, 239, 246, 147, 232, 145, 225, 232, 31, 131, 217, 203, 200, 215, 126, 235, 79, 47, 42, 6, 223, 89, 193, 115, 235, 140], [167, 239, 140, 63, 156, 167, 142, 174, 94, 147, 201, 213, 225, 104, 61, 73, 221, 75, 42, 183, 254, 236, 79, 63, 44, 46, 223, 68, 217, 123, 249, 140], [150, 207, 23, 95, 154, 167, 142, 237, 188, 18, 233, 208, 225, 106, 44, 65, 221, 115, 106, 226, 126, 239, 11, 127, 169, 166, 79, 72, 81, 115, 161, 132], [53, 87, 77, 207, 182, 7, 239, 248, 20, 234, 251, 230, 81, 99, 157, 97, 126, 196, 23, 238, 233, 178, 77, 124, 36, 42, 239, 132, 61, 251, 240, 177], [148, 255, 223, 87, 242, 107, 103, 252, 52, 174, 111, 248, 115, 231, 137, 53, 55, 254, 108, 106, 113, 245, 169, 110, 127, 174, 101, 4, 227, 243, 198, 161], [101, 172, 151, 94, 217, 205, 118, 232, 251, 219, 105, 8, 122, 248, 77, 63, 153, 238, 114, 10, 103, 123, 218, 123, 63, 62, 85, 46, 202, 114, 226, 142], [120, 119, 252, 103, 168, 10, 85, 61, 33, 190, 183, 173, 223, 55, 202, 19, 22, 215, 237, 34, 120, 195, 201, 8, 250, 235, 101, 181, 96, 242, 142, 3], [101, 173, 222, 119, 249, 127, 114, 232, 251, 242, 107, 8, 126, 248, 77, 51, 179, 255, 110, 50, 37, 243, 203, 125, 95, 255, 69, 63, 192, 99, 75, 134], [165, 245, 233, 101, 244, 31, 111, 184, 123, 226, 122, 76, 211, 121, 217, 48, 127, 204, 118, 110, 233, 240, 253, 30, 110, 255, 37, 36, 237, 250, 82, 177], [236, 120, 253, 119, 44, 77, 83, 144, 227, 190, 183, 8, 247, 213, 65, 59, 129, 244, 125, 90, 136, 101, 250, 32, 199, 251, 101, 57, 66, 187, 70, 99], [94, 252, 215, 253, 59, 34, 123, 232, 191, 182, 230, 162, 111, 247, 137, 214, 119, 245, 237, 234, 40, 213, 171, 255, 155, 230, 71, 150, 34, 183, 37, 107], [105, 48, 246, 94, 77, 253, 242, 120, 239, 193, 255, 14, 122, 249, 44, 246, 249, 252, 122, 10, 133, 227, 187, 237, 31, 62, 71, 10, 203, 239, 106, 150], [248, 121, 189, 116, 45, 77, 87, 189, 235, 252, 180, 8, 255, 189, 65, 51, 140, 244, 125, 74, 201, 107, 251, 60, 251, 217, 101, 60, 74, 184, 70, 123], [237, 252, 173, 181, 237, 77, 221, 251, 123, 254, 180, 13, 223, 252, 83, 59, 221, 213, 127, 63, 239, 103, 251, 147, 255, 219, 239, 60, 88, 120, 86, 122], [231, 236, 230, 190, 109, 179, 184, 202, 159, 214, 208, 155, 236, 238, 119, 79, 223, 255, 251, 151, 174, 71, 207, 255, 158, 114, 223, 251, 151, 125, 61, 222], [21, 108, 255, 127, 60, 47, 238, 248, 126, 162, 250, 66, 243, 232, 149, 97, 125, 236, 126, 111, 232, 240, 207, 62, 172, 174, 231, 4, 205, 251, 81, 160], [161, 230, 119, 87, 114, 11, 238, 252, 119, 162, 242, 250, 115, 107, 156, 113, 127, 221, 102, 234, 225, 246, 205, 95, 62, 170, 101, 132, 109, 176, 98, 173], [212, 240, 255, 247, 50, 75, 127, 168, 62, 183, 247, 176, 215, 119, 8, 121, 55, 221, 125, 226, 120, 212, 235, 31, 203, 239, 119, 69, 96, 162, 167, 227], [237, 60, 189, 118, 169, 237, 95, 50, 171, 254, 167, 24, 255, 255, 67, 59, 133, 247, 125, 14, 105, 111, 250, 42, 219, 251, 101, 56, 66, 225, 70, 115], [105, 180, 189, 101, 189, 95, 89, 25, 127, 251, 183, 28, 223, 61, 90, 50, 149, 214, 124, 28, 191, 108, 251, 130, 207, 255, 53, 52, 202, 232, 218, 123], [195, 225, 246, 95, 250, 159, 254, 253, 255, 186, 243, 248, 104, 251, 156, 183, 63, 255, 127, 234, 228, 255, 143, 255, 62, 122, 85, 138, 79, 182, 227, 173], [103, 165, 215, 94, 251, 155, 252, 233, 191, 177, 107, 218, 76, 250, 108, 149, 191, 255, 243, 176, 118, 219, 139, 223, 62, 126, 71, 110, 239, 230, 235, 132], [252, 29, 189, 119, 172, 111, 87, 57, 252, 190, 191, 56, 255, 125, 1, 115, 135, 246, 124, 42, 121, 225, 249, 46, 251, 239, 101, 20, 96, 107, 68, 115], [252, 120, 188, 245, 173, 126, 85, 158, 227, 186, 166, 24, 219, 189, 65, 59, 201, 212, 125, 88, 24, 103, 251, 160, 219, 219, 117, 63, 226, 121, 86, 34], [77, 120, 189, 119, 44, 94, 121, 16, 109, 234, 182, 8, 223, 55, 129, 122, 134, 117, 124, 106, 137, 244, 241, 40, 207, 251, 103, 16, 194, 179, 71, 105], [116, 164, 252, 101, 172, 111, 87, 169, 123, 190, 191, 24, 211, 121, 221, 59, 153, 212, 124, 58, 104, 100, 235, 2, 239, 223, 101, 53, 192, 251, 214, 34], [116, 134, 149, 190, 156, 45, 70, 174, 55, 147, 171, 88, 227, 227, 9, 219, 217, 198, 108, 58, 59, 99, 219, 183, 168, 246, 213, 92, 152, 251, 240, 0], [236, 166, 156, 251, 173, 43, 212, 137, 214, 187, 181, 25, 199, 105, 73, 139, 217, 198, 236, 58, 58, 103, 203, 49, 206, 211, 117, 125, 210, 249, 218, 130], [117, 87, 93, 103, 44, 14, 71, 184, 100, 250, 187, 108, 211, 81, 137, 123, 84, 212, 108, 106, 201, 224, 105, 44, 225, 171, 229, 148, 104, 251, 80, 49], [85, 79, 217, 103, 146, 14, 79, 188, 112, 168, 250, 198, 83, 99, 153, 112, 118, 204, 36, 238, 201, 144, 205, 46, 248, 172, 37, 132, 45, 243, 96, 33], [196, 172, 188, 250, 137, 237, 21, 207, 158, 55, 172, 25, 134, 247, 104, 75, 129, 115, 233, 16, 50, 77, 154, 49, 201, 211, 95, 127, 144, 72, 133, 98], [166, 174, 150, 146, 205, 225, 148, 199, 159, 183, 160, 59, 36, 248, 101, 143, 201, 123, 226, 17, 54, 111, 138, 242, 56, 18, 95, 127, 209, 92, 175, 142], [220, 36, 188, 240, 137, 109, 81, 139, 178, 183, 165, 25, 215, 119, 75, 27, 129, 87, 233, 16, 58, 71, 219, 16, 203, 211, 85, 127, 128, 97, 7, 34], [78, 172, 132, 184, 201, 229, 148, 135, 155, 151, 136, 25, 172, 251, 65, 159, 193, 115, 235, 145, 54, 75, 218, 243, 205, 22, 223, 123, 146, 84, 135, 138], [110, 164, 181, 251, 141, 237, 216, 175, 175, 51, 169, 24, 250, 255, 92, 191, 201, 211, 127, 145, 59, 107, 219, 241, 159, 218, 215, 126, 210, 68, 194, 42], [252, 197, 252, 117, 140, 90, 71, 173, 243, 182, 166, 8, 243, 117, 9, 115, 89, 244, 108, 106, 104, 229, 235, 14, 235, 235, 69, 21, 226, 248, 70, 42], [98, 177, 183, 254, 237, 205, 220, 239, 239, 209, 176, 8, 254, 253, 124, 118, 207, 243, 127, 1, 203, 107, 219, 241, 155, 222, 95, 58, 82, 78, 167, 186], [85, 77, 79, 103, 150, 10, 111, 181, 100, 224, 234, 198, 83, 96, 145, 96, 114, 228, 28, 238, 233, 176, 61, 46, 244, 174, 167, 0, 45, 243, 208, 40], [119, 169, 150, 190, 221, 157, 182, 233, 255, 181, 169, 11, 238, 250, 110, 146, 249, 231, 235, 87, 30, 107, 154, 249, 187, 118, 207, 123, 193, 111, 235, 134], [118, 172, 150, 222, 205, 241, 178, 234, 191, 145, 168, 10, 108, 254, 101, 254, 249, 247, 234, 210, 14, 107, 154, 253, 155, 118, 223, 123, 147, 125, 46, 14], [58, 236, 151, 158, 31, 195, 149, 238, 159, 182, 232, 163, 238, 227, 101, 207, 233, 247, 235, 129, 42, 107, 203, 253, 189, 54, 223, 63, 247, 124, 53, 238], [34, 238, 204, 189, 77, 143, 180, 239, 159, 183, 224, 155, 230, 232, 110, 83, 249, 239, 235, 129, 102, 127, 207, 255, 124, 54, 223, 61, 81, 60, 223, 138], [131, 173, 215, 157, 223, 203, 172, 231, 127, 161, 98, 82, 172, 178, 132, 229, 123, 239, 106, 152, 119, 249, 135, 255, 60, 54, 95, 71, 231, 36, 235, 140], [17, 143, 74, 247, 182, 138, 110, 183, 52, 162, 90, 198, 245, 35, 176, 64, 119, 238, 47, 174, 240, 152, 5, 111, 236, 174, 175, 128, 45, 242, 225, 9], [71, 233, 151, 222, 252, 143, 174, 238, 159, 177, 232, 64, 244, 250, 101, 238, 215, 127, 122, 73, 1, 75, 219, 253, 189, 62, 95, 62, 152, 120, 33, 156], [32, 221, 26, 190, 220, 135, 150, 228, 222, 0, 232, 11, 118, 251, 32, 80, 216, 107, 2, 67, 66, 171, 26, 63, 249, 151, 7, 27, 88, 59, 232, 138], [52, 205, 18, 190, 95, 195, 150, 236, 159, 128, 233, 11, 102, 248, 45, 128, 248, 251, 130, 99, 78, 171, 26, 255, 185, 134, 15, 43, 113, 27, 172, 138], [54, 239, 67, 142, 94, 131, 135, 232, 23, 132, 248, 171, 98, 232, 45, 20, 122, 233, 131, 227, 74, 179, 12, 255, 184, 4, 143, 43, 117, 59, 54, 194], [20, 77, 107, 238, 84, 198, 239, 240, 68, 136, 250, 2, 113, 235, 49, 96, 248, 239, 23, 235, 200, 187, 40, 47, 241, 173, 175, 0, 53, 187, 116, 168], [60, 141, 2, 170, 221, 165, 206, 233, 150, 0, 233, 3, 97, 251, 56, 64, 249, 251, 19, 227, 90, 43, 26, 191, 249, 197, 143, 105, 49, 75, 240, 130], [54, 173, 98, 170, 221, 227, 238, 232, 95, 161, 233, 11, 96, 232, 61, 0, 249, 235, 19, 243, 30, 51, 92, 255, 63, 92, 143, 127, 213, 111, 250, 130], [118, 100, 242, 198, 215, 171, 254, 200, 123, 160, 249, 10, 74, 251, 125, 52, 251, 253, 82, 122, 12, 179, 152, 206, 63, 156, 5, 47, 197, 251, 98, 134], [213, 207, 199, 39, 208, 10, 110, 173, 112, 162, 235, 200, 211, 98, 137, 83, 51, 198, 104, 110, 121, 209, 205, 46, 188, 166, 101, 20, 44, 243, 194, 32], [81, 196, 214, 119, 162, 11, 110, 175, 119, 162, 98, 16, 243, 99, 140, 113, 51, 246, 104, 42, 112, 252, 75, 63, 238, 174, 85, 68, 96, 176, 192, 40], [68, 173, 151, 222, 217, 175, 38, 235, 254, 179, 169, 24, 212, 243, 72, 119, 147, 127, 106, 24, 50, 75, 138, 119, 189, 150, 95, 126, 144, 96, 131, 160], [178, 237, 150, 186, 157, 205, 150, 238, 159, 177, 168, 25, 230, 248, 104, 130, 201, 107, 235, 83, 110, 107, 218, 183, 185, 214, 31, 123, 208, 26, 255, 138], [118, 120, 151, 190, 155, 207, 19, 200, 191, 181, 224, 41, 70, 255, 69, 26, 253, 255, 235, 81, 26, 75, 218, 240, 153, 180, 15, 127, 129, 59, 167, 242], [66, 236, 148, 210, 137, 172, 36, 135, 190, 147, 128, 24, 164, 243, 65, 223, 161, 119, 104, 144, 50, 79, 154, 115, 153, 150, 95, 123, 128, 80, 163, 10], [114, 172, 178, 250, 221, 221, 210, 238, 251, 145, 168, 25, 46, 248, 108, 130, 233, 251, 235, 17, 126, 107, 154, 247, 187, 214, 23, 123, 81, 14, 238, 142], [133, 175, 209, 229, 244, 10, 238, 190, 118, 162, 250, 240, 243, 99, 145, 80, 123, 238, 110, 238, 224, 254, 205, 110, 108, 174, 231, 132, 228, 250, 67, 169], [167, 167, 103, 5, 244, 31, 238, 121, 119, 232, 251, 207, 195, 104, 156, 37, 124, 204, 102, 238, 253, 242, 237, 222, 46, 30, 197, 196, 111, 175, 210, 212], [182, 231, 230, 159, 203, 147, 190, 233, 159, 178, 193, 251, 104, 236, 174, 231, 127, 253, 251, 231, 230, 99, 143, 255, 62, 50, 207, 63, 23, 126, 189, 199], [222, 168, 246, 190, 31, 153, 184, 200, 191, 149, 195, 27, 236, 234, 126, 198, 235, 247, 251, 145, 54, 111, 154, 253, 155, 243, 223, 123, 167, 44, 61, 134], [216, 168, 148, 250, 137, 237, 84, 134, 191, 179, 162, 25, 174, 243, 64, 219, 161, 243, 232, 16, 58, 77, 218, 179, 155, 243, 87, 123, 130, 81, 143, 10], [5, 46, 223, 229, 116, 42, 235, 48, 124, 170, 114, 226, 213, 97, 145, 97, 126, 109, 118, 111, 161, 244, 77, 78, 108, 174, 171, 128, 205, 169, 65, 161], [149, 135, 89, 231, 180, 43, 111, 184, 116, 170, 123, 224, 211, 99, 153, 96, 119, 76, 46, 110, 233, 176, 77, 14, 236, 174, 165, 132, 36, 251, 64, 33], [5, 228, 95, 229, 116, 74, 111, 184, 124, 162, 107, 64, 215, 115, 152, 113, 115, 92, 126, 106, 113, 244, 125, 78, 237, 174, 103, 68, 96, 243, 193, 160], [133, 46, 219, 229, 116, 42, 239, 178, 116, 170, 122, 224, 245, 99, 145, 96, 127, 108, 54, 238, 161, 240, 141, 110, 236, 174, 39, 128, 100, 241, 67, 161], [126, 172, 246, 254, 221, 221, 82, 234, 235, 181, 172, 9, 110, 249, 79, 150, 233, 255, 235, 16, 14, 107, 218, 249, 251, 223, 5, 59, 99, 70, 110, 14], [126, 237, 150, 126, 205, 253, 86, 206, 251, 151, 168, 9, 110, 249, 76, 150, 169, 247, 235, 81, 30, 107, 154, 241, 187, 214, 85, 127, 195, 94, 238, 142], [40, 173, 122, 107, 213, 155, 70, 237, 87, 128, 108, 8, 115, 233, 52, 48, 88, 234, 2, 124, 90, 186, 92, 63, 127, 221, 133, 109, 115, 138, 218, 136], [52, 237, 64, 238, 157, 131, 70, 237, 87, 0, 168, 11, 99, 233, 44, 16, 88, 234, 0, 226, 106, 43, 90, 63, 185, 196, 133, 125, 113, 58, 254, 138], [2, 189, 187, 186, 221, 237, 177, 207, 158, 93, 72, 27, 180, 255, 108, 0, 205, 59, 19, 17, 50, 122, 186, 247, 109, 22, 158, 107, 144, 72, 181, 251], [21, 95, 79, 166, 18, 130, 46, 244, 172, 192, 74, 231, 117, 230, 161, 80, 54, 229, 57, 231, 193, 145, 28, 110, 176, 166, 47, 128, 44, 243, 33, 153], [79, 189, 147, 90, 251, 217, 182, 206, 251, 241, 232, 40, 110, 254, 108, 180, 179, 127, 234, 16, 23, 123, 138, 243, 61, 22, 7, 127, 194, 87, 139, 134], [161, 173, 142, 61, 156, 183, 206, 231, 214, 178, 234, 4, 163, 234, 23, 65, 201, 239, 106, 157, 252, 251, 87, 63, 175, 191, 223, 17, 216, 120, 234, 140], [166, 236, 247, 95, 248, 75, 239, 236, 127, 178, 122, 154, 96, 231, 221, 53, 121, 254, 98, 234, 97, 246, 239, 255, 61, 54, 199, 38, 227, 241, 230, 164], [102, 180, 186, 154, 201, 229, 176, 207, 155, 87, 44, 25, 140, 254, 110, 8, 137, 27, 243, 17, 54, 107, 186, 243, 95, 23, 95, 111, 208, 76, 175, 222], [164, 173, 215, 95, 250, 235, 102, 239, 119, 163, 106, 56, 114, 243, 140, 53, 59, 254, 98, 42, 114, 250, 139, 127, 62, 190, 85, 71, 227, 242, 230, 172], [244, 80, 157, 243, 136, 110, 85, 152, 162, 186, 163, 24, 215, 115, 73, 27, 129, 213, 125, 122, 120, 101, 219, 0, 201, 203, 101, 116, 200, 115, 194, 2], [148, 205, 5, 63, 152, 35, 134, 172, 150, 18, 136, 80, 243, 98, 9, 67, 217, 67, 104, 231, 120, 69, 75, 63, 168, 166, 207, 92, 208, 115, 161, 8], [46, 165, 215, 87, 243, 43, 239, 251, 63, 161, 107, 72, 104, 231, 172, 149, 59, 254, 98, 62, 48, 243, 187, 222, 63, 190, 69, 70, 195, 230, 226, 160], [101, 172, 150, 94, 237, 157, 178, 238, 251, 147, 104, 8, 108, 248, 76, 182, 153, 106, 106, 82, 6, 107, 138, 115, 31, 62, 85, 107, 194, 115, 74, 142], [4, 175, 223, 87, 242, 43, 231, 248, 63, 170, 123, 224, 114, 231, 141, 117, 55, 254, 102, 106, 97, 240, 47, 111, 60, 174, 101, 132, 229, 243, 103, 161], [248, 243, 220, 101, 40, 14, 92, 188, 35, 190, 182, 189, 223, 47, 74, 27, 134, 213, 237, 98, 232, 212, 209, 0, 230, 251, 117, 145, 104, 240, 142, 27], [37, 53, 229, 69, 212, 15, 239, 57, 119, 226, 123, 78, 211, 121, 149, 52, 61, 204, 118, 46, 225, 240, 253, 14, 109, 62, 101, 4, 109, 250, 82, 176], [236, 126, 248, 118, 44, 125, 83, 16, 242, 190, 183, 40, 255, 213, 195, 58, 133, 244, 237, 90, 200, 101, 234, 34, 195, 249, 101, 57, 66, 249, 70, 99], [82, 110, 215, 204, 122, 50, 122, 202, 183, 182, 234, 234, 111, 231, 133, 218, 119, 245, 239, 162, 8, 211, 239, 253, 150, 238, 103, 159, 37, 243, 38, 41], [82, 106, 119, 237, 234, 75, 127, 206, 63, 182, 210, 171, 111, 231, 161, 92, 127, 253, 239, 192, 8, 223, 235, 255, 246, 230, 255, 151, 55, 185, 36, 107], [124, 164, 188, 101, 172, 47, 69, 169, 123, 191, 191, 24, 215, 121, 89, 59, 153, 212, 108, 59, 56, 101, 203, 2, 205, 223, 101, 53, 192, 248, 214, 34], [228, 175, 198, 158, 222, 34, 166, 233, 86, 179, 201, 210, 64, 226, 45, 85, 121, 238, 34, 250, 50, 210, 207, 127, 60, 38, 207, 108, 149, 107, 178, 128], [252, 167, 148, 243, 173, 41, 68, 141, 87, 187, 165, 24, 207, 97, 73, 155, 217, 198, 236, 58, 58, 103, 203, 146, 238, 211, 85, 125, 194, 249, 218, 2], [100, 230, 132, 187, 173, 41, 70, 142, 215, 187, 169, 93, 226, 225, 76, 223, 217, 198, 110, 18, 62, 99, 203, 179, 172, 182, 213, 127, 218, 255, 218, 4], [132, 238, 134, 151, 222, 139, 166, 239, 158, 179, 194, 211, 228, 224, 108, 11, 249, 110, 98, 147, 114, 254, 79, 127, 60, 6, 223, 101, 209, 120, 185, 140], [125, 87, 93, 101, 164, 14, 71, 188, 100, 168, 187, 108, 211, 49, 137, 115, 22, 196, 108, 110, 201, 176, 93, 12, 224, 175, 101, 148, 104, 251, 64, 49], [252, 104, 148, 112, 173, 63, 84, 158, 235, 187, 176, 120, 223, 185, 65, 154, 137, 212, 237, 80, 26, 103, 203, 160, 219, 219, 117, 63, 194, 121, 74, 3], [72, 172, 156, 186, 137, 237, 16, 143, 143, 23, 168, 25, 174, 179, 64, 91, 129, 19, 233, 16, 58, 77, 218, 49, 201, 211, 84, 123, 144, 64, 143, 10], [244, 112, 189, 119, 8, 108, 65, 152, 242, 186, 167, 24, 215, 119, 73, 59, 129, 84, 108, 58, 41, 101, 251, 0, 201, 203, 117, 60, 192, 113, 70, 34], [45, 167, 194, 15, 210, 2, 102, 169, 118, 130, 107, 194, 113, 227, 169, 208, 121, 206, 34, 238, 120, 146, 205, 46, 44, 166, 165, 12, 237, 243, 240, 128], [124, 160, 148, 242, 141, 61, 80, 138, 239, 185, 185, 88, 202, 251, 76, 154, 201, 215, 253, 17, 26, 99, 219, 147, 155, 223, 213, 127, 218, 75, 202, 2], [124, 100, 252, 117, 136, 74, 85, 172, 242, 182, 166, 8, 243, 113, 73, 27, 153, 212, 108, 34, 104, 231, 235, 8, 251, 227, 85, 21, 224, 248, 70, 42], [85, 77, 127, 103, 38, 10, 111, 180, 100, 224, 234, 66, 243, 115, 145, 114, 50, 228, 124, 110, 201, 177, 189, 44, 241, 173, 103, 4, 36, 179, 97, 40], [114, 169, 150, 158, 219, 241, 144, 202, 175, 145, 168, 11, 110, 250, 101, 214, 233, 243, 234, 17, 14, 107, 218, 249, 153, 86, 207, 59, 144, 124, 47, 142], [242, 224, 142, 190, 13, 143, 252, 236, 191, 182, 160, 27, 230, 203, 104, 82, 249, 253, 107, 67, 74, 71, 218, 211, 249, 178, 207, 61, 80, 58, 183, 138], [2, 174, 87, 159, 82, 139, 168, 103, 126, 163, 114, 242, 165, 162, 180, 100, 115, 111, 34, 128, 113, 220, 5, 127, 60, 38, 127, 192, 37, 128, 161, 172], [52, 77, 26, 238, 84, 135, 215, 224, 196, 0, 232, 3, 119, 251, 33, 64, 248, 250, 3, 98, 72, 187, 24, 63, 241, 173, 135, 9, 113, 59, 112, 138], [20, 77, 43, 239, 148, 134, 239, 240, 116, 132, 235, 2, 113, 211, 49, 96, 120, 252, 27, 235, 72, 179, 25, 46, 241, 173, 175, 0, 49, 187, 244, 162], [32, 173, 26, 186, 221, 229, 214, 238, 150, 5, 232, 3, 55, 251, 32, 64, 217, 250, 3, 146, 58, 43, 90, 63, 249, 135, 135, 121, 208, 75, 244, 138], [52, 172, 170, 250, 221, 229, 214, 194, 86, 161, 233, 11, 34, 249, 117, 0, 217, 251, 3, 147, 42, 59, 90, 191, 121, 199, 141, 123, 209, 91, 254, 138], [54, 108, 242, 78, 221, 173, 230, 202, 255, 130, 232, 10, 106, 232, 53, 144, 249, 235, 66, 178, 10, 179, 154, 251, 51, 30, 7, 43, 193, 123, 114, 134], [85, 111, 89, 103, 160, 10, 111, 188, 112, 170, 122, 228, 211, 99, 137, 115, 22, 68, 108, 110, 201, 208, 205, 42, 228, 174, 101, 132, 44, 243, 66, 41], [70, 173, 148, 26, 137, 201, 160, 238, 251, 177, 168, 24, 238, 224, 68, 159, 161, 107, 106, 16, 54, 107, 154, 113, 157, 22, 87, 127, 210, 69, 139, 134], [64, 198, 221, 103, 178, 10, 110, 175, 124, 162, 106, 224, 247, 99, 136, 80, 51, 198, 108, 44, 96, 216, 205, 111, 172, 174, 101, 4, 100, 242, 227, 8], [242, 173, 22, 186, 157, 201, 86, 235, 159, 146, 160, 25, 230, 249, 104, 67, 201, 227, 107, 67, 110, 107, 218, 183, 185, 210, 95, 59, 16, 8, 52, 10], [70, 232, 150, 158, 137, 160, 36, 166, 186, 147, 128, 24, 228, 242, 96, 206, 161, 99, 106, 18, 34, 77, 154, 115, 157, 54, 95, 123, 128, 80, 171, 10], [114, 172, 146, 250, 93, 237, 144, 238, 251, 21, 168, 25, 46, 216, 100, 146, 201, 251, 235, 17, 46, 111, 154, 183, 155, 214, 21, 123, 208, 12, 46, 142], [167, 45, 70, 29, 220, 155, 238, 234, 255, 160, 226, 139, 224, 232, 21, 53, 89, 238, 122, 255, 60, 251, 207, 254, 62, 30, 133, 120, 71, 173, 114, 140], [246, 173, 102, 156, 207, 147, 190, 233, 159, 178, 129, 155, 72, 232, 62, 71, 239, 253, 251, 231, 116, 103, 206, 255, 50, 114, 207, 63, 85, 61, 187, 134], [237, 113, 29, 103, 40, 15, 67, 152, 96, 184, 167, 76, 215, 81, 73, 59, 16, 84, 108, 122, 41, 100, 249, 8, 205, 137, 101, 52, 200, 179, 66, 32], [117, 71, 89, 101, 180, 10, 111, 57, 116, 170, 123, 224, 211, 97, 153, 112, 54, 204, 12, 110, 233, 176, 77, 14, 228, 174, 37, 132, 108, 250, 192, 33], [120, 236, 146, 250, 201, 237, 86, 198, 183, 147, 168, 24, 174, 243, 97, 152, 161, 251, 234, 16, 10, 79, 218, 51, 155, 215, 21, 123, 146, 81, 142, 10], [133, 14, 91, 229, 52, 46, 111, 48, 116, 170, 123, 100, 215, 99, 145, 96, 54, 108, 22, 110, 225, 176, 13, 14, 236, 174, 39, 128, 44, 227, 65, 161], [40, 173, 168, 90, 221, 253, 195, 207, 86, 37, 236, 8, 34, 249, 116, 16, 89, 234, 66, 24, 26, 122, 90, 55, 127, 143, 21, 121, 211, 75, 222, 170], [164, 168, 177, 91, 221, 253, 145, 143, 218, 55, 33, 24, 166, 248, 76, 46, 137, 26, 98, 17, 50, 110, 250, 179, 13, 150, 85, 127, 208, 72, 215, 166], [51, 205, 43, 142, 148, 199, 207, 229, 84, 64, 206, 2, 113, 236, 52, 96, 120, 234, 19, 239, 105, 155, 125, 63, 241, 46, 143, 0, 53, 58, 240, 236], [107, 169, 146, 26, 219, 221, 178, 206, 251, 145, 236, 8, 44, 250, 103, 148, 185, 251, 226, 80, 22, 107, 154, 243, 63, 94, 21, 123, 211, 7, 107, 142], [213, 77, 75, 165, 18, 14, 111, 248, 52, 224, 202, 198, 87, 103, 177, 112, 54, 229, 124, 238, 233, 145, 61, 46, 240, 173, 175, 4, 45, 179, 33, 112], [166, 238, 215, 93, 218, 75, 174, 238, 126, 178, 226, 194, 96, 226, 149, 101, 123, 126, 106, 232, 96, 247, 207, 127, 61, 38, 199, 6, 197, 248, 103, 172], [164, 172, 214, 95, 218, 139, 166, 237, 127, 179, 99, 242, 96, 242, 172, 53, 59, 254, 98, 162, 114, 250, 143, 127, 60, 46, 85, 70, 229, 242, 226, 140], [102, 160, 189, 250, 205, 237, 145, 143, 187, 119, 160, 25, 158, 255, 77, 26, 137, 19, 121, 17, 18, 111, 250, 241, 219, 154, 93, 127, 208, 72, 199, 250], [85, 69, 213, 103, 132, 10, 71, 188, 100, 162, 251, 98, 211, 113, 153, 98, 90, 196, 108, 106, 73, 176, 77, 44, 164, 174, 101, 20, 109, 251, 208, 32], [220, 204, 132, 190, 153, 33, 70, 175, 151, 19, 137, 24, 238, 226, 9, 207, 81, 198, 232, 35, 122, 79, 203, 63, 184, 182, 221, 95, 178, 119, 136, 4], [52, 237, 214, 95, 90, 171, 182, 234, 254, 145, 104, 34, 100, 248, 109, 50, 179, 127, 98, 66, 6, 115, 138, 121, 60, 166, 71, 47, 209, 115, 43, 134], [20, 47, 223, 69, 242, 34, 110, 248, 52, 170, 122, 230, 81, 99, 129, 80, 63, 222, 38, 238, 160, 208, 205, 111, 124, 174, 39, 132, 101, 227, 98, 161], [1, 143, 91, 87, 18, 170, 174, 182, 124, 162, 74, 194, 241, 226, 176, 96, 54, 110, 34, 174, 161, 152, 13, 111, 36, 166, 43, 0, 45, 227, 1, 168], [85, 77, 91, 231, 22, 2, 111, 56, 100, 128, 203, 66, 119, 115, 185, 112, 114, 244, 24, 234, 73, 145, 61, 44, 241, 173, 167, 0, 37, 178, 97, 40], [32, 172, 153, 250, 221, 237, 149, 207, 150, 23, 104, 24, 182, 241, 96, 8, 201, 58, 2, 16, 50, 106, 90, 55, 237, 150, 20, 121, 208, 88, 212, 170], [32, 169, 186, 126, 205, 213, 212, 231, 211, 23, 224, 9, 34, 249, 100, 16, 201, 250, 226, 65, 78, 107, 218, 179, 91, 223, 84, 123, 209, 88, 254, 138], [65, 15, 217, 37, 178, 10, 110, 188, 116, 170, 122, 228, 247, 99, 129, 208, 54, 110, 100, 110, 225, 152, 77, 47, 172, 174, 39, 128, 108, 243, 3, 41], [114, 173, 22, 186, 77, 237, 80, 234, 151, 21, 160, 9, 38, 251, 96, 149, 201, 243, 235, 0, 62, 107, 218, 181, 155, 210, 85, 123, 82, 72, 78, 10], [114, 172, 150, 186, 25, 237, 146, 198, 191, 149, 169, 25, 174, 218, 100, 146, 137, 115, 234, 17, 14, 107, 154, 181, 153, 214, 31, 123, 208, 9, 15, 142], [227, 105, 70, 30, 93, 137, 224, 236, 223, 144, 224, 139, 98, 200, 53, 21, 200, 236, 106, 197, 46, 107, 206, 253, 63, 58, 195, 59, 85, 28, 122, 142], [246, 168, 198, 188, 79, 131, 152, 232, 159, 178, 192, 155, 67, 200, 52, 74, 233, 245, 107, 195, 108, 111, 206, 251, 145, 118, 219, 63, 117, 45, 51, 134], [93, 7, 81, 101, 180, 14, 111, 57, 116, 170, 123, 100, 211, 33, 152, 112, 54, 196, 12, 110, 249, 176, 77, 14, 228, 174, 37, 132, 104, 242, 64, 33], [21, 23, 91, 229, 48, 46, 111, 48, 116, 168, 123, 68, 215, 115, 160, 112, 54, 124, 28, 238, 241, 144, 17, 14, 228, 174, 39, 128, 40, 226, 33, 33], [224, 168, 177, 88, 205, 253, 145, 207, 219, 55, 160, 24, 162, 248, 68, 62, 137, 26, 226, 16, 26, 107, 242, 17, 13, 146, 85, 123, 208, 72, 214, 174], [81, 21, 31, 237, 18, 78, 111, 49, 36, 192, 203, 64, 247, 119, 1, 112, 118, 244, 92, 234, 73, 145, 49, 12, 241, 175, 39, 0, 40, 242, 33, 48], [49, 141, 43, 190, 157, 231, 195, 239, 86, 1, 200, 2, 51, 248, 48, 96, 217, 234, 18, 137, 58, 187, 122, 63, 185, 134, 138, 120, 145, 74, 244, 170], [0, 140, 31, 249, 156, 205, 37, 239, 158, 17, 72, 16, 180, 243, 32, 96, 193, 59, 18, 24, 115, 28, 26, 55, 237, 134, 30, 104, 144, 8, 161, 168], [98, 233, 186, 90, 221, 221, 212, 199, 251, 21, 164, 25, 42, 220, 102, 22, 137, 171, 227, 16, 94, 107, 178, 241, 59, 89, 20, 123, 211, 12, 254, 206], [213, 68, 109, 101, 34, 10, 111, 56, 96, 162, 135, 128, 211, 71, 145, 123, 54, 212, 125, 238, 232, 213, 205, 8, 224, 235, 231, 148, 36, 243, 2, 32], [0, 172, 207, 93, 84, 139, 174, 230, 54, 162, 226, 194, 241, 226, 5, 64, 121, 238, 106, 136, 96, 250, 201, 110, 44, 166, 215, 0, 65, 224, 98, 168]]}} \ No newline at end of file diff --git a/detection/tests/data/ter-11171579861604.jpg b/detection/tests/data/ter-11171579861604.jpg deleted file mode 100644 index 9a895fe..0000000 Binary files a/detection/tests/data/ter-11171579861604.jpg and /dev/null differ diff --git a/detection/tests/data/ter-11171579861604.jpg.topleft.jpg b/detection/tests/data/ter-11171579861604.jpg.topleft.jpg deleted file mode 100644 index 5833a91..0000000 Binary files a/detection/tests/data/ter-11171579861604.jpg.topleft.jpg and /dev/null differ diff --git a/detection/tests/run.py b/detection/tests/run.py deleted file mode 100755 index f3d56d4..0000000 --- a/detection/tests/run.py +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env python3 -import time -import unittest - -import atexit -import cv2 -import requests -import subprocess -import random -import os -import json - -BASE_DIR = os.path.abspath(os.path.dirname(__file__) + "/..") - -class Testing(unittest.TestCase): - def setUp(self): - self.server = self.start_server() - - def tearDown(self): - self.server.terminate() - self.server.wait() - - def start_server(self): - port = random.randint(40000, 60000) - cmd = ['python3', 'app.py', '-l', '127.0.0.1', '-p', str(port)] - p = subprocess.Popen(cmd, cwd=BASE_DIR) - start = time.time() - while p.poll() == None and time.time() - start < 1200: - try: - url = 'http://localhost:%d' % port - if 'emblem' in requests.get(url, timeout=1).text: - self.base_url = url - return p - except: - time.sleep(1) - raise Exception("Failed to start server") - - def do_test_roi_cloud_comparison(self, std, ter): - std_file_Path = os.path.join(BASE_DIR, 'tests/data/', std) - ter_file_Path = os.path.join(BASE_DIR, 'tests/data/', ter) - - std_img = cv2.imread(std_file_Path) - ter_img = cv2.imread(ter_file_Path) - - std_img_encode = cv2.imencode('.jpg', std_img)[1] - ter_img_encode = cv2.imencode('.jpg', ter_img)[1] - - files = {"std_file": ('std_file.jpg', std_img_encode, 'image/jpg'), - "ter_file": ('ter_file.jpg', ter_img_encode, 'image/jpg')} - # form = {"threshold":'50',"angle":'45'} - form = {"threshold": '50'} - begin = time.time() - api = '/qr_roi_cloud_comparison' - r = requests.post(self.base_url + api, files=files, - data=form).json() - print("std file", std_file_Path) - print("ter file", ter_file_Path) - print(r) - print("%.3fs processed %s" % (time.time() - begin, r)) - self.assertEqual(r['data']['status'], 'OK') - - def test_roi_cmp(self): - self.do_test_roi_cloud_comparison('11173059793161.jpg', '11173059793161_pos_M2012K11AC_fix.jpg') - #std_file_Path =os.path.join(BASE_DIR, 'tests/data/','11173059793161.jpg') - #ter_file_Path =os.path.join(BASE_DIR, 'tests/data/','11173059793161_pos_M2012K11AC_fix.jpg') - #self.do_test_roi_cloud_comparison('0079080983780.jpg', '0079080983780_pos_iPhone14Pro_1706842853.5876188.jpg') - -if __name__ == '__main__': - unittest.main() diff --git a/detection/thirdTool/__init__.py b/detection/thirdTool/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/detection/thirdTool/qr_box_detect.py b/detection/thirdTool/qr_box_detect.py deleted file mode 100644 index cfb6def..0000000 --- a/detection/thirdTool/qr_box_detect.py +++ /dev/null @@ -1,56 +0,0 @@ - -import torch.nn as nn -import torch -import numpy as np -from .yolo5x_qr.models.common import DetectMultiBackend -from .yolo5x_qr.utils.augmentations import (letterbox) -from .yolo5x_qr.utils.torch_utils import select_device, smart_inference_mode -from .yolo5x_qr.utils.general import (LOGGER, Profile, check_img_size, cv2, - non_max_suppression, scale_boxes) - -class QR_Box_detect(nn.Module): - def __init__(self, model_path=None, device='cpu'): - super(QR_Box_detect, self).__init__() - self.conf_thres=0.80 - self.iou_thres=0.45 - self.classes=None - self.max_det=1 - self.agnostic_nms=False - self.model_path = model_path - self.device = select_device(device,model_path=self.model_path) - self.model = DetectMultiBackend(weights=self.model_path, device=self.device) - - - def forward(self,input,imgsz=512): - - #图像按比例缩放 - stride, names, pt = self.model.stride, self.model.names, self.model.pt - imgsz = check_img_size(imgsz, s=stride) # check image size - im0 = cv2.cvtColor(np.array(input), cv2.COLOR_RGB2BGR) # BGR - im = letterbox(im0, imgsz, stride=32, auto=True)[0] # padded resize - im = im.transpose((2, 0, 1))[::-1] # HWC to CHW, BGR to RGB - im = np.ascontiguousarray(im) # contiguous - - - im = torch.from_numpy(im).to(self.model.device) - im = im.half() if self.model.fp16 else im.float() # - im /= 255 - if len(im.shape) == 3: - im = im[None] # expand for batch dim - - - try: - pred = self.model.model(im, augment=False,visualize=False) - pred = non_max_suppression(pred, self.conf_thres, self.iou_thres, self.classes, self.agnostic_nms, max_det=self.max_det) - det = pred[0] - if len(det): - # Rescale boxes from img_size to im0 size - det[:, :4] = scale_boxes(im.shape[2:], det[:, :4], im0.shape).round() - return det[:, :4].view(2, 2).cpu().numpy(),det[:,4:5].cpu().numpy() - return None,None - except RuntimeError as error: - print('Error', error) - print('If you encounter CUDA out of memory, try to set --tile with a smaller number.') - return None, None - - diff --git a/detection/thirdTool/qrsrgan.py b/detection/thirdTool/qrsrgan.py deleted file mode 100644 index 9bbfe08..0000000 --- a/detection/thirdTool/qrsrgan.py +++ /dev/null @@ -1,46 +0,0 @@ -import argparse -import cv2 -import glob -import os -from basicsr.archs.rrdbnet_arch import RRDBNet -import torch.nn as nn -from .realesrgan import Real_ESRGANer -# from realesrgan.archs.srvgg_arch import SRVGGNetCompact - -class RealsrGan(nn.Module): - def __init__(self, num_in_ch=3, scale=4,model_path=None,device='0'): - super(RealsrGan, self).__init__() - self.model = RRDBNet(num_in_ch=3, num_out_ch=3, num_feat=64, num_block=23, num_grow_ch=32, scale=4) - self.netscale = 4 - self.model_path = model_path - self.device = device - # restorer - self.upsampler = Real_ESRGANer( - scale=self.netscale, - device=self.device, - model_path=self.model_path, - model=self.model, - tile=0, - tile_pad=10, - pre_pad=0, - half=False) - - def forward(self,input): - - # img = cv2.imread(input, cv2.IMREAD_UNCHANGED) - # if len(img.shape) == 3 and img.shape[2] == 4: - # img_mode = 'RGBA' - # else: - # img_mode = None - - try: - output, _ = self.upsampler.enhance(input, outscale=4) - except RuntimeError as error: - print('Error', error) - print('If you encounter CUDA out of memory, try to set --tile with a smaller number.') - # else: - # if img_mode == 'RGBA': # RGBA images should be saved in png format - # extension = 'png' - # save_path = os.path.join(args.output, f'{imgname}_{args.suffix}.{extension}') - # cv2.imwrite(save_path, output) - return output diff --git a/detection/thirdTool/realesrgan/__init__.py b/detection/thirdTool/realesrgan/__init__.py deleted file mode 100644 index 2276f1e..0000000 --- a/detection/thirdTool/realesrgan/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# flake8: noqa -from .archs import * -from .data import * -from .models import * -from .utils import * -from .version import * diff --git a/detection/thirdTool/realesrgan/archs/__init__.py b/detection/thirdTool/realesrgan/archs/__init__.py deleted file mode 100644 index 6f87d36..0000000 --- a/detection/thirdTool/realesrgan/archs/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -import importlib -from basicsr.utils import scandir -from os import path as osp - -# automatically scan and import arch modules for registry -# scan all the files that end with '_arch.py' under the archs folder -arch_folder = osp.dirname(osp.abspath(__file__)) -arch_filenames = [osp.splitext(osp.basename(v))[0] for v in scandir(arch_folder) if v.endswith('_arch.py')] -# import all the arch modules -_arch_modules = [importlib.import_module(f'thirdTool.realesrgan.archs.{file_name}') for file_name in arch_filenames] diff --git a/detection/thirdTool/realesrgan/archs/discriminator_arch.py b/detection/thirdTool/realesrgan/archs/discriminator_arch.py deleted file mode 100644 index 4b66ab1..0000000 --- a/detection/thirdTool/realesrgan/archs/discriminator_arch.py +++ /dev/null @@ -1,67 +0,0 @@ -from basicsr.utils.registry import ARCH_REGISTRY -from torch import nn as nn -from torch.nn import functional as F -from torch.nn.utils import spectral_norm - - -@ARCH_REGISTRY.register() -class UNetDiscriminatorSN(nn.Module): - """Defines a U-Net discriminator with spectral normalization (SN) - - It is used in Real-ESRGAN: Training Real-World Blind Super-Resolution with Pure Synthetic Data. - - Arg: - num_in_ch (int): Channel number of inputs. Default: 3. - num_feat (int): Channel number of base intermediate features. Default: 64. - skip_connection (bool): Whether to use skip connections between U-Net. Default: True. - """ - - def __init__(self, num_in_ch, num_feat=64, skip_connection=True): - super(UNetDiscriminatorSN, self).__init__() - self.skip_connection = skip_connection - norm = spectral_norm - # the first convolution - self.conv0 = nn.Conv2d(num_in_ch, num_feat, kernel_size=3, stride=1, padding=1) - # downsample - self.conv1 = norm(nn.Conv2d(num_feat, num_feat * 2, 4, 2, 1, bias=False)) - self.conv2 = norm(nn.Conv2d(num_feat * 2, num_feat * 4, 4, 2, 1, bias=False)) - self.conv3 = norm(nn.Conv2d(num_feat * 4, num_feat * 8, 4, 2, 1, bias=False)) - # upsample - self.conv4 = norm(nn.Conv2d(num_feat * 8, num_feat * 4, 3, 1, 1, bias=False)) - self.conv5 = norm(nn.Conv2d(num_feat * 4, num_feat * 2, 3, 1, 1, bias=False)) - self.conv6 = norm(nn.Conv2d(num_feat * 2, num_feat, 3, 1, 1, bias=False)) - # extra convolutions - self.conv7 = norm(nn.Conv2d(num_feat, num_feat, 3, 1, 1, bias=False)) - self.conv8 = norm(nn.Conv2d(num_feat, num_feat, 3, 1, 1, bias=False)) - self.conv9 = nn.Conv2d(num_feat, 1, 3, 1, 1) - - def forward(self, x): - # downsample - x0 = F.leaky_relu(self.conv0(x), negative_slope=0.2, inplace=True) - x1 = F.leaky_relu(self.conv1(x0), negative_slope=0.2, inplace=True) - x2 = F.leaky_relu(self.conv2(x1), negative_slope=0.2, inplace=True) - x3 = F.leaky_relu(self.conv3(x2), negative_slope=0.2, inplace=True) - - # upsample - x3 = F.interpolate(x3, scale_factor=2, mode='bilinear', align_corners=False) - x4 = F.leaky_relu(self.conv4(x3), negative_slope=0.2, inplace=True) - - if self.skip_connection: - x4 = x4 + x2 - x4 = F.interpolate(x4, scale_factor=2, mode='bilinear', align_corners=False) - x5 = F.leaky_relu(self.conv5(x4), negative_slope=0.2, inplace=True) - - if self.skip_connection: - x5 = x5 + x1 - x5 = F.interpolate(x5, scale_factor=2, mode='bilinear', align_corners=False) - x6 = F.leaky_relu(self.conv6(x5), negative_slope=0.2, inplace=True) - - if self.skip_connection: - x6 = x6 + x0 - - # extra convolutions - out = F.leaky_relu(self.conv7(x6), negative_slope=0.2, inplace=True) - out = F.leaky_relu(self.conv8(out), negative_slope=0.2, inplace=True) - out = self.conv9(out) - - return out diff --git a/detection/thirdTool/realesrgan/archs/srvgg_arch.py b/detection/thirdTool/realesrgan/archs/srvgg_arch.py deleted file mode 100644 index 3946096..0000000 --- a/detection/thirdTool/realesrgan/archs/srvgg_arch.py +++ /dev/null @@ -1,69 +0,0 @@ -from basicsr.utils.registry import ARCH_REGISTRY -from torch import nn as nn -from torch.nn import functional as F - - -@ARCH_REGISTRY.register() -class SRVGGNetCompact(nn.Module): - """A compact VGG-style network structure for super-resolution. - - It is a compact network structure, which performs upsampling in the last layer and no convolution is - conducted on the HR feature space. - - Args: - num_in_ch (int): Channel number of inputs. Default: 3. - num_out_ch (int): Channel number of outputs. Default: 3. - num_feat (int): Channel number of intermediate features. Default: 64. - num_conv (int): Number of convolution layers in the body network. Default: 16. - upscale (int): Upsampling factor. Default: 4. - act_type (str): Activation type, options: 'relu', 'prelu', 'leakyrelu'. Default: prelu. - """ - - def __init__(self, num_in_ch=3, num_out_ch=3, num_feat=64, num_conv=16, upscale=4, act_type='prelu'): - super(SRVGGNetCompact, self).__init__() - self.num_in_ch = num_in_ch - self.num_out_ch = num_out_ch - self.num_feat = num_feat - self.num_conv = num_conv - self.upscale = upscale - self.act_type = act_type - - self.body = nn.ModuleList() - # the first conv - self.body.append(nn.Conv2d(num_in_ch, num_feat, 3, 1, 1)) - # the first activation - if act_type == 'relu': - activation = nn.ReLU(inplace=True) - elif act_type == 'prelu': - activation = nn.PReLU(num_parameters=num_feat) - elif act_type == 'leakyrelu': - activation = nn.LeakyReLU(negative_slope=0.1, inplace=True) - self.body.append(activation) - - # the body structure - for _ in range(num_conv): - self.body.append(nn.Conv2d(num_feat, num_feat, 3, 1, 1)) - # activation - if act_type == 'relu': - activation = nn.ReLU(inplace=True) - elif act_type == 'prelu': - activation = nn.PReLU(num_parameters=num_feat) - elif act_type == 'leakyrelu': - activation = nn.LeakyReLU(negative_slope=0.1, inplace=True) - self.body.append(activation) - - # the last conv - self.body.append(nn.Conv2d(num_feat, num_out_ch * upscale * upscale, 3, 1, 1)) - # upsample - self.upsampler = nn.PixelShuffle(upscale) - - def forward(self, x): - out = x - for i in range(0, len(self.body)): - out = self.body[i](out) - - out = self.upsampler(out) - # add the nearest upsampled image, so that the network learns the residual - base = F.interpolate(x, scale_factor=self.upscale, mode='nearest') - out += base - return out diff --git a/detection/thirdTool/realesrgan/data/__init__.py b/detection/thirdTool/realesrgan/data/__init__.py deleted file mode 100644 index 2004fa1..0000000 --- a/detection/thirdTool/realesrgan/data/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -import importlib -from basicsr.utils import scandir -from os import path as osp - -# automatically scan and import dataset modules for registry -# scan all the files that end with '_dataset.py' under the data folder -data_folder = osp.dirname(osp.abspath(__file__)) -dataset_filenames = [osp.splitext(osp.basename(v))[0] for v in scandir(data_folder) if v.endswith('_dataset.py')] -# import all the dataset modules -_dataset_modules = [importlib.import_module(f'thirdTool.realesrgan.data.{file_name}') for file_name in dataset_filenames] diff --git a/detection/thirdTool/realesrgan/data/realesrgan_dataset.py b/detection/thirdTool/realesrgan/data/realesrgan_dataset.py deleted file mode 100644 index 4cf2d9e..0000000 --- a/detection/thirdTool/realesrgan/data/realesrgan_dataset.py +++ /dev/null @@ -1,192 +0,0 @@ -import cv2 -import math -import numpy as np -import os -import os.path as osp -import random -import time -import torch -from basicsr.data.degradations import circular_lowpass_kernel, random_mixed_kernels -from basicsr.data.transforms import augment -from basicsr.utils import FileClient, get_root_logger, imfrombytes, img2tensor -from basicsr.utils.registry import DATASET_REGISTRY -from torch.utils import data as data - - -@DATASET_REGISTRY.register() -class RealESRGANDataset(data.Dataset): - """Dataset used for Real-ESRGAN model: - Real-ESRGAN: Training Real-World Blind Super-Resolution with Pure Synthetic Data. - - It loads gt (Ground-Truth) images, and augments them. - It also generates blur kernels and sinc kernels for generating low-quality images. - Note that the low-quality images are processed in tensors on GPUS for faster processing. - - Args: - opt (dict): Config for train datasets. It contains the following keys: - dataroot_gt (str): Data root path for gt. - meta_info (str): Path for meta information file. - io_backend (dict): IO backend type and other kwarg. - use_hflip (bool): Use horizontal flips. - use_rot (bool): Use rotation (use vertical flip and transposing h and w for implementation). - Please see more options in the codes. - """ - - def __init__(self, opt): - super(RealESRGANDataset, self).__init__() - self.opt = opt - self.file_client = None - self.io_backend_opt = opt['io_backend'] - self.gt_folder = opt['dataroot_gt'] - - # file client (lmdb io backend) - if self.io_backend_opt['type'] == 'lmdb': - self.io_backend_opt['db_paths'] = [self.gt_folder] - self.io_backend_opt['client_keys'] = ['gt'] - if not self.gt_folder.endswith('.lmdb'): - raise ValueError(f"'dataroot_gt' should end with '.lmdb', but received {self.gt_folder}") - with open(osp.join(self.gt_folder, 'meta_info.txt')) as fin: - self.paths = [line.split('.')[0] for line in fin] - else: - # disk backend with meta_info - # Each line in the meta_info describes the relative path to an image - with open(self.opt['meta_info']) as fin: - paths = [line.strip().split(' ')[0] for line in fin] - self.paths = [os.path.join(self.gt_folder, v) for v in paths] - - # blur settings for the first degradation - self.blur_kernel_size = opt['blur_kernel_size'] - self.kernel_list = opt['kernel_list'] - self.kernel_prob = opt['kernel_prob'] # a list for each kernel probability - self.blur_sigma = opt['blur_sigma'] - self.betag_range = opt['betag_range'] # betag used in generalized Gaussian blur kernels - self.betap_range = opt['betap_range'] # betap used in plateau blur kernels - self.sinc_prob = opt['sinc_prob'] # the probability for sinc filters - - # blur settings for the second degradation - self.blur_kernel_size2 = opt['blur_kernel_size2'] - self.kernel_list2 = opt['kernel_list2'] - self.kernel_prob2 = opt['kernel_prob2'] - self.blur_sigma2 = opt['blur_sigma2'] - self.betag_range2 = opt['betag_range2'] - self.betap_range2 = opt['betap_range2'] - self.sinc_prob2 = opt['sinc_prob2'] - - # a final sinc filter - self.final_sinc_prob = opt['final_sinc_prob'] - - self.kernel_range = [2 * v + 1 for v in range(3, 11)] # kernel size ranges from 7 to 21 - # TODO: kernel range is now hard-coded, should be in the configure file - self.pulse_tensor = torch.zeros(21, 21).float() # convolving with pulse tensor brings no blurry effect - self.pulse_tensor[10, 10] = 1 - - def __getitem__(self, index): - if self.file_client is None: - self.file_client = FileClient(self.io_backend_opt.pop('type'), **self.io_backend_opt) - - # -------------------------------- Load gt images -------------------------------- # - # Shape: (h, w, c); channel order: BGR; image range: [0, 1], float32. - gt_path = self.paths[index] - # avoid errors caused by high latency in reading files - retry = 3 - while retry > 0: - try: - img_bytes = self.file_client.get(gt_path, 'gt') - except (IOError, OSError) as e: - logger = get_root_logger() - logger.warn(f'File client error: {e}, remaining retry times: {retry - 1}') - # change another file to read - index = random.randint(0, self.__len__()) - gt_path = self.paths[index] - time.sleep(1) # sleep 1s for occasional server congestion - else: - break - finally: - retry -= 1 - img_gt = imfrombytes(img_bytes, float32=True) - - # -------------------- Do augmentation for training: flip, rotation -------------------- # - img_gt = augment(img_gt, self.opt['use_hflip'], self.opt['use_rot']) - - # crop or pad to 400 - # TODO: 400 is hard-coded. You may change it accordingly - h, w = img_gt.shape[0:2] - crop_pad_size = 400 - # pad - if h < crop_pad_size or w < crop_pad_size: - pad_h = max(0, crop_pad_size - h) - pad_w = max(0, crop_pad_size - w) - img_gt = cv2.copyMakeBorder(img_gt, 0, pad_h, 0, pad_w, cv2.BORDER_REFLECT_101) - # crop - if img_gt.shape[0] > crop_pad_size or img_gt.shape[1] > crop_pad_size: - h, w = img_gt.shape[0:2] - # randomly choose top and left coordinates - top = random.randint(0, h - crop_pad_size) - left = random.randint(0, w - crop_pad_size) - img_gt = img_gt[top:top + crop_pad_size, left:left + crop_pad_size, ...] - - # ------------------------ Generate kernels (used in the first degradation) ------------------------ # - kernel_size = random.choice(self.kernel_range) - if np.random.uniform() < self.opt['sinc_prob']: - # this sinc filter setting is for kernels ranging from [7, 21] - if kernel_size < 13: - omega_c = np.random.uniform(np.pi / 3, np.pi) - else: - omega_c = np.random.uniform(np.pi / 5, np.pi) - kernel = circular_lowpass_kernel(omega_c, kernel_size, pad_to=False) - else: - kernel = random_mixed_kernels( - self.kernel_list, - self.kernel_prob, - kernel_size, - self.blur_sigma, - self.blur_sigma, [-math.pi, math.pi], - self.betag_range, - self.betap_range, - noise_range=None) - # pad kernel - pad_size = (21 - kernel_size) // 2 - kernel = np.pad(kernel, ((pad_size, pad_size), (pad_size, pad_size))) - - # ------------------------ Generate kernels (used in the second degradation) ------------------------ # - kernel_size = random.choice(self.kernel_range) - if np.random.uniform() < self.opt['sinc_prob2']: - if kernel_size < 13: - omega_c = np.random.uniform(np.pi / 3, np.pi) - else: - omega_c = np.random.uniform(np.pi / 5, np.pi) - kernel2 = circular_lowpass_kernel(omega_c, kernel_size, pad_to=False) - else: - kernel2 = random_mixed_kernels( - self.kernel_list2, - self.kernel_prob2, - kernel_size, - self.blur_sigma2, - self.blur_sigma2, [-math.pi, math.pi], - self.betag_range2, - self.betap_range2, - noise_range=None) - - # pad kernel - pad_size = (21 - kernel_size) // 2 - kernel2 = np.pad(kernel2, ((pad_size, pad_size), (pad_size, pad_size))) - - # ------------------------------------- the final sinc kernel ------------------------------------- # - if np.random.uniform() < self.opt['final_sinc_prob']: - kernel_size = random.choice(self.kernel_range) - omega_c = np.random.uniform(np.pi / 3, np.pi) - sinc_kernel = circular_lowpass_kernel(omega_c, kernel_size, pad_to=21) - sinc_kernel = torch.FloatTensor(sinc_kernel) - else: - sinc_kernel = self.pulse_tensor - - # BGR to RGB, HWC to CHW, numpy to tensor - img_gt = img2tensor([img_gt], bgr2rgb=True, float32=True)[0] - kernel = torch.FloatTensor(kernel) - kernel2 = torch.FloatTensor(kernel2) - - return_d = {'gt': img_gt, 'kernel1': kernel, 'kernel2': kernel2, 'sinc_kernel': sinc_kernel, 'gt_path': gt_path} - return return_d - - def __len__(self): - return len(self.paths) diff --git a/detection/thirdTool/realesrgan/data/realesrgan_paired_dataset.py b/detection/thirdTool/realesrgan/data/realesrgan_paired_dataset.py deleted file mode 100644 index 386c8d7..0000000 --- a/detection/thirdTool/realesrgan/data/realesrgan_paired_dataset.py +++ /dev/null @@ -1,108 +0,0 @@ -import os -from basicsr.data.data_util import paired_paths_from_folder, paired_paths_from_lmdb -from basicsr.data.transforms import augment, paired_random_crop -from basicsr.utils import FileClient, imfrombytes, img2tensor -from basicsr.utils.registry import DATASET_REGISTRY -from torch.utils import data as data -from torchvision.transforms.functional import normalize - - -@DATASET_REGISTRY.register() -class RealESRGANPairedDataset(data.Dataset): - """Paired image dataset for image restoration. - - Read LQ (Low Quality, e.g. LR (Low Resolution), blurry, noisy, etc) and GT image pairs. - - There are three modes: - 1. 'lmdb': Use lmdb files. - If opt['io_backend'] == lmdb. - 2. 'meta_info': Use meta information file to generate paths. - If opt['io_backend'] != lmdb and opt['meta_info'] is not None. - 3. 'folder': Scan folders to generate paths. - The rest. - - Args: - opt (dict): Config for train datasets. It contains the following keys: - dataroot_gt (str): Data root path for gt. - dataroot_lq (str): Data root path for lq. - meta_info (str): Path for meta information file. - io_backend (dict): IO backend type and other kwarg. - filename_tmpl (str): Template for each filename. Note that the template excludes the file extension. - Default: '{}'. - gt_size (int): Cropped patched size for gt patches. - use_hflip (bool): Use horizontal flips. - use_rot (bool): Use rotation (use vertical flip and transposing h - and w for implementation). - - scale (bool): Scale, which will be added automatically. - phase (str): 'train' or 'val'. - """ - - def __init__(self, opt): - super(RealESRGANPairedDataset, self).__init__() - self.opt = opt - self.file_client = None - self.io_backend_opt = opt['io_backend'] - # mean and std for normalizing the input images - self.mean = opt['mean'] if 'mean' in opt else None - self.std = opt['std'] if 'std' in opt else None - - self.gt_folder, self.lq_folder = opt['dataroot_gt'], opt['dataroot_lq'] - self.filename_tmpl = opt['filename_tmpl'] if 'filename_tmpl' in opt else '{}' - - # file client (lmdb io backend) - if self.io_backend_opt['type'] == 'lmdb': - self.io_backend_opt['db_paths'] = [self.lq_folder, self.gt_folder] - self.io_backend_opt['client_keys'] = ['lq', 'gt'] - self.paths = paired_paths_from_lmdb([self.lq_folder, self.gt_folder], ['lq', 'gt']) - elif 'meta_info' in self.opt and self.opt['meta_info'] is not None: - # disk backend with meta_info - # Each line in the meta_info describes the relative path to an image - with open(self.opt['meta_info']) as fin: - paths = [line.strip() for line in fin] - self.paths = [] - for path in paths: - gt_path, lq_path = path.split(', ') - gt_path = os.path.join(self.gt_folder, gt_path) - lq_path = os.path.join(self.lq_folder, lq_path) - self.paths.append(dict([('gt_path', gt_path), ('lq_path', lq_path)])) - else: - # disk backend - # it will scan the whole folder to get meta info - # it will be time-consuming for folders with too many files. It is recommended using an extra meta txt file - self.paths = paired_paths_from_folder([self.lq_folder, self.gt_folder], ['lq', 'gt'], self.filename_tmpl) - - def __getitem__(self, index): - if self.file_client is None: - self.file_client = FileClient(self.io_backend_opt.pop('type'), **self.io_backend_opt) - - scale = self.opt['scale'] - - # Load gt and lq images. Dimension order: HWC; channel order: BGR; - # image range: [0, 1], float32. - gt_path = self.paths[index]['gt_path'] - img_bytes = self.file_client.get(gt_path, 'gt') - img_gt = imfrombytes(img_bytes, float32=True) - lq_path = self.paths[index]['lq_path'] - img_bytes = self.file_client.get(lq_path, 'lq') - img_lq = imfrombytes(img_bytes, float32=True) - - # augmentation for training - if self.opt['phase'] == 'train': - gt_size = self.opt['gt_size'] - # random crop - img_gt, img_lq = paired_random_crop(img_gt, img_lq, gt_size, scale, gt_path) - # flip, rotation - img_gt, img_lq = augment([img_gt, img_lq], self.opt['use_hflip'], self.opt['use_rot']) - - # BGR to RGB, HWC to CHW, numpy to tensor - img_gt, img_lq = img2tensor([img_gt, img_lq], bgr2rgb=True, float32=True) - # normalize - if self.mean is not None or self.std is not None: - normalize(img_lq, self.mean, self.std, inplace=True) - normalize(img_gt, self.mean, self.std, inplace=True) - - return {'lq': img_lq, 'gt': img_gt, 'lq_path': lq_path, 'gt_path': gt_path} - - def __len__(self): - return len(self.paths) diff --git a/detection/thirdTool/realesrgan/models/__init__.py b/detection/thirdTool/realesrgan/models/__init__.py deleted file mode 100644 index bc378be..0000000 --- a/detection/thirdTool/realesrgan/models/__init__.py +++ /dev/null @@ -1,10 +0,0 @@ -import importlib -from basicsr.utils import scandir -from os import path as osp - -# automatically scan and import model modules for registry -# scan all the files that end with '_model.py' under the model folder -model_folder = osp.dirname(osp.abspath(__file__)) -model_filenames = [osp.splitext(osp.basename(v))[0] for v in scandir(model_folder) if v.endswith('_model.py')] -# import all the model modules -_model_modules = [importlib.import_module(f'thirdTool.realesrgan.models.{file_name}') for file_name in model_filenames] diff --git a/detection/thirdTool/realesrgan/models/realesrgan_model.py b/detection/thirdTool/realesrgan/models/realesrgan_model.py deleted file mode 100644 index c298a09..0000000 --- a/detection/thirdTool/realesrgan/models/realesrgan_model.py +++ /dev/null @@ -1,258 +0,0 @@ -import numpy as np -import random -import torch -from basicsr.data.degradations import random_add_gaussian_noise_pt, random_add_poisson_noise_pt -from basicsr.data.transforms import paired_random_crop -from basicsr.models.srgan_model import SRGANModel -from basicsr.utils import DiffJPEG, USMSharp -from basicsr.utils.img_process_util import filter2D -from basicsr.utils.registry import MODEL_REGISTRY -from collections import OrderedDict -from torch.nn import functional as F - - -@MODEL_REGISTRY.register() -class RealESRGANModel(SRGANModel): - """RealESRGAN Model for Real-ESRGAN: Training Real-World Blind Super-Resolution with Pure Synthetic Data. - - It mainly performs: - 1. randomly synthesize LQ images in GPU tensors - 2. optimize the networks with GAN training. - """ - - def __init__(self, opt): - super(RealESRGANModel, self).__init__(opt) - self.jpeger = DiffJPEG(differentiable=False).cuda() # simulate JPEG compression artifacts - self.usm_sharpener = USMSharp().cuda() # do usm sharpening - self.queue_size = opt.get('queue_size', 180) - - @torch.no_grad() - def _dequeue_and_enqueue(self): - """It is the training pair pool for increasing the diversity in a batch. - - Batch processing limits the diversity of synthetic degradations in a batch. For example, samples in a - batch could not have different resize scaling factors. Therefore, we employ this training pair pool - to increase the degradation diversity in a batch. - """ - # initialize - b, c, h, w = self.lq.size() - if not hasattr(self, 'queue_lr'): - assert self.queue_size % b == 0, f'queue size {self.queue_size} should be divisible by batch size {b}' - self.queue_lr = torch.zeros(self.queue_size, c, h, w).cuda() - _, c, h, w = self.gt.size() - self.queue_gt = torch.zeros(self.queue_size, c, h, w).cuda() - self.queue_ptr = 0 - if self.queue_ptr == self.queue_size: # the pool is full - # do dequeue and enqueue - # shuffle - idx = torch.randperm(self.queue_size) - self.queue_lr = self.queue_lr[idx] - self.queue_gt = self.queue_gt[idx] - # get first b samples - lq_dequeue = self.queue_lr[0:b, :, :, :].clone() - gt_dequeue = self.queue_gt[0:b, :, :, :].clone() - # update the queue - self.queue_lr[0:b, :, :, :] = self.lq.clone() - self.queue_gt[0:b, :, :, :] = self.gt.clone() - - self.lq = lq_dequeue - self.gt = gt_dequeue - else: - # only do enqueue - self.queue_lr[self.queue_ptr:self.queue_ptr + b, :, :, :] = self.lq.clone() - self.queue_gt[self.queue_ptr:self.queue_ptr + b, :, :, :] = self.gt.clone() - self.queue_ptr = self.queue_ptr + b - - @torch.no_grad() - def feed_data(self, data): - """Accept data from dataloader, and then add two-order degradations to obtain LQ images. - """ - if self.is_train and self.opt.get('high_order_degradation', True): - # training data synthesis - self.gt = data['gt'].to(self.device) - self.gt_usm = self.usm_sharpener(self.gt) - - self.kernel1 = data['kernel1'].to(self.device) - self.kernel2 = data['kernel2'].to(self.device) - self.sinc_kernel = data['sinc_kernel'].to(self.device) - - ori_h, ori_w = self.gt.size()[2:4] - - # ----------------------- The first degradation process ----------------------- # - # blur - out = filter2D(self.gt_usm, self.kernel1) - # random resize - updown_type = random.choices(['up', 'down', 'keep'], self.opt['resize_prob'])[0] - if updown_type == 'up': - scale = np.random.uniform(1, self.opt['resize_range'][1]) - elif updown_type == 'down': - scale = np.random.uniform(self.opt['resize_range'][0], 1) - else: - scale = 1 - mode = random.choice(['area', 'bilinear', 'bicubic']) - out = F.interpolate(out, scale_factor=scale, mode=mode) - # add noise - gray_noise_prob = self.opt['gray_noise_prob'] - if np.random.uniform() < self.opt['gaussian_noise_prob']: - out = random_add_gaussian_noise_pt( - out, sigma_range=self.opt['noise_range'], clip=True, rounds=False, gray_prob=gray_noise_prob) - else: - out = random_add_poisson_noise_pt( - out, - scale_range=self.opt['poisson_scale_range'], - gray_prob=gray_noise_prob, - clip=True, - rounds=False) - # JPEG compression - jpeg_p = out.new_zeros(out.size(0)).uniform_(*self.opt['jpeg_range']) - out = torch.clamp(out, 0, 1) # clamp to [0, 1], otherwise JPEGer will result in unpleasant artifacts - out = self.jpeger(out, quality=jpeg_p) - - # ----------------------- The second degradation process ----------------------- # - # blur - if np.random.uniform() < self.opt['second_blur_prob']: - out = filter2D(out, self.kernel2) - # random resize - updown_type = random.choices(['up', 'down', 'keep'], self.opt['resize_prob2'])[0] - if updown_type == 'up': - scale = np.random.uniform(1, self.opt['resize_range2'][1]) - elif updown_type == 'down': - scale = np.random.uniform(self.opt['resize_range2'][0], 1) - else: - scale = 1 - mode = random.choice(['area', 'bilinear', 'bicubic']) - out = F.interpolate( - out, size=(int(ori_h / self.opt['scale'] * scale), int(ori_w / self.opt['scale'] * scale)), mode=mode) - # add noise - gray_noise_prob = self.opt['gray_noise_prob2'] - if np.random.uniform() < self.opt['gaussian_noise_prob2']: - out = random_add_gaussian_noise_pt( - out, sigma_range=self.opt['noise_range2'], clip=True, rounds=False, gray_prob=gray_noise_prob) - else: - out = random_add_poisson_noise_pt( - out, - scale_range=self.opt['poisson_scale_range2'], - gray_prob=gray_noise_prob, - clip=True, - rounds=False) - - # JPEG compression + the final sinc filter - # We also need to resize images to desired sizes. We group [resize back + sinc filter] together - # as one operation. - # We consider two orders: - # 1. [resize back + sinc filter] + JPEG compression - # 2. JPEG compression + [resize back + sinc filter] - # Empirically, we find other combinations (sinc + JPEG + Resize) will introduce twisted lines. - if np.random.uniform() < 0.5: - # resize back + the final sinc filter - mode = random.choice(['area', 'bilinear', 'bicubic']) - out = F.interpolate(out, size=(ori_h // self.opt['scale'], ori_w // self.opt['scale']), mode=mode) - out = filter2D(out, self.sinc_kernel) - # JPEG compression - jpeg_p = out.new_zeros(out.size(0)).uniform_(*self.opt['jpeg_range2']) - out = torch.clamp(out, 0, 1) - out = self.jpeger(out, quality=jpeg_p) - else: - # JPEG compression - jpeg_p = out.new_zeros(out.size(0)).uniform_(*self.opt['jpeg_range2']) - out = torch.clamp(out, 0, 1) - out = self.jpeger(out, quality=jpeg_p) - # resize back + the final sinc filter - mode = random.choice(['area', 'bilinear', 'bicubic']) - out = F.interpolate(out, size=(ori_h // self.opt['scale'], ori_w // self.opt['scale']), mode=mode) - out = filter2D(out, self.sinc_kernel) - - # clamp and round - self.lq = torch.clamp((out * 255.0).round(), 0, 255) / 255. - - # random crop - gt_size = self.opt['gt_size'] - (self.gt, self.gt_usm), self.lq = paired_random_crop([self.gt, self.gt_usm], self.lq, gt_size, - self.opt['scale']) - - # training pair pool - self._dequeue_and_enqueue() - # sharpen self.gt again, as we have changed the self.gt with self._dequeue_and_enqueue - self.gt_usm = self.usm_sharpener(self.gt) - self.lq = self.lq.contiguous() # for the warning: grad and param do not obey the gradient layout contract - else: - # for paired training or validation - self.lq = data['lq'].to(self.device) - if 'gt' in data: - self.gt = data['gt'].to(self.device) - self.gt_usm = self.usm_sharpener(self.gt) - - def nondist_validation(self, dataloader, current_iter, tb_logger, save_img): - # do not use the synthetic process during validation - self.is_train = False - super(RealESRGANModel, self).nondist_validation(dataloader, current_iter, tb_logger, save_img) - self.is_train = True - - def optimize_parameters(self, current_iter): - # usm sharpening - l1_gt = self.gt_usm - percep_gt = self.gt_usm - gan_gt = self.gt_usm - if self.opt['l1_gt_usm'] is False: - l1_gt = self.gt - if self.opt['percep_gt_usm'] is False: - percep_gt = self.gt - if self.opt['gan_gt_usm'] is False: - gan_gt = self.gt - - # optimize net_g - for p in self.net_d.parameters(): - p.requires_grad = False - - self.optimizer_g.zero_grad() - self.output = self.net_g(self.lq) - - l_g_total = 0 - loss_dict = OrderedDict() - if (current_iter % self.net_d_iters == 0 and current_iter > self.net_d_init_iters): - # pixel loss - if self.cri_pix: - l_g_pix = self.cri_pix(self.output, l1_gt) - l_g_total += l_g_pix - loss_dict['l_g_pix'] = l_g_pix - # perceptual loss - if self.cri_perceptual: - l_g_percep, l_g_style = self.cri_perceptual(self.output, percep_gt) - if l_g_percep is not None: - l_g_total += l_g_percep - loss_dict['l_g_percep'] = l_g_percep - if l_g_style is not None: - l_g_total += l_g_style - loss_dict['l_g_style'] = l_g_style - # gan loss - fake_g_pred = self.net_d(self.output) - l_g_gan = self.cri_gan(fake_g_pred, True, is_disc=False) - l_g_total += l_g_gan - loss_dict['l_g_gan'] = l_g_gan - - l_g_total.backward() - self.optimizer_g.step() - - # optimize net_d - for p in self.net_d.parameters(): - p.requires_grad = True - - self.optimizer_d.zero_grad() - # real - real_d_pred = self.net_d(gan_gt) - l_d_real = self.cri_gan(real_d_pred, True, is_disc=True) - loss_dict['l_d_real'] = l_d_real - loss_dict['out_d_real'] = torch.mean(real_d_pred.detach()) - l_d_real.backward() - # fake - fake_d_pred = self.net_d(self.output.detach().clone()) # clone for pt1.9 - l_d_fake = self.cri_gan(fake_d_pred, False, is_disc=True) - loss_dict['l_d_fake'] = l_d_fake - loss_dict['out_d_fake'] = torch.mean(fake_d_pred.detach()) - l_d_fake.backward() - self.optimizer_d.step() - - if self.ema_decay > 0: - self.model_ema(decay=self.ema_decay) - - self.log_dict = self.reduce_loss_dict(loss_dict) diff --git a/detection/thirdTool/realesrgan/models/realesrnet_model.py b/detection/thirdTool/realesrgan/models/realesrnet_model.py deleted file mode 100644 index d11668f..0000000 --- a/detection/thirdTool/realesrgan/models/realesrnet_model.py +++ /dev/null @@ -1,188 +0,0 @@ -import numpy as np -import random -import torch -from basicsr.data.degradations import random_add_gaussian_noise_pt, random_add_poisson_noise_pt -from basicsr.data.transforms import paired_random_crop -from basicsr.models.sr_model import SRModel -from basicsr.utils import DiffJPEG, USMSharp -from basicsr.utils.img_process_util import filter2D -from basicsr.utils.registry import MODEL_REGISTRY -from torch.nn import functional as F - - -@MODEL_REGISTRY.register() -class RealESRNetModel(SRModel): - """RealESRNet Model for Real-ESRGAN: Training Real-World Blind Super-Resolution with Pure Synthetic Data. - - It is trained without GAN losses. - It mainly performs: - 1. randomly synthesize LQ images in GPU tensors - 2. optimize the networks with GAN training. - """ - - def __init__(self, opt): - super(RealESRNetModel, self).__init__(opt) - self.jpeger = DiffJPEG(differentiable=False).cuda() # simulate JPEG compression artifacts - self.usm_sharpener = USMSharp().cuda() # do usm sharpening - self.queue_size = opt.get('queue_size', 180) - - @torch.no_grad() - def _dequeue_and_enqueue(self): - """It is the training pair pool for increasing the diversity in a batch. - - Batch processing limits the diversity of synthetic degradations in a batch. For example, samples in a - batch could not have different resize scaling factors. Therefore, we employ this training pair pool - to increase the degradation diversity in a batch. - """ - # initialize - b, c, h, w = self.lq.size() - if not hasattr(self, 'queue_lr'): - assert self.queue_size % b == 0, f'queue size {self.queue_size} should be divisible by batch size {b}' - self.queue_lr = torch.zeros(self.queue_size, c, h, w).cuda() - _, c, h, w = self.gt.size() - self.queue_gt = torch.zeros(self.queue_size, c, h, w).cuda() - self.queue_ptr = 0 - if self.queue_ptr == self.queue_size: # the pool is full - # do dequeue and enqueue - # shuffle - idx = torch.randperm(self.queue_size) - self.queue_lr = self.queue_lr[idx] - self.queue_gt = self.queue_gt[idx] - # get first b samples - lq_dequeue = self.queue_lr[0:b, :, :, :].clone() - gt_dequeue = self.queue_gt[0:b, :, :, :].clone() - # update the queue - self.queue_lr[0:b, :, :, :] = self.lq.clone() - self.queue_gt[0:b, :, :, :] = self.gt.clone() - - self.lq = lq_dequeue - self.gt = gt_dequeue - else: - # only do enqueue - self.queue_lr[self.queue_ptr:self.queue_ptr + b, :, :, :] = self.lq.clone() - self.queue_gt[self.queue_ptr:self.queue_ptr + b, :, :, :] = self.gt.clone() - self.queue_ptr = self.queue_ptr + b - - @torch.no_grad() - def feed_data(self, data): - """Accept data from dataloader, and then add two-order degradations to obtain LQ images. - """ - if self.is_train and self.opt.get('high_order_degradation', True): - # training data synthesis - self.gt = data['gt'].to(self.device) - # USM sharpen the GT images - if self.opt['gt_usm'] is True: - self.gt = self.usm_sharpener(self.gt) - - self.kernel1 = data['kernel1'].to(self.device) - self.kernel2 = data['kernel2'].to(self.device) - self.sinc_kernel = data['sinc_kernel'].to(self.device) - - ori_h, ori_w = self.gt.size()[2:4] - - # ----------------------- The first degradation process ----------------------- # - # blur - out = filter2D(self.gt, self.kernel1) - # random resize - updown_type = random.choices(['up', 'down', 'keep'], self.opt['resize_prob'])[0] - if updown_type == 'up': - scale = np.random.uniform(1, self.opt['resize_range'][1]) - elif updown_type == 'down': - scale = np.random.uniform(self.opt['resize_range'][0], 1) - else: - scale = 1 - mode = random.choice(['area', 'bilinear', 'bicubic']) - out = F.interpolate(out, scale_factor=scale, mode=mode) - # add noise - gray_noise_prob = self.opt['gray_noise_prob'] - if np.random.uniform() < self.opt['gaussian_noise_prob']: - out = random_add_gaussian_noise_pt( - out, sigma_range=self.opt['noise_range'], clip=True, rounds=False, gray_prob=gray_noise_prob) - else: - out = random_add_poisson_noise_pt( - out, - scale_range=self.opt['poisson_scale_range'], - gray_prob=gray_noise_prob, - clip=True, - rounds=False) - # JPEG compression - jpeg_p = out.new_zeros(out.size(0)).uniform_(*self.opt['jpeg_range']) - out = torch.clamp(out, 0, 1) # clamp to [0, 1], otherwise JPEGer will result in unpleasant artifacts - out = self.jpeger(out, quality=jpeg_p) - - # ----------------------- The second degradation process ----------------------- # - # blur - if np.random.uniform() < self.opt['second_blur_prob']: - out = filter2D(out, self.kernel2) - # random resize - updown_type = random.choices(['up', 'down', 'keep'], self.opt['resize_prob2'])[0] - if updown_type == 'up': - scale = np.random.uniform(1, self.opt['resize_range2'][1]) - elif updown_type == 'down': - scale = np.random.uniform(self.opt['resize_range2'][0], 1) - else: - scale = 1 - mode = random.choice(['area', 'bilinear', 'bicubic']) - out = F.interpolate( - out, size=(int(ori_h / self.opt['scale'] * scale), int(ori_w / self.opt['scale'] * scale)), mode=mode) - # add noise - gray_noise_prob = self.opt['gray_noise_prob2'] - if np.random.uniform() < self.opt['gaussian_noise_prob2']: - out = random_add_gaussian_noise_pt( - out, sigma_range=self.opt['noise_range2'], clip=True, rounds=False, gray_prob=gray_noise_prob) - else: - out = random_add_poisson_noise_pt( - out, - scale_range=self.opt['poisson_scale_range2'], - gray_prob=gray_noise_prob, - clip=True, - rounds=False) - - # JPEG compression + the final sinc filter - # We also need to resize images to desired sizes. We group [resize back + sinc filter] together - # as one operation. - # We consider two orders: - # 1. [resize back + sinc filter] + JPEG compression - # 2. JPEG compression + [resize back + sinc filter] - # Empirically, we find other combinations (sinc + JPEG + Resize) will introduce twisted lines. - if np.random.uniform() < 0.5: - # resize back + the final sinc filter - mode = random.choice(['area', 'bilinear', 'bicubic']) - out = F.interpolate(out, size=(ori_h // self.opt['scale'], ori_w // self.opt['scale']), mode=mode) - out = filter2D(out, self.sinc_kernel) - # JPEG compression - jpeg_p = out.new_zeros(out.size(0)).uniform_(*self.opt['jpeg_range2']) - out = torch.clamp(out, 0, 1) - out = self.jpeger(out, quality=jpeg_p) - else: - # JPEG compression - jpeg_p = out.new_zeros(out.size(0)).uniform_(*self.opt['jpeg_range2']) - out = torch.clamp(out, 0, 1) - out = self.jpeger(out, quality=jpeg_p) - # resize back + the final sinc filter - mode = random.choice(['area', 'bilinear', 'bicubic']) - out = F.interpolate(out, size=(ori_h // self.opt['scale'], ori_w // self.opt['scale']), mode=mode) - out = filter2D(out, self.sinc_kernel) - - # clamp and round - self.lq = torch.clamp((out * 255.0).round(), 0, 255) / 255. - - # random crop - gt_size = self.opt['gt_size'] - self.gt, self.lq = paired_random_crop(self.gt, self.lq, gt_size, self.opt['scale']) - - # training pair pool - self._dequeue_and_enqueue() - self.lq = self.lq.contiguous() # for the warning: grad and param do not obey the gradient layout contract - else: - # for paired training or validation - self.lq = data['lq'].to(self.device) - if 'gt' in data: - self.gt = data['gt'].to(self.device) - self.gt_usm = self.usm_sharpener(self.gt) - - def nondist_validation(self, dataloader, current_iter, tb_logger, save_img): - # do not use the synthetic process during validation - self.is_train = False - super(RealESRNetModel, self).nondist_validation(dataloader, current_iter, tb_logger, save_img) - self.is_train = True diff --git a/detection/thirdTool/realesrgan/train.py b/detection/thirdTool/realesrgan/train.py deleted file mode 100644 index 8a9cec9..0000000 --- a/detection/thirdTool/realesrgan/train.py +++ /dev/null @@ -1,11 +0,0 @@ -# flake8: noqa -import os.path as osp -from basicsr.train import train_pipeline - -import realesrgan.archs -import realesrgan.data -import realesrgan.models - -if __name__ == '__main__': - root_path = osp.abspath(osp.join(__file__, osp.pardir, osp.pardir)) - train_pipeline(root_path) diff --git a/detection/thirdTool/realesrgan/utils.py b/detection/thirdTool/realesrgan/utils.py deleted file mode 100644 index ce60759..0000000 --- a/detection/thirdTool/realesrgan/utils.py +++ /dev/null @@ -1,287 +0,0 @@ -import cv2 -import math -import numpy as np -import os -import queue -import threading -import torch -from basicsr.utils.download_util import load_file_from_url -from torch.nn import functional as F -from thirdTool.yolo5x_qr.utils.torch_utils import select_device - -ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - - -class Real_ESRGANer(): - """A helper class for upsampling images with RealESRGAN. - - Args: - scale (int): Upsampling scale factor used in the networks. It is usually 2 or 4. - model_path (str): The path to the pretrained model. It can be urls (will first download it automatically). - model (nn.Module): The defined network. Default: None. - tile (int): As too large images result in the out of GPU memory issue, so this tile option will first crop - input images into tiles, and then process each of them. Finally, they will be merged into one image. - 0 denotes for do not use tile. Default: 0. - tile_pad (int): The pad size for each tile, to remove border artifacts. Default: 10. - pre_pad (int): Pad the input images to avoid border artifacts. Default: 10. - half (float): Whether to use half precision during inference. Default: False. - """ - - def __init__(self, scale, device, model_path, model=None, tile=0, tile_pad=10, pre_pad=10, half=False): - self.scale = scale - self.tile_size = tile - self.tile_pad = tile_pad - self.pre_pad = pre_pad - self.mod_scale = None - self.half = half - - # initialize model - # self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') - self.device = select_device(device, model_path=model_path) - - # self.device = torch.device('cpu') - # if the model_path starts with https, it will first download models to the folder: realesrgan/weights - if model_path.startswith('https://'): - model_path = load_file_from_url( - url=model_path, model_dir=os.path.join(ROOT_DIR, 'realesrgan/weights'), progress=True, file_name=None) - - # loadnet = torch.load(model_path,map_location=torch.device('cpu')) - loadnet = torch.load(model_path, map_location=self.device) - - # prefer to use params_ema - if 'params_ema' in loadnet: - keyname = 'params_ema' - else: - keyname = 'params' - model.load_state_dict(loadnet[keyname], strict=True) - model.eval() - self.model = model.to(self.device) - if self.half: - self.model = self.model.half() - - def pre_process(self, img): - """Pre-process, such as pre-pad and mod pad, so that the images can be divisible - """ - img = torch.from_numpy(np.transpose(img, (2, 0, 1))).float() - self.img = img.unsqueeze(0).to(self.device) - if self.half: - self.img = self.img.half() - - # pre_pad - if self.pre_pad != 0: - self.img = F.pad(self.img, (0, self.pre_pad, 0, self.pre_pad), 'reflect') - # mod pad for divisible borders - if self.scale == 2: - self.mod_scale = 2 - elif self.scale == 1: - self.mod_scale = 4 - if self.mod_scale is not None: - self.mod_pad_h, self.mod_pad_w = 0, 0 - _, _, h, w = self.img.size() - if (h % self.mod_scale != 0): - self.mod_pad_h = (self.mod_scale - h % self.mod_scale) - if (w % self.mod_scale != 0): - self.mod_pad_w = (self.mod_scale - w % self.mod_scale) - self.img = F.pad(self.img, (0, self.mod_pad_w, 0, self.mod_pad_h), 'reflect') - - def process(self): - # model inference - self.output = self.model(self.img) - - def tile_process(self): - """It will first crop input images to tiles, and then process each tile. - Finally, all the processed tiles are merged into one images. - - Modified from: https://github.com/ata4/esrgan-launcher - """ - batch, channel, height, width = self.img.shape - output_height = height * self.scale - output_width = width * self.scale - output_shape = (batch, channel, output_height, output_width) - - # start with black image - self.output = self.img.new_zeros(output_shape) - tiles_x = math.ceil(width / self.tile_size) - tiles_y = math.ceil(height / self.tile_size) - - # loop over all tiles - for y in range(tiles_y): - for x in range(tiles_x): - # extract tile from input image - ofs_x = x * self.tile_size - ofs_y = y * self.tile_size - # input tile area on total image - input_start_x = ofs_x - input_end_x = min(ofs_x + self.tile_size, width) - input_start_y = ofs_y - input_end_y = min(ofs_y + self.tile_size, height) - - # input tile area on total image with padding - input_start_x_pad = max(input_start_x - self.tile_pad, 0) - input_end_x_pad = min(input_end_x + self.tile_pad, width) - input_start_y_pad = max(input_start_y - self.tile_pad, 0) - input_end_y_pad = min(input_end_y + self.tile_pad, height) - - # input tile dimensions - input_tile_width = input_end_x - input_start_x - input_tile_height = input_end_y - input_start_y - tile_idx = y * tiles_x + x + 1 - input_tile = self.img[:, :, input_start_y_pad:input_end_y_pad, input_start_x_pad:input_end_x_pad] - - # upscale tile - try: - with torch.no_grad(): - output_tile = self.model(input_tile) - except RuntimeError as error: - print('Error', error) - print(f'\tTile {tile_idx}/{tiles_x * tiles_y}') - - # output tile area on total image - output_start_x = input_start_x * self.scale - output_end_x = input_end_x * self.scale - output_start_y = input_start_y * self.scale - output_end_y = input_end_y * self.scale - - # output tile area without padding - output_start_x_tile = (input_start_x - input_start_x_pad) * self.scale - output_end_x_tile = output_start_x_tile + input_tile_width * self.scale - output_start_y_tile = (input_start_y - input_start_y_pad) * self.scale - output_end_y_tile = output_start_y_tile + input_tile_height * self.scale - - # put tile into output image - self.output[:, :, output_start_y:output_end_y, - output_start_x:output_end_x] = output_tile[:, :, output_start_y_tile:output_end_y_tile, - output_start_x_tile:output_end_x_tile] - - def post_process(self): - # remove extra pad - if self.mod_scale is not None: - _, _, h, w = self.output.size() - self.output = self.output[:, :, 0:h - self.mod_pad_h * self.scale, 0:w - self.mod_pad_w * self.scale] - # remove prepad - if self.pre_pad != 0: - _, _, h, w = self.output.size() - self.output = self.output[:, :, 0:h - self.pre_pad * self.scale, 0:w - self.pre_pad * self.scale] - return self.output - - @torch.no_grad() - def enhance(self, img, outscale=None, alpha_upsampler='realesrgan'): - h_input, w_input = img.shape[0:2] - # img: numpy - img = img.astype(np.float32) - if np.max(img) > 256: # 16-bit image - max_range = 65535 - print('\tInput is a 16-bit image') - else: - max_range = 255 - img = img / max_range - if len(img.shape) == 2: # gray image - img_mode = 'L' - img = cv2.cvtColor(img, cv2.COLOR_GRAY2RGB) - elif img.shape[2] == 4: # RGBA image with alpha channel - img_mode = 'RGBA' - alpha = img[:, :, 3] - img = img[:, :, 0:3] - img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) - if alpha_upsampler == 'realesrgan': - alpha = cv2.cvtColor(alpha, cv2.COLOR_GRAY2RGB) - else: - img_mode = 'RGB' - img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) - - # ------------------- process image (without the alpha channel) ------------------- # - self.pre_process(img) - if self.tile_size > 0: - self.tile_process() - else: - self.process() - output_img = self.post_process() - output_img = output_img.data.squeeze().float().cpu().clamp_(0, 1).numpy() - output_img = np.transpose(output_img[[2, 1, 0], :, :], (1, 2, 0)) - if img_mode == 'L': - output_img = cv2.cvtColor(output_img, cv2.COLOR_BGR2GRAY) - - # ------------------- process the alpha channel if necessary ------------------- # - if img_mode == 'RGBA': - if alpha_upsampler == 'realesrgan': - self.pre_process(alpha) - if self.tile_size > 0: - self.tile_process() - else: - self.process() - output_alpha = self.post_process() - output_alpha = output_alpha.data.squeeze().float().cpu().clamp_(0, 1).numpy() - output_alpha = np.transpose(output_alpha[[2, 1, 0], :, :], (1, 2, 0)) - output_alpha = cv2.cvtColor(output_alpha, cv2.COLOR_BGR2GRAY) - else: # use the cv2 resize for alpha channel - h, w = alpha.shape[0:2] - output_alpha = cv2.resize(alpha, (w * self.scale, h * self.scale), interpolation=cv2.INTER_LINEAR) - - # merge the alpha channel - output_img = cv2.cvtColor(output_img, cv2.COLOR_BGR2BGRA) - output_img[:, :, 3] = output_alpha - - # ------------------------------ return ------------------------------ # - if max_range == 65535: # 16-bit image - output = (output_img * 65535.0).round().astype(np.uint16) - else: - output = (output_img * 255.0).round().astype(np.uint8) - - if outscale is not None and outscale != float(self.scale): - output = cv2.resize( - output, ( - int(w_input * outscale), - int(h_input * outscale), - ), interpolation=cv2.INTER_LANCZOS4) - - return output, img_mode - - -class PrefetchReader(threading.Thread): - """Prefetch images. - - Args: - img_list (list[str]): A image list of image paths to be read. - num_prefetch_queue (int): Number of prefetch queue. - """ - - def __init__(self, img_list, num_prefetch_queue): - super().__init__() - self.que = queue.Queue(num_prefetch_queue) - self.img_list = img_list - - def run(self): - for img_path in self.img_list: - img = cv2.imread(img_path, cv2.IMREAD_UNCHANGED) - self.que.put(img) - - self.que.put(None) - - def __next__(self): - next_item = self.que.get() - if next_item is None: - raise StopIteration - return next_item - - def __iter__(self): - return self - - -class IOConsumer(threading.Thread): - - def __init__(self, opt, que, qid): - super().__init__() - self._queue = que - self.qid = qid - self.opt = opt - - def run(self): - while True: - msg = self._queue.get() - if isinstance(msg, str) and msg == 'quit': - break - - output = msg['output'] - save_path = msg['save_path'] - cv2.imwrite(save_path, output) - print(f'IO worker {self.qid} is done.') diff --git a/detection/thirdTool/realesrgan/version.py b/detection/thirdTool/realesrgan/version.py deleted file mode 100644 index 52caea0..0000000 --- a/detection/thirdTool/realesrgan/version.py +++ /dev/null @@ -1,5 +0,0 @@ -# GENERATED VERSION FILE -# TIME: Thu Dec 23 16:27:38 2021 -__version__ = '0.2.3.0' -__gitsha__ = '3e65d21' -version_info = (0, 2, 3, 0) diff --git a/detection/thirdTool/realesrgan/weights/README.md b/detection/thirdTool/realesrgan/weights/README.md deleted file mode 100644 index 4d7b7e6..0000000 --- a/detection/thirdTool/realesrgan/weights/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Weights - -Put the downloaded weights to this folder. diff --git a/detection/thirdTool/yolo5x_qr/__init__.py b/detection/thirdTool/yolo5x_qr/__init__.py deleted file mode 100644 index 933add1..0000000 --- a/detection/thirdTool/yolo5x_qr/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- -''' -@File : __init__.py.py -@Contact : zpyovo@hotmail.com -@License : (C)Copyright 2018-2019, Lab501-TransferLearning-SCUT -@Description : - -@Modify Time @Author @Version @Desciption ------------- ------- -------- ----------- -2023/8/2 15:55 Pengyu Zhang 1.0 None -''' - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -from .models import * -from .utils import * diff --git a/detection/thirdTool/yolo5x_qr/models/Functions.py b/detection/thirdTool/yolo5x_qr/models/Functions.py deleted file mode 100644 index 1c13ced..0000000 --- a/detection/thirdTool/yolo5x_qr/models/Functions.py +++ /dev/null @@ -1,221 +0,0 @@ -import torch -from torch import nn -from torch.nn import functional as F -from torch.autograd import Function, Variable -from torch.nn import Module, parameter - - -import warnings -try: - from queue import Queue -except ImportError: - from Queue import Queue - -# from torch.nn.modules.batchnorm import _BatchNorm -from functools import partial - - -from timm.layers import DropPath, trunc_normal_ -# from timm import register_model -# from timm.layers.helpers import to_2tuple -# from timm.data import IMAGENET_DEFAULT_MEAN, IMAGENET_DEFAULT_STD - - -# LVC -class Encoding(nn.Module): - def __init__(self, in_channels, num_codes): - super(Encoding, self).__init__() - # init codewords and smoothing factor - self.in_channels, self.num_codes = in_channels, num_codes - num_codes = 64 - std = 1. / ((num_codes * in_channels)**0.5) - # [num_codes, channels] - self.codewords = nn.Parameter( - torch.empty(num_codes, in_channels, dtype=torch.float).uniform_(-std, std), requires_grad=True) - # [num_codes] - self.scale = nn.Parameter(torch.empty(num_codes, dtype=torch.float).uniform_(-1, 0), requires_grad=True) - - @staticmethod - def scaled_l2(x, codewords, scale): - num_codes, in_channels = codewords.size() - b = x.size(0) - expanded_x = x.unsqueeze(2).expand((b, x.size(1), num_codes, in_channels)) - - # ---处理codebook (num_code, c1) - reshaped_codewords = codewords.view((1, 1, num_codes, in_channels)) - - # 把scale从1, num_code变成 batch, c2, N, num_codes - reshaped_scale = scale.view((1, 1, num_codes)) # N, num_codes - - # ---计算rik = z1 - d # b, N, num_codes - scaled_l2_norm = reshaped_scale * (expanded_x - reshaped_codewords).pow(2).sum(dim=3) - return scaled_l2_norm - - @staticmethod - def aggregate(assignment_weights, x, codewords): - num_codes, in_channels = codewords.size() - - # ---处理codebook - reshaped_codewords = codewords.view((1, 1, num_codes, in_channels)) - b = x.size(0) - - # ---处理特征向量x b, c1, N - expanded_x = x.unsqueeze(2).expand((b, x.size(1), num_codes, in_channels)) - - #变换rei b, N, num_codes,- - assignment_weights = assignment_weights.unsqueeze(3) # b, N, num_codes, - - # ---开始计算eik,必须在Rei计算完之后 - encoded_feat = (assignment_weights * (expanded_x - reshaped_codewords)).sum(1) - return encoded_feat - - def forward(self, x): - assert x.dim() == 4 and x.size(1) == self.in_channels - b, in_channels, w, h = x.size() - - # [batch_size, height x width, channels] - x = x.view(b, self.in_channels, -1).transpose(1, 2).contiguous() - - # assignment_weights: [batch_size, channels, num_codes] - assignment_weights = F.softmax(self.scaled_l2(x, self.codewords, self.scale), dim=2) - - # aggregate - encoded_feat = self.aggregate(assignment_weights, x, self.codewords) - return encoded_feat - - -# 1*1 3*3 1*1 -class ConvBlock(nn.Module): - def __init__(self, in_channels, out_channels, stride=1, res_conv=False, act_layer=nn.ReLU, groups=1, - norm_layer=partial(nn.BatchNorm2d, eps=1e-6), drop_block=None, drop_path=None): - super(ConvBlock, self).__init__() - self.in_channels = in_channels - expansion = 4 - c = out_channels // expansion - - self.conv1 = nn.Conv2d(in_channels, c, kernel_size=1, stride=1, padding=0, bias=False) # [64, 256, 1, 1] - self.bn1 = norm_layer(c) - self.act1 = act_layer(inplace=True) - - self.conv2 = nn.Conv2d(c, c, kernel_size=3, stride=stride, groups=groups, padding=1, bias=False) - self.bn2 = norm_layer(c) - self.act2 = act_layer(inplace=True) - - self.conv3 = nn.Conv2d(c, out_channels, kernel_size=1, stride=1, padding=0, bias=False) - self.bn3 = norm_layer(out_channels) - self.act3 = act_layer(inplace=True) - - if res_conv: - self.residual_conv = nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=1, padding=0, bias=False) - self.residual_bn = norm_layer(out_channels) - - self.res_conv = res_conv - self.drop_block = drop_block - self.drop_path = drop_path - - def zero_init_last_bn(self): - nn.init.zeros_(self.bn3.weight) - - def forward(self, x, return_x_2=True): - residual = x - - x = self.conv1(x) - x = self.bn1(x) - if self.drop_block is not None: - x = self.drop_block(x) - x = self.act1(x) - - x = self.conv2(x) #if x_t_r is None else self.conv2(x + x_t_r) - x = self.bn2(x) - if self.drop_block is not None: - x = self.drop_block(x) - x2 = self.act2(x) - - x = self.conv3(x2) - x = self.bn3(x) - if self.drop_block is not None: - x = self.drop_block(x) - - if self.drop_path is not None: - x = self.drop_path(x) - - if self.res_conv: - residual = self.residual_conv(residual) - residual = self.residual_bn(residual) - - x += residual - x = self.act3(x) - - if return_x_2: - return x, x2 - else: - return x - - -class Mean(nn.Module): - def __init__(self, dim, keep_dim=False): - super(Mean, self).__init__() - self.dim = dim - self.keep_dim = keep_dim - - def forward(self, input): - return input.mean(self.dim, self.keep_dim) - - -class Mlp(nn.Module): - """ - Implementation of MLP with 1*1 convolutions. Input: tensor with shape [B, C, H, W] - """ - def __init__(self, in_features, hidden_features=None, - out_features=None, act_layer=nn.GELU, drop=0.): - super().__init__() - out_features = out_features or in_features - hidden_features = hidden_features or in_features - self.fc1 = nn.Conv2d(in_features, hidden_features, 1) - self.act = act_layer() - self.fc2 = nn.Conv2d(hidden_features, out_features, 1) - self.drop = nn.Dropout(drop) - self.apply(self._init_weights) - - def _init_weights(self, m): - if isinstance(m, nn.Conv2d): - trunc_normal_(m.weight, std=.02) - if m.bias is not None: - nn.init.constant_(m.bias, 0) - - def forward(self, x): - x = self.fc1(x) - x = self.act(x) - x = self.drop(x) - x = self.fc2(x) - x = self.drop(x) - return x - - -class LayerNormChannel(nn.Module): - """ - LayerNorm only for Channel Dimension. - Input: tensor in shape [B, C, H, W] - """ - def __init__(self, num_channels, eps=1e-05): - super().__init__() - self.weight = nn.Parameter(torch.ones(num_channels)) - self.bias = nn.Parameter(torch.zeros(num_channels)) - self.eps = eps - - def forward(self, x): - u = x.mean(1, keepdim=True) - s = (x - u).pow(2).mean(1, keepdim=True) - x = (x - u) / torch.sqrt(s + self.eps) - x = self.weight.unsqueeze(-1).unsqueeze(-1) * x \ - + self.bias.unsqueeze(-1).unsqueeze(-1) - return x - - -class GroupNorm(nn.GroupNorm): - """ - Group Normalization with 1 group. - Input: tensor in shape [B, C, H, W] - """ - def __init__(self, num_channels, **kwargs): - super().__init__(1, num_channels, **kwargs) \ No newline at end of file diff --git a/detection/thirdTool/yolo5x_qr/models/__init__.py b/detection/thirdTool/yolo5x_qr/models/__init__.py deleted file mode 100644 index f064e82..0000000 --- a/detection/thirdTool/yolo5x_qr/models/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env python -# -*- encoding: utf-8 -*- -''' -@File : __init__.py.py -@Contact : zpyovo@hotmail.com -@License : (C)Copyright 2018-2019, Lab501-TransferLearning-SCUT -@Description : - -@Modify Time @Author @Version @Desciption ------------- ------- -------- ----------- -2023/8/2 15:55 Pengyu Zhang 1.0 None -''' - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function -import importlib -from basicsr.utils import scandir -from os import path as osp - -# automatically scan and import arch modules for registry -# scan all the files that end with '_arch.py' under the archs folder -arch_folder = osp.dirname(osp.abspath(__file__)) -arch_filenames = [osp.splitext(osp.basename(v))[0] for v in scandir(arch_folder) if v.endswith('_modles.py')] -# import all the arch modules -_arch_modules = [importlib.import_module(f'yolo5x_qr.models.{file_name}') for file_name in arch_filenames] - diff --git a/detection/thirdTool/yolo5x_qr/models/common.py b/detection/thirdTool/yolo5x_qr/models/common.py deleted file mode 100644 index 1c9e51a..0000000 --- a/detection/thirdTool/yolo5x_qr/models/common.py +++ /dev/null @@ -1,1491 +0,0 @@ -# YOLOv5 🚀 by Ultralytics, GPL-3.0 license -""" -Common modules -""" - -import ast -import contextlib -import json -import math -import platform -import warnings -import zipfile -from collections import OrderedDict, namedtuple -from copy import copy -from pathlib import Path -from urllib.parse import urlparse - -import cv2 -import numpy as np -import pandas as pd -import requests -import torch -import torch.nn as nn -from PIL import Image -from torch.cuda import amp - -from thirdTool.yolo5x_qr.utils import TryExcept -from thirdTool.yolo5x_qr.utils.dataloaders import exif_transpose, letterbox -from thirdTool.yolo5x_qr.utils.general import (LOGGER, ROOT, Profile, check_requirements, check_suffix, check_version, colorstr, - increment_path, is_jupyter, make_divisible, non_max_suppression, scale_boxes, xywh2xyxy, - xyxy2xywh, yaml_load) -from thirdTool.yolo5x_qr.utils.plots import Annotator, colors, save_one_box -from thirdTool.yolo5x_qr.utils.torch_utils import copy_attr, smart_inference_mode - -from torch.nn import functional as F -from .Functions import Encoding, Mean, DropPath, Mlp, GroupNorm, ConvBlock - - -def autopad(k, p=None, d=1): # kernel, padding, dilation - # Pad to 'same' shape outputs - if d > 1: - k = d * (k - 1) + 1 if isinstance(k, int) else [d * (x - 1) + 1 for x in k] # actual kernel-size - if p is None: - p = k // 2 if isinstance(k, int) else [x // 2 for x in k] # auto-pad - return p - -def export_formats(): - # YOLOv5 export formats - x = [ - ['PyTorch', '-', '.pt', True, True], - ['TorchScript', 'torchscript', '.torchscript', True, True], - ['ONNX', 'onnx', '.onnx', True, True], - ['OpenVINO', 'openvino', '_openvino_model', True, False], - ['TensorRT', 'engine', '.engine', False, True], - ['CoreML', 'coreml', '.mlmodel', True, False], - ['TensorFlow SavedModel', 'saved_model', '_saved_model', True, True], - ['TensorFlow GraphDef', 'pb', '.pb', True, True], - ['TensorFlow Lite', 'tflite', '.tflite', True, False], - ['TensorFlow Edge TPU', 'edgetpu', '_edgetpu.tflite', False, False], - ['TensorFlow.js', 'tfjs', '_web_model', False, False], - ['PaddlePaddle', 'paddle', '_paddle_model', True, True],] - return pd.DataFrame(x, columns=['Format', 'Argument', 'Suffix', 'CPU', 'GPU']) - -class Conv(nn.Module): - # Standard convolution with args(ch_in, ch_out, kernel, stride, padding, groups, dilation, activation) - default_act = nn.SiLU() # default activation - - def __init__(self, c1, c2, k=1, s=1, p=None, g=1, d=1, act=True): - super().__init__() - self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p, d), groups=g, dilation=d, bias=False) - self.bn = nn.BatchNorm2d(c2) - self.act = self.default_act if act is True else act if isinstance(act, nn.Module) else nn.Identity() - - def forward(self, x): - return self.act(self.bn(self.conv(x))) - - def forward_fuse(self, x): - return self.act(self.conv(x)) - - -class DWConv(Conv): - # Depth-wise convolution - def __init__(self, c1, c2, k=1, s=1, d=1, act=True): # ch_in, ch_out, kernel, stride, dilation, activation - super().__init__(c1, c2, k, s, g=math.gcd(c1, c2), d=d, act=act) - - -class DWConvTranspose2d(nn.ConvTranspose2d): - # Depth-wise transpose convolution - def __init__(self, c1, c2, k=1, s=1, p1=0, p2=0): # ch_in, ch_out, kernel, stride, padding, padding_out - super().__init__(c1, c2, k, s, p1, p2, groups=math.gcd(c1, c2)) - - -class TransformerLayer(nn.Module): - # Transformer layer https://arxiv.org/abs/2010.11929 (LayerNorm layers removed for better performance) - def __init__(self, c, num_heads): - super().__init__() - self.q = nn.Linear(c, c, bias=False) - self.k = nn.Linear(c, c, bias=False) - self.v = nn.Linear(c, c, bias=False) - self.ma = nn.MultiheadAttention(embed_dim=c, num_heads=num_heads) - self.fc1 = nn.Linear(c, c, bias=False) - self.fc2 = nn.Linear(c, c, bias=False) - - def forward(self, x): - x = self.ma(self.q(x), self.k(x), self.v(x))[0] + x - x = self.fc2(self.fc1(x)) + x - return x - - -class TransformerBlock(nn.Module): - # Vision Transformer https://arxiv.org/abs/2010.11929 - def __init__(self, c1, c2, num_heads, num_layers): - super().__init__() - self.conv = None - if c1 != c2: - self.conv = Conv(c1, c2) - self.linear = nn.Linear(c2, c2) # learnable position embedding - self.tr = nn.Sequential(*(TransformerLayer(c2, num_heads) for _ in range(num_layers))) - self.c2 = c2 - - def forward(self, x): - if self.conv is not None: - x = self.conv(x) - b, _, w, h = x.shape - p = x.flatten(2).permute(2, 0, 1) - return self.tr(p + self.linear(p)).permute(1, 2, 0).reshape(b, self.c2, w, h) - - -class Bottleneck(nn.Module): - # Standard bottleneck - def __init__(self, c1, c2, shortcut=True, g=1, e=0.5): # ch_in, ch_out, shortcut, groups, expansion - super().__init__() - c_ = int(c2 * e) # hidden channels - self.cv1 = Conv(c1, c_, 1, 1) - self.cv2 = Conv(c_, c2, 3, 1, g=g) - self.add = shortcut and c1 == c2 - - def forward(self, x): - return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x)) - - -class BottleneckCSP(nn.Module): - # CSP Bottleneck https://github.com/WongKinYiu/CrossStagePartialNetworks - def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5): # ch_in, ch_out, number, shortcut, groups, expansion - super().__init__() - c_ = int(c2 * e) # hidden channels - self.cv1 = Conv(c1, c_, 1, 1) - self.cv2 = nn.Conv2d(c1, c_, 1, 1, bias=False) - self.cv3 = nn.Conv2d(c_, c_, 1, 1, bias=False) - self.cv4 = Conv(2 * c_, c2, 1, 1) - self.bn = nn.BatchNorm2d(2 * c_) # applied to cat(cv2, cv3) - self.act = nn.SiLU() - self.m = nn.Sequential(*(Bottleneck(c_, c_, shortcut, g, e=1.0) for _ in range(n))) - - def forward(self, x): - y1 = self.cv3(self.m(self.cv1(x))) - y2 = self.cv2(x) - return self.cv4(self.act(self.bn(torch.cat((y1, y2), 1)))) - - -class CrossConv(nn.Module): - # Cross Convolution Downsample - def __init__(self, c1, c2, k=3, s=1, g=1, e=1.0, shortcut=False): - # ch_in, ch_out, kernel, stride, groups, expansion, shortcut - super().__init__() - c_ = int(c2 * e) # hidden channels - self.cv1 = Conv(c1, c_, (1, k), (1, s)) - self.cv2 = Conv(c_, c2, (k, 1), (s, 1), g=g) - self.add = shortcut and c1 == c2 - - def forward(self, x): - return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x)) - - -class C3(nn.Module): - # CSP Bottleneck with 3 convolutions - def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5): # ch_in, ch_out, number, shortcut, groups, expansion - super().__init__() - c_ = int(c2 * e) # hidden channels - self.cv1 = Conv(c1, c_, 1, 1) - self.cv2 = Conv(c1, c_, 1, 1) - self.cv3 = Conv(2 * c_, c2, 1) # optional act=FReLU(c2) - self.m = nn.Sequential(*(Bottleneck(c_, c_, shortcut, g, e=1.0) for _ in range(n))) - - def forward(self, x): - return self.cv3(torch.cat((self.m(self.cv1(x)), self.cv2(x)), 1)) - - -class C3x(C3): - # C3 module with cross-convolutions - def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5): - super().__init__(c1, c2, n, shortcut, g, e) - c_ = int(c2 * e) - self.m = nn.Sequential(*(CrossConv(c_, c_, 3, 1, g, 1.0, shortcut) for _ in range(n))) - - -class C3TR(C3): - # C3 module with TransformerBlock() - def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5): - super().__init__(c1, c2, n, shortcut, g, e) - c_ = int(c2 * e) - self.m = TransformerBlock(c_, c_, 4, n) - - -class C3SPP(C3): - # C3 module with SPP() - def __init__(self, c1, c2, k=(5, 9, 13), n=1, shortcut=True, g=1, e=0.5): - super().__init__(c1, c2, n, shortcut, g, e) - c_ = int(c2 * e) - self.m = SPP(c_, c_, k) - - -class C3Ghost(C3): - # C3 module with GhostBottleneck() - def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5): - super().__init__(c1, c2, n, shortcut, g, e) - c_ = int(c2 * e) # hidden channels - self.m = nn.Sequential(*(GhostBottleneck(c_, c_) for _ in range(n))) - - -class SPP(nn.Module): - # Spatial Pyramid Pooling (SPP) layer https://arxiv.org/abs/1406.4729 - def __init__(self, c1, c2, k=(5, 9, 13)): - super().__init__() - c_ = c1 // 2 # hidden channels - self.cv1 = Conv(c1, c_, 1, 1) - self.cv2 = Conv(c_ * (len(k) + 1), c2, 1, 1) - self.m = nn.ModuleList([nn.MaxPool2d(kernel_size=x, stride=1, padding=x // 2) for x in k]) - - def forward(self, x): - x = self.cv1(x) - with warnings.catch_warnings(): - warnings.simplefilter('ignore') # suppress torch 1.9.0 max_pool2d() warning - return self.cv2(torch.cat([x] + [m(x) for m in self.m], 1)) - - -class SPPF(nn.Module): - # Spatial Pyramid Pooling - Fast (SPPF) layer for YOLOv5 by Glenn Jocher - def __init__(self, c1, c2, k=5): # equivalent to SPP(k=(5, 9, 13)) - super().__init__() - c_ = c1 // 2 # hidden channels - self.cv1 = Conv(c1, c_, 1, 1) - self.cv2 = Conv(c_ * 4, c2, 1, 1) - self.m = nn.MaxPool2d(kernel_size=k, stride=1, padding=k // 2) - - def forward(self, x): - x = self.cv1(x) - with warnings.catch_warnings(): - warnings.simplefilter('ignore') # suppress torch 1.9.0 max_pool2d() warning - y1 = self.m(x) - y2 = self.m(y1) - return self.cv2(torch.cat((x, y1, y2, self.m(y2)), 1)) - - -class Focus(nn.Module): - # Focus wh information into c-space - def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True): # ch_in, ch_out, kernel, stride, padding, groups - super().__init__() - self.conv = Conv(c1 * 4, c2, k, s, p, g, act=act) - # self.contract = Contract(gain=2) - - def forward(self, x): # x(b,c,w,h) -> y(b,4c,w/2,h/2) - return self.conv(torch.cat((x[..., ::2, ::2], x[..., 1::2, ::2], x[..., ::2, 1::2], x[..., 1::2, 1::2]), 1)) - # return self.conv(self.contract(x)) - - -class GhostConv(nn.Module): - # Ghost Convolution https://github.com/huawei-noah/ghostnet - def __init__(self, c1, c2, k=1, s=1, g=1, act=True): # ch_in, ch_out, kernel, stride, groups - super().__init__() - c_ = c2 // 2 # hidden channels - self.cv1 = Conv(c1, c_, k, s, None, g, act=act) - self.cv2 = Conv(c_, c_, 5, 1, None, c_, act=act) - - def forward(self, x): - y = self.cv1(x) - return torch.cat((y, self.cv2(y)), 1) - - -class GhostBottleneck(nn.Module): - # Ghost Bottleneck https://github.com/huawei-noah/ghostnet - def __init__(self, c1, c2, k=3, s=1): # ch_in, ch_out, kernel, stride - super().__init__() - c_ = c2 // 2 - self.conv = nn.Sequential( - GhostConv(c1, c_, 1, 1), # pw - DWConv(c_, c_, k, s, act=False) if s == 2 else nn.Identity(), # dw - GhostConv(c_, c2, 1, 1, act=False)) # pw-linear - self.shortcut = nn.Sequential(DWConv(c1, c1, k, s, act=False), Conv(c1, c2, 1, 1, - act=False)) if s == 2 else nn.Identity() - - def forward(self, x): - return self.conv(x) + self.shortcut(x) - - -class Contract(nn.Module): - # Contract width-height into channels, i.e. x(1,64,80,80) to x(1,256,40,40) - def __init__(self, gain=2): - super().__init__() - self.gain = gain - - def forward(self, x): - b, c, h, w = x.size() # assert (h / s == 0) and (W / s == 0), 'Indivisible gain' - s = self.gain - x = x.view(b, c, h // s, s, w // s, s) # x(1,64,40,2,40,2) - x = x.permute(0, 3, 5, 1, 2, 4).contiguous() # x(1,2,2,64,40,40) - return x.view(b, c * s * s, h // s, w // s) # x(1,256,40,40) - - -class Expand(nn.Module): - # Expand channels into width-height, i.e. x(1,64,80,80) to x(1,16,160,160) - def __init__(self, gain=2): - super().__init__() - self.gain = gain - - def forward(self, x): - b, c, h, w = x.size() # assert C / s ** 2 == 0, 'Indivisible gain' - s = self.gain - x = x.view(b, s, s, c // s ** 2, h, w) # x(1,2,2,16,80,80) - x = x.permute(0, 3, 4, 1, 5, 2).contiguous() # x(1,16,80,2,80,2) - return x.view(b, c // s ** 2, h * s, w * s) # x(1,16,160,160) - - -class Concat(nn.Module): - # Concatenate a list of tensors along dimension - def __init__(self, dimension=1): - super().__init__() - self.d = dimension - - def forward(self, x): - return torch.cat(x, self.d) - -#by AI&CV C2F -class v8_C2fBottleneck(nn.Module): - # Standard bottleneck - def __init__(self, c1, c2, shortcut=True, g=1, k=(3, 3), e=0.5): # ch_in, ch_out, shortcut, groups, kernels, expand - super().__init__() - c_ = int(c2 * e) # hidden channels - self.cv1 = Conv(c1, c_, k[0], 1) - self.cv2 = Conv(c_, c2, k[1], 1, g=g) - self.add = shortcut and c1 == c2 - - def forward(self, x): - return x + self.cv2(self.cv1(x)) if self.add else self.cv2(self.cv1(x)) - - -class C2f(nn.Module): - # CSP Bottleneck with 2 convolutions - def __init__(self, c1, c2, n=1, shortcut=False, g=1, e=0.5): # ch_in, ch_out, number, shortcut, groups, expansion - super().__init__() - self.c = int(c2 * e) # hidden channels - self.cv1 = Conv(c1, 2 * self.c, 1, 1) - self.cv2 = Conv((2 + n) * self.c, c2, 1) # optional act=FReLU(c2) - self.m = nn.ModuleList( - v8_C2fBottleneck(self.c, self.c, shortcut, g, k=((3, 3), (3, 3)), e=1.0) for _ in range(n)) - - def forward(self, x): - y = list(self.cv1(x).split((self.c, self.c), 1)) - y.extend(m(y[-1]) for m in self.m) - return self.cv2(torch.cat(y, 1)) - - -# by AI&CV EVCBlock ####### start ######################### - -class SiLU(nn.Module): - """export-friendly version of nn.SiLU()""" - - @staticmethod - def forward(x): - return x * torch.sigmoid(x) - - -def get_activation(name="silu", inplace=True): - if name == "silu": - module = nn.SiLU(inplace=inplace) - elif name == "relu": - module = nn.ReLU(inplace=inplace) - elif name == "lrelu": - module = nn.LeakyReLU(0.1, inplace=inplace) - else: - raise AttributeError("Unsupported act type: {}".format(name)) - return module - -class BaseConv(nn.Module): - """A Conv2d -> Batchnorm -> silu/leaky relu block""" # CBL - - def __init__( - self, in_channels, out_channels, ksize, stride, groups=1, bias=False, act="silu" - ): - super().__init__() - # same padding - pad = (ksize - 1) // 2 - self.conv = nn.Conv2d( - in_channels, - out_channels, - kernel_size=ksize, - stride=stride, - padding=pad, - groups=groups, - bias=bias, - ) - self.bn = nn.BatchNorm2d(out_channels) - self.act = get_activation(act, inplace=True) - - def forward(self, x): - return self.act(self.bn(self.conv(x))) - - def fuseforward(self, x): - return self.act(self.conv(x)) - -class DWConv(nn.Module): - """Depthwise Conv + Conv""" - def __init__(self, in_channels, out_channels, ksize, stride=1, act="silu"): - super().__init__() - self.dconv = BaseConv( - in_channels, - in_channels, - ksize=ksize, - stride=stride, - groups=in_channels, - act=act, - ) - self.pconv = BaseConv( - in_channels, out_channels, ksize=1, stride=1, groups=1, act=act - ) - - def forward(self, x): - x = self.dconv(x) - return self.pconv(x) - -class LVCBlock(nn.Module): - def __init__(self, c1, c2, num_codes, channel_ratio=0.25, base_channel=64): - super(LVCBlock, self).__init__() - self.c2 = c2 - self.num_codes = num_codes - num_codes = 64 - - self.conv_1 = ConvBlock(c1, c1, res_conv=True, stride=1) - - self.LVC = nn.Sequential( - nn.Conv2d(c1, c1, 1, bias=False), - nn.BatchNorm2d(c1), - nn.ReLU(inplace=True), - Encoding(c1, num_codes=num_codes), - nn.BatchNorm1d(num_codes), - nn.ReLU(inplace=True), - Mean(dim=1)) - self.fc = nn.Sequential(nn.Linear(c1, c1), nn.Sigmoid()) - - def forward(self, x): - x = self.conv_1(x, return_x_2=False) - en = self.LVC(x) - gam = self.fc(en) - b, in_channels, _, _ = x.size() - y = gam.view(b, in_channels, 1, 1) - x = F.relu_(x + x * y) - return x - - -# LightMLPBlock -class LightMLPBlock(nn.Module): - def __init__(self, c1, c2, ksize=1, stride=1, act="silu", - mlp_ratio=4., drop=0., act_layer=nn.GELU, - use_layer_scale=True, layer_scale_init_value=1e-5, drop_path=0., norm_layer=GroupNorm): # act_layer=nn.GELU, - super().__init__() - self.dw = DWConv(c1, c2, ksize=1, stride=1, act="silu") - self.linear = nn.Linear(c2, c2) # learnable position embedding - self.c2 = c2 - - self.norm1 = norm_layer(c1) - self.norm2 = norm_layer(c1) - - mlp_hidden_dim = int(c1 * mlp_ratio) - self.mlp = Mlp(in_features=c1, hidden_features=mlp_hidden_dim, act_layer=nn.GELU, - drop=drop) - - self.drop_path = DropPath(drop_path) if drop_path > 0. \ - else nn.Identity() - - self.use_layer_scale = use_layer_scale - if use_layer_scale: - self.layer_scale_1 = nn.Parameter( - layer_scale_init_value * torch.ones((c2)), requires_grad=True) - self.layer_scale_2 = nn.Parameter( - layer_scale_init_value * torch.ones((c2)), requires_grad=True) - - def forward(self, x): - if self.use_layer_scale: - x = x + self.drop_path(self.layer_scale_1.unsqueeze(-1).unsqueeze(-1) * self.dw(self.norm1(x))) - x = x + self.drop_path(self.layer_scale_2.unsqueeze(-1).unsqueeze(-1) * self.mlp(self.norm2(x))) - else: - x = x + self.drop_path(self.dw(self.norm1(x))) - x = x + self.drop_path(self.mlp(self.norm2(x))) - return x - - -# EVCBlock -class EVCBlock(nn.Module): - def __init__(self, c1, c2, channel_ratio=4, base_channel=16): - super().__init__() - expansion = 2 - ch = c2 * expansion - # Stem stage: get the feature maps by conv block (copied form resnet.py) 进入conformer框架之前的处理 - self.conv1 = nn.Conv2d(c1, c1, kernel_size=7, stride=1, padding=3, bias=False) # 1 / 2 [112, 112] - self.bn1 = nn.BatchNorm2d(c1) - self.act1 = nn.ReLU(inplace=True) - self.maxpool = nn.MaxPool2d(kernel_size=3, stride=1, padding=1) # 1 / 4 [56, 56] - - # LVC - self.lvc = LVCBlock(c1, c2, num_codes=64) # c1值暂时未定 - # LightMLPBlock - self.l_MLP = LightMLPBlock(c1, c2, ksize=1, stride=1, act="silu", act_layer=nn.GELU, mlp_ratio=4., drop=0., - use_layer_scale=True, layer_scale_init_value=1e-5, drop_path=0., norm_layer=GroupNorm) - self.cnv1 = nn.Conv2d(ch, c2, kernel_size=1, stride=1, padding=0) - - def forward(self, x): - x1 = self.maxpool(self.act1(self.bn1(self.conv1(x)))) - # LVCBlock - x_lvc = self.lvc(x1) - # LightMLPBlock - x_lmlp = self.l_MLP(x1) - # concat - x = torch.cat((x_lvc, x_lmlp), dim=1) - x = self.cnv1(x) - return x -# by AI&CV EVCBlock ####### end ######################### - -# ------------------------------------ODConv start ------------------------------------- -class ODConv2d_3rd(nn.Conv2d): - - def __init__(self, in_channels, out_channels, kernel_size, - stride=1, padding=0, dilation=1, groups=1, bias=True, - K=4, r=1 / 16, save_parameters=False, - padding_mode='zeros', device=None, dtype=None) -> None: - factory_kwargs = {'device': device, 'dtype': dtype} - self.K = K - self.r = r - self.save_parameters = save_parameters - - super().__init__(in_channels, out_channels, kernel_size, stride, - padding, dilation, groups, bias, padding_mode) - - del self.weight - self.weight = nn.Parameter(torch.empty(( - K, - out_channels, - in_channels // groups, - *self.kernel_size, - ), **factory_kwargs)) - - if bias: - del self.bias - self.bias = nn.Parameter(torch.empty(K, out_channels, **factory_kwargs)) - - hidden_dim = max(int(in_channels * r), 16) # 设置下限为16 - self.gap = nn.AdaptiveAvgPool2d(1) - self.reduction = nn.Linear(in_channels, hidden_dim) - self.fc = nn.Conv2d(in_channels, hidden_dim, 1, bias=False) - self.bn = nn.BatchNorm2d(hidden_dim) - self.act = nn.ReLU(inplace=True) - # self.act = nn.SiLU(inplace=True) - - self.fc_f = nn.Linear(hidden_dim, out_channels) - if not save_parameters or self.kernel_size[0] * self.kernel_size[1] > 1: - self.fc_s = nn.Linear(hidden_dim, self.kernel_size[0] * self.kernel_size[1]) - if not save_parameters or in_channels // groups > 1: - self.fc_c = nn.Linear(hidden_dim, in_channels // groups) - if not save_parameters or K > 1: - self.fc_w = nn.Linear(hidden_dim, K) - - self.reset_parameters() - - def reset_parameters(self) -> None: - fan_out = self.kernel_size[0] * self.kernel_size[1] * self.out_channels // self.groups - for i in range(self.K): - self.weight.data[i].normal_(0, math.sqrt(2.0 / fan_out)) - if self.bias is not None: - self.bias.data.zero_() - - def extra_repr(self): - return super().extra_repr() + f', K={self.K}, r={self.r:.4}' - - def get_weight_bias(self, context): - B, C, H, W = context.shape - - if C != self.in_channels: - raise ValueError( - f"Expected context{[B, C, H, W]} to have {self.in_channels} channels, but got {C} channels instead") - - # x = self.gap(context).squeeze(-1).squeeze(-1) # B, c_in - # x = self.reduction(x) # B, hidden_dim - x = self.gap(context) - x = self.fc(x) - if x.size(0) > 1: - x = self.bn(x) - x = x.squeeze(-1).squeeze(-1) - x = self.act(x) - - attn_f = self.fc_f(x).sigmoid() # B, c_out - attn = attn_f.view(B, 1, -1, 1, 1, 1) # B, 1, c_out, 1, 1, 1 - if hasattr(self, 'fc_s'): - attn_s = self.fc_s(x).sigmoid() # B, k * k - attn = attn * attn_s.view(B, 1, 1, 1, *self.kernel_size) # B, 1, c_out, 1, k, k - if hasattr(self, 'fc_c'): - attn_c = self.fc_c(x).sigmoid() # B, c_in // groups - attn = attn * attn_c.view(B, 1, 1, -1, 1, 1) # B, 1, c_out, c_in // groups, k, k - if hasattr(self, 'fc_w'): - attn_w = self.fc_w(x).softmax(-1) # B, n - attn = attn * attn_w.view(B, -1, 1, 1, 1, 1) # B, n, c_out, c_in // groups, k, k - - weight = (attn * self.weight).sum(1) # B, c_out, c_in // groups, k, k - weight = weight.view(-1, self.in_channels // self.groups, *self.kernel_size) # B * c_out, c_in // groups, k, k - - bias = None - if self.bias is not None: - if hasattr(self, 'fc_w'): - bias = attn_w @ self.bias - else: - bias = self.bias.tile(B, 1) - bias = bias.view(-1) # B * c_out - - return weight, bias - - def forward(self, input, context=None): - B, C, H, W = input.shape - - if C != self.in_channels: - raise ValueError( - f"Expected input{[B, C, H, W]} to have {self.in_channels} channels, but got {C} channels instead") - - weight, bias = self.get_weight_bias(context or input) - - output = nn.functional.conv2d( - input.view(1, B * C, H, W), weight, bias, - self.stride, self.padding, self.dilation, B * self.groups) # 1, B * c_out, h_out, w_out - output = output.view(B, self.out_channels, *output.shape[2:]) - - return output - - def debug(self, input, context=None): - B, C, H, W = input.shape - - if C != self.in_channels: - raise ValueError( - f"Expected input{[B, C, H, W]} to have {self.in_channels} channels, but got {C} channels instead") - - output_size = [ - ((H, W)[i] + 2 * self.padding[i] - self.dilation[i] * (self.kernel_size[i] - 1) - 1) // self.stride[i] + 1 - for i in range(2) - ] - - weight, bias = self.get_weight_bias(context or input) - - weight = weight.view(B, self.groups, self.out_channels // self.groups, - -1) # B, groups, c_out // groups, c_in // groups * k * k - - unfold = nn.functional.unfold( - input, self.kernel_size, self.dilation, self.padding, self.stride) # B, c_in * k * k, H_out * W_out - unfold = unfold.view(B, self.groups, -1, - output_size[0] * output_size[1]) # B, groups, c_in // groups * k * k, H_out * W_out - - output = weight @ unfold # B, groups, c_out // groups, H_out * W_out - output = output.view(B, self.out_channels, *output_size) # B, c_out, H_out * W_out - - if bias is not None: - output = output + bias.view(B, self.out_channels, 1, 1) - - return output - - -class ODConv_3rd(nn.Module): - # Standard convolution - def __init__(self, c1, c2, k=1, s=1, kerNums=1, g=1, p=None, - act=True): # ch_in, ch_out, kernel, stride, padding, groups - super().__init__() - self.conv = ODConv2d_3rd(c1, c2, k, s, autopad(k, p), groups=g, K=kerNums) - self.bn = nn.BatchNorm2d(c2) - self.act = nn.SiLU() if act is True else (act if isinstance(act, nn.Module) else nn.Identity()) - - def forward(self, x): - return self.act(self.bn(self.conv(x))) - - def forward_fuse(self, x): - return self.act(self.conv(x)) - - -# ------------------------------------ODConv end ------------------------------------- -# ------------------------------------Convnext start ------------------------------------- -# ConvNextBlock -class ConvNextBlock(nn.Module): - - def __init__(self, inputdim, dim, drop_path=0., layer_scale_init_value=1e-6, - kersize=7): # demo: [64, 64, 1] 1 denotes the number of repeats - super().__init__() - # 匹配yolov5配置文件加入outdim输出通道 - # self.flag = True if dim == outdim else False - - self.dwconv = nn.Conv2d(dim, dim, kernel_size=kersize, padding=kersize // 2, groups=dim) # depthwise conv - self.norm = LayerNorm_s(dim, eps=1e-6) - self.pwconv1 = nn.Linear(dim, 4 * dim) - self.act = nn.GELU() - self.pwconv2 = nn.Linear(4 * dim, dim) - self.gamma = nn.Parameter(layer_scale_init_value * torch.ones((dim)), - requires_grad=True) if layer_scale_init_value > 0 else None - self.drop_path = DropPath(drop_path) if drop_path > 0. else nn.Identity() - - def forward(self, x): - # if self.flag == False: - # raise ValueError( - # f"Expected input out to have {dim} channels, but got {outdim} channels instead") - - input = x - x = self.dwconv(x) - x = x.permute(0, 2, 3, 1) # (N, C, H, W) -> (N, H, W, C) - x = self.norm(x) - x = self.pwconv1(x) - x = self.act(x) - x = self.pwconv2(x) - if self.gamma is not None: - x = self.gamma * x - x = x.permute(0, 3, 1, 2) # (N, H, W, C) -> (N, C, H, W) - - x = input + self.drop_path(x) - return x - - -class LayerNorm_s(nn.Module): - - def __init__(self, normalized_shape, eps=1e-6, data_format="channels_last"): - super().__init__() - self.weight = nn.Parameter(torch.ones(normalized_shape)) - self.bias = nn.Parameter(torch.zeros(normalized_shape)) - self.eps = eps - self.data_format = data_format - if self.data_format not in ["channels_last", "channels_first"]: - raise NotImplementedError - self.normalized_shape = (normalized_shape,) - - def forward(self, x): - if self.data_format == "channels_last": - return F.layer_norm(x, self.normalized_shape, self.weight, self.bias, self.eps) - elif self.data_format == "channels_first": - u = x.mean(1, keepdim=True) - s = (x - u).pow(2).mean(1, keepdim=True) - x = (x - u) / torch.sqrt(s + self.eps) - x = self.weight[:, None, None] * x + self.bias[:, None, None] - return x - - -class DropPath(nn.Module): - """Drop paths (Stochastic Depth) per sample (when applied in main path of residual blocks). - """ - - def __init__(self, drop_prob=None): - super(DropPath, self).__init__() - self.drop_prob = drop_prob - - def forward(self, x): - return drop_path_f(x, self.drop_prob, self.training) - - -def drop_path_f(x, drop_prob: float = 0., training: bool = False): - """Drop paths (Stochastic Depth) per sample (when applied in main path of residual blocks). - This is the same as the DropConnect impl I created for EfficientNet, etc networks, however, - the original name is misleading as 'Drop Connect' is a different form of dropout in a separate paper... - See discussion: https://github.com/tensorflow/tpu/issues/494#issuecomment-532968956 ... I've opted for - changing the layer and argument names to 'drop path' rather than mix DropConnect as a layer name and use - 'survival rate' as the argument. - """ - if drop_prob == 0. or not training: - return x - keep_prob = 1 - drop_prob - shape = (x.shape[0],) + (1,) * (x.ndim - 1) # work with diff dim tensors, not just 2D ConvNets - random_tensor = keep_prob + torch.rand(shape, dtype=x.dtype, device=x.device) - random_tensor.floor_() # binarize - output = x.div(keep_prob) * random_tensor - return output - - -# ------------------------------------Convnext end ------------------------------------- - -class DetectMultiBackend(nn.Module): - # YOLOv5 MultiBackend class for python inference on various backends - def __init__(self, weights='yolov5s.pt', device=torch.device('cpu'), dnn=False, data=None, fp16=False, fuse=True): - # Usage: - # PyTorch: weights = *.pt - # TorchScript: *.torchscript - # ONNX Runtime: *.onnx - # ONNX OpenCV DNN: *.onnx --dnn - # OpenVINO: *_openvino_model - # CoreML: *.mlmodel - # TensorRT: *.engine - # TensorFlow SavedModel: *_saved_model - # TensorFlow GraphDef: *.pb - # TensorFlow Lite: *.tflite - # TensorFlow Edge TPU: *_edgetpu.tflite - # PaddlePaddle: *_paddle_model - from .experimental import attempt_download, attempt_load # scoped to avoid circular import - - super().__init__() - w = str(weights[0] if isinstance(weights, list) else weights) - pt, jit, onnx, xml, engine, coreml, saved_model, pb, tflite, edgetpu, tfjs, paddle, triton = self._model_type(w) - fp16 &= pt or jit or onnx or engine # FP16 - nhwc = coreml or saved_model or pb or tflite or edgetpu # BHWC formats (vs torch BCWH) - stride = 32 # default stride - cuda = torch.cuda.is_available() and device.type != 'cpu' # use CUDA - if not (pt or triton): - w = attempt_download(w) # download if not local - - if pt: # PyTorch - model = attempt_load(weights if isinstance(weights, list) else w, device=device, inplace=True, fuse=fuse) - stride = max(int(model.stride.max()), 32) # model stride - names = model.module.names if hasattr(model, 'module') else model.names # get class names - model.half() if fp16 else model.float() - self.model = model # explicitly assign for to(), cpu(), cuda(), half() - elif jit: # TorchScript - LOGGER.info(f'Loading {w} for TorchScript inference...') - extra_files = {'config.txt': ''} # model metadata - model = torch.jit.load(w, _extra_files=extra_files, map_location=device) - model.half() if fp16 else model.float() - if extra_files['config.txt']: # load metadata dict - d = json.loads(extra_files['config.txt'], - object_hook=lambda d: {int(k) if k.isdigit() else k: v - for k, v in d.items()}) - stride, names = int(d['stride']), d['names'] - elif dnn: # ONNX OpenCV DNN - LOGGER.info(f'Loading {w} for ONNX OpenCV DNN inference...') - check_requirements('opencv-python>=4.5.4') - net = cv2.dnn.readNetFromONNX(w) - elif onnx: # ONNX Runtime - LOGGER.info(f'Loading {w} for ONNX Runtime inference...') - check_requirements(('onnx', 'onnxruntime-gpu' if cuda else 'onnxruntime')) - import onnxruntime - providers = ['CUDAExecutionProvider', 'CPUExecutionProvider'] if cuda else ['CPUExecutionProvider'] - session = onnxruntime.InferenceSession(w, providers=providers) - output_names = [x.name for x in session.get_outputs()] - meta = session.get_modelmeta().custom_metadata_map # metadata - if 'stride' in meta: - stride, names = int(meta['stride']), eval(meta['names']) - elif xml: # OpenVINO - LOGGER.info(f'Loading {w} for OpenVINO inference...') - check_requirements('openvino') # requires openvino-dev: https://pypi.org/project/openvino-dev/ - from openvino.runtime import Core, Layout, get_batch - ie = Core() - if not Path(w).is_file(): # if not *.xml - w = next(Path(w).glob('*.xml')) # get *.xml file from *_openvino_model dir - network = ie.read_model(model=w, weights=Path(w).with_suffix('.bin')) - if network.get_parameters()[0].get_layout().empty: - network.get_parameters()[0].set_layout(Layout('NCHW')) - batch_dim = get_batch(network) - if batch_dim.is_static: - batch_size = batch_dim.get_length() - executable_network = ie.compile_model(network, device_name='CPU') # device_name="MYRIAD" for Intel NCS2 - stride, names = self._load_metadata(Path(w).with_suffix('.yaml')) # load metadata - elif engine: # TensorRT - LOGGER.info(f'Loading {w} for TensorRT inference...') - import tensorrt as trt # https://developer.nvidia.com/nvidia-tensorrt-download - check_version(trt.__version__, '7.0.0', hard=True) # require tensorrt>=7.0.0 - if device.type == 'cpu': - device = torch.device('cuda:0') - Binding = namedtuple('Binding', ('name', 'dtype', 'shape', 'data', 'ptr')) - logger = trt.Logger(trt.Logger.INFO) - with open(w, 'rb') as f, trt.Runtime(logger) as runtime: - model = runtime.deserialize_cuda_engine(f.read()) - context = model.create_execution_context() - bindings = OrderedDict() - output_names = [] - fp16 = False # default updated below - dynamic = False - for i in range(model.num_bindings): - name = model.get_binding_name(i) - dtype = trt.nptype(model.get_binding_dtype(i)) - if model.binding_is_input(i): - if -1 in tuple(model.get_binding_shape(i)): # dynamic - dynamic = True - context.set_binding_shape(i, tuple(model.get_profile_shape(0, i)[2])) - if dtype == np.float16: - fp16 = True - else: # output - output_names.append(name) - shape = tuple(context.get_binding_shape(i)) - im = torch.from_numpy(np.empty(shape, dtype=dtype)).to(device) - bindings[name] = Binding(name, dtype, shape, im, int(im.data_ptr())) - binding_addrs = OrderedDict((n, d.ptr) for n, d in bindings.items()) - batch_size = bindings['images'].shape[0] # if dynamic, this is instead max batch size - elif coreml: # CoreML - LOGGER.info(f'Loading {w} for CoreML inference...') - import coremltools as ct - model = ct.models.MLModel(w) - elif saved_model: # TF SavedModel - LOGGER.info(f'Loading {w} for TensorFlow SavedModel inference...') - import tensorflow as tf - keras = False # assume TF1 saved_model - model = tf.keras.models.load_model(w) if keras else tf.saved_model.load(w) - elif pb: # GraphDef https://www.tensorflow.org/guide/migrate#a_graphpb_or_graphpbtxt - LOGGER.info(f'Loading {w} for TensorFlow GraphDef inference...') - import tensorflow as tf - - def wrap_frozen_graph(gd, inputs, outputs): - x = tf.compat.v1.wrap_function(lambda: tf.compat.v1.import_graph_def(gd, name=''), []) # wrapped - ge = x.graph.as_graph_element - return x.prune(tf.nest.map_structure(ge, inputs), tf.nest.map_structure(ge, outputs)) - - def gd_outputs(gd): - name_list, input_list = [], [] - for node in gd.node: # tensorflow.core.framework.node_def_pb2.NodeDef - name_list.append(node.name) - input_list.extend(node.input) - return sorted(f'{x}:0' for x in list(set(name_list) - set(input_list)) if not x.startswith('NoOp')) - - gd = tf.Graph().as_graph_def() # TF GraphDef - with open(w, 'rb') as f: - gd.ParseFromString(f.read()) - frozen_func = wrap_frozen_graph(gd, inputs='x:0', outputs=gd_outputs(gd)) - elif tflite or edgetpu: # https://www.tensorflow.org/lite/guide/python#install_tensorflow_lite_for_python - try: # https://coral.ai/docs/edgetpu/tflite-python/#update-existing-tf-lite-code-for-the-edge-tpu - from tflite_runtime.interpreter import Interpreter, load_delegate - except ImportError: - import tensorflow as tf - Interpreter, load_delegate = tf.lite.Interpreter, tf.lite.experimental.load_delegate, - if edgetpu: # TF Edge TPU https://coral.ai/software/#edgetpu-runtime - LOGGER.info(f'Loading {w} for TensorFlow Lite Edge TPU inference...') - delegate = { - 'Linux': 'libedgetpu.so.1', - 'Darwin': 'libedgetpu.1.dylib', - 'Windows': 'edgetpu.dll'}[platform.system()] - interpreter = Interpreter(model_path=w, experimental_delegates=[load_delegate(delegate)]) - else: # TFLite - LOGGER.info(f'Loading {w} for TensorFlow Lite inference...') - interpreter = Interpreter(model_path=w) # load TFLite model - interpreter.allocate_tensors() # allocate - input_details = interpreter.get_input_details() # inputs - output_details = interpreter.get_output_details() # outputs - # load metadata - with contextlib.suppress(zipfile.BadZipFile): - with zipfile.ZipFile(w, 'r') as model: - meta_file = model.namelist()[0] - meta = ast.literal_eval(model.read(meta_file).decode('utf-8')) - stride, names = int(meta['stride']), meta['names'] - elif tfjs: # TF.js - raise NotImplementedError('ERROR: YOLOv5 TF.js inference is not supported') - elif paddle: # PaddlePaddle - LOGGER.info(f'Loading {w} for PaddlePaddle inference...') - check_requirements('paddlepaddle-gpu' if cuda else 'paddlepaddle') - import paddle.inference as pdi - if not Path(w).is_file(): # if not *.pdmodel - w = next(Path(w).rglob('*.pdmodel')) # get *.pdmodel file from *_paddle_model dir - weights = Path(w).with_suffix('.pdiparams') - config = pdi.Config(str(w), str(weights)) - if cuda: - config.enable_use_gpu(memory_pool_init_size_mb=2048, device_id=0) - predictor = pdi.create_predictor(config) - input_handle = predictor.get_input_handle(predictor.get_input_names()[0]) - output_names = predictor.get_output_names() - elif triton: # NVIDIA Triton Inference Server - LOGGER.info(f'Using {w} as Triton Inference Server...') - check_requirements('tritonclient[all]') - from thirdTool.yolo5x_qr.utils.triton import TritonRemoteModel - model = TritonRemoteModel(url=w) - nhwc = model.runtime.startswith('tensorflow') - else: - raise NotImplementedError(f'ERROR: {w} is not a supported format') - - # class names - if 'names' not in locals(): - names = yaml_load(data)['names'] if data else {i: f'class{i}' for i in range(999)} - if names[0] == 'n01440764' and len(names) == 1000: # ImageNet - names = yaml_load(ROOT / 'data/ImageNet.yaml')['names'] # human-readable names - - self.__dict__.update(locals()) # assign all variables to self - - def forward(self, im, augment=False, visualize=False): - # YOLOv5 MultiBackend inference - b, ch, h, w = im.shape # batch, channel, height, width - if self.fp16 and im.dtype != torch.float16: - im = im.half() # to FP16 - if self.nhwc: - im = im.permute(0, 2, 3, 1) # torch BCHW to numpy BHWC shape(1,320,192,3) - - if self.pt: # PyTorch - y = self.model(im, augment=augment, visualize=visualize) if augment or visualize else self.model(im) - elif self.jit: # TorchScript - y = self.model(im) - elif self.dnn: # ONNX OpenCV DNN - im = im.cpu().numpy() # torch to numpy - self.net.setInput(im) - y = self.net.forward() - elif self.onnx: # ONNX Runtime - im = im.cpu().numpy() # torch to numpy - y = self.session.run(self.output_names, {self.session.get_inputs()[0].name: im}) - elif self.xml: # OpenVINO - im = im.cpu().numpy() # FP32 - y = list(self.executable_network([im]).values()) - elif self.engine: # TensorRT - if self.dynamic and im.shape != self.bindings['images'].shape: - i = self.model.get_binding_index('images') - self.context.set_binding_shape(i, im.shape) # reshape if dynamic - self.bindings['images'] = self.bindings['images']._replace(shape=im.shape) - for name in self.output_names: - i = self.model.get_binding_index(name) - self.bindings[name].data.resize_(tuple(self.context.get_binding_shape(i))) - s = self.bindings['images'].shape - assert im.shape == s, f"input size {im.shape} {'>' if self.dynamic else 'not equal to'} max model size {s}" - self.binding_addrs['images'] = int(im.data_ptr()) - self.context.execute_v2(list(self.binding_addrs.values())) - y = [self.bindings[x].data for x in sorted(self.output_names)] - elif self.coreml: # CoreML - im = im.cpu().numpy() - im = Image.fromarray((im[0] * 255).astype('uint8')) - # im = im.resize((192, 320), Image.ANTIALIAS) - y = self.model.predict({'image': im}) # coordinates are xywh normalized - if 'confidence' in y: - box = xywh2xyxy(y['coordinates'] * [[w, h, w, h]]) # xyxy pixels - conf, cls = y['confidence'].max(1), y['confidence'].argmax(1).astype(np.float) - y = np.concatenate((box, conf.reshape(-1, 1), cls.reshape(-1, 1)), 1) - else: - y = list(reversed(y.values())) # reversed for segmentation models (pred, proto) - elif self.paddle: # PaddlePaddle - im = im.cpu().numpy().astype(np.float32) - self.input_handle.copy_from_cpu(im) - self.predictor.run() - y = [self.predictor.get_output_handle(x).copy_to_cpu() for x in self.output_names] - elif self.triton: # NVIDIA Triton Inference Server - y = self.model(im) - else: # TensorFlow (SavedModel, GraphDef, Lite, Edge TPU) - im = im.cpu().numpy() - if self.saved_model: # SavedModel - y = self.model(im, training=False) if self.keras else self.model(im) - elif self.pb: # GraphDef - y = self.frozen_func(x=self.tf.constant(im)) - else: # Lite or Edge TPU - input = self.input_details[0] - int8 = input['dtype'] == np.uint8 # is TFLite quantized uint8 model - if int8: - scale, zero_point = input['quantization'] - im = (im / scale + zero_point).astype(np.uint8) # de-scale - self.interpreter.set_tensor(input['index'], im) - self.interpreter.invoke() - y = [] - for output in self.output_details: - x = self.interpreter.get_tensor(output['index']) - if int8: - scale, zero_point = output['quantization'] - x = (x.astype(np.float32) - zero_point) * scale # re-scale - y.append(x) - y = [x if isinstance(x, np.ndarray) else x.numpy() for x in y] - y[0][..., :4] *= [w, h, w, h] # xywh normalized to pixels - - if isinstance(y, (list, tuple)): - return self.from_numpy(y[0]) if len(y) == 1 else [self.from_numpy(x) for x in y] - else: - return self.from_numpy(y) - - def from_numpy(self, x): - return torch.from_numpy(x).to(self.device) if isinstance(x, np.ndarray) else x - - def warmup(self, imgsz=(1, 3, 640, 640)): - # Warmup model by running inference once - warmup_types = self.pt, self.jit, self.onnx, self.engine, self.saved_model, self.pb, self.triton - if any(warmup_types) and (self.device.type != 'cpu' or self.triton): - im = torch.empty(*imgsz, dtype=torch.half if self.fp16 else torch.float, device=self.device) # input - for _ in range(2 if self.jit else 1): # - self.forward(im) # warmup - - @staticmethod - def _model_type(p='path/to/model.pt'): - # Return model type from model path, i.e. path='path/to/model.onnx' -> type=onnx - # types = [pt, jit, onnx, xml, engine, coreml, saved_model, pb, tflite, edgetpu, tfjs, paddle] - # from export import export_formats - from thirdTool.yolo5x_qr.utils.downloads import is_url - sf = list(export_formats().Suffix) # export suffixes - if not is_url(p, check=False): - check_suffix(p, sf) # checks - url = urlparse(p) # if url may be Triton inference server - types = [s in Path(p).name for s in sf] - types[8] &= not types[9] # tflite &= not edgetpu - triton = not any(types) and all([any(s in url.scheme for s in ['http', 'grpc']), url.netloc]) - return types + [triton] - - @staticmethod - def _load_metadata(f=Path('path/to/meta.yaml')): - # Load metadata from meta.yaml if it exists - if f.exists(): - d = yaml_load(f) - return d['stride'], d['names'] # assign stride, names - return None, None - - -class AutoShape(nn.Module): - # YOLOv5 input-robust model wrapper for passing cv2/np/PIL/torch inputs. Includes preprocessing, inference and NMS - conf = 0.25 # NMS confidence threshold - iou = 0.45 # NMS IoU threshold - agnostic = False # NMS class-agnostic - multi_label = False # NMS multiple labels per box - classes = None # (optional list) filter by class, i.e. = [0, 15, 16] for COCO persons, cats and dogs - max_det = 1000 # maximum number of detections per image - amp = False # Automatic Mixed Precision (AMP) inference - - def __init__(self, model, verbose=True): - super().__init__() - if verbose: - LOGGER.info('Adding AutoShape... ') - copy_attr(self, model, include=('yaml', 'nc', 'hyp', 'names', 'stride', 'abc'), exclude=()) # copy attributes - self.dmb = isinstance(model, DetectMultiBackend) # DetectMultiBackend() instance - self.pt = not self.dmb or model.pt # PyTorch model - self.model = model.eval() - if self.pt: - m = self.model.model.model[-1] if self.dmb else self.model.model[-1] # Detect() - m.inplace = False # Detect.inplace=False for safe multithread inference - m.export = True # do not output loss values - - def _apply(self, fn): - # Apply to(), cpu(), cuda(), half() to model tensors that are not parameters or registered buffers - self = super()._apply(fn) - if self.pt: - m = self.model.model.model[-1] if self.dmb else self.model.model[-1] # Detect() - m.stride = fn(m.stride) - m.grid = list(map(fn, m.grid)) - if isinstance(m.anchor_grid, list): - m.anchor_grid = list(map(fn, m.anchor_grid)) - return self - - @smart_inference_mode() - def forward(self, ims, size=640, augment=False, profile=False): - # Inference from various sources. For size(height=640, width=1280), RGB images example inputs are: - # file: ims = 'data/images/zidane.jpg' # str or PosixPath - # URI: = 'https://ultralytics.com/images/zidane.jpg' - # OpenCV: = cv2.imread('image.jpg')[:,:,::-1] # HWC BGR to RGB x(640,1280,3) - # PIL: = Image.open('image.jpg') or ImageGrab.grab() # HWC x(640,1280,3) - # numpy: = np.zeros((640,1280,3)) # HWC - # torch: = torch.zeros(16,3,320,640) # BCHW (scaled to size=640, 0-1 values) - # multiple: = [Image.open('image1.jpg'), Image.open('image2.jpg'), ...] # list of images - - dt = (Profile(), Profile(), Profile()) - with dt[0]: - if isinstance(size, int): # expand - size = (size, size) - p = next(self.model.parameters()) if self.pt else torch.empty(1, device=self.model.device) # param - autocast = self.amp and (p.device.type != 'cpu') # Automatic Mixed Precision (AMP) inference - if isinstance(ims, torch.Tensor): # torch - with amp.autocast(autocast): - return self.model(ims.to(p.device).type_as(p), augment=augment) # inference - - # Pre-process - n, ims = (len(ims), list(ims)) if isinstance(ims, (list, tuple)) else (1, [ims]) # number, list of images - shape0, shape1, files = [], [], [] # image and inference shapes, filenames - for i, im in enumerate(ims): - f = f'image{i}' # filename - if isinstance(im, (str, Path)): # filename or uri - im, f = Image.open(requests.get(im, stream=True).raw if str(im).startswith('http') else im), im - im = np.asarray(exif_transpose(im)) - elif isinstance(im, Image.Image): # PIL Image - im, f = np.asarray(exif_transpose(im)), getattr(im, 'filename', f) or f - files.append(Path(f).with_suffix('.jpg').name) - if im.shape[0] < 5: # image in CHW - im = im.transpose((1, 2, 0)) # reverse dataloader .transpose(2, 0, 1) - im = im[..., :3] if im.ndim == 3 else cv2.cvtColor(im, cv2.COLOR_GRAY2BGR) # enforce 3ch input - s = im.shape[:2] # HWC - shape0.append(s) # image shape - g = max(size) / max(s) # gain - shape1.append([int(y * g) for y in s]) - ims[i] = im if im.data.contiguous else np.ascontiguousarray(im) # update - shape1 = [make_divisible(x, self.stride) for x in np.array(shape1).max(0)] # inf shape - x = [letterbox(im, shape1, auto=False)[0] for im in ims] # pad - x = np.ascontiguousarray(np.array(x).transpose((0, 3, 1, 2))) # stack and BHWC to BCHW - x = torch.from_numpy(x).to(p.device).type_as(p) / 255 # uint8 to fp16/32 - - with amp.autocast(autocast): - # Inference - with dt[1]: - y = self.model(x, augment=augment) # forward - - # Post-process - with dt[2]: - y = non_max_suppression(y if self.dmb else y[0], - self.conf, - self.iou, - self.classes, - self.agnostic, - self.multi_label, - max_det=self.max_det) # NMS - for i in range(n): - scale_boxes(shape1, y[i][:, :4], shape0[i]) - - return Detections(ims, y, files, dt, self.names, x.shape) - - -class Detections: - # YOLOv5 detections class for inference results - def __init__(self, ims, pred, files, times=(0, 0, 0), names=None, shape=None): - super().__init__() - d = pred[0].device # device - gn = [torch.tensor([*(im.shape[i] for i in [1, 0, 1, 0]), 1, 1], device=d) for im in ims] # normalizations - self.ims = ims # list of images as numpy arrays - self.pred = pred # list of tensors pred[0] = (xyxy, conf, cls) - self.names = names # class names - self.files = files # image filenames - self.times = times # profiling times - self.xyxy = pred # xyxy pixels - self.xywh = [xyxy2xywh(x) for x in pred] # xywh pixels - self.xyxyn = [x / g for x, g in zip(self.xyxy, gn)] # xyxy normalized - self.xywhn = [x / g for x, g in zip(self.xywh, gn)] # xywh normalized - self.n = len(self.pred) # number of images (batch size) - self.t = tuple(x.t / self.n * 1E3 for x in times) # timestamps (ms) - self.s = tuple(shape) # inference BCHW shape - - def _run(self, pprint=False, show=False, save=False, crop=False, render=False, labels=True, save_dir=Path('')): - s, crops = '', [] - for i, (im, pred) in enumerate(zip(self.ims, self.pred)): - s += f'\nimage {i + 1}/{len(self.pred)}: {im.shape[0]}x{im.shape[1]} ' # string - if pred.shape[0]: - for c in pred[:, -1].unique(): - n = (pred[:, -1] == c).sum() # detections per class - s += f"{n} {self.names[int(c)]}{'s' * (n > 1)}, " # add to string - s = s.rstrip(', ') - if show or save or render or crop: - annotator = Annotator(im, example=str(self.names)) - for *box, conf, cls in reversed(pred): # xyxy, confidence, class - label = f'{self.names[int(cls)]} {conf:.2f}' - if crop: - file = save_dir / 'crops' / self.names[int(cls)] / self.files[i] if save else None - crops.append({ - 'box': box, - 'conf': conf, - 'cls': cls, - 'label': label, - 'im': save_one_box(box, im, file=file, save=save)}) - else: # all others - annotator.box_label(box, label if labels else '', color=colors(cls)) - im = annotator.im - else: - s += '(no detections)' - - im = Image.fromarray(im.astype(np.uint8)) if isinstance(im, np.ndarray) else im # from np - if show: - if is_jupyter(): - from IPython.display import display - display(im) - else: - im.show(self.files[i]) - if save: - f = self.files[i] - im.save(save_dir / f) # save - if i == self.n - 1: - LOGGER.info(f"Saved {self.n} image{'s' * (self.n > 1)} to {colorstr('bold', save_dir)}") - if render: - self.ims[i] = np.asarray(im) - if pprint: - s = s.lstrip('\n') - return f'{s}\nSpeed: %.1fms pre-process, %.1fms inference, %.1fms NMS per image at shape {self.s}' % self.t - if crop: - if save: - LOGGER.info(f'Saved results to {save_dir}\n') - return crops - - @TryExcept('Showing images is not supported in this environment') - def show(self, labels=True): - self._run(show=True, labels=labels) # show results - - def save(self, labels=True, save_dir='runs/detect/exp', exist_ok=False): - save_dir = increment_path(save_dir, exist_ok, mkdir=True) # increment save_dir - self._run(save=True, labels=labels, save_dir=save_dir) # save results - - def crop(self, save=True, save_dir='runs/detect/exp', exist_ok=False): - save_dir = increment_path(save_dir, exist_ok, mkdir=True) if save else None - return self._run(crop=True, save=save, save_dir=save_dir) # crop results - - def render(self, labels=True): - self._run(render=True, labels=labels) # render results - return self.ims - - def pandas(self): - # return detections as pandas DataFrames, i.e. print(results.pandas().xyxy[0]) - new = copy(self) # return copy - ca = 'xmin', 'ymin', 'xmax', 'ymax', 'confidence', 'class', 'name' # xyxy columns - cb = 'xcenter', 'ycenter', 'width', 'height', 'confidence', 'class', 'name' # xywh columns - for k, c in zip(['xyxy', 'xyxyn', 'xywh', 'xywhn'], [ca, ca, cb, cb]): - a = [[x[:5] + [int(x[5]), self.names[int(x[5])]] for x in x.tolist()] for x in getattr(self, k)] # update - setattr(new, k, [pd.DataFrame(x, columns=c) for x in a]) - return new - - def tolist(self): - # return a list of Detections objects, i.e. 'for result in results.tolist():' - r = range(self.n) # iterable - x = [Detections([self.ims[i]], [self.pred[i]], [self.files[i]], self.times, self.names, self.s) for i in r] - # for d in x: - # for k in ['ims', 'pred', 'xyxy', 'xyxyn', 'xywh', 'xywhn']: - # setattr(d, k, getattr(d, k)[0]) # pop out of list - return x - - def print(self): - LOGGER.info(self.__str__()) - - def __len__(self): # override len(results) - return self.n - - def __str__(self): # override print(results) - return self._run(pprint=True) # print results - - def __repr__(self): - return f'YOLOv5 {self.__class__} instance\n' + self.__str__() - - -class Proto(nn.Module): - # YOLOv5 mask Proto module for segmentation models - def __init__(self, c1, c_=256, c2=32): # ch_in, number of protos, number of masks - super().__init__() - self.cv1 = Conv(c1, c_, k=3) - self.upsample = nn.Upsample(scale_factor=2, mode='nearest') - self.cv2 = Conv(c_, c_, k=3) - self.cv3 = Conv(c_, c2) - - def forward(self, x): - return self.cv3(self.cv2(self.upsample(self.cv1(x)))) - - -class Classify(nn.Module): - # YOLOv5 classification head, i.e. x(b,c1,20,20) to x(b,c2) - def __init__(self, - c1, - c2, - k=1, - s=1, - p=None, - g=1, - dropout_p=0.0): # ch_in, ch_out, kernel, stride, padding, groups, dropout probability - super().__init__() - c_ = 1280 # efficientnet_b0 size - self.conv = Conv(c1, c_, k, s, autopad(k, p), g) - self.pool = nn.AdaptiveAvgPool2d(1) # to x(b,c_,1,1) - self.drop = nn.Dropout(p=dropout_p, inplace=True) - self.linear = nn.Linear(c_, c2) # to x(b,c2) - - def forward(self, x): - if isinstance(x, list): - x = torch.cat(x, 1) - return self.linear(self.drop(self.pool(self.conv(x)).flatten(1))) - - -from torch.autograd import Function - -# by AI&CV SOCA ####### start ######################### -# SOCA moudle 单幅图像超分辨率 -class Covpool(Function): - @staticmethod - def forward(ctx, input): - x = input - batchSize = x.data.shape[0] - dim = x.data.shape[1] - h = x.data.shape[2] - w = x.data.shape[3] - M = h * w - x = x.reshape(batchSize, dim, M) - I_hat = (-1. / M / M) * torch.ones(M, M, device=x.device) + (1. / M) * torch.eye(M, M, device=x.device) - I_hat = I_hat.view(1, M, M).repeat(batchSize, 1, 1).type(x.dtype) - y = x.bmm(I_hat).bmm(x.transpose(1, 2)) - ctx.save_for_backward(input, I_hat) - return y - - @staticmethod - def backward(ctx, grad_output): - input, I_hat = ctx.saved_tensors - x = input - batchSize = x.data.shape[0] - dim = x.data.shape[1] - h = x.data.shape[2] - w = x.data.shape[3] - M = h * w - x = x.reshape(batchSize, dim, M) - grad_input = grad_output + grad_output.transpose(1, 2) - grad_input = grad_input.bmm(x).bmm(I_hat) - grad_input = grad_input.reshape(batchSize, dim, h, w) - return grad_input - - -class Sqrtm(Function): - @staticmethod - def forward(ctx, input, iterN): - x = input - batchSize = x.data.shape[0] - dim = x.data.shape[1] - dtype = x.dtype - I3 = 3.0 * torch.eye(dim, dim, device=x.device).view(1, dim, dim).repeat(batchSize, 1, 1).type(dtype) - normA = (1.0 / 3.0) * x.mul(I3).sum(dim=1).sum(dim=1) - A = x.div(normA.view(batchSize, 1, 1).expand_as(x)) - Y = torch.zeros(batchSize, iterN, dim, dim, requires_grad=False, device=x.device) - Z = torch.eye(dim, dim, device=x.device).view(1, dim, dim).repeat(batchSize, iterN, 1, 1) - if iterN < 2: - ZY = 0.5 * (I3 - A) - Y[:, 0, :, :] = A.bmm(ZY) - else: - ZY = 0.5 * (I3 - A) - Y[:, 0, :, :] = A.bmm(ZY) - Z[:, 0, :, :] = ZY - for i in range(1, iterN - 1): - ZY = 0.5 * (I3 - Z[:, i - 1, :, :].bmm(Y[:, i - 1, :, :])) - Y[:, i, :, :] = Y[:, i - 1, :, :].bmm(ZY) - Z[:, i, :, :] = ZY.bmm(Z[:, i - 1, :, :]) - ZY = 0.5 * Y[:, iterN - 2, :, :].bmm(I3 - Z[:, iterN - 2, :, :].bmm(Y[:, iterN - 2, :, :])) - y = ZY * torch.sqrt(normA).view(batchSize, 1, 1).expand_as(x) - ctx.save_for_backward(input, A, ZY, normA, Y, Z) - ctx.iterN = iterN - return y - - @staticmethod - def backward(ctx, grad_output): - input, A, ZY, normA, Y, Z = ctx.saved_tensors - iterN = ctx.iterN - x = input - batchSize = x.data.shape[0] - dim = x.data.shape[1] - dtype = x.dtype - der_postCom = grad_output * torch.sqrt(normA).view(batchSize, 1, 1).expand_as(x) - der_postComAux = (grad_output * ZY).sum(dim=1).sum(dim=1).div(2 * torch.sqrt(normA)) - I3 = 3.0 * torch.eye(dim, dim, device=x.device).view(1, dim, dim).repeat(batchSize, 1, 1).type(dtype) - if iterN < 2: - der_NSiter = 0.5 * (der_postCom.bmm(I3 - A) - A.bmm(der_sacleTrace)) - else: - dldY = 0.5 * (der_postCom.bmm(I3 - Y[:, iterN - 2, :, :].bmm(Z[:, iterN - 2, :, :])) - - Z[:, iterN - 2, :, :].bmm(Y[:, iterN - 2, :, :]).bmm(der_postCom)) - dldZ = -0.5 * Y[:, iterN - 2, :, :].bmm(der_postCom).bmm(Y[:, iterN - 2, :, :]) - for i in range(iterN - 3, -1, -1): - YZ = I3 - Y[:, i, :, :].bmm(Z[:, i, :, :]) - ZY = Z[:, i, :, :].bmm(Y[:, i, :, :]) - dldY_ = 0.5 * (dldY.bmm(YZ) - - Z[:, i, :, :].bmm(dldZ).bmm(Z[:, i, :, :]) - - ZY.bmm(dldY)) - dldZ_ = 0.5 * (YZ.bmm(dldZ) - - Y[:, i, :, :].bmm(dldY).bmm(Y[:, i, :, :]) - - dldZ.bmm(ZY)) - dldY = dldY_ - dldZ = dldZ_ - der_NSiter = 0.5 * (dldY.bmm(I3 - A) - dldZ - A.bmm(dldY)) - grad_input = der_NSiter.div(normA.view(batchSize, 1, 1).expand_as(x)) - grad_aux = der_NSiter.mul(x).sum(dim=1).sum(dim=1) - for i in range(batchSize): - grad_input[i, :, :] += (der_postComAux[i] \ - - grad_aux[i] / (normA[i] * normA[i])) \ - * torch.ones(dim, device=x.device).diag() - return grad_input, None - - -def CovpoolLayer(var): - return Covpool.apply(var) - - -def SqrtmLayer(var, iterN): - return Sqrtm.apply(var, iterN) - - -class SOCA(nn.Module): - # second-order Channel attention - def __init__(self, channel, reduction=8): - super(SOCA, self).__init__() - self.max_pool = nn.MaxPool2d(kernel_size=2) - - self.conv_du = nn.Sequential( - nn.Conv2d(channel, channel // reduction, 1, padding=0, bias=True), - nn.ReLU(inplace=True), - nn.Conv2d(channel // reduction, channel, 1, padding=0, bias=True), - nn.Sigmoid() - ) - - def forward(self, x): - batch_size, C, h, w = x.shape # x: NxCxHxW - N = int(h * w) - min_h = min(h, w) - h1 = 1000 - w1 = 1000 - if h < h1 and w < w1: - x_sub = x - elif h < h1 and w > w1: - W = (w - w1) // 2 - x_sub = x[:, :, :, W:(W + w1)] - elif w < w1 and h > h1: - H = (h - h1) // 2 - x_sub = x[:, :, H:H + h1, :] - else: - H = (h - h1) // 2 - W = (w - w1) // 2 - x_sub = x[:, :, H:(H + h1), W:(W + w1)] - cov_mat = CovpoolLayer(x_sub) # Global Covariance pooling layer - cov_mat_sqrt = SqrtmLayer(cov_mat, - 5) # Matrix square root layer( including pre-norm,Newton-Schulz iter. and post-com. with 5 iteration) - cov_mat_sum = torch.mean(cov_mat_sqrt, 1) - cov_mat_sum = cov_mat_sum.view(batch_size, C, 1, 1) - y_cov = self.conv_du(cov_mat_sum) - return y_cov * x - - -# by AI&CV SOCA ####### end ######################### \ No newline at end of file diff --git a/detection/thirdTool/yolo5x_qr/models/experimental.py b/detection/thirdTool/yolo5x_qr/models/experimental.py deleted file mode 100644 index 4f8ccfd..0000000 --- a/detection/thirdTool/yolo5x_qr/models/experimental.py +++ /dev/null @@ -1,111 +0,0 @@ -# YOLOv5 🚀 by Ultralytics, GPL-3.0 license -""" -Experimental modules -""" -import math - -import numpy as np -import torch -import torch.nn as nn - -from ..utils.downloads import attempt_download - - -class Sum(nn.Module): - # Weighted sum of 2 or more layers https://arxiv.org/abs/1911.09070 - def __init__(self, n, weight=False): # n: number of inputs - super().__init__() - self.weight = weight # apply weights boolean - self.iter = range(n - 1) # iter object - if weight: - self.w = nn.Parameter(-torch.arange(1.0, n) / 2, requires_grad=True) # layer weights - - def forward(self, x): - y = x[0] # no weight - if self.weight: - w = torch.sigmoid(self.w) * 2 - for i in self.iter: - y = y + x[i + 1] * w[i] - else: - for i in self.iter: - y = y + x[i + 1] - return y - - -class MixConv2d(nn.Module): - # Mixed Depth-wise Conv https://arxiv.org/abs/1907.09595 - def __init__(self, c1, c2, k=(1, 3), s=1, equal_ch=True): # ch_in, ch_out, kernel, stride, ch_strategy - super().__init__() - n = len(k) # number of convolutions - if equal_ch: # equal c_ per group - i = torch.linspace(0, n - 1E-6, c2).floor() # c2 indices - c_ = [(i == g).sum() for g in range(n)] # intermediate channels - else: # equal weight.numel() per group - b = [c2] + [0] * n - a = np.eye(n + 1, n, k=-1) - a -= np.roll(a, 1, axis=1) - a *= np.array(k) ** 2 - a[0] = 1 - c_ = np.linalg.lstsq(a, b, rcond=None)[0].round() # solve for equal weight indices, ax = b - - self.m = nn.ModuleList([ - nn.Conv2d(c1, int(c_), k, s, k // 2, groups=math.gcd(c1, int(c_)), bias=False) for k, c_ in zip(k, c_)]) - self.bn = nn.BatchNorm2d(c2) - self.act = nn.SiLU() - - def forward(self, x): - return self.act(self.bn(torch.cat([m(x) for m in self.m], 1))) - - -class Ensemble(nn.ModuleList): - # Ensemble of models - def __init__(self): - super().__init__() - - def forward(self, x, augment=False, profile=False, visualize=False): - y = [module(x, augment, profile, visualize)[0] for module in self] - # y = torch.stack(y).max(0)[0] # max ensemble - # y = torch.stack(y).mean(0) # mean ensemble - y = torch.cat(y, 1) # nms ensemble - return y, None # inference, train output - - -def attempt_load(weights, device=None, inplace=True, fuse=True): - # Loads an ensemble of models weights=[a,b,c] or a single model weights=[a] or weights=a - from ..models.yolo import Detect, Model - - model = Ensemble() - for w in weights if isinstance(weights, list) else [weights]: - ckpt = torch.load(attempt_download(w), map_location='cpu') # load - ckpt = (ckpt.get('ema') or ckpt['model']).to(device).float() # FP32 model - - # Model compatibility updates - if not hasattr(ckpt, 'stride'): - ckpt.stride = torch.tensor([32.]) - if hasattr(ckpt, 'names') and isinstance(ckpt.names, (list, tuple)): - ckpt.names = dict(enumerate(ckpt.names)) # convert to dict - - model.append(ckpt.fuse().eval() if fuse and hasattr(ckpt, 'fuse') else ckpt.eval()) # model in eval mode - - # Module compatibility updates - for m in model.modules(): - t = type(m) - if t in (nn.Hardswish, nn.LeakyReLU, nn.ReLU, nn.ReLU6, nn.SiLU, Detect, Model): - m.inplace = inplace # torch 1.7.0 compatibility - if t is Detect and not isinstance(m.anchor_grid, list): - delattr(m, 'anchor_grid') - setattr(m, 'anchor_grid', [torch.zeros(1)] * m.nl) - elif t is nn.Upsample and not hasattr(m, 'recompute_scale_factor'): - m.recompute_scale_factor = None # torch 1.11.0 compatibility - - # Return model - if len(model) == 1: - return model[-1] - - # Return detection ensemble - print(f'Ensemble created with {weights}\n') - for k in 'names', 'nc', 'yaml': - setattr(model, k, getattr(model[0], k)) - model.stride = model[torch.argmax(torch.tensor([m.stride.max() for m in model])).int()].stride # max stride - assert all(model[0].nc == m.nc for m in model), f'Models have different class counts: {[m.nc for m in model]}' - return model diff --git a/detection/thirdTool/yolo5x_qr/models/yolo.py b/detection/thirdTool/yolo5x_qr/models/yolo.py deleted file mode 100644 index 0e724b2..0000000 --- a/detection/thirdTool/yolo5x_qr/models/yolo.py +++ /dev/null @@ -1,396 +0,0 @@ -# YOLOv5 🚀 by Ultralytics, GPL-3.0 license -""" -YOLO-specific modules - -Usage: - $ python models/yolo.py --cfg yolov5s.yaml -""" - -import argparse -import contextlib -import os -import platform -import sys -from copy import deepcopy -from pathlib import Path - -FILE = Path(__file__).resolve() -ROOT = FILE.parents[1] # YOLOv5 root directory -if str(ROOT) not in sys.path: - sys.path.append(str(ROOT)) # add ROOT to PATH -if platform.system() != 'Windows': - ROOT = Path(os.path.relpath(ROOT, Path.cwd())) # relative - -from thirdTool.yolo5x_qr.models.common import * -from thirdTool.yolo5x_qr.models.experimental import * -from thirdTool.yolo5x_qr.utils.autoanchor import check_anchor_order -from thirdTool.yolo5x_qr.utils.general import LOGGER, check_version, check_yaml, make_divisible, print_args -from thirdTool.yolo5x_qr.utils.plots import feature_visualization -from thirdTool.yolo5x_qr.utils.torch_utils import (fuse_conv_and_bn, initialize_weights, model_info, profile, scale_img, select_device, - time_sync) - -try: - import thop # for FLOPs computation -except ImportError: - thop = None - - -class Detect(nn.Module): - # YOLOv5 Detect head for detection models - stride = None # strides computed during build - dynamic = False # force grid reconstruction - export = False # export mode - - def __init__(self, nc=80, anchors=(), ch=(), inplace=True): # detection layer - super().__init__() - self.nc = nc # number of classes - self.no = nc + 5 # number of outputs per anchor - self.nl = len(anchors) # number of detection layers - self.na = len(anchors[0]) // 2 # number of anchors - self.grid = [torch.empty(0) for _ in range(self.nl)] # init grid - self.anchor_grid = [torch.empty(0) for _ in range(self.nl)] # init anchor grid - self.register_buffer('anchors', torch.tensor(anchors).float().view(self.nl, -1, 2)) # shape(nl,na,2) - self.m = nn.ModuleList(nn.Conv2d(x, self.no * self.na, 1) for x in ch) # output conv - self.inplace = inplace # use inplace ops (e.g. slice assignment) - - def forward(self, x): - z = [] # inference output - for i in range(self.nl): - x[i] = self.m[i](x[i]) # conv - bs, _, ny, nx = x[i].shape # x(bs,255,20,20) to x(bs,3,20,20,85) - x[i] = x[i].view(bs, self.na, self.no, ny, nx).permute(0, 1, 3, 4, 2).contiguous() - - if not self.training: # inference - if self.dynamic or self.grid[i].shape[2:4] != x[i].shape[2:4]: - self.grid[i], self.anchor_grid[i] = self._make_grid(nx, ny, i) - - if isinstance(self, Segment): # (boxes + masks) - xy, wh, conf, mask = x[i].split((2, 2, self.nc + 1, self.no - self.nc - 5), 4) - xy = (xy.sigmoid() * 2 + self.grid[i]) * self.stride[i] # xy - wh = (wh.sigmoid() * 2) ** 2 * self.anchor_grid[i] # wh - y = torch.cat((xy, wh, conf.sigmoid(), mask), 4) - else: # Detect (boxes only) - xy, wh, conf = x[i].sigmoid().split((2, 2, self.nc + 1), 4) - xy = (xy * 2 + self.grid[i]) * self.stride[i] # xy - wh = (wh * 2) ** 2 * self.anchor_grid[i] # wh - y = torch.cat((xy, wh, conf), 4) - z.append(y.view(bs, self.na * nx * ny, self.no)) - - return x if self.training else (torch.cat(z, 1),) if self.export else (torch.cat(z, 1), x) - - def _make_grid(self, nx=20, ny=20, i=0, torch_1_10=check_version(torch.__version__, '1.10.0')): - d = self.anchors[i].device - t = self.anchors[i].dtype - shape = 1, self.na, ny, nx, 2 # grid shape - y, x = torch.arange(ny, device=d, dtype=t), torch.arange(nx, device=d, dtype=t) - yv, xv = torch.meshgrid(y, x, indexing='ij') if torch_1_10 else torch.meshgrid(y, x) # torch>=0.7 compatibility - grid = torch.stack((xv, yv), 2).expand(shape) - 0.5 # add grid offset, i.e. y = 2.0 * x - 0.5 - anchor_grid = (self.anchors[i] * self.stride[i]).view((1, self.na, 1, 1, 2)).expand(shape) - return grid, anchor_grid - - -class Segment(Detect): - # YOLOv5 Segment head for segmentation models - def __init__(self, nc=80, anchors=(), nm=32, npr=256, ch=(), inplace=True): - super().__init__(nc, anchors, ch, inplace) - self.nm = nm # number of masks - self.npr = npr # number of protos - self.no = 5 + nc + self.nm # number of outputs per anchor - self.m = nn.ModuleList(nn.Conv2d(x, self.no * self.na, 1) for x in ch) # output conv - self.proto = Proto(ch[0], self.npr, self.nm) # protos - self.detect = Detect.forward - - def forward(self, x): - p = self.proto(x[0]) - x = self.detect(self, x) - return (x, p) if self.training else (x[0], p) if self.export else (x[0], p, x[1]) - - -class BaseModel(nn.Module): - # YOLOv5 base model - def forward(self, x, profile=False, visualize=False): - return self._forward_once(x, profile, visualize) # single-scale inference, train - - def _forward_once(self, x, profile=False, visualize=False): - y, dt = [], [] # outputs - for m in self.model: - if m.f != -1: # if not from previous layer - 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 - if profile: - self._profile_one_layer(m, x, dt) - x = m(x) # run - y.append(x if m.i in self.save else None) # save output - if visualize: - feature_visualization(x, m.type, m.i, save_dir=visualize) - return x - - def _profile_one_layer(self, m, x, dt): - c = m == self.model[-1] # is final layer, copy input as inplace fix - o = thop.profile(m, inputs=(x.copy() if c else x,), verbose=False)[0] / 1E9 * 2 if thop else 0 # FLOPs - t = time_sync() - for _ in range(10): - m(x.copy() if c else x) - dt.append((time_sync() - t) * 100) - if m == self.model[0]: - LOGGER.info(f"{'time (ms)':>10s} {'GFLOPs':>10s} {'params':>10s} module") - LOGGER.info(f'{dt[-1]:10.2f} {o:10.2f} {m.np:10.0f} {m.type}') - if c: - LOGGER.info(f"{sum(dt):10.2f} {'-':>10s} {'-':>10s} Total") - - def fuse(self): # fuse model Conv2d() + BatchNorm2d() layers - LOGGER.info('Fusing layers... ') - for m in self.model.modules(): - if isinstance(m, (Conv, DWConv)) and hasattr(m, 'bn'): - m.conv = fuse_conv_and_bn(m.conv, m.bn) # update conv - delattr(m, 'bn') # remove batchnorm - m.forward = m.forward_fuse # update forward - self.info() - return self - - def info(self, verbose=False, img_size=640): # print model information - model_info(self, verbose, img_size) - - def _apply(self, fn): - # Apply to(), cpu(), cuda(), half() to model tensors that are not parameters or registered buffers - self = super()._apply(fn) - m = self.model[-1] # Detect() - if isinstance(m, (Detect, Segment)): - m.stride = fn(m.stride) - m.grid = list(map(fn, m.grid)) - if isinstance(m.anchor_grid, list): - m.anchor_grid = list(map(fn, m.anchor_grid)) - return self - - -class DetectionModel(BaseModel): - # YOLOv5 detection model - def __init__(self, cfg='yolov5s.yaml', ch=3, nc=None, anchors=None): # model, input channels, number of classes - super().__init__() - if isinstance(cfg, dict): - self.yaml = cfg # model dict - else: # is *.yaml - import yaml # for torch hub - self.yaml_file = Path(cfg).name - with open(cfg, encoding='ascii', errors='ignore') as f: - self.yaml = yaml.safe_load(f) # model dict - - # Define model - ch = self.yaml['ch'] = self.yaml.get('ch', ch) # input channels - if nc and nc != self.yaml['nc']: - LOGGER.info(f"Overriding model.yaml nc={self.yaml['nc']} with nc={nc}") - self.yaml['nc'] = nc # override yaml value - if anchors: - LOGGER.info(f'Overriding model.yaml anchors with anchors={anchors}') - self.yaml['anchors'] = round(anchors) # override yaml value - self.model, self.save = parse_model(deepcopy(self.yaml), ch=[ch]) # model, savelist - self.names = [str(i) for i in range(self.yaml['nc'])] # default names - self.inplace = self.yaml.get('inplace', True) - - # Build strides, anchors - m = self.model[-1] # Detect() - if isinstance(m, (Detect, Segment)): - s = 256 # 2x min stride - m.inplace = self.inplace - forward = lambda x: self.forward(x)[0] if isinstance(m, Segment) else self.forward(x) - m.stride = torch.tensor([s / x.shape[-2] for x in forward(torch.zeros(1, ch, s, s))]) # forward - check_anchor_order(m) - m.anchors /= m.stride.view(-1, 1, 1) - self.stride = m.stride - self._initialize_biases() # only run once - - # Init weights, biases - initialize_weights(self) - self.info() - LOGGER.info('') - - def forward(self, x, augment=False, profile=False, visualize=False): - if augment: - return self._forward_augment(x) # augmented inference, None - return self._forward_once(x, profile, visualize) # single-scale inference, train - - def _forward_augment(self, x): - img_size = x.shape[-2:] # height, width - s = [1, 0.83, 0.67] # scales - f = [None, 3, None] # flips (2-ud, 3-lr) - y = [] # outputs - for si, fi in zip(s, f): - xi = scale_img(x.flip(fi) if fi else x, si, gs=int(self.stride.max())) - yi = self._forward_once(xi)[0] # forward - # cv2.imwrite(f'img_{si}.jpg', 255 * xi[0].cpu().numpy().transpose((1, 2, 0))[:, :, ::-1]) # save - yi = self._descale_pred(yi, fi, si, img_size) - y.append(yi) - y = self._clip_augmented(y) # clip augmented tails - return torch.cat(y, 1), None # augmented inference, train - - def _descale_pred(self, p, flips, scale, img_size): - # de-scale predictions following augmented inference (inverse operation) - if self.inplace: - p[..., :4] /= scale # de-scale - if flips == 2: - p[..., 1] = img_size[0] - p[..., 1] # de-flip ud - elif flips == 3: - p[..., 0] = img_size[1] - p[..., 0] # de-flip lr - else: - x, y, wh = p[..., 0:1] / scale, p[..., 1:2] / scale, p[..., 2:4] / scale # de-scale - if flips == 2: - y = img_size[0] - y # de-flip ud - elif flips == 3: - x = img_size[1] - x # de-flip lr - p = torch.cat((x, y, wh, p[..., 4:]), -1) - return p - - def _clip_augmented(self, y): - # Clip YOLOv5 augmented inference tails - nl = self.model[-1].nl # number of detection layers (P3-P5) - g = sum(4 ** x for x in range(nl)) # grid points - e = 1 # exclude layer count - i = (y[0].shape[1] // g) * sum(4 ** x for x in range(e)) # indices - y[0] = y[0][:, :-i] # large - i = (y[-1].shape[1] // g) * sum(4 ** (nl - 1 - x) for x in range(e)) # indices - y[-1] = y[-1][:, i:] # small - return y - - def _initialize_biases(self, cf=None): # initialize biases into Detect(), cf is class frequency - # https://arxiv.org/abs/1708.02002 section 3.3 - # cf = torch.bincount(torch.tensor(np.concatenate(dataset.labels, 0)[:, 0]).long(), minlength=nc) + 1. - m = self.model[-1] # Detect() module - for mi, s in zip(m.m, m.stride): # from - b = mi.bias.view(m.na, -1) # conv.bias(255) to (3,85) - b.data[:, 4] += math.log(8 / (640 / s) ** 2) # obj (8 objects per 640 image) - b.data[:, 5:5 + m.nc] += math.log(0.6 / (m.nc - 0.99999)) if cf is None else torch.log(cf / cf.sum()) # cls - mi.bias = torch.nn.Parameter(b.view(-1), requires_grad=True) - - -Model = DetectionModel # retain YOLOv5 'Model' class for backwards compatibility - - -class SegmentationModel(DetectionModel): - # YOLOv5 segmentation model - def __init__(self, cfg='yolov5s-seg.yaml', ch=3, nc=None, anchors=None): - super().__init__(cfg, ch, nc, anchors) - - -class ClassificationModel(BaseModel): - # YOLOv5 classification model - def __init__(self, cfg=None, model=None, nc=1000, cutoff=10): # yaml, model, number of classes, cutoff index - super().__init__() - self._from_detection_model(model, nc, cutoff) if model is not None else self._from_yaml(cfg) - - def _from_detection_model(self, model, nc=1000, cutoff=10): - # Create a YOLOv5 classification model from a YOLOv5 detection model - if isinstance(model, DetectMultiBackend): - model = model.model # unwrap DetectMultiBackend - model.model = model.model[:cutoff] # backbone - m = model.model[-1] # last layer - ch = m.conv.in_channels if hasattr(m, 'conv') else m.cv1.conv.in_channels # ch into module - c = Classify(ch, nc) # Classify() - c.i, c.f, c.type = m.i, m.f, 'models.common.Classify' # index, from, type - model.model[-1] = c # replace - self.model = model.model - self.stride = model.stride - self.save = [] - self.nc = nc - - def _from_yaml(self, cfg): - # Create a YOLOv5 classification model from a *.yaml file - self.model = None - - -def parse_model(d, ch): # model_dict, input_channels(3) - # Parse a YOLOv5 model.yaml dictionary - LOGGER.info(f"\n{'':>3}{'from':>18}{'n':>3}{'params':>10} {'module':<40}{'arguments':<30}") - anchors, nc, gd, gw, act = d['anchors'], d['nc'], d['depth_multiple'], d['width_multiple'], d.get('activation') - if act: - Conv.default_act = eval(act) # redefine default activation, i.e. Conv.default_act = nn.SiLU() - LOGGER.info(f"{colorstr('activation:')} {act}") # print - na = (len(anchors[0]) // 2) if isinstance(anchors, list) else anchors # number of anchors - no = na * (nc + 5) # number of outputs = anchors * (classes + 5) - - layers, save, c2 = [], [], ch[-1] # layers, savelist, ch out - for i, (f, n, m, args) in enumerate(d['backbone'] + d['head']): # from, number, module, args - m = eval(m) if isinstance(m, str) else m # eval strings - for j, a in enumerate(args): - with contextlib.suppress(NameError): - args[j] = eval(a) if isinstance(a, str) else a # eval strings - - n = n_ = max(round(n * gd), 1) if n > 1 else n # depth gain - if m in { - Conv, GhostConv, Bottleneck, GhostBottleneck, SPP, SPPF, DWConv, MixConv2d, Focus, CrossConv, - BottleneckCSP, C3, C3TR, C3SPP, C3Ghost, nn.ConvTranspose2d, DWConvTranspose2d, C3x, EVCBlock, ODConv_3rd, ConvNextBlock}: - c1, c2 = ch[f], args[0] - if c2 != no: # if not output - c2 = make_divisible(c2 * gw, 8) - - args = [c1, c2, *args[1:]] - if m in {BottleneckCSP, C3, C3TR, C3Ghost, C3x, EVCBlock}: - args.insert(2, n) # number of repeats - n = 1 - elif m is nn.BatchNorm2d: - args = [ch[f]] - elif m is Concat: - c2 = sum(ch[x] for x in f) - # TODO: channel, gw, gd - elif m in {Detect, Segment}: - args.append([ch[x] for x in f]) - if isinstance(args[1], int): # number of anchors - args[1] = [list(range(args[1] * 2))] * len(f) - if m is Segment: - args[3] = make_divisible(args[3] * gw, 8) - elif m is Contract: - c2 = ch[f] * args[0] ** 2 - elif m is Expand: - c2 = ch[f] // args[0] ** 2 - elif m is SOCA: - c1, c2 = ch[f], args[0] - if c2 != no: - c2 = make_divisible(c2 * gw, 8) - args = [c1, *args[1:]] - else: - c2 = ch[f] - - m_ = nn.Sequential(*(m(*args) for _ in range(n))) if n > 1 else m(*args) # module - t = str(m)[8:-2].replace('__main__.', '') # module type - np = sum(x.numel() for x in m_.parameters()) # number params - m_.i, m_.f, m_.type, m_.np = i, f, t, np # attach index, 'from' index, type, number params - LOGGER.info(f'{i:>3}{str(f):>18}{n_:>3}{np:10.0f} {t:<40}{str(args):<30}') # print - save.extend(x % i for x in ([f] if isinstance(f, int) else f) if x != -1) # append to savelist - layers.append(m_) - if i == 0: - ch = [] - ch.append(c2) - return nn.Sequential(*layers), sorted(save) - - -if __name__ == '__main__': - parser = argparse.ArgumentParser() - parser.add_argument('--cfg', type=str, default='yolov5s.yaml', help='model.yaml') - parser.add_argument('--batch-size', type=int, default=1, help='total batch size for all GPUs') - parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu') - parser.add_argument('--profile', action='store_true', help='profile model speed') - parser.add_argument('--line-profile', action='store_true', help='profile model speed layer by layer') - parser.add_argument('--test', action='store_true', help='test all yolo*.yaml') - opt = parser.parse_args() - opt.cfg = check_yaml(opt.cfg) # check YAML - print_args(vars(opt)) - device = select_device(opt.device) - - # Create model - im = torch.rand(opt.batch_size, 3, 640, 640).to(device) - model = Model(opt.cfg).to(device) - - # Options - if opt.line_profile: # profile layer by layer - model(im, profile=True) - - elif opt.profile: # profile forward-backward - results = profile(input=im, ops=[model], n=3) - - elif opt.test: # test all models - for cfg in Path(ROOT / 'models').rglob('yolo*.yaml'): - try: - _ = Model(cfg) - except Exception as e: - print(f'Error in {cfg}: {e}') - - else: # report fused model summary - model.fuse() diff --git a/detection/thirdTool/yolo5x_qr/torch-demo.py b/detection/thirdTool/yolo5x_qr/torch-demo.py deleted file mode 100644 index 2233387..0000000 --- a/detection/thirdTool/yolo5x_qr/torch-demo.py +++ /dev/null @@ -1,151 +0,0 @@ -# YOLOv5 🚀 by Ultralytics, GPL-3.0 license -""" -Run YOLOv5 detection inference on images, videos, directories, globs, YouTube, webcam, streams, etc. - -Usage - sources: - $ python detect.py --weights yolov5s.pt --source 0 # webcam - img.jpg # image - vid.mp4 # video - screen # screenshot - path/ # directory - list.txt # list of images - list.streams # list of streams - 'path/*.jpg' # glob - 'https://youtu.be/Zgi9g1ksQHc' # YouTube - 'rtsp://example.com/media.mp4' # RTSP, RTMP, HTTP stream - -Usage - formats: - $ python detect.py --weights yolov5s.pt # PyTorch - yolov5s.torchscript # TorchScript - yolov5s.onnx # ONNX Runtime or OpenCV DNN with --dnn - yolov5s_openvino_model # OpenVINO - yolov5s.engine # TensorRT - yolov5s.mlmodel # CoreML (macOS-only) - yolov5s_saved_model # TensorFlow SavedModel - yolov5s.pb # TensorFlow GraphDef - yolov5s.tflite # TensorFlow Lite - yolov5s_edgetpu.tflite # TensorFlow Edge TPU - yolov5s_paddle_model # PaddlePaddle -""" - - -import os -import sys -from pathlib import Path - -import torch -import numpy as np -from PIL import Image - -FILE = Path(__file__).resolve() -ROOT = FILE.parents[0] # YOLOv5 root directory -if str(ROOT) not in sys.path: - sys.path.append(str(ROOT)) # add ROOT to PATH -ROOT = Path(os.path.relpath(ROOT, Path.cwd())) # relative - -from models.common import DetectMultiBackend -from utils.general import (LOGGER, Profile, check_img_size, cv2, - non_max_suppression, scale_boxes) -from utils.augmentations import (letterbox) -from utils.torch_utils import select_device, smart_inference_mode - - -@smart_inference_mode() -def run( - model = '', # model path or triton URL - imagePath ='', # file/dir/URL/glob/screen/0(webcam) - imgsz=(512, 512), # inference size (height, width) - conf_thres=0.25, # confidence threshold - iou_thres=0.45, # NMS IOU threshold - max_det=1, # maximum detections per image - save_crop=True, # save cropped prediction boxes - save_dir='data/QR2023_roi/images/', # 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 -): - - if save_crop: - # Directories - Path(save_dir).mkdir(parents=True, exist_ok=True) - - stride, names, pt = model.stride, model.names, model.pt - imgsz = check_img_size(imgsz, s=stride) # check image size - im0 = cv2.imread(imagePath) # BGR - im = letterbox(im0, imgsz, stride=32, auto=True)[0] # padded resize - im = im.transpose((2, 0, 1))[::-1] # HWC to CHW, BGR to RGB - im = np.ascontiguousarray(im) # contiguous - p = imagePath - - bs = 1 - # Run inference - model.warmup(imgsz=(1 if pt or model.triton else bs, 3, *imgsz)) # warmup - seen, windows, dt = 0, [], (Profile(), Profile(), Profile()) - - with dt[0]: - im = torch.from_numpy(im).to(model.device) - im = im.half() if model.fp16 else im.float() # uint8 to fp16/32 - im /= 255 # 0 - 255 to 0.0 - 1.0 - if len(im.shape) == 3: - im = im[None] # expand for batch dim - - # Inference - with dt[1]: - visualize = False - pred = model(im, augment=augment, visualize=visualize) - - # NMS - with dt[2]: - pred = non_max_suppression(pred, conf_thres, iou_thres, classes, agnostic_nms, max_det=max_det) - - - # Process predictions - for i, det in enumerate(pred): # per image - - p = Path(p) - save_path = os.path.join(save_dir, p.name) - - if len(det): - # Rescale boxes from img_size to im0 size - det[:, :4] = scale_boxes(im.shape[2:], det[:, :4], im0.shape).round() - - # Write results - a = det[:, :4] - b = det[:,4:5] - for *xyxy, conf, cls in reversed(det): - - x_min, y_min, x_max, y_max = xyxy[:4] - x_min, y_min, x_max, y_max = int(x_min), int(y_min), int(x_max), int(y_max) - quarter_width = (x_max - x_min) // 2 - quarter_height = (y_max - y_min) // 2 - - # Save results (image with detections) - if save_crop: - # 以左上顶点坐标为原点截图4分之一原图 - # Convert im0 (NumPy array) to PIL image - im0 = Image.fromarray(np.uint8(im0)) - cropped_im = im0.crop((x_min, y_min, x_min + quarter_width, y_min + quarter_height)) - cropped_im.save(save_path) - - # Print time (inference-only) - LOGGER.info(f"{p}{'' if len(det) else '(no detections), '}{dt[1].dt * 1E3:.1f}ms") - - -if __name__ == '__main__': - # check_requirements(exclude=('tensorboard', 'thop')) - meta_info = '/project/dataset/QR2023/terminal-box/meta_info_big_box_terminal.txt' - with open(meta_info) as fin: - paths = [line.strip() for line in fin] - data_len = len(paths) - - - # Load model - weights = '/project/yolov5-qr/runs/train_QR/exp10/weights/qr_roi_cloud_detect_20230831.pt' - device = '4' - device = select_device(device) - qrbox_model = DetectMultiBackend(weights, device=device) - - - for index in range(0, data_len): - imagePath = paths[index] - run(model=qrbox_model, imagePath=imagePath) diff --git a/detection/thirdTool/yolo5x_qr/utils/__init__.py b/detection/thirdTool/yolo5x_qr/utils/__init__.py deleted file mode 100644 index 24e137c..0000000 --- a/detection/thirdTool/yolo5x_qr/utils/__init__.py +++ /dev/null @@ -1,82 +0,0 @@ -# YOLOv5 🚀 by Ultralytics, GPL-3.0 license -""" -utils/initialization -""" - -import contextlib -import platform -import threading - - -def emojis(str=''): - # Return platform-dependent emoji-safe version of string - return str.encode().decode('ascii', 'ignore') if platform.system() == 'Windows' else str - - -class TryExcept(contextlib.ContextDecorator): - # YOLOv5 TryExcept class. Usage: @TryExcept() decorator or 'with TryExcept():' context manager - def __init__(self, msg=''): - self.msg = msg - - def __enter__(self): - pass - - def __exit__(self, exc_type, value, traceback): - if value: - print(emojis(f"{self.msg}{': ' if self.msg else ''}{value}")) - return True - - -def threaded(func): - # Multi-threads a target function and returns thread. Usage: @threaded decorator - def wrapper(*args, **kwargs): - thread = threading.Thread(target=func, args=args, kwargs=kwargs, daemon=True) - thread.start() - return thread - - return wrapper - - -def join_threads(verbose=False): - # Join all daemon threads, i.e. atexit.register(lambda: join_threads()) - main_thread = threading.current_thread() - for t in threading.enumerate(): - if t is not main_thread: - if verbose: - print(f'Joining thread {t.name}') - t.join() - - -def notebook_init(verbose=True): - # Check system software and hardware - print('Checking setup...') - - import os - import shutil - - from ..utils.general import check_font, is_colab - from ..utils.torch_utils import select_device # imports - - check_font() - - # import psutil - - if is_colab(): - shutil.rmtree('/content/sample_data', ignore_errors=True) # remove colab /sample_data directory - - # System info - display = None - if verbose: - gb = 1 << 30 # bytes to GiB (1024 ** 3) - ram = psutil.virtual_memory().total - total, used, free = shutil.disk_usage('/') - with contextlib.suppress(Exception): # clear display if ipython is installed - from IPython import display - display.clear_output() - s = f'({os.cpu_count()} CPUs, {ram / gb:.1f} GB RAM, {(total - free) / gb:.1f}/{total / gb:.1f} GB disk)' - else: - s = '' - - select_device(newline=False) - print(emojis(f'Setup complete ✅ {s}')) - return display diff --git a/detection/thirdTool/yolo5x_qr/utils/augmentations.py b/detection/thirdTool/yolo5x_qr/utils/augmentations.py deleted file mode 100644 index b8f84a0..0000000 --- a/detection/thirdTool/yolo5x_qr/utils/augmentations.py +++ /dev/null @@ -1,397 +0,0 @@ -# YOLOv5 🚀 by Ultralytics, GPL-3.0 license -""" -Image augmentation functions -""" - -import math -import random - -import cv2 -import numpy as np -import torch -import torchvision.transforms as T -import torchvision.transforms.functional as TF - -from ..utils.general import LOGGER, check_version, colorstr, resample_segments, segment2box, xywhn2xyxy -from ..utils.metrics import bbox_ioa - -IMAGENET_MEAN = 0.485, 0.456, 0.406 # RGB mean -IMAGENET_STD = 0.229, 0.224, 0.225 # RGB standard deviation - - -class Albumentations: - # YOLOv5 Albumentations class (optional, only used if package is installed) - def __init__(self, size=640): - self.transform = None - prefix = colorstr('albumentations: ') - try: - import albumentations as A - check_version(A.__version__, '1.0.3', hard=True) # version requirement - - T = [ - A.RandomResizedCrop(height=size, width=size, scale=(0.8, 1.0), ratio=(0.9, 1.11), p=0.0), - A.Blur(p=0.01), - A.MedianBlur(p=0.01), - A.ToGray(p=0.01), - A.CLAHE(p=0.01), - A.RandomBrightnessContrast(p=0.0), - A.RandomGamma(p=0.0), - A.ImageCompression(quality_lower=75, p=0.0)] # transforms - self.transform = A.Compose(T, bbox_params=A.BboxParams(format='yolo', label_fields=['class_labels'])) - - LOGGER.info(prefix + ', '.join(f'{x}'.replace('always_apply=False, ', '') for x in T if x.p)) - except ImportError: # package not installed, skip - pass - except Exception as e: - LOGGER.info(f'{prefix}{e}') - - def __call__(self, im, labels, p=1.0): - if self.transform and random.random() < p: - new = self.transform(image=im, bboxes=labels[:, 1:], class_labels=labels[:, 0]) # transformed - im, labels = new['image'], np.array([[c, *b] for c, b in zip(new['class_labels'], new['bboxes'])]) - return im, labels - - -def normalize(x, mean=IMAGENET_MEAN, std=IMAGENET_STD, inplace=False): - # Denormalize RGB images x per ImageNet stats in BCHW format, i.e. = (x - mean) / std - return TF.normalize(x, mean, std, inplace=inplace) - - -def denormalize(x, mean=IMAGENET_MEAN, std=IMAGENET_STD): - # Denormalize RGB images x per ImageNet stats in BCHW format, i.e. = x * std + mean - for i in range(3): - x[:, i] = x[:, i] * std[i] + mean[i] - return x - - -def augment_hsv(im, hgain=0.5, sgain=0.5, vgain=0.5): - # HSV color-space augmentation - if hgain or sgain or vgain: - r = np.random.uniform(-1, 1, 3) * [hgain, sgain, vgain] + 1 # random gains - hue, sat, val = cv2.split(cv2.cvtColor(im, cv2.COLOR_BGR2HSV)) - dtype = im.dtype # uint8 - - x = np.arange(0, 256, dtype=r.dtype) - lut_hue = ((x * r[0]) % 180).astype(dtype) - lut_sat = np.clip(x * r[1], 0, 255).astype(dtype) - lut_val = np.clip(x * r[2], 0, 255).astype(dtype) - - im_hsv = cv2.merge((cv2.LUT(hue, lut_hue), cv2.LUT(sat, lut_sat), cv2.LUT(val, lut_val))) - cv2.cvtColor(im_hsv, cv2.COLOR_HSV2BGR, dst=im) # no return needed - - -def hist_equalize(im, clahe=True, bgr=False): - # Equalize histogram on BGR image 'im' with im.shape(n,m,3) and range 0-255 - yuv = cv2.cvtColor(im, cv2.COLOR_BGR2YUV if bgr else cv2.COLOR_RGB2YUV) - if clahe: - c = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8)) - yuv[:, :, 0] = c.apply(yuv[:, :, 0]) - else: - yuv[:, :, 0] = cv2.equalizeHist(yuv[:, :, 0]) # equalize Y channel histogram - return cv2.cvtColor(yuv, cv2.COLOR_YUV2BGR if bgr else cv2.COLOR_YUV2RGB) # convert YUV image to RGB - - -def replicate(im, labels): - # Replicate labels - h, w = im.shape[:2] - boxes = labels[:, 1:].astype(int) - x1, y1, x2, y2 = boxes.T - s = ((x2 - x1) + (y2 - y1)) / 2 # side length (pixels) - for i in s.argsort()[:round(s.size * 0.5)]: # smallest indices - x1b, y1b, x2b, y2b = boxes[i] - bh, bw = y2b - y1b, x2b - x1b - yc, xc = int(random.uniform(0, h - bh)), int(random.uniform(0, w - bw)) # offset x, y - x1a, y1a, x2a, y2a = [xc, yc, xc + bw, yc + bh] - im[y1a:y2a, x1a:x2a] = im[y1b:y2b, x1b:x2b] # im4[ymin:ymax, xmin:xmax] - labels = np.append(labels, [[labels[i, 0], x1a, y1a, x2a, y2a]], axis=0) - - return im, labels - - -def letterbox(im, new_shape=(640, 640), color=(114, 114, 114), auto=True, scaleFill=False, scaleup=True, stride=32): - # Resize and pad image while meeting stride-multiple constraints - shape = im.shape[:2] # current shape [height, width] - if isinstance(new_shape, int): - new_shape = (new_shape, new_shape) - - # Scale ratio (new / old) - r = min(new_shape[0] / shape[0], new_shape[1] / shape[1]) - if not scaleup: # only scale down, do not scale up (for better val mAP) - r = min(r, 1.0) - - # Compute padding - ratio = r, r # width, height ratios - new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r)) - dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1] # wh padding - if auto: # minimum rectangle - dw, dh = np.mod(dw, stride), np.mod(dh, stride) # wh padding - elif scaleFill: # stretch - dw, dh = 0.0, 0.0 - new_unpad = (new_shape[1], new_shape[0]) - ratio = new_shape[1] / shape[1], new_shape[0] / shape[0] # width, height ratios - - dw /= 2 # divide padding into 2 sides - dh /= 2 - - if shape[::-1] != new_unpad: # resize - im = cv2.resize(im, new_unpad, interpolation=cv2.INTER_LINEAR) - top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1)) - left, right = int(round(dw - 0.1)), int(round(dw + 0.1)) - im = cv2.copyMakeBorder(im, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color) # add border - return im, ratio, (dw, dh) - - -def random_perspective(im, - targets=(), - segments=(), - degrees=10, - translate=.1, - scale=.1, - shear=10, - perspective=0.0, - border=(0, 0)): - # torchvision.transforms.RandomAffine(degrees=(-10, 10), translate=(0.1, 0.1), scale=(0.9, 1.1), shear=(-10, 10)) - # targets = [cls, xyxy] - - height = im.shape[0] + border[0] * 2 # shape(h,w,c) - width = im.shape[1] + border[1] * 2 - - # Center - C = np.eye(3) - C[0, 2] = -im.shape[1] / 2 # x translation (pixels) - C[1, 2] = -im.shape[0] / 2 # y translation (pixels) - - # Perspective - P = np.eye(3) - P[2, 0] = random.uniform(-perspective, perspective) # x perspective (about y) - P[2, 1] = random.uniform(-perspective, perspective) # y perspective (about x) - - # Rotation and Scale - R = np.eye(3) - a = random.uniform(-degrees, degrees) - # a += random.choice([-180, -90, 0, 90]) # add 90deg rotations to small rotations - s = random.uniform(1 - scale, 1 + scale) - # s = 2 ** random.uniform(-scale, scale) - R[:2] = cv2.getRotationMatrix2D(angle=a, center=(0, 0), scale=s) - - # Shear - S = np.eye(3) - S[0, 1] = math.tan(random.uniform(-shear, shear) * math.pi / 180) # x shear (deg) - S[1, 0] = math.tan(random.uniform(-shear, shear) * math.pi / 180) # y shear (deg) - - # Translation - T = np.eye(3) - T[0, 2] = random.uniform(0.5 - translate, 0.5 + translate) * width # x translation (pixels) - T[1, 2] = random.uniform(0.5 - translate, 0.5 + translate) * height # y translation (pixels) - - # Combined rotation matrix - M = T @ S @ R @ P @ C # order of operations (right to left) is IMPORTANT - if (border[0] != 0) or (border[1] != 0) or (M != np.eye(3)).any(): # image changed - if perspective: - im = cv2.warpPerspective(im, M, dsize=(width, height), borderValue=(114, 114, 114)) - else: # affine - im = cv2.warpAffine(im, M[:2], dsize=(width, height), borderValue=(114, 114, 114)) - - # Visualize - # import matplotlib.pyplot as plt - # ax = plt.subplots(1, 2, figsize=(12, 6))[1].ravel() - # ax[0].imshow(im[:, :, ::-1]) # base - # ax[1].imshow(im2[:, :, ::-1]) # warped - - # Transform label coordinates - n = len(targets) - if n: - use_segments = any(x.any() for x in segments) and len(segments) == n - new = np.zeros((n, 4)) - if use_segments: # warp segments - segments = resample_segments(segments) # upsample - for i, segment in enumerate(segments): - xy = np.ones((len(segment), 3)) - xy[:, :2] = segment - xy = xy @ M.T # transform - xy = xy[:, :2] / xy[:, 2:3] if perspective else xy[:, :2] # perspective rescale or affine - - # clip - new[i] = segment2box(xy, width, height) - - else: # warp boxes - xy = np.ones((n * 4, 3)) - xy[:, :2] = targets[:, [1, 2, 3, 4, 1, 4, 3, 2]].reshape(n * 4, 2) # x1y1, x2y2, x1y2, x2y1 - xy = xy @ M.T # transform - xy = (xy[:, :2] / xy[:, 2:3] if perspective else xy[:, :2]).reshape(n, 8) # perspective rescale or affine - - # create new boxes - x = xy[:, [0, 2, 4, 6]] - y = xy[:, [1, 3, 5, 7]] - new = np.concatenate((x.min(1), y.min(1), x.max(1), y.max(1))).reshape(4, n).T - - # clip - new[:, [0, 2]] = new[:, [0, 2]].clip(0, width) - new[:, [1, 3]] = new[:, [1, 3]].clip(0, height) - - # filter candidates - i = box_candidates(box1=targets[:, 1:5].T * s, box2=new.T, area_thr=0.01 if use_segments else 0.10) - targets = targets[i] - targets[:, 1:5] = new[i] - - return im, targets - - -def copy_paste(im, labels, segments, p=0.5): - # Implement Copy-Paste augmentation https://arxiv.org/abs/2012.07177, labels as nx5 np.array(cls, xyxy) - n = len(segments) - if p and n: - h, w, c = im.shape # height, width, channels - im_new = np.zeros(im.shape, np.uint8) - for j in random.sample(range(n), k=round(p * n)): - l, s = labels[j], segments[j] - box = w - l[3], l[2], w - l[1], l[4] - ioa = bbox_ioa(box, labels[:, 1:5]) # intersection over area - if (ioa < 0.30).all(): # allow 30% obscuration of existing labels - labels = np.concatenate((labels, [[l[0], *box]]), 0) - segments.append(np.concatenate((w - s[:, 0:1], s[:, 1:2]), 1)) - cv2.drawContours(im_new, [segments[j].astype(np.int32)], -1, (1, 1, 1), cv2.FILLED) - - result = cv2.flip(im, 1) # augment segments (flip left-right) - i = cv2.flip(im_new, 1).astype(bool) - im[i] = result[i] # cv2.imwrite('debug.jpg', im) # debug - - return im, labels, segments - - -def cutout(im, labels, p=0.5): - # Applies image cutout augmentation https://arxiv.org/abs/1708.04552 - if random.random() < p: - h, w = im.shape[:2] - scales = [0.5] * 1 + [0.25] * 2 + [0.125] * 4 + [0.0625] * 8 + [0.03125] * 16 # image size fraction - for s in scales: - mask_h = random.randint(1, int(h * s)) # create random masks - mask_w = random.randint(1, int(w * s)) - - # box - xmin = max(0, random.randint(0, w) - mask_w // 2) - ymin = max(0, random.randint(0, h) - mask_h // 2) - xmax = min(w, xmin + mask_w) - ymax = min(h, ymin + mask_h) - - # apply random color mask - im[ymin:ymax, xmin:xmax] = [random.randint(64, 191) for _ in range(3)] - - # return unobscured labels - if len(labels) and s > 0.03: - box = np.array([xmin, ymin, xmax, ymax], dtype=np.float32) - ioa = bbox_ioa(box, xywhn2xyxy(labels[:, 1:5], w, h)) # intersection over area - labels = labels[ioa < 0.60] # remove >60% obscured labels - - return labels - - -def mixup(im, labels, im2, labels2): - # Applies MixUp augmentation https://arxiv.org/pdf/1710.09412.pdf - r = np.random.beta(32.0, 32.0) # mixup ratio, alpha=beta=32.0 - im = (im * r + im2 * (1 - r)).astype(np.uint8) - labels = np.concatenate((labels, labels2), 0) - return im, labels - - -def box_candidates(box1, box2, wh_thr=2, ar_thr=100, area_thr=0.1, eps=1e-16): # box1(4,n), box2(4,n) - # Compute candidate boxes: box1 before augment, box2 after augment, wh_thr (pixels), aspect_ratio_thr, area_ratio - w1, h1 = box1[2] - box1[0], box1[3] - box1[1] - w2, h2 = box2[2] - box2[0], box2[3] - box2[1] - ar = np.maximum(w2 / (h2 + eps), h2 / (w2 + eps)) # aspect ratio - return (w2 > wh_thr) & (h2 > wh_thr) & (w2 * h2 / (w1 * h1 + eps) > area_thr) & (ar < ar_thr) # candidates - - -def classify_albumentations( - augment=True, - size=224, - scale=(0.08, 1.0), - ratio=(0.75, 1.0 / 0.75), # 0.75, 1.33 - hflip=0.5, - vflip=0.0, - jitter=0.4, - mean=IMAGENET_MEAN, - std=IMAGENET_STD, - auto_aug=False): - # YOLOv5 classification Albumentations (optional, only used if package is installed) - prefix = colorstr('albumentations: ') - try: - import albumentations as A - from albumentations.pytorch import ToTensorV2 - check_version(A.__version__, '1.0.3', hard=True) # version requirement - if augment: # Resize and crop - T = [A.RandomResizedCrop(height=size, width=size, scale=scale, ratio=ratio)] - if auto_aug: - # TODO: implement AugMix, AutoAug & RandAug in albumentation - LOGGER.info(f'{prefix}auto augmentations are currently not supported') - else: - if hflip > 0: - T += [A.HorizontalFlip(p=hflip)] - if vflip > 0: - T += [A.VerticalFlip(p=vflip)] - if jitter > 0: - color_jitter = (float(jitter),) * 3 # repeat value for brightness, contrast, satuaration, 0 hue - T += [A.ColorJitter(*color_jitter, 0)] - else: # Use fixed crop for eval set (reproducibility) - T = [A.SmallestMaxSize(max_size=size), A.CenterCrop(height=size, width=size)] - T += [A.Normalize(mean=mean, std=std), ToTensorV2()] # Normalize and convert to Tensor - LOGGER.info(prefix + ', '.join(f'{x}'.replace('always_apply=False, ', '') for x in T if x.p)) - return A.Compose(T) - - except ImportError: # package not installed, skip - LOGGER.warning(f'{prefix}⚠️ not found, install with `pip install albumentations` (recommended)') - except Exception as e: - LOGGER.info(f'{prefix}{e}') - - -def classify_transforms(size=224): - # Transforms to apply if albumentations not installed - assert isinstance(size, int), f'ERROR: classify_transforms size {size} must be integer, not (list, tuple)' - # T.Compose([T.ToTensor(), T.Resize(size), T.CenterCrop(size), T.Normalize(IMAGENET_MEAN, IMAGENET_STD)]) - return T.Compose([CenterCrop(size), ToTensor(), T.Normalize(IMAGENET_MEAN, IMAGENET_STD)]) - - -class LetterBox: - # YOLOv5 LetterBox class for image preprocessing, i.e. T.Compose([LetterBox(size), ToTensor()]) - def __init__(self, size=(640, 640), auto=False, stride=32): - super().__init__() - self.h, self.w = (size, size) if isinstance(size, int) else size - self.auto = auto # pass max size integer, automatically solve for short side using stride - self.stride = stride # used with auto - - def __call__(self, im): # im = np.array HWC - imh, imw = im.shape[:2] - r = min(self.h / imh, self.w / imw) # ratio of new/old - h, w = round(imh * r), round(imw * r) # resized image - hs, ws = (math.ceil(x / self.stride) * self.stride for x in (h, w)) if self.auto else self.h, self.w - top, left = round((hs - h) / 2 - 0.1), round((ws - w) / 2 - 0.1) - im_out = np.full((self.h, self.w, 3), 114, dtype=im.dtype) - im_out[top:top + h, left:left + w] = cv2.resize(im, (w, h), interpolation=cv2.INTER_LINEAR) - return im_out - - -class CenterCrop: - # YOLOv5 CenterCrop class for image preprocessing, i.e. T.Compose([CenterCrop(size), ToTensor()]) - def __init__(self, size=640): - super().__init__() - self.h, self.w = (size, size) if isinstance(size, int) else size - - def __call__(self, im): # im = np.array HWC - imh, imw = im.shape[:2] - m = min(imh, imw) # min dimension - top, left = (imh - m) // 2, (imw - m) // 2 - return cv2.resize(im[top:top + m, left:left + m], (self.w, self.h), interpolation=cv2.INTER_LINEAR) - - -class ToTensor: - # YOLOv5 ToTensor class for image preprocessing, i.e. T.Compose([LetterBox(size), ToTensor()]) - def __init__(self, half=False): - super().__init__() - self.half = half - - def __call__(self, im): # im = np.array HWC in BGR order - im = np.ascontiguousarray(im.transpose((2, 0, 1))[::-1]) # HWC to CHW -> BGR to RGB -> contiguous - im = torch.from_numpy(im) # to torch - im = im.half() if self.half else im.float() # uint8 to fp16/32 - im /= 255.0 # 0-255 to 0.0-1.0 - return im diff --git a/detection/thirdTool/yolo5x_qr/utils/autoanchor.py b/detection/thirdTool/yolo5x_qr/utils/autoanchor.py deleted file mode 100644 index 027d1f7..0000000 --- a/detection/thirdTool/yolo5x_qr/utils/autoanchor.py +++ /dev/null @@ -1,169 +0,0 @@ -# YOLOv5 🚀 by Ultralytics, GPL-3.0 license -""" -AutoAnchor utils -""" - -import random - -import numpy as np -import torch -import yaml -from tqdm import tqdm - -from ..utils import TryExcept -from ..utils.general import LOGGER, TQDM_BAR_FORMAT, colorstr - -PREFIX = colorstr('AutoAnchor: ') - - -def check_anchor_order(m): - # Check anchor order against stride order for YOLOv5 Detect() module m, and correct if necessary - a = m.anchors.prod(-1).mean(-1).view(-1) # mean anchor area per output layer - da = a[-1] - a[0] # delta a - ds = m.stride[-1] - m.stride[0] # delta s - if da and (da.sign() != ds.sign()): # same order - LOGGER.info(f'{PREFIX}Reversing anchor order') - m.anchors[:] = m.anchors.flip(0) - - -@TryExcept(f'{PREFIX}ERROR') -def check_anchors(dataset, model, thr=4.0, imgsz=640): - # Check anchor fit to data, recompute if necessary - m = model.module.model[-1] if hasattr(model, 'module') else model.model[-1] # Detect() - shapes = imgsz * dataset.shapes / dataset.shapes.max(1, keepdims=True) - scale = np.random.uniform(0.9, 1.1, size=(shapes.shape[0], 1)) # augment scale - wh = torch.tensor(np.concatenate([l[:, 3:5] * s for s, l in zip(shapes * scale, dataset.labels)])).float() # wh - - def metric(k): # compute metric - r = wh[:, None] / k[None] - x = torch.min(r, 1 / r).min(2)[0] # ratio metric - best = x.max(1)[0] # best_x - aat = (x > 1 / thr).float().sum(1).mean() # anchors above threshold - bpr = (best > 1 / thr).float().mean() # best possible recall - return bpr, aat - - stride = m.stride.to(m.anchors.device).view(-1, 1, 1) # model strides - anchors = m.anchors.clone() * stride # current anchors - bpr, aat = metric(anchors.cpu().view(-1, 2)) - s = f'\n{PREFIX}{aat:.2f} anchors/target, {bpr:.3f} Best Possible Recall (BPR). ' - if bpr > 0.98: # threshold to recompute - LOGGER.info(f'{s}Current anchors are a good fit to dataset ✅') - else: - LOGGER.info(f'{s}Anchors are a poor fit to dataset ⚠️, attempting to improve...') - na = m.anchors.numel() // 2 # number of anchors - anchors = kmean_anchors(dataset, n=na, img_size=imgsz, thr=thr, gen=1000, verbose=False) - new_bpr = metric(anchors)[0] - if new_bpr > bpr: # replace anchors - anchors = torch.tensor(anchors, device=m.anchors.device).type_as(m.anchors) - m.anchors[:] = anchors.clone().view_as(m.anchors) - check_anchor_order(m) # must be in pixel-space (not grid-space) - m.anchors /= stride - s = f'{PREFIX}Done ✅ (optional: update model *.yaml to use these anchors in the future)' - else: - s = f'{PREFIX}Done ⚠️ (original anchors better than new anchors, proceeding with original anchors)' - LOGGER.info(s) - - -def kmean_anchors(dataset='./data/coco128.yaml', n=9, img_size=640, thr=4.0, gen=1000, verbose=True): - """ Creates kmeans-evolved anchors from training dataset - - Arguments: - dataset: path to data.yaml, or a loaded dataset - n: number of anchors - img_size: image size used for training - thr: anchor-label wh ratio threshold hyperparameter hyp['anchor_t'] used for training, default=4.0 - gen: generations to evolve anchors using genetic algorithm - verbose: print all results - - Return: - k: kmeans evolved anchors - - Usage: - from utils.autoanchor import *; _ = kmean_anchors() - """ - from scipy.cluster.vq import kmeans - - npr = np.random - thr = 1 / thr - - def metric(k, wh): # compute metrics - r = wh[:, None] / k[None] - x = torch.min(r, 1 / r).min(2)[0] # ratio metric - # x = wh_iou(wh, torch.tensor(k)) # iou metric - return x, x.max(1)[0] # x, best_x - - def anchor_fitness(k): # mutation fitness - _, best = metric(torch.tensor(k, dtype=torch.float32), wh) - return (best * (best > thr).float()).mean() # fitness - - def print_results(k, verbose=True): - k = k[np.argsort(k.prod(1))] # sort small to large - x, best = metric(k, wh0) - bpr, aat = (best > thr).float().mean(), (x > thr).float().mean() * n # best possible recall, anch > thr - s = f'{PREFIX}thr={thr:.2f}: {bpr:.4f} best possible recall, {aat:.2f} anchors past thr\n' \ - f'{PREFIX}n={n}, img_size={img_size}, metric_all={x.mean():.3f}/{best.mean():.3f}-mean/best, ' \ - f'past_thr={x[x > thr].mean():.3f}-mean: ' - for x in k: - s += '%i,%i, ' % (round(x[0]), round(x[1])) - if verbose: - LOGGER.info(s[:-2]) - return k - - if isinstance(dataset, str): # *.yaml file - with open(dataset, errors='ignore') as f: - data_dict = yaml.safe_load(f) # model dict - from utils.dataloaders import LoadImagesAndLabels - dataset = LoadImagesAndLabels(data_dict['train'], augment=True, rect=True) - - # Get label wh - shapes = img_size * dataset.shapes / dataset.shapes.max(1, keepdims=True) - wh0 = np.concatenate([l[:, 3:5] * s for s, l in zip(shapes, dataset.labels)]) # wh - - # Filter - i = (wh0 < 3.0).any(1).sum() - if i: - LOGGER.info(f'{PREFIX}WARNING ⚠️ Extremely small objects found: {i} of {len(wh0)} labels are <3 pixels in size') - wh = wh0[(wh0 >= 2.0).any(1)].astype(np.float32) # filter > 2 pixels - # wh = wh * (npr.rand(wh.shape[0], 1) * 0.9 + 0.1) # multiply by random scale 0-1 - - # Kmeans init - try: - LOGGER.info(f'{PREFIX}Running kmeans for {n} anchors on {len(wh)} points...') - assert n <= len(wh) # apply overdetermined constraint - s = wh.std(0) # sigmas for whitening - k = kmeans(wh / s, n, iter=30)[0] * s # points - assert n == len(k) # kmeans may return fewer points than requested if wh is insufficient or too similar - except Exception: - LOGGER.warning(f'{PREFIX}WARNING ⚠️ switching strategies from kmeans to random init') - k = np.sort(npr.rand(n * 2)).reshape(n, 2) * img_size # random init - wh, wh0 = (torch.tensor(x, dtype=torch.float32) for x in (wh, wh0)) - k = print_results(k, verbose=False) - - # Plot - # k, d = [None] * 20, [None] * 20 - # for i in tqdm(range(1, 21)): - # k[i-1], d[i-1] = kmeans(wh / s, i) # points, mean distance - # fig, ax = plt.subplots(1, 2, figsize=(14, 7), tight_layout=True) - # ax = ax.ravel() - # ax[0].plot(np.arange(1, 21), np.array(d) ** 2, marker='.') - # fig, ax = plt.subplots(1, 2, figsize=(14, 7)) # plot wh - # ax[0].hist(wh[wh[:, 0]<100, 0],400) - # ax[1].hist(wh[wh[:, 1]<100, 1],400) - # fig.savefig('wh.png', dpi=200) - - # Evolve - f, sh, mp, s = anchor_fitness(k), k.shape, 0.9, 0.1 # fitness, generations, mutation prob, sigma - pbar = tqdm(range(gen), bar_format=TQDM_BAR_FORMAT) # progress bar - for _ in pbar: - v = np.ones(sh) - while (v == 1).all(): # mutate until a change occurs (prevent duplicates) - v = ((npr.random(sh) < mp) * random.random() * npr.randn(*sh) * s + 1).clip(0.3, 3.0) - kg = (k.copy() * v).clip(min=2.0) - fg = anchor_fitness(kg) - if fg > f: - f, k = fg, kg.copy() - pbar.desc = f'{PREFIX}Evolving anchors with Genetic Algorithm: fitness = {f:.4f}' - if verbose: - print_results(k, verbose) - - return print_results(k).astype(np.float32) diff --git a/detection/thirdTool/yolo5x_qr/utils/dataloaders.py b/detection/thirdTool/yolo5x_qr/utils/dataloaders.py deleted file mode 100644 index 10eedd2..0000000 --- a/detection/thirdTool/yolo5x_qr/utils/dataloaders.py +++ /dev/null @@ -1,1221 +0,0 @@ -# YOLOv5 🚀 by Ultralytics, GPL-3.0 license -""" -Dataloaders and dataset utils -""" - -import contextlib -import glob -import hashlib -import json -import math -import os -import random -import shutil -import time -from itertools import repeat -from multiprocessing.pool import Pool, ThreadPool -from pathlib import Path -from threading import Thread -from urllib.parse import urlparse - -import numpy as np -# import psutil -import torch -import torch.nn.functional as F -import torchvision -import yaml -from PIL import ExifTags, Image, ImageOps -from torch.utils.data import DataLoader, Dataset, dataloader, distributed -from tqdm import tqdm - -from ..utils.augmentations import (Albumentations, augment_hsv, classify_albumentations, classify_transforms, copy_paste, - letterbox, mixup, random_perspective) -from ..utils.general import (DATASETS_DIR, LOGGER, NUM_THREADS, TQDM_BAR_FORMAT, check_dataset, check_requirements, - check_yaml, clean_str, cv2, is_colab, is_kaggle, segments2boxes, unzip_file, xyn2xy, - xywh2xyxy, xywhn2xyxy, xyxy2xywhn) -from ..utils.torch_utils import torch_distributed_zero_first - -# Parameters -HELP_URL = 'See https://github.com/ultralytics/yolov5/wiki/Train-Custom-Data' -IMG_FORMATS = 'bmp', 'dng', 'jpeg', 'jpg', 'mpo', 'png', 'tif', 'tiff', 'webp', 'pfm' # include image suffixes -VID_FORMATS = 'asf', 'avi', 'gif', 'm4v', 'mkv', 'mov', 'mp4', 'mpeg', 'mpg', 'ts', 'wmv' # include video suffixes -LOCAL_RANK = int(os.getenv('LOCAL_RANK', -1)) # https://pytorch.org/docs/stable/elastic/run.html -RANK = int(os.getenv('RANK', -1)) -PIN_MEMORY = str(os.getenv('PIN_MEMORY', True)).lower() == 'true' # global pin_memory for dataloaders - -# Get orientation exif tag -for orientation in ExifTags.TAGS.keys(): - if ExifTags.TAGS[orientation] == 'Orientation': - break - - -def get_hash(paths): - # Returns a single hash value of a list of paths (files or dirs) - size = sum(os.path.getsize(p) for p in paths if os.path.exists(p)) # sizes - h = hashlib.sha256(str(size).encode()) # hash sizes - h.update(''.join(paths).encode()) # hash paths - return h.hexdigest() # return hash - - -def exif_size(img): - # Returns exif-corrected PIL size - s = img.size # (width, height) - with contextlib.suppress(Exception): - rotation = dict(img._getexif().items())[orientation] - if rotation in [6, 8]: # rotation 270 or 90 - s = (s[1], s[0]) - return s - - -def exif_transpose(image): - """ - Transpose a PIL image accordingly if it has an EXIF Orientation tag. - Inplace version of https://github.com/python-pillow/Pillow/blob/master/src/PIL/ImageOps.py exif_transpose() - - :param image: The image to transpose. - :return: An image. - """ - exif = image.getexif() - orientation = exif.get(0x0112, 1) # default 1 - if orientation > 1: - method = { - 2: Image.FLIP_LEFT_RIGHT, - 3: Image.ROTATE_180, - 4: Image.FLIP_TOP_BOTTOM, - 5: Image.TRANSPOSE, - 6: Image.ROTATE_270, - 7: Image.TRANSVERSE, - 8: Image.ROTATE_90}.get(orientation) - if method is not None: - image = image.transpose(method) - del exif[0x0112] - image.info['exif'] = exif.tobytes() - return image - - -def seed_worker(worker_id): - # Set dataloader worker seed https://pytorch.org/docs/stable/notes/randomness.html#dataloader - worker_seed = torch.initial_seed() % 2 ** 32 - np.random.seed(worker_seed) - random.seed(worker_seed) - - -def create_dataloader(path, - imgsz, - batch_size, - stride, - single_cls=False, - hyp=None, - augment=False, - cache=False, - pad=0.0, - rect=False, - rank=-1, - workers=8, - image_weights=False, - quad=False, - prefix='', - shuffle=False, - seed=0): - if rect and shuffle: - LOGGER.warning('WARNING ⚠️ --rect is incompatible with DataLoader shuffle, setting shuffle=False') - shuffle = False - with torch_distributed_zero_first(rank): # init dataset *.cache only once if DDP - dataset = LoadImagesAndLabels( - path, - imgsz, - batch_size, - augment=augment, # augmentation - hyp=hyp, # hyperparameters - rect=rect, # rectangular batches - cache_images=cache, - single_cls=single_cls, - stride=int(stride), - pad=pad, - image_weights=image_weights, - prefix=prefix) - - batch_size = min(batch_size, len(dataset)) - nd = torch.cuda.device_count() # number of CUDA devices - nw = min([os.cpu_count() // max(nd, 1), batch_size if batch_size > 1 else 0, workers]) # number of workers - sampler = None if rank == -1 else distributed.DistributedSampler(dataset, shuffle=shuffle) - loader = DataLoader if image_weights else InfiniteDataLoader # only DataLoader allows for attribute updates - generator = torch.Generator() - generator.manual_seed(6148914691236517205 + seed + RANK) - return loader(dataset, - batch_size=batch_size, - shuffle=shuffle and sampler is None, - num_workers=nw, - sampler=sampler, - pin_memory=PIN_MEMORY, - collate_fn=LoadImagesAndLabels.collate_fn4 if quad else LoadImagesAndLabels.collate_fn, - worker_init_fn=seed_worker, - generator=generator), dataset - - -class InfiniteDataLoader(dataloader.DataLoader): - """ Dataloader that reuses workers - - Uses same syntax as vanilla DataLoader - """ - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - object.__setattr__(self, 'batch_sampler', _RepeatSampler(self.batch_sampler)) - self.iterator = super().__iter__() - - def __len__(self): - return len(self.batch_sampler.sampler) - - def __iter__(self): - for _ in range(len(self)): - yield next(self.iterator) - - -class _RepeatSampler: - """ Sampler that repeats forever - - Args: - sampler (Sampler) - """ - - def __init__(self, sampler): - self.sampler = sampler - - def __iter__(self): - while True: - yield from iter(self.sampler) - - -class LoadScreenshots: - # YOLOv5 screenshot dataloader, i.e. `python detect.py --source "screen 0 100 100 512 256"` - def __init__(self, source, img_size=640, stride=32, auto=True, transforms=None): - # source = [screen_number left top width height] (pixels) - check_requirements('mss') - import mss - - source, *params = source.split() - self.screen, left, top, width, height = 0, None, None, None, None # default to full screen 0 - if len(params) == 1: - self.screen = int(params[0]) - elif len(params) == 4: - left, top, width, height = (int(x) for x in params) - elif len(params) == 5: - self.screen, left, top, width, height = (int(x) for x in params) - self.img_size = img_size - self.stride = stride - self.transforms = transforms - self.auto = auto - self.mode = 'stream' - self.frame = 0 - self.sct = mss.mss() - - # Parse monitor shape - monitor = self.sct.monitors[self.screen] - self.top = monitor['top'] if top is None else (monitor['top'] + top) - self.left = monitor['left'] if left is None else (monitor['left'] + left) - self.width = width or monitor['width'] - self.height = height or monitor['height'] - self.monitor = {'left': self.left, 'top': self.top, 'width': self.width, 'height': self.height} - - def __iter__(self): - return self - - def __next__(self): - # mss screen capture: get raw pixels from the screen as np array - im0 = np.array(self.sct.grab(self.monitor))[:, :, :3] # [:, :, :3] BGRA to BGR - s = f'screen {self.screen} (LTWH): {self.left},{self.top},{self.width},{self.height}: ' - - if self.transforms: - im = self.transforms(im0) # transforms - else: - im = letterbox(im0, self.img_size, stride=self.stride, auto=self.auto)[0] # padded resize - im = im.transpose((2, 0, 1))[::-1] # HWC to CHW, BGR to RGB - im = np.ascontiguousarray(im) # contiguous - self.frame += 1 - return str(self.screen), im, im0, None, s # screen, img, original img, im0s, s - - -class LoadImages: - # YOLOv5 image/video dataloader, i.e. `python detect.py --source image.jpg/vid.mp4` - def __init__(self, path, img_size=640, stride=32, auto=True, transforms=None, vid_stride=1): - if isinstance(path, str) and Path(path).suffix == '.txt': # *.txt file with img/vid/dir on each line - path = Path(path).read_text().rsplit() - files = [] - for p in sorted(path) if isinstance(path, (list, tuple)) else [path]: - p = str(Path(p).resolve()) - if '*' in p: - files.extend(sorted(glob.glob(p, recursive=True))) # glob - elif os.path.isdir(p): - files.extend(sorted(glob.glob(os.path.join(p, '*.*')))) # dir - elif os.path.isfile(p): - files.append(p) # files - else: - raise FileNotFoundError(f'{p} does not exist') - - images = [x for x in files if x.split('.')[-1].lower() in IMG_FORMATS] - videos = [x for x in files if x.split('.')[-1].lower() in VID_FORMATS] - ni, nv = len(images), len(videos) - - self.img_size = img_size - self.stride = stride - self.files = images + videos - self.nf = ni + nv # number of files - self.video_flag = [False] * ni + [True] * nv - self.mode = 'image' - self.auto = auto - self.transforms = transforms # optional - self.vid_stride = vid_stride # video frame-rate stride - if any(videos): - self._new_video(videos[0]) # new video - else: - self.cap = None - assert self.nf > 0, f'No images or videos found in {p}. ' \ - f'Supported formats are:\nimages: {IMG_FORMATS}\nvideos: {VID_FORMATS}' - - def __iter__(self): - self.count = 0 - return self - - def __next__(self): - if self.count == self.nf: - raise StopIteration - path = self.files[self.count] - - if self.video_flag[self.count]: - # Read video - self.mode = 'video' - for _ in range(self.vid_stride): - self.cap.grab() - ret_val, im0 = self.cap.retrieve() - while not ret_val: - self.count += 1 - self.cap.release() - if self.count == self.nf: # last video - raise StopIteration - path = self.files[self.count] - self._new_video(path) - ret_val, im0 = self.cap.read() - - self.frame += 1 - # im0 = self._cv2_rotate(im0) # for use if cv2 autorotation is False - s = f'video {self.count + 1}/{self.nf} ({self.frame}/{self.frames}) {path}: ' - - else: - # Read image - self.count += 1 - im0 = cv2.imread(path) # BGR - assert im0 is not None, f'Image Not Found {path}' - s = f'image {self.count}/{self.nf} {path}: ' - - if self.transforms: - im = self.transforms(im0) # transforms - else: - im = letterbox(im0, self.img_size, stride=self.stride, auto=self.auto)[0] # padded resize - im = im.transpose((2, 0, 1))[::-1] # HWC to CHW, BGR to RGB - im = np.ascontiguousarray(im) # contiguous - - return path, im, im0, self.cap, s - - def _new_video(self, path): - # Create a new video capture object - self.frame = 0 - self.cap = cv2.VideoCapture(path) - self.frames = int(self.cap.get(cv2.CAP_PROP_FRAME_COUNT) / self.vid_stride) - self.orientation = int(self.cap.get(cv2.CAP_PROP_ORIENTATION_META)) # rotation degrees - # self.cap.set(cv2.CAP_PROP_ORIENTATION_AUTO, 0) # disable https://github.com/ultralytics/yolov5/issues/8493 - - def _cv2_rotate(self, im): - # Rotate a cv2 video manually - if self.orientation == 0: - return cv2.rotate(im, cv2.ROTATE_90_CLOCKWISE) - elif self.orientation == 180: - return cv2.rotate(im, cv2.ROTATE_90_COUNTERCLOCKWISE) - elif self.orientation == 90: - return cv2.rotate(im, cv2.ROTATE_180) - return im - - def __len__(self): - return self.nf # number of files - - -class LoadStreams: - # YOLOv5 streamloader, i.e. `python detect.py --source 'rtsp://example.com/media.mp4' # RTSP, RTMP, HTTP streams` - def __init__(self, sources='file.streams', img_size=640, stride=32, auto=True, transforms=None, vid_stride=1): - torch.backends.cudnn.benchmark = True # faster for fixed-size inference - self.mode = 'stream' - self.img_size = img_size - self.stride = stride - self.vid_stride = vid_stride # video frame-rate stride - sources = Path(sources).read_text().rsplit() if os.path.isfile(sources) else [sources] - n = len(sources) - self.sources = [clean_str(x) for x in sources] # clean source names for later - self.imgs, self.fps, self.frames, self.threads = [None] * n, [0] * n, [0] * n, [None] * n - for i, s in enumerate(sources): # index, source - # Start thread to read frames from video stream - st = f'{i + 1}/{n}: {s}... ' - if urlparse(s).hostname in ('www.youtube.com', 'youtube.com', 'youtu.be'): # if source is YouTube video - # YouTube format i.e. 'https://www.youtube.com/watch?v=Zgi9g1ksQHc' or 'https://youtu.be/Zgi9g1ksQHc' - check_requirements(('pafy', 'youtube_dl==2020.12.2')) - import pafy - s = pafy.new(s).getbest(preftype='mp4').url # YouTube URL - s = eval(s) if s.isnumeric() else s # i.e. s = '0' local webcam - if s == 0: - assert not is_colab(), '--source 0 webcam unsupported on Colab. Rerun command in a local environment.' - assert not is_kaggle(), '--source 0 webcam unsupported on Kaggle. Rerun command in a local environment.' - cap = cv2.VideoCapture(s) - assert cap.isOpened(), f'{st}Failed to open {s}' - w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) - h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) - fps = cap.get(cv2.CAP_PROP_FPS) # warning: may return 0 or nan - self.frames[i] = max(int(cap.get(cv2.CAP_PROP_FRAME_COUNT)), 0) or float('inf') # infinite stream fallback - self.fps[i] = max((fps if math.isfinite(fps) else 0) % 100, 0) or 30 # 30 FPS fallback - - _, self.imgs[i] = cap.read() # guarantee first frame - self.threads[i] = Thread(target=self.update, args=([i, cap, s]), daemon=True) - LOGGER.info(f'{st} Success ({self.frames[i]} frames {w}x{h} at {self.fps[i]:.2f} FPS)') - self.threads[i].start() - LOGGER.info('') # newline - - # check for common shapes - s = np.stack([letterbox(x, img_size, stride=stride, auto=auto)[0].shape for x in self.imgs]) - self.rect = np.unique(s, axis=0).shape[0] == 1 # rect inference if all shapes equal - self.auto = auto and self.rect - self.transforms = transforms # optional - if not self.rect: - LOGGER.warning('WARNING ⚠️ Stream shapes differ. For optimal performance supply similarly-shaped streams.') - - def update(self, i, cap, stream): - # Read stream `i` frames in daemon thread - n, f = 0, self.frames[i] # frame number, frame array - while cap.isOpened() and n < f: - n += 1 - cap.grab() # .read() = .grab() followed by .retrieve() - if n % self.vid_stride == 0: - success, im = cap.retrieve() - if success: - self.imgs[i] = im - else: - LOGGER.warning('WARNING ⚠️ Video stream unresponsive, please check your IP camera connection.') - self.imgs[i] = np.zeros_like(self.imgs[i]) - cap.open(stream) # re-open stream if signal was lost - time.sleep(0.0) # wait time - - def __iter__(self): - self.count = -1 - return self - - def __next__(self): - self.count += 1 - if not all(x.is_alive() for x in self.threads) or cv2.waitKey(1) == ord('q'): # q to quit - cv2.destroyAllWindows() - raise StopIteration - - im0 = self.imgs.copy() - if self.transforms: - im = np.stack([self.transforms(x) for x in im0]) # transforms - else: - im = np.stack([letterbox(x, self.img_size, stride=self.stride, auto=self.auto)[0] for x in im0]) # resize - im = im[..., ::-1].transpose((0, 3, 1, 2)) # BGR to RGB, BHWC to BCHW - im = np.ascontiguousarray(im) # contiguous - - return self.sources, im, im0, None, '' - - def __len__(self): - return len(self.sources) # 1E12 frames = 32 streams at 30 FPS for 30 years - - -def img2label_paths(img_paths): - # Define label paths as a function of image paths - sa, sb = f'{os.sep}images{os.sep}', f'{os.sep}labels{os.sep}' # /images/, /labels/ substrings - return [sb.join(x.rsplit(sa, 1)).rsplit('.', 1)[0] + '.txt' for x in img_paths] - - -class LoadImagesAndLabels(Dataset): - # YOLOv5 train_loader/val_loader, loads images and labels for training and validation - cache_version = 0.6 # dataset labels *.cache version - rand_interp_methods = [cv2.INTER_NEAREST, cv2.INTER_LINEAR, cv2.INTER_CUBIC, cv2.INTER_AREA, cv2.INTER_LANCZOS4] - - def __init__(self, - path, - img_size=640, - batch_size=16, - augment=False, - hyp=None, - rect=False, - image_weights=False, - cache_images=False, - single_cls=False, - stride=32, - pad=0.0, - min_items=0, - prefix=''): - self.img_size = img_size - self.augment = augment - self.hyp = hyp - self.image_weights = image_weights - self.rect = False if image_weights else rect - self.mosaic = self.augment and not self.rect # load 4 images at a time into a mosaic (only during training) - self.mosaic_border = [-img_size // 2, -img_size // 2] - self.stride = stride - self.path = path - self.albumentations = Albumentations(size=img_size) if augment else None - - try: - f = [] # image files - for p in path if isinstance(path, list) else [path]: - p = Path(p) # os-agnostic - if p.is_dir(): # dir - f += glob.glob(str(p / '**' / '*.*'), recursive=True) - # f = list(p.rglob('*.*')) # pathlib - elif p.is_file(): # file - with open(p) as t: - t = t.read().strip().splitlines() - parent = str(p.parent) + os.sep - f += [x.replace('./', parent, 1) if x.startswith('./') else x for x in t] # to global path - # f += [p.parent / x.lstrip(os.sep) for x in t] # to global path (pathlib) - else: - raise FileNotFoundError(f'{prefix}{p} does not exist') - self.im_files = sorted(x.replace('/', os.sep) for x in f if x.split('.')[-1].lower() in IMG_FORMATS) - # self.img_files = sorted([x for x in f if x.suffix[1:].lower() in IMG_FORMATS]) # pathlib - assert self.im_files, f'{prefix}No images found' - except Exception as e: - raise Exception(f'{prefix}Error loading data from {path}: {e}\n{HELP_URL}') from e - - # Check cache - self.label_files = img2label_paths(self.im_files) # labels - cache_path = (p if p.is_file() else Path(self.label_files[0]).parent).with_suffix('.cache') - try: - cache, exists = np.load(cache_path, allow_pickle=True).item(), True # load dict - assert cache['version'] == self.cache_version # matches current version - assert cache['hash'] == get_hash(self.label_files + self.im_files) # identical hash - except Exception: - cache, exists = self.cache_labels(cache_path, prefix), False # run cache ops - - # Display cache - nf, nm, ne, nc, n = cache.pop('results') # found, missing, empty, corrupt, total - if exists and LOCAL_RANK in {-1, 0}: - d = f'Scanning {cache_path}... {nf} images, {nm + ne} backgrounds, {nc} corrupt' - tqdm(None, desc=prefix + d, total=n, initial=n, bar_format=TQDM_BAR_FORMAT) # display cache results - if cache['msgs']: - LOGGER.info('\n'.join(cache['msgs'])) # display warnings - assert nf > 0 or not augment, f'{prefix}No labels found in {cache_path}, can not start training. {HELP_URL}' - - # Read cache - [cache.pop(k) for k in ('hash', 'version', 'msgs')] # remove items - labels, shapes, self.segments = zip(*cache.values()) - nl = len(np.concatenate(labels, 0)) # number of labels - assert nl > 0 or not augment, f'{prefix}All labels empty in {cache_path}, can not start training. {HELP_URL}' - self.labels = list(labels) - self.shapes = np.array(shapes) - self.im_files = list(cache.keys()) # update - self.label_files = img2label_paths(cache.keys()) # update - - # Filter images - if min_items: - include = np.array([len(x) >= min_items for x in self.labels]).nonzero()[0].astype(int) - LOGGER.info(f'{prefix}{n - len(include)}/{n} images filtered from dataset') - self.im_files = [self.im_files[i] for i in include] - self.label_files = [self.label_files[i] for i in include] - self.labels = [self.labels[i] for i in include] - self.segments = [self.segments[i] for i in include] - self.shapes = self.shapes[include] # wh - - # Create indices - n = len(self.shapes) # number of images - bi = np.floor(np.arange(n) / batch_size).astype(int) # batch index - nb = bi[-1] + 1 # number of batches - self.batch = bi # batch index of image - self.n = n - self.indices = range(n) - - # Update labels - include_class = [] # filter labels to include only these classes (optional) - include_class_array = np.array(include_class).reshape(1, -1) - for i, (label, segment) in enumerate(zip(self.labels, self.segments)): - if include_class: - j = (label[:, 0:1] == include_class_array).any(1) - self.labels[i] = label[j] - if segment: - self.segments[i] = segment[j] - if single_cls: # single-class training, merge all classes into 0 - self.labels[i][:, 0] = 0 - - # Rectangular Training - if self.rect: - # Sort by aspect ratio - s = self.shapes # wh - ar = s[:, 1] / s[:, 0] # aspect ratio - irect = ar.argsort() - self.im_files = [self.im_files[i] for i in irect] - self.label_files = [self.label_files[i] for i in irect] - self.labels = [self.labels[i] for i in irect] - self.segments = [self.segments[i] for i in irect] - self.shapes = s[irect] # wh - ar = ar[irect] - - # Set training image shapes - shapes = [[1, 1]] * nb - for i in range(nb): - ari = ar[bi == i] - mini, maxi = ari.min(), ari.max() - if maxi < 1: - shapes[i] = [maxi, 1] - elif mini > 1: - shapes[i] = [1, 1 / mini] - - self.batch_shapes = np.ceil(np.array(shapes) * img_size / stride + pad).astype(int) * stride - - # Cache images into RAM/disk for faster training - if cache_images == 'ram' and not self.check_cache_ram(prefix=prefix): - cache_images = False - self.ims = [None] * n - self.npy_files = [Path(f).with_suffix('.npy') for f in self.im_files] - if cache_images: - b, gb = 0, 1 << 30 # bytes of cached images, bytes per gigabytes - self.im_hw0, self.im_hw = [None] * n, [None] * n - fcn = self.cache_images_to_disk if cache_images == 'disk' else self.load_image - results = ThreadPool(NUM_THREADS).imap(fcn, range(n)) - pbar = tqdm(enumerate(results), total=n, bar_format=TQDM_BAR_FORMAT, disable=LOCAL_RANK > 0) - for i, x in pbar: - if cache_images == 'disk': - b += self.npy_files[i].stat().st_size - else: # 'ram' - self.ims[i], self.im_hw0[i], self.im_hw[i] = x # im, hw_orig, hw_resized = load_image(self, i) - b += self.ims[i].nbytes - pbar.desc = f'{prefix}Caching images ({b / gb:.1f}GB {cache_images})' - pbar.close() - - def check_cache_ram(self, safety_margin=0.1, prefix=''): - # Check image caching requirements vs available memory - b, gb = 0, 1 << 30 # bytes of cached images, bytes per gigabytes - n = min(self.n, 30) # extrapolate from 30 random images - for _ in range(n): - im = cv2.imread(random.choice(self.im_files)) # sample image - ratio = self.img_size / max(im.shape[0], im.shape[1]) # max(h, w) # ratio - b += im.nbytes * ratio ** 2 - mem_required = b * self.n / n # GB required to cache dataset into RAM - mem = psutil.virtual_memory() - cache = mem_required * (1 + safety_margin) < mem.available # to cache or not to cache, that is the question - if not cache: - LOGGER.info(f'{prefix}{mem_required / gb:.1f}GB RAM required, ' - f'{mem.available / gb:.1f}/{mem.total / gb:.1f}GB available, ' - f"{'caching images ✅' if cache else 'not caching images ⚠️'}") - return cache - - def cache_labels(self, path=Path('./labels.cache'), prefix=''): - # Cache dataset labels, check images and read shapes - x = {} # dict - nm, nf, ne, nc, msgs = 0, 0, 0, 0, [] # number missing, found, empty, corrupt, messages - desc = f'{prefix}Scanning {path.parent / path.stem}...' - with Pool(NUM_THREADS) as pool: - pbar = tqdm(pool.imap(verify_image_label, zip(self.im_files, self.label_files, repeat(prefix))), - desc=desc, - total=len(self.im_files), - bar_format=TQDM_BAR_FORMAT) - for im_file, lb, shape, segments, nm_f, nf_f, ne_f, nc_f, msg in pbar: - nm += nm_f - nf += nf_f - ne += ne_f - nc += nc_f - if im_file: - x[im_file] = [lb, shape, segments] - if msg: - msgs.append(msg) - pbar.desc = f'{desc} {nf} images, {nm + ne} backgrounds, {nc} corrupt' - - pbar.close() - if msgs: - LOGGER.info('\n'.join(msgs)) - if nf == 0: - LOGGER.warning(f'{prefix}WARNING ⚠️ No labels found in {path}. {HELP_URL}') - x['hash'] = get_hash(self.label_files + self.im_files) - x['results'] = nf, nm, ne, nc, len(self.im_files) - x['msgs'] = msgs # warnings - x['version'] = self.cache_version # cache version - try: - np.save(path, x) # save cache for next time - path.with_suffix('.cache.npy').rename(path) # remove .npy suffix - LOGGER.info(f'{prefix}New cache created: {path}') - except Exception as e: - LOGGER.warning(f'{prefix}WARNING ⚠️ Cache directory {path.parent} is not writeable: {e}') # not writeable - return x - - def __len__(self): - return len(self.im_files) - - # def __iter__(self): - # self.count = -1 - # print('ran dataset iter') - # #self.shuffled_vector = np.random.permutation(self.nF) if self.augment else np.arange(self.nF) - # return self - - def __getitem__(self, index): - index = self.indices[index] # linear, shuffled, or image_weights - - hyp = self.hyp - mosaic = self.mosaic and random.random() < hyp['mosaic'] - if mosaic: - # Load mosaic - img, labels = self.load_mosaic(index) - shapes = None - - # MixUp augmentation - if random.random() < hyp['mixup']: - img, labels = mixup(img, labels, *self.load_mosaic(random.randint(0, self.n - 1))) - - else: - # Load image - img, (h0, w0), (h, w) = self.load_image(index) - - # Letterbox - shape = self.batch_shapes[self.batch[index]] if self.rect else self.img_size # final letterboxed shape - img, ratio, pad = letterbox(img, shape, auto=False, scaleup=self.augment) - shapes = (h0, w0), ((h / h0, w / w0), pad) # for COCO mAP rescaling - - labels = self.labels[index].copy() - if labels.size: # normalized xywh to pixel xyxy format - labels[:, 1:] = xywhn2xyxy(labels[:, 1:], ratio[0] * w, ratio[1] * h, padw=pad[0], padh=pad[1]) - - if self.augment: - img, labels = random_perspective(img, - labels, - degrees=hyp['degrees'], - translate=hyp['translate'], - scale=hyp['scale'], - shear=hyp['shear'], - perspective=hyp['perspective']) - - nl = len(labels) # number of labels - if nl: - labels[:, 1:5] = xyxy2xywhn(labels[:, 1:5], w=img.shape[1], h=img.shape[0], clip=True, eps=1E-3) - - if self.augment: - # Albumentations - img, labels = self.albumentations(img, labels) - nl = len(labels) # update after albumentations - - # HSV color-space - augment_hsv(img, hgain=hyp['hsv_h'], sgain=hyp['hsv_s'], vgain=hyp['hsv_v']) - - # Flip up-down - if random.random() < hyp['flipud']: - img = np.flipud(img) - if nl: - labels[:, 2] = 1 - labels[:, 2] - - # Flip left-right - if random.random() < hyp['fliplr']: - img = np.fliplr(img) - if nl: - labels[:, 1] = 1 - labels[:, 1] - - # Cutouts - # labels = cutout(img, labels, p=0.5) - # nl = len(labels) # update after cutout - - labels_out = torch.zeros((nl, 6)) - if nl: - labels_out[:, 1:] = torch.from_numpy(labels) - - # Convert - img = img.transpose((2, 0, 1))[::-1] # HWC to CHW, BGR to RGB - img = np.ascontiguousarray(img) - - return torch.from_numpy(img), labels_out, self.im_files[index], shapes - - def load_image(self, i): - # Loads 1 image from dataset index 'i', returns (im, original hw, resized hw) - im, f, fn = self.ims[i], self.im_files[i], self.npy_files[i], - if im is None: # not cached in RAM - if fn.exists(): # load npy - im = np.load(fn) - else: # read image - im = cv2.imread(f) # BGR - assert im is not None, f'Image Not Found {f}' - h0, w0 = im.shape[:2] # orig hw - r = self.img_size / max(h0, w0) # ratio - if r != 1: # if sizes are not equal - interp = cv2.INTER_LINEAR if (self.augment or r > 1) else cv2.INTER_AREA - im = cv2.resize(im, (math.ceil(w0 * r), math.ceil(h0 * r)), interpolation=interp) - return im, (h0, w0), im.shape[:2] # im, hw_original, hw_resized - return self.ims[i], self.im_hw0[i], self.im_hw[i] # im, hw_original, hw_resized - - def cache_images_to_disk(self, i): - # Saves an image as an *.npy file for faster loading - f = self.npy_files[i] - if not f.exists(): - np.save(f.as_posix(), cv2.imread(self.im_files[i])) - - def load_mosaic(self, index): - # YOLOv5 4-mosaic loader. Loads 1 image + 3 random images into a 4-image mosaic - labels4, segments4 = [], [] - s = self.img_size - yc, xc = (int(random.uniform(-x, 2 * s + x)) for x in self.mosaic_border) # mosaic center x, y - indices = [index] + random.choices(self.indices, k=3) # 3 additional image indices - random.shuffle(indices) - for i, index in enumerate(indices): - # Load image - img, _, (h, w) = self.load_image(index) - - # place img in img4 - if i == 0: # top left - img4 = np.full((s * 2, s * 2, img.shape[2]), 114, dtype=np.uint8) # base image with 4 tiles - x1a, y1a, x2a, y2a = max(xc - w, 0), max(yc - h, 0), xc, yc # xmin, ymin, xmax, ymax (large image) - x1b, y1b, x2b, y2b = w - (x2a - x1a), h - (y2a - y1a), w, h # xmin, ymin, xmax, ymax (small image) - elif i == 1: # top right - x1a, y1a, x2a, y2a = xc, max(yc - h, 0), min(xc + w, s * 2), yc - x1b, y1b, x2b, y2b = 0, h - (y2a - y1a), min(w, x2a - x1a), h - elif i == 2: # bottom left - x1a, y1a, x2a, y2a = max(xc - w, 0), yc, xc, min(s * 2, yc + h) - x1b, y1b, x2b, y2b = w - (x2a - x1a), 0, w, min(y2a - y1a, h) - elif i == 3: # bottom right - x1a, y1a, x2a, y2a = xc, yc, min(xc + w, s * 2), min(s * 2, yc + h) - x1b, y1b, x2b, y2b = 0, 0, min(w, x2a - x1a), min(y2a - y1a, h) - - img4[y1a:y2a, x1a:x2a] = img[y1b:y2b, x1b:x2b] # img4[ymin:ymax, xmin:xmax] - padw = x1a - x1b - padh = y1a - y1b - - # Labels - labels, segments = self.labels[index].copy(), self.segments[index].copy() - if labels.size: - labels[:, 1:] = xywhn2xyxy(labels[:, 1:], w, h, padw, padh) # normalized xywh to pixel xyxy format - segments = [xyn2xy(x, w, h, padw, padh) for x in segments] - labels4.append(labels) - segments4.extend(segments) - - # Concat/clip labels - labels4 = np.concatenate(labels4, 0) - for x in (labels4[:, 1:], *segments4): - np.clip(x, 0, 2 * s, out=x) # clip when using random_perspective() - # img4, labels4 = replicate(img4, labels4) # replicate - - # Augment - img4, labels4, segments4 = copy_paste(img4, labels4, segments4, p=self.hyp['copy_paste']) - img4, labels4 = random_perspective(img4, - labels4, - segments4, - degrees=self.hyp['degrees'], - translate=self.hyp['translate'], - scale=self.hyp['scale'], - shear=self.hyp['shear'], - perspective=self.hyp['perspective'], - border=self.mosaic_border) # border to remove - - return img4, labels4 - - def load_mosaic9(self, index): - # YOLOv5 9-mosaic loader. Loads 1 image + 8 random images into a 9-image mosaic - labels9, segments9 = [], [] - s = self.img_size - indices = [index] + random.choices(self.indices, k=8) # 8 additional image indices - random.shuffle(indices) - hp, wp = -1, -1 # height, width previous - for i, index in enumerate(indices): - # Load image - img, _, (h, w) = self.load_image(index) - - # place img in img9 - if i == 0: # center - img9 = np.full((s * 3, s * 3, img.shape[2]), 114, dtype=np.uint8) # base image with 4 tiles - h0, w0 = h, w - c = s, s, s + w, s + h # xmin, ymin, xmax, ymax (base) coordinates - elif i == 1: # top - c = s, s - h, s + w, s - elif i == 2: # top right - c = s + wp, s - h, s + wp + w, s - elif i == 3: # right - c = s + w0, s, s + w0 + w, s + h - elif i == 4: # bottom right - c = s + w0, s + hp, s + w0 + w, s + hp + h - elif i == 5: # bottom - c = s + w0 - w, s + h0, s + w0, s + h0 + h - elif i == 6: # bottom left - c = s + w0 - wp - w, s + h0, s + w0 - wp, s + h0 + h - elif i == 7: # left - c = s - w, s + h0 - h, s, s + h0 - elif i == 8: # top left - c = s - w, s + h0 - hp - h, s, s + h0 - hp - - padx, pady = c[:2] - x1, y1, x2, y2 = (max(x, 0) for x in c) # allocate coords - - # Labels - labels, segments = self.labels[index].copy(), self.segments[index].copy() - if labels.size: - labels[:, 1:] = xywhn2xyxy(labels[:, 1:], w, h, padx, pady) # normalized xywh to pixel xyxy format - segments = [xyn2xy(x, w, h, padx, pady) for x in segments] - labels9.append(labels) - segments9.extend(segments) - - # Image - img9[y1:y2, x1:x2] = img[y1 - pady:, x1 - padx:] # img9[ymin:ymax, xmin:xmax] - hp, wp = h, w # height, width previous - - # Offset - yc, xc = (int(random.uniform(0, s)) for _ in self.mosaic_border) # mosaic center x, y - img9 = img9[yc:yc + 2 * s, xc:xc + 2 * s] - - # Concat/clip labels - labels9 = np.concatenate(labels9, 0) - labels9[:, [1, 3]] -= xc - labels9[:, [2, 4]] -= yc - c = np.array([xc, yc]) # centers - segments9 = [x - c for x in segments9] - - for x in (labels9[:, 1:], *segments9): - np.clip(x, 0, 2 * s, out=x) # clip when using random_perspective() - # img9, labels9 = replicate(img9, labels9) # replicate - - # Augment - img9, labels9, segments9 = copy_paste(img9, labels9, segments9, p=self.hyp['copy_paste']) - img9, labels9 = random_perspective(img9, - labels9, - segments9, - degrees=self.hyp['degrees'], - translate=self.hyp['translate'], - scale=self.hyp['scale'], - shear=self.hyp['shear'], - perspective=self.hyp['perspective'], - border=self.mosaic_border) # border to remove - - return img9, labels9 - - @staticmethod - def collate_fn(batch): - im, label, path, shapes = zip(*batch) # transposed - for i, lb in enumerate(label): - lb[:, 0] = i # add target image index for build_targets() - return torch.stack(im, 0), torch.cat(label, 0), path, shapes - - @staticmethod - def collate_fn4(batch): - im, label, path, shapes = zip(*batch) # transposed - n = len(shapes) // 4 - im4, label4, path4, shapes4 = [], [], path[:n], shapes[:n] - - ho = torch.tensor([[0.0, 0, 0, 1, 0, 0]]) - wo = torch.tensor([[0.0, 0, 1, 0, 0, 0]]) - s = torch.tensor([[1, 1, 0.5, 0.5, 0.5, 0.5]]) # scale - for i in range(n): # zidane torch.zeros(16,3,720,1280) # BCHW - i *= 4 - if random.random() < 0.5: - im1 = F.interpolate(im[i].unsqueeze(0).float(), scale_factor=2.0, mode='bilinear', - align_corners=False)[0].type(im[i].type()) - lb = label[i] - else: - im1 = torch.cat((torch.cat((im[i], im[i + 1]), 1), torch.cat((im[i + 2], im[i + 3]), 1)), 2) - lb = torch.cat((label[i], label[i + 1] + ho, label[i + 2] + wo, label[i + 3] + ho + wo), 0) * s - im4.append(im1) - label4.append(lb) - - for i, lb in enumerate(label4): - lb[:, 0] = i # add target image index for build_targets() - - return torch.stack(im4, 0), torch.cat(label4, 0), path4, shapes4 - - -# Ancillary functions -------------------------------------------------------------------------------------------------- -def flatten_recursive(path=DATASETS_DIR / 'coco128'): - # Flatten a recursive directory by bringing all files to top level - new_path = Path(f'{str(path)}_flat') - if os.path.exists(new_path): - shutil.rmtree(new_path) # delete output folder - os.makedirs(new_path) # make new output folder - for file in tqdm(glob.glob(f'{str(Path(path))}/**/*.*', recursive=True)): - shutil.copyfile(file, new_path / Path(file).name) - - -def extract_boxes(path=DATASETS_DIR / 'coco128'): # from utils.dataloaders import *; extract_boxes() - # Convert detection dataset into classification dataset, with one directory per class - path = Path(path) # images dir - shutil.rmtree(path / 'classification') if (path / 'classification').is_dir() else None # remove existing - files = list(path.rglob('*.*')) - n = len(files) # number of files - for im_file in tqdm(files, total=n): - if im_file.suffix[1:] in IMG_FORMATS: - # image - im = cv2.imread(str(im_file))[..., ::-1] # BGR to RGB - h, w = im.shape[:2] - - # labels - lb_file = Path(img2label_paths([str(im_file)])[0]) - if Path(lb_file).exists(): - with open(lb_file) as f: - lb = np.array([x.split() for x in f.read().strip().splitlines()], dtype=np.float32) # labels - - for j, x in enumerate(lb): - c = int(x[0]) # class - f = (path / 'classifier') / f'{c}' / f'{path.stem}_{im_file.stem}_{j}.jpg' # new filename - if not f.parent.is_dir(): - f.parent.mkdir(parents=True) - - b = x[1:] * [w, h, w, h] # box - # b[2:] = b[2:].max() # rectangle to square - b[2:] = b[2:] * 1.2 + 3 # pad - b = xywh2xyxy(b.reshape(-1, 4)).ravel().astype(int) - - b[[0, 2]] = np.clip(b[[0, 2]], 0, w) # clip boxes outside of image - b[[1, 3]] = np.clip(b[[1, 3]], 0, h) - assert cv2.imwrite(str(f), im[b[1]:b[3], b[0]:b[2]]), f'box failure in {f}' - - -def autosplit(path=DATASETS_DIR / 'coco128/images', weights=(0.9, 0.1, 0.0), annotated_only=False): - """ Autosplit a dataset into train/val/test splits and save path/autosplit_*.txt files - Usage: from utils.dataloaders import *; autosplit() - Arguments - path: Path to images directory - weights: Train, val, test weights (list, tuple) - annotated_only: Only use images with an annotated txt file - """ - path = Path(path) # images dir - files = sorted(x for x in path.rglob('*.*') if x.suffix[1:].lower() in IMG_FORMATS) # image files only - n = len(files) # number of files - random.seed(0) # for reproducibility - indices = random.choices([0, 1, 2], weights=weights, k=n) # assign each image to a split - - txt = ['autosplit_train.txt', 'autosplit_val.txt', 'autosplit_test.txt'] # 3 txt files - for x in txt: - if (path.parent / x).exists(): - (path.parent / x).unlink() # remove existing - - print(f'Autosplitting images from {path}' + ', using *.txt labeled images only' * annotated_only) - for i, img in tqdm(zip(indices, files), total=n): - if not annotated_only or Path(img2label_paths([str(img)])[0]).exists(): # check label - with open(path.parent / txt[i], 'a') as f: - f.write(f'./{img.relative_to(path.parent).as_posix()}' + '\n') # add image to txt file - - -def verify_image_label(args): - # Verify one image-label pair - im_file, lb_file, prefix = args - nm, nf, ne, nc, msg, segments = 0, 0, 0, 0, '', [] # number (missing, found, empty, corrupt), message, segments - try: - # verify images - im = Image.open(im_file) - im.verify() # PIL verify - shape = exif_size(im) # image size - assert (shape[0] > 9) & (shape[1] > 9), f'image size {shape} <10 pixels' - assert im.format.lower() in IMG_FORMATS, f'invalid image format {im.format}' - if im.format.lower() in ('jpg', 'jpeg'): - with open(im_file, 'rb') as f: - f.seek(-2, 2) - if f.read() != b'\xff\xd9': # corrupt JPEG - ImageOps.exif_transpose(Image.open(im_file)).save(im_file, 'JPEG', subsampling=0, quality=100) - msg = f'{prefix}WARNING ⚠️ {im_file}: corrupt JPEG restored and saved' - - # verify labels - if os.path.isfile(lb_file): - nf = 1 # label found - with open(lb_file) as f: - lb = [x.split() for x in f.read().strip().splitlines() if len(x)] - if any(len(x) > 6 for x in lb): # is segment - classes = np.array([x[0] for x in lb], dtype=np.float32) - segments = [np.array(x[1:], dtype=np.float32).reshape(-1, 2) for x in lb] # (cls, xy1...) - lb = np.concatenate((classes.reshape(-1, 1), segments2boxes(segments)), 1) # (cls, xywh) - lb = np.array(lb, dtype=np.float32) - nl = len(lb) - if nl: - assert lb.shape[1] == 5, f'labels require 5 columns, {lb.shape[1]} columns detected' - assert (lb >= 0).all(), f'negative label values {lb[lb < 0]}' - assert (lb[:, 1:] <= 1).all(), f'non-normalized or out of bounds coordinates {lb[:, 1:][lb[:, 1:] > 1]}' - _, i = np.unique(lb, axis=0, return_index=True) - if len(i) < nl: # duplicate row check - lb = lb[i] # remove duplicates - if segments: - segments = [segments[x] for x in i] - msg = f'{prefix}WARNING ⚠️ {im_file}: {nl - len(i)} duplicate labels removed' - else: - ne = 1 # label empty - lb = np.zeros((0, 5), dtype=np.float32) - else: - nm = 1 # label missing - lb = np.zeros((0, 5), dtype=np.float32) - return im_file, lb, shape, segments, nm, nf, ne, nc, msg - except Exception as e: - nc = 1 - msg = f'{prefix}WARNING ⚠️ {im_file}: ignoring corrupt image/label: {e}' - return [None, None, None, None, nm, nf, ne, nc, msg] - - -class HUBDatasetStats(): - """ Class for generating HUB dataset JSON and `-hub` dataset directory - - Arguments - path: Path to data.yaml or data.zip (with data.yaml inside data.zip) - autodownload: Attempt to download dataset if not found locally - - Usage - from utils.dataloaders import HUBDatasetStats - stats = HUBDatasetStats('coco128.yaml', autodownload=True) # usage 1 - stats = HUBDatasetStats('path/to/coco128.zip') # usage 2 - stats.get_json(save=False) - stats.process_images() - """ - - def __init__(self, path='coco128.yaml', autodownload=False): - # Initialize class - zipped, data_dir, yaml_path = self._unzip(Path(path)) - try: - with open(check_yaml(yaml_path), errors='ignore') as f: - data = yaml.safe_load(f) # data dict - if zipped: - data['path'] = data_dir - except Exception as e: - raise Exception('error/HUB/dataset_stats/yaml_load') from e - - check_dataset(data, autodownload) # download dataset if missing - self.hub_dir = Path(data['path'] + '-hub') - self.im_dir = self.hub_dir / 'images' - self.im_dir.mkdir(parents=True, exist_ok=True) # makes /images - self.stats = {'nc': data['nc'], 'names': list(data['names'].values())} # statistics dictionary - self.data = data - - @staticmethod - def _find_yaml(dir): - # Return data.yaml file - files = list(dir.glob('*.yaml')) or list(dir.rglob('*.yaml')) # try root level first and then recursive - assert files, f'No *.yaml file found in {dir}' - if len(files) > 1: - files = [f for f in files if f.stem == dir.stem] # prefer *.yaml files that match dir name - assert files, f'Multiple *.yaml files found in {dir}, only 1 *.yaml file allowed' - assert len(files) == 1, f'Multiple *.yaml files found: {files}, only 1 *.yaml file allowed in {dir}' - return files[0] - - def _unzip(self, path): - # Unzip data.zip - if not str(path).endswith('.zip'): # path is data.yaml - return False, None, path - assert Path(path).is_file(), f'Error unzipping {path}, file not found' - unzip_file(path, path=path.parent) - dir = path.with_suffix('') # dataset directory == zip name - assert dir.is_dir(), f'Error unzipping {path}, {dir} not found. path/to/abc.zip MUST unzip to path/to/abc/' - return True, str(dir), self._find_yaml(dir) # zipped, data_dir, yaml_path - - def _hub_ops(self, f, max_dim=1920): - # HUB ops for 1 image 'f': resize and save at reduced quality in /dataset-hub for web/app viewing - f_new = self.im_dir / Path(f).name # dataset-hub image filename - try: # use PIL - im = Image.open(f) - r = max_dim / max(im.height, im.width) # ratio - if r < 1.0: # image too large - im = im.resize((int(im.width * r), int(im.height * r))) - im.save(f_new, 'JPEG', quality=50, optimize=True) # save - except Exception as e: # use OpenCV - LOGGER.info(f'WARNING ⚠️ HUB ops PIL failure {f}: {e}') - im = cv2.imread(f) - im_height, im_width = im.shape[:2] - r = max_dim / max(im_height, im_width) # ratio - if r < 1.0: # image too large - im = cv2.resize(im, (int(im_width * r), int(im_height * r)), interpolation=cv2.INTER_AREA) - cv2.imwrite(str(f_new), im) - - def get_json(self, save=False, verbose=False): - # Return dataset JSON for Ultralytics HUB - def _round(labels): - # Update labels to integer class and 6 decimal place floats - return [[int(c), *(round(x, 4) for x in points)] for c, *points in labels] - - for split in 'train', 'val', 'test': - if self.data.get(split) is None: - self.stats[split] = None # i.e. no test set - continue - dataset = LoadImagesAndLabels(self.data[split]) # load dataset - x = np.array([ - np.bincount(label[:, 0].astype(int), minlength=self.data['nc']) - for label in tqdm(dataset.labels, total=dataset.n, desc='Statistics')]) # shape(128x80) - self.stats[split] = { - 'instance_stats': { - 'total': int(x.sum()), - 'per_class': x.sum(0).tolist()}, - 'image_stats': { - 'total': dataset.n, - 'unlabelled': int(np.all(x == 0, 1).sum()), - 'per_class': (x > 0).sum(0).tolist()}, - 'labels': [{ - str(Path(k).name): _round(v.tolist())} for k, v in zip(dataset.im_files, dataset.labels)]} - - # Save, print and return - if save: - stats_path = self.hub_dir / 'stats.json' - print(f'Saving {stats_path.resolve()}...') - with open(stats_path, 'w') as f: - json.dump(self.stats, f) # save stats.json - if verbose: - print(json.dumps(self.stats, indent=2, sort_keys=False)) - return self.stats - - def process_images(self): - # Compress images for Ultralytics HUB - for split in 'train', 'val', 'test': - if self.data.get(split) is None: - continue - dataset = LoadImagesAndLabels(self.data[split]) # load dataset - desc = f'{split} images' - for _ in tqdm(ThreadPool(NUM_THREADS).imap(self._hub_ops, dataset.im_files), total=dataset.n, desc=desc): - pass - print(f'Done. All images saved to {self.im_dir}') - return self.im_dir - - -# Classification dataloaders ------------------------------------------------------------------------------------------- -class ClassificationDataset(torchvision.datasets.ImageFolder): - """ - YOLOv5 Classification Dataset. - Arguments - root: Dataset path - transform: torchvision transforms, used by default - album_transform: Albumentations transforms, used if installed - """ - - def __init__(self, root, augment, imgsz, cache=False): - super().__init__(root=root) - self.torch_transforms = classify_transforms(imgsz) - self.album_transforms = classify_albumentations(augment, imgsz) if augment else None - self.cache_ram = cache is True or cache == 'ram' - self.cache_disk = cache == 'disk' - self.samples = [list(x) + [Path(x[0]).with_suffix('.npy'), None] for x in self.samples] # file, index, npy, im - - def __getitem__(self, i): - f, j, fn, im = self.samples[i] # filename, index, filename.with_suffix('.npy'), image - if self.cache_ram and im is None: - im = self.samples[i][3] = cv2.imread(f) - elif self.cache_disk: - if not fn.exists(): # load npy - np.save(fn.as_posix(), cv2.imread(f)) - im = np.load(fn) - else: # read image - im = cv2.imread(f) # BGR - if self.album_transforms: - sample = self.album_transforms(image=cv2.cvtColor(im, cv2.COLOR_BGR2RGB))['image'] - else: - sample = self.torch_transforms(im) - return sample, j - - -def create_classification_dataloader(path, - imgsz=224, - batch_size=16, - augment=True, - cache=False, - rank=-1, - workers=8, - shuffle=True): - # Returns Dataloader object to be used with YOLOv5 Classifier - with torch_distributed_zero_first(rank): # init dataset *.cache only once if DDP - dataset = ClassificationDataset(root=path, imgsz=imgsz, augment=augment, cache=cache) - batch_size = min(batch_size, len(dataset)) - nd = torch.cuda.device_count() - nw = min([os.cpu_count() // max(nd, 1), batch_size if batch_size > 1 else 0, workers]) - sampler = None if rank == -1 else distributed.DistributedSampler(dataset, shuffle=shuffle) - generator = torch.Generator() - generator.manual_seed(6148914691236517205 + RANK) - return InfiniteDataLoader(dataset, - batch_size=batch_size, - shuffle=shuffle and sampler is None, - num_workers=nw, - sampler=sampler, - pin_memory=PIN_MEMORY, - worker_init_fn=seed_worker, - generator=generator) # or DataLoader(persistent_workers=True) diff --git a/detection/thirdTool/yolo5x_qr/utils/downloads.py b/detection/thirdTool/yolo5x_qr/utils/downloads.py deleted file mode 100644 index cee669c..0000000 --- a/detection/thirdTool/yolo5x_qr/utils/downloads.py +++ /dev/null @@ -1,127 +0,0 @@ -# YOLOv5 🚀 by Ultralytics, GPL-3.0 license -""" -Download utils -""" - -import logging -import subprocess -import urllib -from pathlib import Path - -import requests -import torch - - -def is_url(url, check=True): - # Check if string is URL and check if URL exists - try: - url = str(url) - result = urllib.parse.urlparse(url) - assert all([result.scheme, result.netloc]) # check if is url - return (urllib.request.urlopen(url).getcode() == 200) if check else True # check if exists online - except (AssertionError, urllib.request.HTTPError): - return False - - -def gsutil_getsize(url=''): - # gs://bucket/file size https://cloud.google.com/storage/docs/gsutil/commands/du - output = subprocess.check_output(['gsutil', 'du', url], shell=True, encoding='utf-8') - if output: - return int(output.split()[0]) - return 0 - - -def url_getsize(url='https://ultralytics.com/images/bus.jpg'): - # Return downloadable file size in bytes - response = requests.head(url, allow_redirects=True) - return int(response.headers.get('content-length', -1)) - - -def curl_download(url, filename, *, silent: bool = False) -> bool: - """ - Download a file from a url to a filename using curl. - """ - silent_option = 'sS' if silent else '' # silent - proc = subprocess.run([ - 'curl', - '-#', - f'-{silent_option}L', - url, - '--output', - filename, - '--retry', - '9', - '-C', - '-',]) - return proc.returncode == 0 - - -def safe_download(file, url, url2=None, min_bytes=1E0, error_msg=''): - # Attempts to download file from url or url2, checks and removes incomplete downloads < min_bytes - from ..utils.general import LOGGER - - file = Path(file) - assert_msg = f"Downloaded file '{file}' does not exist or size is < min_bytes={min_bytes}" - try: # url1 - LOGGER.info(f'Downloading {url} to {file}...') - torch.hub.download_url_to_file(url, str(file), progress=LOGGER.level <= logging.INFO) - assert file.exists() and file.stat().st_size > min_bytes, assert_msg # check - except Exception as e: # url2 - if file.exists(): - file.unlink() # remove partial downloads - LOGGER.info(f'ERROR: {e}\nRe-attempting {url2 or url} to {file}...') - # curl download, retry and resume on fail - curl_download(url2 or url, file) - finally: - if not file.exists() or file.stat().st_size < min_bytes: # check - if file.exists(): - file.unlink() # remove partial downloads - LOGGER.info(f'ERROR: {assert_msg}\n{error_msg}') - LOGGER.info('') - - -def attempt_download(file, repo='ultralytics/yolov5', release='v7.0'): - # Attempt file download from GitHub release assets if not found locally. release = 'latest', 'v7.0', etc. - from ..utils.general import LOGGER - - def github_assets(repository, version='latest'): - # Return GitHub repo tag (i.e. 'v7.0') and assets (i.e. ['yolov5s.pt', 'yolov5m.pt', ...]) - if version != 'latest': - version = f'tags/{version}' # i.e. tags/v7.0 - response = requests.get(f'https://api.github.com/repos/{repository}/releases/{version}').json() # github api - return response['tag_name'], [x['name'] for x in response['assets']] # tag, assets - - file = Path(str(file).strip().replace("'", '')) - if not file.exists(): - # URL specified - name = Path(urllib.parse.unquote(str(file))).name # decode '%2F' to '/' etc. - if str(file).startswith(('http:/', 'https:/')): # download - url = str(file).replace(':/', '://') # Pathlib turns :// -> :/ - file = name.split('?')[0] # parse authentication https://url.com/file.txt?auth... - if Path(file).is_file(): - LOGGER.info(f'Found {url} locally at {file}') # file already exists - else: - safe_download(file=file, url=url, min_bytes=1E5) - return file - - # GitHub assets - assets = [f'yolov5{size}{suffix}.pt' for size in 'nsmlx' for suffix in ('', '6', '-cls', '-seg')] # default - try: - tag, assets = github_assets(repo, release) - except Exception: - try: - tag, assets = github_assets(repo) # latest release - except Exception: - try: - tag = subprocess.check_output('git tag', shell=True, stderr=subprocess.STDOUT).decode().split()[-1] - except Exception: - tag = release - - file.parent.mkdir(parents=True, exist_ok=True) # make parent dir (if required) - if name in assets: - safe_download(file, - url=f'https://github.com/{repo}/releases/download/{tag}/{name}', - min_bytes=1E5, - error_msg=f'{file} missing, try downloading from https://github.com/{repo}/releases/{tag}') - - return str(file) diff --git a/detection/thirdTool/yolo5x_qr/utils/general.py b/detection/thirdTool/yolo5x_qr/utils/general.py deleted file mode 100644 index b64e878..0000000 --- a/detection/thirdTool/yolo5x_qr/utils/general.py +++ /dev/null @@ -1,1140 +0,0 @@ -# YOLOv5 🚀 by Ultralytics, GPL-3.0 license -""" -General utils -""" - -import contextlib -import glob -import inspect -import logging -import logging.config -import math -import os -import platform -import random -import re -import signal -import subprocess -import sys -import time -import urllib -from copy import deepcopy -from datetime import datetime -from itertools import repeat -from multiprocessing.pool import ThreadPool -from pathlib import Path -from subprocess import check_output -from tarfile import is_tarfile -from typing import Optional -from zipfile import ZipFile, is_zipfile - -import cv2 -import numpy as np -import pandas as pd -import pkg_resources as pkg -import torch -import torchvision -import yaml - -from ..utils import TryExcept, emojis -from ..utils.downloads import curl_download, gsutil_getsize -from ..utils.metrics import box_iou, fitness - -FILE = Path(__file__).resolve() -ROOT = FILE.parents[1] # YOLOv5 root directory -RANK = int(os.getenv('RANK', -1)) - -# Settings -NUM_THREADS = min(8, max(1, os.cpu_count() - 1)) # number of YOLOv5 multiprocessing threads -DATASETS_DIR = Path(os.getenv('YOLOv5_DATASETS_DIR', ROOT.parent / 'datasets')) # global datasets directory -AUTOINSTALL = str(os.getenv('YOLOv5_AUTOINSTALL', True)).lower() == 'true' # global auto-install mode -VERBOSE = str(os.getenv('YOLOv5_VERBOSE', True)).lower() == 'true' # global verbose mode -TQDM_BAR_FORMAT = '{l_bar}{bar:10}{r_bar}' # tqdm bar format -FONT = 'Arial.ttf' # https://ultralytics.com/assets/Arial.ttf - -torch.set_printoptions(linewidth=320, precision=5, profile='long') -np.set_printoptions(linewidth=320, formatter={'float_kind': '{:11.5g}'.format}) # format short g, %precision=5 -pd.options.display.max_columns = 10 -cv2.setNumThreads(0) # prevent OpenCV from multithreading (incompatible with PyTorch DataLoader) -os.environ['NUMEXPR_MAX_THREADS'] = str(NUM_THREADS) # NumExpr max threads -os.environ['OMP_NUM_THREADS'] = '1' if platform.system() == 'darwin' else str(NUM_THREADS) # OpenMP (PyTorch and SciPy) - - -def is_ascii(s=''): - # Is string composed of all ASCII (no UTF) characters? (note str().isascii() introduced in python 3.7) - s = str(s) # convert list, tuple, None, etc. to str - return len(s.encode().decode('ascii', 'ignore')) == len(s) - - -def is_chinese(s='人工智能'): - # Is string composed of any Chinese characters? - return bool(re.search('[\u4e00-\u9fff]', str(s))) - - -def is_colab(): - # Is environment a Google Colab instance? - return 'google.colab' in sys.modules - - -def is_jupyter(): - """ - Check if the current script is running inside a Jupyter Notebook. - Verified on Colab, Jupyterlab, Kaggle, Paperspace. - - Returns: - bool: True if running inside a Jupyter Notebook, False otherwise. - """ - with contextlib.suppress(Exception): - from IPython import get_ipython - return get_ipython() is not None - return False - - -def is_kaggle(): - # Is environment a Kaggle Notebook? - return os.environ.get('PWD') == '/kaggle/working' and os.environ.get('KAGGLE_URL_BASE') == 'https://www.kaggle.com' - - -def is_docker() -> bool: - """Check if the process runs inside a docker container.""" - if Path('/.dockerenv').exists(): - return True - try: # check if docker is in control groups - with open('/proc/self/cgroup') as file: - return any('docker' in line for line in file) - except OSError: - return False - - -def is_writeable(dir, test=False): - # Return True if directory has write permissions, test opening a file with write permissions if test=True - if not test: - return os.access(dir, os.W_OK) # possible issues on Windows - file = Path(dir) / 'tmp.txt' - try: - with open(file, 'w'): # open file with write permissions - pass - file.unlink() # remove file - return True - except OSError: - return False - - -LOGGING_NAME = 'yolov5' - - -def set_logging(name=LOGGING_NAME, verbose=True): - # sets up logging for the given name - rank = int(os.getenv('RANK', -1)) # rank in world for Multi-GPU trainings - level = logging.INFO if verbose and rank in {-1, 0} else logging.ERROR - logging.config.dictConfig({ - 'version': 1, - 'disable_existing_loggers': False, - 'formatters': { - name: { - 'format': '%(message)s'}}, - 'handlers': { - name: { - 'class': 'logging.StreamHandler', - 'formatter': name, - 'level': level,}}, - 'loggers': { - name: { - 'level': level, - 'handlers': [name], - 'propagate': False,}}}) - - -set_logging(LOGGING_NAME) # run before defining LOGGER -LOGGER = logging.getLogger(LOGGING_NAME) # define globally (used in train.py, val.py, detect.py, etc.) -if platform.system() == 'Windows': - for fn in LOGGER.info, LOGGER.warning: - setattr(LOGGER, fn.__name__, lambda x: fn(emojis(x))) # emoji safe logging - - -def user_config_dir(dir='Ultralytics', env_var='YOLOV5_CONFIG_DIR'): - # Return path of user configuration directory. Prefer environment variable if exists. Make dir if required. - env = os.getenv(env_var) - if env: - path = Path(env) # use environment variable - else: - cfg = {'Windows': 'AppData/Roaming', 'Linux': '.config', 'Darwin': 'Library/Application Support'} # 3 OS dirs - path = Path.home() / cfg.get(platform.system(), '') # OS-specific config dir - path = (path if is_writeable(path) else Path('/tmp')) / dir # GCP and AWS lambda fix, only /tmp is writeable - path.mkdir(exist_ok=True) # make if required - return path - - -CONFIG_DIR = user_config_dir() # Ultralytics settings dir - - -class Profile(contextlib.ContextDecorator): - # YOLOv5 Profile class. Usage: @Profile() decorator or 'with Profile():' context manager - def __init__(self, t=0.0): - self.t = t - self.cuda = torch.cuda.is_available() - - def __enter__(self): - self.start = self.time() - return self - - def __exit__(self, type, value, traceback): - self.dt = self.time() - self.start # delta-time - self.t += self.dt # accumulate dt - - def time(self): - if self.cuda: - torch.cuda.synchronize() - return time.time() - - -class Timeout(contextlib.ContextDecorator): - # YOLOv5 Timeout class. Usage: @Timeout(seconds) decorator or 'with Timeout(seconds):' context manager - def __init__(self, seconds, *, timeout_msg='', suppress_timeout_errors=True): - self.seconds = int(seconds) - self.timeout_message = timeout_msg - self.suppress = bool(suppress_timeout_errors) - - def _timeout_handler(self, signum, frame): - raise TimeoutError(self.timeout_message) - - def __enter__(self): - if platform.system() != 'Windows': # not supported on Windows - signal.signal(signal.SIGALRM, self._timeout_handler) # Set handler for SIGALRM - signal.alarm(self.seconds) # start countdown for SIGALRM to be raised - - def __exit__(self, exc_type, exc_val, exc_tb): - if platform.system() != 'Windows': - signal.alarm(0) # Cancel SIGALRM if it's scheduled - if self.suppress and exc_type is TimeoutError: # Suppress TimeoutError - return True - - -class WorkingDirectory(contextlib.ContextDecorator): - # Usage: @WorkingDirectory(dir) decorator or 'with WorkingDirectory(dir):' context manager - def __init__(self, new_dir): - self.dir = new_dir # new dir - self.cwd = Path.cwd().resolve() # current dir - - def __enter__(self): - os.chdir(self.dir) - - def __exit__(self, exc_type, exc_val, exc_tb): - os.chdir(self.cwd) - - -def methods(instance): - # Get class/instance methods - return [f for f in dir(instance) if callable(getattr(instance, f)) and not f.startswith('__')] - - -def print_args(args: Optional[dict] = None, show_file=True, show_func=False): - # Print function arguments (optional args dict) - x = inspect.currentframe().f_back # previous frame - file, _, func, _, _ = inspect.getframeinfo(x) - if args is None: # get args automatically - args, _, _, frm = inspect.getargvalues(x) - args = {k: v for k, v in frm.items() if k in args} - try: - file = Path(file).resolve().relative_to(ROOT).with_suffix('') - except ValueError: - file = Path(file).stem - s = (f'{file}: ' if show_file else '') + (f'{func}: ' if show_func else '') - LOGGER.info(colorstr(s) + ', '.join(f'{k}={v}' for k, v in args.items())) - - -def init_seeds(seed=0, deterministic=False): - # Initialize random number generator (RNG) seeds https://pytorch.org/docs/stable/notes/randomness.html - random.seed(seed) - np.random.seed(seed) - torch.manual_seed(seed) - torch.cuda.manual_seed(seed) - torch.cuda.manual_seed_all(seed) # for Multi-GPU, exception safe - # torch.backends.cudnn.benchmark = True # AutoBatch problem https://github.com/ultralytics/yolov5/issues/9287 - if deterministic and check_version(torch.__version__, '1.12.0'): # https://github.com/ultralytics/yolov5/pull/8213 - torch.use_deterministic_algorithms(True) - torch.backends.cudnn.deterministic = True - os.environ['CUBLAS_WORKSPACE_CONFIG'] = ':4096:8' - os.environ['PYTHONHASHSEED'] = str(seed) - - -def intersect_dicts(da, db, exclude=()): - # Dictionary intersection of matching keys and shapes, omitting 'exclude' keys, using da values - return {k: v for k, v in da.items() if k in db and all(x not in k for x in exclude) and v.shape == db[k].shape} - - -def get_default_args(func): - # Get func() default arguments - signature = inspect.signature(func) - return {k: v.default for k, v in signature.parameters.items() if v.default is not inspect.Parameter.empty} - - -def get_latest_run(search_dir='.'): - # Return path to most recent 'last.pt' in /runs (i.e. to --resume from) - last_list = glob.glob(f'{search_dir}/**/last*.pt', recursive=True) - return max(last_list, key=os.path.getctime) if last_list else '' - - -def file_age(path=__file__): - # Return days since last file update - dt = (datetime.now() - datetime.fromtimestamp(Path(path).stat().st_mtime)) # delta - return dt.days # + dt.seconds / 86400 # fractional days - - -def file_date(path=__file__): - # Return human-readable file modification date, i.e. '2021-3-26' - t = datetime.fromtimestamp(Path(path).stat().st_mtime) - return f'{t.year}-{t.month}-{t.day}' - - -def file_size(path): - # Return file/dir size (MB) - mb = 1 << 20 # bytes to MiB (1024 ** 2) - path = Path(path) - if path.is_file(): - return path.stat().st_size / mb - elif path.is_dir(): - return sum(f.stat().st_size for f in path.glob('**/*') if f.is_file()) / mb - else: - return 0.0 - - -def check_online(): - # Check internet connectivity - import socket - - def run_once(): - # Check once - try: - socket.create_connection(('1.1.1.1', 443), 5) # check host accessibility - return True - except OSError: - return False - - return run_once() or run_once() # check twice to increase robustness to intermittent connectivity issues - - -def git_describe(path=ROOT): # path must be a directory - # Return human-readable git description, i.e. v5.0-5-g3e25f1e https://git-scm.com/docs/git-describe - try: - assert (Path(path) / '.git').is_dir() - return check_output(f'git -C {path} describe --tags --long --always', shell=True).decode()[:-1] - except Exception: - return '' - - -@TryExcept() -@WorkingDirectory(ROOT) -def check_git_status(repo='ultralytics/yolov5', branch='master'): - # YOLOv5 status check, recommend 'git pull' if code is out of date - url = f'https://github.com/{repo}' - msg = f', for updates see {url}' - s = colorstr('github: ') # string - assert Path('.git').exists(), s + 'skipping check (not a git repository)' + msg - assert check_online(), s + 'skipping check (offline)' + msg - - splits = re.split(pattern=r'\s', string=check_output('git remote -v', shell=True).decode()) - matches = [repo in s for s in splits] - if any(matches): - remote = splits[matches.index(True) - 1] - else: - remote = 'ultralytics' - check_output(f'git remote add {remote} {url}', shell=True) - check_output(f'git fetch {remote}', shell=True, timeout=5) # git fetch - local_branch = check_output('git rev-parse --abbrev-ref HEAD', shell=True).decode().strip() # checked out - n = int(check_output(f'git rev-list {local_branch}..{remote}/{branch} --count', shell=True)) # commits behind - if n > 0: - pull = 'git pull' if remote == 'origin' else f'git pull {remote} {branch}' - s += f"⚠️ YOLOv5 is out of date by {n} commit{'s' * (n > 1)}. Use '{pull}' or 'git clone {url}' to update." - else: - s += f'up to date with {url} ✅' - LOGGER.info(s) - - -@WorkingDirectory(ROOT) -def check_git_info(path='.'): - # YOLOv5 git info check, return {remote, branch, commit} - check_requirements('gitpython') - import git - try: - repo = git.Repo(path) - remote = repo.remotes.origin.url.replace('.git', '') # i.e. 'https://github.com/ultralytics/yolov5' - commit = repo.head.commit.hexsha # i.e. '3134699c73af83aac2a481435550b968d5792c0d' - try: - branch = repo.active_branch.name # i.e. 'main' - except TypeError: # not on any branch - branch = None # i.e. 'detached HEAD' state - return {'remote': remote, 'branch': branch, 'commit': commit} - except git.exc.InvalidGitRepositoryError: # path is not a git dir - return {'remote': None, 'branch': None, 'commit': None} - - -def check_python(minimum='3.7.0'): - # Check current python version vs. required python version - check_version(platform.python_version(), minimum, name='Python ', hard=True) - - -def check_version(current='0.0.0', minimum='0.0.0', name='version ', pinned=False, hard=False, verbose=False): - # Check version vs. required version - current, minimum = (pkg.parse_version(x) for x in (current, minimum)) - result = (current == minimum) if pinned else (current >= minimum) # bool - s = f'WARNING ⚠️ {name}{minimum} is required by YOLOv5, but {name}{current} is currently installed' # string - if hard: - assert result, emojis(s) # assert min requirements met - if verbose and not result: - LOGGER.warning(s) - return result - - -@TryExcept() -def check_requirements(requirements=ROOT / 'requirements.txt', exclude=(), install=True, cmds=''): - # Check installed dependencies meet YOLOv5 requirements (pass *.txt file or list of packages or single package str) - prefix = colorstr('red', 'bold', 'requirements:') - check_python() # check python version - if isinstance(requirements, Path): # requirements.txt file - file = requirements.resolve() - assert file.exists(), f'{prefix} {file} not found, check failed.' - with file.open() as f: - requirements = [f'{x.name}{x.specifier}' for x in pkg.parse_requirements(f) if x.name not in exclude] - elif isinstance(requirements, str): - requirements = [requirements] - - s = '' - n = 0 - for r in requirements: - try: - pkg.require(r) - except (pkg.VersionConflict, pkg.DistributionNotFound): # exception if requirements not met - s += f'"{r}" ' - n += 1 - - if s and install and AUTOINSTALL: # check environment variable - LOGGER.info(f"{prefix} YOLOv5 requirement{'s' * (n > 1)} {s}not found, attempting AutoUpdate...") - try: - # assert check_online(), "AutoUpdate skipped (offline)" - LOGGER.info(check_output(f'pip install {s} {cmds}', shell=True).decode()) - source = file if 'file' in locals() else requirements - s = f"{prefix} {n} package{'s' * (n > 1)} updated per {source}\n" \ - f"{prefix} ⚠️ {colorstr('bold', 'Restart runtime or rerun command for updates to take effect')}\n" - LOGGER.info(s) - except Exception as e: - LOGGER.warning(f'{prefix} ❌ {e}') - - -def check_img_size(imgsz, s=32, floor=0): - # Verify image size is a multiple of stride s in each dimension - if isinstance(imgsz, int): # integer i.e. img_size=640 - new_size = max(make_divisible(imgsz, int(s)), floor) - else: # list i.e. img_size=[640, 480] - imgsz = list(imgsz) # convert to list if tuple - new_size = [max(make_divisible(x, int(s)), floor) for x in imgsz] - if new_size != imgsz: - LOGGER.warning(f'WARNING ⚠️ --img-size {imgsz} must be multiple of max stride {s}, updating to {new_size}') - return new_size - - -def check_imshow(warn=False): - # Check if environment supports image displays - try: - assert not is_jupyter() - assert not is_docker() - cv2.imshow('test', np.zeros((1, 1, 3))) - cv2.waitKey(1) - cv2.destroyAllWindows() - cv2.waitKey(1) - return True - except Exception as e: - if warn: - LOGGER.warning(f'WARNING ⚠️ Environment does not support cv2.imshow() or PIL Image.show()\n{e}') - return False - - -def check_suffix(file='yolov5s.pt', suffix=('.pt',), msg=''): - # Check file(s) for acceptable suffix - if file and suffix: - if isinstance(suffix, str): - suffix = [suffix] - for f in file if isinstance(file, (list, tuple)) else [file]: - s = Path(f).suffix.lower() # file suffix - if len(s): - assert s in suffix, f'{msg}{f} acceptable suffix is {suffix}' - - -def check_yaml(file, suffix=('.yaml', '.yml')): - # Search/download YAML file (if necessary) and return path, checking suffix - return check_file(file, suffix) - - -def check_file(file, suffix=''): - # Search/download file (if necessary) and return path - check_suffix(file, suffix) # optional - file = str(file) # convert to str() - if os.path.isfile(file) or not file: # exists - return file - elif file.startswith(('http:/', 'https:/')): # download - url = file # warning: Pathlib turns :// -> :/ - file = Path(urllib.parse.unquote(file).split('?')[0]).name # '%2F' to '/', split https://url.com/file.txt?auth - if os.path.isfile(file): - LOGGER.info(f'Found {url} locally at {file}') # file already exists - else: - LOGGER.info(f'Downloading {url} to {file}...') - torch.hub.download_url_to_file(url, file) - assert Path(file).exists() and Path(file).stat().st_size > 0, f'File download failed: {url}' # check - return file - elif file.startswith('clearml://'): # ClearML Dataset ID - assert 'clearml' in sys.modules, "ClearML is not installed, so cannot use ClearML dataset. Try running 'pip install clearml'." - return file - else: # search - files = [] - for d in 'data', 'models', 'utils': # search directories - files.extend(glob.glob(str(ROOT / d / '**' / file), recursive=True)) # find file - assert len(files), f'File not found: {file}' # assert file was found - assert len(files) == 1, f"Multiple files match '{file}', specify exact path: {files}" # assert unique - return files[0] # return file - - -def check_font(font=FONT, progress=False): - # Download font to CONFIG_DIR if necessary - font = Path(font) - file = CONFIG_DIR / font.name - if not font.exists() and not file.exists(): - url = f'https://ultralytics.com/assets/{font.name}' - LOGGER.info(f'Downloading {url} to {file}...') - torch.hub.download_url_to_file(url, str(file), progress=progress) - - -def check_dataset(data, autodownload=True): - # Download, check and/or unzip dataset if not found locally - - # Download (optional) - extract_dir = '' - if isinstance(data, (str, Path)) and (is_zipfile(data) or is_tarfile(data)): - download(data, dir=f'{DATASETS_DIR}/{Path(data).stem}', unzip=True, delete=False, curl=False, threads=1) - data = next((DATASETS_DIR / Path(data).stem).rglob('*.yaml')) - extract_dir, autodownload = data.parent, False - - # Read yaml (optional) - if isinstance(data, (str, Path)): - data = yaml_load(data) # dictionary - - # Checks - for k in 'train', 'val', 'names': - assert k in data, emojis(f"data.yaml '{k}:' field missing ❌") - if isinstance(data['names'], (list, tuple)): # old array format - data['names'] = dict(enumerate(data['names'])) # convert to dict - assert all(isinstance(k, int) for k in data['names'].keys()), 'data.yaml names keys must be integers, i.e. 2: car' - data['nc'] = len(data['names']) - - # Resolve paths - path = Path(extract_dir or data.get('path') or '') # optional 'path' default to '.' - if not path.is_absolute(): - path = (ROOT / path).resolve() - data['path'] = path # download scripts - for k in 'train', 'val', 'test': - if data.get(k): # prepend path - if isinstance(data[k], str): - x = (path / data[k]).resolve() - if not x.exists() and data[k].startswith('../'): - x = (path / data[k][3:]).resolve() - data[k] = str(x) - else: - data[k] = [str((path / x).resolve()) for x in data[k]] - - # Parse yaml - train, val, test, s = (data.get(x) for x in ('train', 'val', 'test', 'download')) - if val: - val = [Path(x).resolve() for x in (val if isinstance(val, list) else [val])] # val path - if not all(x.exists() for x in val): - LOGGER.info('\nDataset not found ⚠️, missing paths %s' % [str(x) for x in val if not x.exists()]) - if not s or not autodownload: - raise Exception('Dataset not found ❌') - t = time.time() - if s.startswith('http') and s.endswith('.zip'): # URL - f = Path(s).name # filename - LOGGER.info(f'Downloading {s} to {f}...') - torch.hub.download_url_to_file(s, f) - Path(DATASETS_DIR).mkdir(parents=True, exist_ok=True) # create root - unzip_file(f, path=DATASETS_DIR) # unzip - Path(f).unlink() # remove zip - r = None # success - elif s.startswith('bash '): # bash script - LOGGER.info(f'Running {s} ...') - r = subprocess.run(s, shell=True) - else: # python script - r = exec(s, {'yaml': data}) # return None - dt = f'({round(time.time() - t, 1)}s)' - s = f"success ✅ {dt}, saved to {colorstr('bold', DATASETS_DIR)}" if r in (0, None) else f'failure {dt} ❌' - LOGGER.info(f'Dataset download {s}') - check_font('Arial.ttf' if is_ascii(data['names']) else 'Arial.Unicode.ttf', progress=True) # download fonts - return data # dictionary - - -def check_amp(model): - # Check PyTorch Automatic Mixed Precision (AMP) functionality. Return True on correct operation - from ..models.common import AutoShape, DetectMultiBackend - - def amp_allclose(model, im): - # All close FP32 vs AMP results - m = AutoShape(model, verbose=False) # model - a = m(im).xywhn[0] # FP32 inference - m.amp = True - b = m(im).xywhn[0] # AMP inference - return a.shape == b.shape and torch.allclose(a, b, atol=0.1) # close to 10% absolute tolerance - - prefix = colorstr('AMP: ') - device = next(model.parameters()).device # get model device - if device.type in ('cpu', 'mps'): - return False # AMP only used on CUDA devices - f = ROOT / 'data' / 'images' / 'bus.jpg' # image to check - im = f if f.exists() else 'https://ultralytics.com/images/bus.jpg' if check_online() else np.ones((640, 640, 3)) - try: - assert amp_allclose(deepcopy(model), im) or amp_allclose(DetectMultiBackend('yolov5n.pt', device), im) - LOGGER.info(f'{prefix}checks passed ✅') - return True - except Exception: - help_url = 'https://github.com/ultralytics/yolov5/issues/7908' - LOGGER.warning(f'{prefix}checks failed ❌, disabling Automatic Mixed Precision. See {help_url}') - return False - - -def yaml_load(file='data.yaml'): - # Single-line safe yaml loading - with open(file, errors='ignore') as f: - return yaml.safe_load(f) - - -def yaml_save(file='data.yaml', data={}): - # Single-line safe yaml saving - with open(file, 'w') as f: - yaml.safe_dump({k: str(v) if isinstance(v, Path) else v for k, v in data.items()}, f, sort_keys=False) - - -def unzip_file(file, path=None, exclude=('.DS_Store', '__MACOSX')): - # Unzip a *.zip file to path/, excluding files containing strings in exclude list - if path is None: - path = Path(file).parent # default path - with ZipFile(file) as zipObj: - for f in zipObj.namelist(): # list all archived filenames in the zip - if all(x not in f for x in exclude): - zipObj.extract(f, path=path) - - -def url2file(url): - # Convert URL to filename, i.e. https://url.com/file.txt?auth -> file.txt - url = str(Path(url)).replace(':/', '://') # Pathlib turns :// -> :/ - return Path(urllib.parse.unquote(url)).name.split('?')[0] # '%2F' to '/', split https://url.com/file.txt?auth - - -def download(url, dir='.', unzip=True, delete=True, curl=False, threads=1, retry=3): - # Multithreaded file download and unzip function, used in data.yaml for autodownload - def download_one(url, dir): - # Download 1 file - success = True - if os.path.isfile(url): - f = Path(url) # filename - else: # does not exist - f = dir / Path(url).name - LOGGER.info(f'Downloading {url} to {f}...') - for i in range(retry + 1): - if curl: - success = curl_download(url, f, silent=(threads > 1)) - else: - torch.hub.download_url_to_file(url, f, progress=threads == 1) # torch download - success = f.is_file() - if success: - break - elif i < retry: - LOGGER.warning(f'⚠️ Download failure, retrying {i + 1}/{retry} {url}...') - else: - LOGGER.warning(f'❌ Failed to download {url}...') - - if unzip and success and (f.suffix == '.gz' or is_zipfile(f) or is_tarfile(f)): - LOGGER.info(f'Unzipping {f}...') - if is_zipfile(f): - unzip_file(f, dir) # unzip - elif is_tarfile(f): - subprocess.run(['tar', 'xf', f, '--directory', f.parent], check=True) # unzip - elif f.suffix == '.gz': - subprocess.run(['tar', 'xfz', f, '--directory', f.parent], check=True) # unzip - if delete: - f.unlink() # remove zip - - dir = Path(dir) - dir.mkdir(parents=True, exist_ok=True) # make directory - if threads > 1: - pool = ThreadPool(threads) - pool.imap(lambda x: download_one(*x), zip(url, repeat(dir))) # multithreaded - pool.close() - pool.join() - else: - for u in [url] if isinstance(url, (str, Path)) else url: - download_one(u, dir) - - -def make_divisible(x, divisor): - # Returns nearest x divisible by divisor - if isinstance(divisor, torch.Tensor): - divisor = int(divisor.max()) # to int - return math.ceil(x / divisor) * divisor - - -def clean_str(s): - # Cleans a string by replacing special characters with underscore _ - return re.sub(pattern='[|@#!¡·$€%&()=?¿^*;:,¨´><+]', repl='_', string=s) - - -def one_cycle(y1=0.0, y2=1.0, steps=100): - # lambda function for sinusoidal ramp from y1 to y2 https://arxiv.org/pdf/1812.01187.pdf - return lambda x: ((1 - math.cos(x * math.pi / steps)) / 2) * (y2 - y1) + y1 - - -def colorstr(*input): - # Colors a string https://en.wikipedia.org/wiki/ANSI_escape_code, i.e. colorstr('blue', 'hello world') - *args, string = input if len(input) > 1 else ('blue', 'bold', input[0]) # color arguments, string - colors = { - 'black': '\033[30m', # basic colors - 'red': '\033[31m', - 'green': '\033[32m', - 'yellow': '\033[33m', - 'blue': '\033[34m', - 'magenta': '\033[35m', - 'cyan': '\033[36m', - 'white': '\033[37m', - 'bright_black': '\033[90m', # bright colors - 'bright_red': '\033[91m', - 'bright_green': '\033[92m', - 'bright_yellow': '\033[93m', - 'bright_blue': '\033[94m', - 'bright_magenta': '\033[95m', - 'bright_cyan': '\033[96m', - 'bright_white': '\033[97m', - 'end': '\033[0m', # misc - 'bold': '\033[1m', - 'underline': '\033[4m'} - return ''.join(colors[x] for x in args) + f'{string}' + colors['end'] - - -def labels_to_class_weights(labels, nc=80): - # Get class weights (inverse frequency) from training labels - if labels[0] is None: # no labels loaded - return torch.Tensor() - - labels = np.concatenate(labels, 0) # labels.shape = (866643, 5) for COCO - classes = labels[:, 0].astype(int) # labels = [class xywh] - weights = np.bincount(classes, minlength=nc) # occurrences per class - - # Prepend gridpoint count (for uCE training) - # gpi = ((320 / 32 * np.array([1, 2, 4])) ** 2 * 3).sum() # gridpoints per image - # weights = np.hstack([gpi * len(labels) - weights.sum() * 9, weights * 9]) ** 0.5 # prepend gridpoints to start - - weights[weights == 0] = 1 # replace empty bins with 1 - weights = 1 / weights # number of targets per class - weights /= weights.sum() # normalize - return torch.from_numpy(weights).float() - - -def labels_to_image_weights(labels, nc=80, class_weights=np.ones(80)): - # Produces image weights based on class_weights and image contents - # Usage: index = random.choices(range(n), weights=image_weights, k=1) # weighted image sample - class_counts = np.array([np.bincount(x[:, 0].astype(int), minlength=nc) for x in labels]) - return (class_weights.reshape(1, nc) * class_counts).sum(1) - - -def coco80_to_coco91_class(): # converts 80-index (val2014) to 91-index (paper) - # https://tech.amikelive.com/node-718/what-object-categories-labels-are-in-coco-dataset/ - # a = np.loadtxt('data/coco.names', dtype='str', delimiter='\n') - # b = np.loadtxt('data/coco_paper.names', dtype='str', delimiter='\n') - # x1 = [list(a[i] == b).index(True) + 1 for i in range(80)] # darknet to coco - # x2 = [list(b[i] == a).index(True) if any(b[i] == a) else None for i in range(91)] # coco to darknet - return [ - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 27, 28, 31, 32, 33, 34, - 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, - 64, 65, 67, 70, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 84, 85, 86, 87, 88, 89, 90] - - -def xyxy2xywh(x): - # Convert nx4 boxes from [x1, y1, x2, y2] to [x, y, w, h] where xy1=top-left, xy2=bottom-right - y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x) - y[..., 0] = (x[..., 0] + x[..., 2]) / 2 # x center - y[..., 1] = (x[..., 1] + x[..., 3]) / 2 # y center - y[..., 2] = x[..., 2] - x[..., 0] # width - y[..., 3] = x[..., 3] - x[..., 1] # height - return y - - -def xywh2xyxy(x): - # Convert nx4 boxes from [x, y, w, h] to [x1, y1, x2, y2] where xy1=top-left, xy2=bottom-right - y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x) - y[..., 0] = x[..., 0] - x[..., 2] / 2 # top left x - y[..., 1] = x[..., 1] - x[..., 3] / 2 # top left y - y[..., 2] = x[..., 0] + x[..., 2] / 2 # bottom right x - y[..., 3] = x[..., 1] + x[..., 3] / 2 # bottom right y - return y - - -def xywhn2xyxy(x, w=640, h=640, padw=0, padh=0): - # Convert nx4 boxes from [x, y, w, h] normalized to [x1, y1, x2, y2] where xy1=top-left, xy2=bottom-right - y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x) - y[..., 0] = w * (x[..., 0] - x[..., 2] / 2) + padw # top left x - y[..., 1] = h * (x[..., 1] - x[..., 3] / 2) + padh # top left y - y[..., 2] = w * (x[..., 0] + x[..., 2] / 2) + padw # bottom right x - y[..., 3] = h * (x[..., 1] + x[..., 3] / 2) + padh # bottom right y - return y - - -def xyxy2xywhn(x, w=640, h=640, clip=False, eps=0.0): - # Convert nx4 boxes from [x1, y1, x2, y2] to [x, y, w, h] normalized where xy1=top-left, xy2=bottom-right - if clip: - clip_boxes(x, (h - eps, w - eps)) # warning: inplace clip - y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x) - y[..., 0] = ((x[..., 0] + x[..., 2]) / 2) / w # x center - y[..., 1] = ((x[..., 1] + x[..., 3]) / 2) / h # y center - y[..., 2] = (x[..., 2] - x[..., 0]) / w # width - y[..., 3] = (x[..., 3] - x[..., 1]) / h # height - return y - - -def xyn2xy(x, w=640, h=640, padw=0, padh=0): - # Convert normalized segments into pixel segments, shape (n,2) - y = x.clone() if isinstance(x, torch.Tensor) else np.copy(x) - y[..., 0] = w * x[..., 0] + padw # top left x - y[..., 1] = h * x[..., 1] + padh # top left y - return y - - -def segment2box(segment, width=640, height=640): - # Convert 1 segment label to 1 box label, applying inside-image constraint, i.e. (xy1, xy2, ...) to (xyxy) - x, y = segment.T # segment xy - inside = (x >= 0) & (y >= 0) & (x <= width) & (y <= height) - x, y, = x[inside], y[inside] - return np.array([x.min(), y.min(), x.max(), y.max()]) if any(x) else np.zeros((1, 4)) # xyxy - - -def segments2boxes(segments): - # Convert segment labels to box labels, i.e. (cls, xy1, xy2, ...) to (cls, xywh) - boxes = [] - for s in segments: - x, y = s.T # segment xy - boxes.append([x.min(), y.min(), x.max(), y.max()]) # cls, xyxy - return xyxy2xywh(np.array(boxes)) # cls, xywh - - -def resample_segments(segments, n=1000): - # Up-sample an (n,2) segment - for i, s in enumerate(segments): - s = np.concatenate((s, s[0:1, :]), axis=0) - x = np.linspace(0, len(s) - 1, n) - xp = np.arange(len(s)) - segments[i] = np.concatenate([np.interp(x, xp, s[:, i]) for i in range(2)]).reshape(2, -1).T # segment xy - return segments - - -def scale_boxes(img1_shape, boxes, img0_shape, ratio_pad=None): - # Rescale boxes (xyxy) from img1_shape to img0_shape - if ratio_pad is None: # calculate from img0_shape - gain = min(img1_shape[0] / img0_shape[0], img1_shape[1] / img0_shape[1]) # gain = old / new - pad = (img1_shape[1] - img0_shape[1] * gain) / 2, (img1_shape[0] - img0_shape[0] * gain) / 2 # wh padding - else: - gain = ratio_pad[0][0] - pad = ratio_pad[1] - - boxes[..., [0, 2]] -= pad[0] # x padding - boxes[..., [1, 3]] -= pad[1] # y padding - boxes[..., :4] /= gain - clip_boxes(boxes, img0_shape) - return boxes - - -def scale_segments(img1_shape, segments, img0_shape, ratio_pad=None, normalize=False): - # Rescale coords (xyxy) from img1_shape to img0_shape - if ratio_pad is None: # calculate from img0_shape - gain = min(img1_shape[0] / img0_shape[0], img1_shape[1] / img0_shape[1]) # gain = old / new - pad = (img1_shape[1] - img0_shape[1] * gain) / 2, (img1_shape[0] - img0_shape[0] * gain) / 2 # wh padding - else: - gain = ratio_pad[0][0] - pad = ratio_pad[1] - - segments[:, 0] -= pad[0] # x padding - segments[:, 1] -= pad[1] # y padding - segments /= gain - clip_segments(segments, img0_shape) - if normalize: - segments[:, 0] /= img0_shape[1] # width - segments[:, 1] /= img0_shape[0] # height - return segments - - -def clip_boxes(boxes, shape): - # Clip boxes (xyxy) to image shape (height, width) - if isinstance(boxes, torch.Tensor): # faster individually - boxes[..., 0].clamp_(0, shape[1]) # x1 - boxes[..., 1].clamp_(0, shape[0]) # y1 - boxes[..., 2].clamp_(0, shape[1]) # x2 - boxes[..., 3].clamp_(0, shape[0]) # y2 - else: # np.array (faster grouped) - boxes[..., [0, 2]] = boxes[..., [0, 2]].clip(0, shape[1]) # x1, x2 - boxes[..., [1, 3]] = boxes[..., [1, 3]].clip(0, shape[0]) # y1, y2 - - -def clip_segments(segments, shape): - # Clip segments (xy1,xy2,...) to image shape (height, width) - if isinstance(segments, torch.Tensor): # faster individually - segments[:, 0].clamp_(0, shape[1]) # x - segments[:, 1].clamp_(0, shape[0]) # y - else: # np.array (faster grouped) - segments[:, 0] = segments[:, 0].clip(0, shape[1]) # x - segments[:, 1] = segments[:, 1].clip(0, shape[0]) # y - - -def non_max_suppression( - prediction, - conf_thres=0.25, - iou_thres=0.45, - classes=None, - agnostic=False, - multi_label=False, - labels=(), - max_det=300, - nm=0, # number of masks -): - """Non-Maximum Suppression (NMS) on inference results to reject overlapping detections - - Returns: - list of detections, on (n,6) tensor per image [xyxy, conf, cls] - """ - - # Checks - assert 0 <= conf_thres <= 1, f'Invalid Confidence threshold {conf_thres}, valid values are between 0.0 and 1.0' - assert 0 <= iou_thres <= 1, f'Invalid IoU {iou_thres}, valid values are between 0.0 and 1.0' - if isinstance(prediction, (list, tuple)): # YOLOv5 model in validation model, output = (inference_out, loss_out) - prediction = prediction[0] # select only inference output - - device = prediction.device - mps = 'mps' in device.type # Apple MPS - if mps: # MPS not fully supported yet, convert tensors to CPU before NMS - prediction = prediction.cpu() - bs = prediction.shape[0] # batch size - nc = prediction.shape[2] - nm - 5 # number of classes - xc = prediction[..., 4] > conf_thres # candidates - - # Settings - # min_wh = 2 # (pixels) minimum box width and height - max_wh = 7680 # (pixels) maximum box width and height - max_nms = 30000 # maximum number of boxes into torchvision.ops.nms() - time_limit = 0.5 + 0.05 * bs # seconds to quit after - redundant = True # require redundant detections - multi_label &= nc > 1 # multiple labels per box (adds 0.5ms/img) - merge = False # use merge-NMS - - t = time.time() - mi = 5 + nc # mask start index - output = [torch.zeros((0, 6 + nm), device=prediction.device)] * bs - for xi, x in enumerate(prediction): # image index, image inference - # Apply constraints - # x[((x[..., 2:4] < min_wh) | (x[..., 2:4] > max_wh)).any(1), 4] = 0 # width-height - x = x[xc[xi]] # confidence - - # Cat apriori labels if autolabelling - if labels and len(labels[xi]): - lb = labels[xi] - v = torch.zeros((len(lb), nc + nm + 5), device=x.device) - v[:, :4] = lb[:, 1:5] # box - v[:, 4] = 1.0 # conf - v[range(len(lb)), lb[:, 0].long() + 5] = 1.0 # cls - x = torch.cat((x, v), 0) - - # If none remain process next image - if not x.shape[0]: - continue - - # Compute conf - x[:, 5:] *= x[:, 4:5] # conf = obj_conf * cls_conf - - # Box/Mask - box = xywh2xyxy(x[:, :4]) # center_x, center_y, width, height) to (x1, y1, x2, y2) - mask = x[:, mi:] # zero columns if no masks - - # Detections matrix nx6 (xyxy, conf, cls) - if multi_label: - i, j = (x[:, 5:mi] > conf_thres).nonzero(as_tuple=False).T - x = torch.cat((box[i], x[i, 5 + j, None], j[:, None].float(), mask[i]), 1) - else: # best class only - conf, j = x[:, 5:mi].max(1, keepdim=True) - x = torch.cat((box, conf, j.float(), mask), 1)[conf.view(-1) > conf_thres] - - # Filter by class - if classes is not None: - x = x[(x[:, 5:6] == torch.tensor(classes, device=x.device)).any(1)] - - # Apply finite constraint - # if not torch.isfinite(x).all(): - # x = x[torch.isfinite(x).all(1)] - - # Check shape - n = x.shape[0] # number of boxes - if not n: # no boxes - continue - x = x[x[:, 4].argsort(descending=True)[:max_nms]] # sort by confidence and remove excess boxes - - # Batched NMS - c = x[:, 5:6] * (0 if agnostic else max_wh) # classes - boxes, scores = x[:, :4] + c, x[:, 4] # boxes (offset by class), scores - i = torchvision.ops.nms(boxes, scores, iou_thres) # NMS - i = i[:max_det] # limit detections - if merge and (1 < n < 3E3): # Merge NMS (boxes merged using weighted mean) - # update boxes as boxes(i,4) = weights(i,n) * boxes(n,4) - iou = box_iou(boxes[i], boxes) > iou_thres # iou matrix - weights = iou * scores[None] # box weights - x[i, :4] = torch.mm(weights, x[:, :4]).float() / weights.sum(1, keepdim=True) # merged boxes - if redundant: - i = i[iou.sum(1) > 1] # require redundancy - - output[xi] = x[i] - if mps: - output[xi] = output[xi].to(device) - if (time.time() - t) > time_limit: - LOGGER.warning(f'WARNING ⚠️ NMS time limit {time_limit:.3f}s exceeded') - break # time limit exceeded - - return output - - -def strip_optimizer(f='qr_roi_cloud_detect_20230831.pt', s=''): # from utils.general import *; strip_optimizer() - # Strip optimizer from 'f' to finalize training, optionally save as 's' - x = torch.load(f, map_location=torch.device('cpu')) - if x.get('ema'): - x['model'] = x['ema'] # replace model with ema - for k in 'optimizer', 'best_fitness', 'ema', 'updates': # keys - x[k] = None - x['epoch'] = -1 - x['model'].half() # to FP16 - for p in x['model'].parameters(): - p.requires_grad = False - torch.save(x, s or f) - mb = os.path.getsize(s or f) / 1E6 # filesize - LOGGER.info(f"Optimizer stripped from {f},{f' saved as {s},' if s else ''} {mb:.1f}MB") - - -def print_mutation(keys, results, hyp, save_dir, bucket, prefix=colorstr('evolve: ')): - evolve_csv = save_dir / 'evolve.csv' - evolve_yaml = save_dir / 'hyp_evolve.yaml' - keys = tuple(keys) + tuple(hyp.keys()) # [results + hyps] - keys = tuple(x.strip() for x in keys) - vals = results + tuple(hyp.values()) - n = len(keys) - - # Download (optional) - if bucket: - url = f'gs://{bucket}/evolve.csv' - if gsutil_getsize(url) > (evolve_csv.stat().st_size if evolve_csv.exists() else 0): - subprocess.run(['gsutil', 'cp', f'{url}', f'{save_dir}']) # download evolve.csv if larger than local - - # Log to evolve.csv - s = '' if evolve_csv.exists() else (('%20s,' * n % keys).rstrip(',') + '\n') # add header - with open(evolve_csv, 'a') as f: - f.write(s + ('%20.5g,' * n % vals).rstrip(',') + '\n') - - # Save yaml - with open(evolve_yaml, 'w') as f: - data = pd.read_csv(evolve_csv, skipinitialspace=True) - data = data.rename(columns=lambda x: x.strip()) # strip keys - i = np.argmax(fitness(data.values[:, :4])) # - generations = len(data) - f.write('# YOLOv5 Hyperparameter Evolution Results\n' + f'# Best generation: {i}\n' + - f'# Last generation: {generations - 1}\n' + '# ' + ', '.join(f'{x.strip():>20s}' for x in keys[:7]) + - '\n' + '# ' + ', '.join(f'{x:>20.5g}' for x in data.values[i, :7]) + '\n\n') - yaml.safe_dump(data.loc[i][7:].to_dict(), f, sort_keys=False) - - # Print to screen - LOGGER.info(prefix + f'{generations} generations finished, current result:\n' + prefix + - ', '.join(f'{x.strip():>20s}' for x in keys) + '\n' + prefix + ', '.join(f'{x:20.5g}' - for x in vals) + '\n\n') - - if bucket: - subprocess.run(['gsutil', 'cp', f'{evolve_csv}', f'{evolve_yaml}', f'gs://{bucket}']) # upload - - -def apply_classifier(x, model, img, im0): - # Apply a second stage classifier to YOLO outputs - # Example model = torchvision.models.__dict__['efficientnet_b0'](pretrained=True).to(device).eval() - im0 = [im0] if isinstance(im0, np.ndarray) else im0 - for i, d in enumerate(x): # per image - if d is not None and len(d): - d = d.clone() - - # Reshape and pad cutouts - b = xyxy2xywh(d[:, :4]) # boxes - b[:, 2:] = b[:, 2:].max(1)[0].unsqueeze(1) # rectangle to square - b[:, 2:] = b[:, 2:] * 1.3 + 30 # pad - d[:, :4] = xywh2xyxy(b).long() - - # Rescale boxes from img_size to im0 size - scale_boxes(img.shape[2:], d[:, :4], im0[i].shape) - - # Classes - pred_cls1 = d[:, 5].long() - ims = [] - for a in d: - cutout = im0[i][int(a[1]):int(a[3]), int(a[0]):int(a[2])] - im = cv2.resize(cutout, (224, 224)) # BGR - - im = im[:, :, ::-1].transpose(2, 0, 1) # BGR to RGB, to 3x416x416 - im = np.ascontiguousarray(im, dtype=np.float32) # uint8 to float32 - im /= 255 # 0 - 255 to 0.0 - 1.0 - ims.append(im) - - pred_cls2 = model(torch.Tensor(ims).to(d.device)).argmax(1) # classifier prediction - x[i] = x[i][pred_cls1 == pred_cls2] # retain matching class detections - - return x - - -def increment_path(path, exist_ok=False, sep='', mkdir=False): - # Increment file or directory path, i.e. runs/exp --> runs/exp{sep}2, runs/exp{sep}3, ... etc. - path = Path(path) # os-agnostic - if path.exists() and not exist_ok: - path, suffix = (path.with_suffix(''), path.suffix) if path.is_file() else (path, '') - - # Method 1 - for n in range(2, 9999): - p = f'{path}{sep}{n}{suffix}' # increment path - if not os.path.exists(p): # - break - path = Path(p) - - # Method 2 (deprecated) - # dirs = glob.glob(f"{path}{sep}*") # similar paths - # matches = [re.search(rf"{path.stem}{sep}(\d+)", d) for d in dirs] - # i = [int(m.groups()[0]) for m in matches if m] # indices - # n = max(i) + 1 if i else 2 # increment number - # path = Path(f"{path}{sep}{n}{suffix}") # increment path - - if mkdir: - path.mkdir(parents=True, exist_ok=True) # make directory - - return path - - -# OpenCV Multilanguage-friendly functions ------------------------------------------------------------------------------------ -imshow_ = cv2.imshow # copy to avoid recursion errors - - -def imread(path, flags=cv2.IMREAD_COLOR): - return cv2.imdecode(np.fromfile(path, np.uint8), flags) - - -def imwrite(path, im): - try: - cv2.imencode(Path(path).suffix, im)[1].tofile(path) - return True - except Exception: - return False - - -def imshow(path, im): - imshow_(path.encode('unicode_escape').decode(), im) - - -cv2.imread, cv2.imwrite, cv2.imshow = imread, imwrite, imshow # redefine - -# Variables ------------------------------------------------------------------------------------------------------------ diff --git a/detection/thirdTool/yolo5x_qr/utils/metrics.py b/detection/thirdTool/yolo5x_qr/utils/metrics.py deleted file mode 100644 index 28ca8df..0000000 --- a/detection/thirdTool/yolo5x_qr/utils/metrics.py +++ /dev/null @@ -1,378 +0,0 @@ -# YOLOv5 🚀 by Ultralytics, GPL-3.0 license -""" -Model validation metrics -""" - -import math -import warnings -from pathlib import Path - -import matplotlib.pyplot as plt -import numpy as np -import torch - -from ..utils import TryExcept, threaded - - -def fitness(x): - # Model fitness as a weighted combination of metrics - w = [0.0, 0.0, 0.1, 0.9] # weights for [P, R, mAP@0.5, mAP@0.5:0.95] - return (x[:, :4] * w).sum(1) - - -def smooth(y, f=0.05): - # Box filter of fraction f - nf = round(len(y) * f * 2) // 2 + 1 # number of filter elements (must be odd) - p = np.ones(nf // 2) # ones padding - yp = np.concatenate((p * y[0], y, p * y[-1]), 0) # y padded - return np.convolve(yp, np.ones(nf) / nf, mode='valid') # y-smoothed - - -def ap_per_class(tp, conf, pred_cls, target_cls, plot=False, save_dir='.', names=(), eps=1e-16, prefix=''): - """ Compute the average precision, given the recall and precision curves. - Source: https://github.com/rafaelpadilla/Object-Detection-Metrics. - # Arguments - tp: True positives (nparray, nx1 or nx10). - conf: Objectness value from 0-1 (nparray). - pred_cls: Predicted object classes (nparray). - target_cls: True object classes (nparray). - plot: Plot precision-recall curve at mAP@0.5 - save_dir: Plot save directory - # Returns - The average precision as computed in py-faster-rcnn. - """ - - # Sort by objectness - i = np.argsort(-conf) - tp, conf, pred_cls = tp[i], conf[i], pred_cls[i] - - # Find unique classes - unique_classes, nt = np.unique(target_cls, return_counts=True) - nc = unique_classes.shape[0] # number of classes, number of detections - - # Create Precision-Recall curve and compute AP for each class - px, py = np.linspace(0, 1, 1000), [] # for plotting - ap, p, r = np.zeros((nc, tp.shape[1])), np.zeros((nc, 1000)), np.zeros((nc, 1000)) - for ci, c in enumerate(unique_classes): - i = pred_cls == c - n_l = nt[ci] # number of labels - n_p = i.sum() # number of predictions - if n_p == 0 or n_l == 0: - continue - - # Accumulate FPs and TPs - fpc = (1 - tp[i]).cumsum(0) - tpc = tp[i].cumsum(0) - - # Recall - recall = tpc / (n_l + eps) # recall curve - r[ci] = np.interp(-px, -conf[i], recall[:, 0], left=0) # negative x, xp because xp decreases - - # Precision - precision = tpc / (tpc + fpc) # precision curve - p[ci] = np.interp(-px, -conf[i], precision[:, 0], left=1) # p at pr_score - - # AP from recall-precision curve - for j in range(tp.shape[1]): - ap[ci, j], mpre, mrec = compute_ap(recall[:, j], precision[:, j]) - if plot and j == 0: - py.append(np.interp(px, mrec, mpre)) # precision at mAP@0.5 - - # Compute F1 (harmonic mean of precision and recall) - f1 = 2 * p * r / (p + r + eps) - names = [v for k, v in names.items() if k in unique_classes] # list: only classes that have data - names = dict(enumerate(names)) # to dict - if plot: - plot_pr_curve(px, py, ap, Path(save_dir) / f'{prefix}PR_curve.png', names) - plot_mc_curve(px, f1, Path(save_dir) / f'{prefix}F1_curve.png', names, ylabel='F1') - plot_mc_curve(px, p, Path(save_dir) / f'{prefix}P_curve.png', names, ylabel='Precision') - plot_mc_curve(px, r, Path(save_dir) / f'{prefix}R_curve.png', names, ylabel='Recall') - - i = smooth(f1.mean(0), 0.1).argmax() # max F1 index - p, r, f1 = p[:, i], r[:, i], f1[:, i] - tp = (r * nt).round() # true positives - fp = (tp / (p + eps) - tp).round() # false positives - return tp, fp, p, r, f1, ap, unique_classes.astype(int) - - -def compute_ap(recall, precision): - """ Compute the average precision, given the recall and precision curves - # Arguments - recall: The recall curve (list) - precision: The precision curve (list) - # Returns - Average precision, precision curve, recall curve - """ - - # Append sentinel values to beginning and end - mrec = np.concatenate(([0.0], recall, [1.0])) - mpre = np.concatenate(([1.0], precision, [0.0])) - - # Compute the precision envelope - mpre = np.flip(np.maximum.accumulate(np.flip(mpre))) - - # Integrate area under curve - method = 'interp' # methods: 'continuous', 'interp' - if method == 'interp': - x = np.linspace(0, 1, 101) # 101-point interp (COCO) - ap = np.trapz(np.interp(x, mrec, mpre), x) # integrate - else: # 'continuous' - i = np.where(mrec[1:] != mrec[:-1])[0] # points where x axis (recall) changes - ap = np.sum((mrec[i + 1] - mrec[i]) * mpre[i + 1]) # area under curve - - return ap, mpre, mrec - - -class ConfusionMatrix: - # Updated version of https://github.com/kaanakan/object_detection_confusion_matrix - def __init__(self, nc, conf=0.25, iou_thres=0.45): - self.matrix = np.zeros((nc + 1, nc + 1)) - self.nc = nc # number of classes - self.conf = conf - self.iou_thres = iou_thres - - def process_batch(self, detections, labels): - """ - Return intersection-over-union (Jaccard index) of boxes. - Both sets of boxes are expected to be in (x1, y1, x2, y2) format. - Arguments: - detections (Array[N, 6]), x1, y1, x2, y2, conf, class - labels (Array[M, 5]), class, x1, y1, x2, y2 - Returns: - None, updates confusion matrix accordingly - """ - if detections is None: - gt_classes = labels.int() - for gc in gt_classes: - self.matrix[self.nc, gc] += 1 # background FN - return - - detections = detections[detections[:, 4] > self.conf] - gt_classes = labels[:, 0].int() - detection_classes = detections[:, 5].int() - iou = box_iou(labels[:, 1:], detections[:, :4]) - - x = torch.where(iou > self.iou_thres) - if x[0].shape[0]: - matches = torch.cat((torch.stack(x, 1), iou[x[0], x[1]][:, None]), 1).cpu().numpy() - if x[0].shape[0] > 1: - matches = matches[matches[:, 2].argsort()[::-1]] - matches = matches[np.unique(matches[:, 1], return_index=True)[1]] - matches = matches[matches[:, 2].argsort()[::-1]] - matches = matches[np.unique(matches[:, 0], return_index=True)[1]] - else: - matches = np.zeros((0, 3)) - - n = matches.shape[0] > 0 - m0, m1, _ = matches.transpose().astype(int) - for i, gc in enumerate(gt_classes): - j = m0 == i - if n and sum(j) == 1: - self.matrix[detection_classes[m1[j]], gc] += 1 # correct - else: - self.matrix[self.nc, gc] += 1 # true background - - if n: - for i, dc in enumerate(detection_classes): - if not any(m1 == i): - self.matrix[dc, self.nc] += 1 # predicted background - - def tp_fp(self): - tp = self.matrix.diagonal() # true positives - fp = self.matrix.sum(1) - tp # false positives - # fn = self.matrix.sum(0) - tp # false negatives (missed detections) - return tp[:-1], fp[:-1] # remove background class - - @TryExcept('WARNING ⚠️ ConfusionMatrix plot failure') - def plot(self, normalize=True, save_dir='', names=()): - import seaborn as sn - - array = self.matrix / ((self.matrix.sum(0).reshape(1, -1) + 1E-9) if normalize else 1) # normalize columns - array[array < 0.005] = np.nan # don't annotate (would appear as 0.00) - - fig, ax = plt.subplots(1, 1, figsize=(12, 9), tight_layout=True) - nc, nn = self.nc, len(names) # number of classes, names - sn.set(font_scale=1.0 if nc < 50 else 0.8) # for label size - labels = (0 < nn < 99) and (nn == nc) # apply names to ticklabels - ticklabels = (names + ['background']) if labels else 'auto' - with warnings.catch_warnings(): - warnings.simplefilter('ignore') # suppress empty matrix RuntimeWarning: All-NaN slice encountered - sn.heatmap(array, - ax=ax, - annot=nc < 30, - annot_kws={ - 'size': 8}, - cmap='Blues', - fmt='.2f', - square=True, - vmin=0.0, - xticklabels=ticklabels, - yticklabels=ticklabels).set_facecolor((1, 1, 1)) - ax.set_xlabel('True') - ax.set_ylabel('Predicted') - ax.set_title('Confusion Matrix') - fig.savefig(Path(save_dir) / 'confusion_matrix.png', dpi=250) - plt.close(fig) - - def print(self): - for i in range(self.nc + 1): - print(' '.join(map(str, self.matrix[i]))) - -# by AI&CV Wasserstein ####### start ######################### -def Wasserstein(box1, box2, xywh=True): - box2 = box2.T - if xywh: - b1_cx, b1_cy = (box1[0] + box1[2]) / 2, (box1[1] + box1[3]) / 2 - b1_w, b1_h = box1[2] - box1[0], box1[3] - box1[1] - b2_cx, b2_cy = (box2[0] + box2[0]) / 2, (box2[1] + box2[3]) / 2 - b1_w, b1_h = box2[2] - box2[0], box2[3] - box2[1] - else: - b1_cx, b1_cy, b1_w, b1_h = box1[0], box1[1], box1[2], box1[3] - b2_cx, b2_cy, b2_w, b2_h = box2[0], box2[1], box2[2], box2[3] - cx_L2Norm = torch.pow((b1_cx - b2_cx), 2) - cy_L2Norm = torch.pow((b1_cy - b2_cy), 2) - p1 = cx_L2Norm + cy_L2Norm - w_FroNorm = torch.pow((b1_w - b2_w)/2, 2) - h_FroNorm = torch.pow((b1_h - b2_h)/2, 2) - p2 = w_FroNorm + h_FroNorm - return p1 + p2 -# by AI&CV Wasserstein ####### end ######################### -def bbox_iou(box1, box2, xywh=True, GIoU=False, DIoU=False, CIoU=False, eps=1e-7): - # Returns Intersection over Union (IoU) of box1(1,4) to box2(n,4) - - # Get the coordinates of bounding boxes - if xywh: # transform from xywh to xyxy - (x1, y1, w1, h1), (x2, y2, w2, h2) = box1.chunk(4, -1), box2.chunk(4, -1) - w1_, h1_, w2_, h2_ = w1 / 2, h1 / 2, w2 / 2, h2 / 2 - b1_x1, b1_x2, b1_y1, b1_y2 = x1 - w1_, x1 + w1_, y1 - h1_, y1 + h1_ - b2_x1, b2_x2, b2_y1, b2_y2 = x2 - w2_, x2 + w2_, y2 - h2_, y2 + h2_ - else: # x1, y1, x2, y2 = box1 - b1_x1, b1_y1, b1_x2, b1_y2 = box1.chunk(4, -1) - b2_x1, b2_y1, b2_x2, b2_y2 = box2.chunk(4, -1) - w1, h1 = b1_x2 - b1_x1, (b1_y2 - b1_y1).clamp(eps) - w2, h2 = b2_x2 - b2_x1, (b2_y2 - b2_y1).clamp(eps) - - # Intersection area - inter = (b1_x2.minimum(b2_x2) - b1_x1.maximum(b2_x1)).clamp(0) * \ - (b1_y2.minimum(b2_y2) - b1_y1.maximum(b2_y1)).clamp(0) - - # Union Area - union = w1 * h1 + w2 * h2 - inter + eps - - # IoU - iou = inter / union - if CIoU or DIoU or GIoU: - cw = b1_x2.maximum(b2_x2) - b1_x1.minimum(b2_x1) # convex (smallest enclosing box) width - ch = b1_y2.maximum(b2_y2) - b1_y1.minimum(b2_y1) # convex height - if CIoU or DIoU: # Distance or Complete IoU https://arxiv.org/abs/1911.08287v1 - c2 = cw ** 2 + ch ** 2 + eps # convex diagonal squared - rho2 = ((b2_x1 + b2_x2 - b1_x1 - b1_x2) ** 2 + (b2_y1 + b2_y2 - b1_y1 - b1_y2) ** 2) / 4 # center dist ** 2 - if CIoU: # https://github.com/Zzh-tju/DIoU-SSD-pytorch/blob/master/utils/box/box_utils.py#L47 - v = (4 / math.pi ** 2) * (torch.atan(w2 / h2) - torch.atan(w1 / h1)).pow(2) - with torch.no_grad(): - alpha = v / (v - iou + (1 + eps)) - return iou - (rho2 / c2 + v * alpha) # CIoU - return iou - rho2 / c2 # DIoU - c_area = cw * ch + eps # convex area - return iou - (c_area - union) / c_area # GIoU https://arxiv.org/pdf/1902.09630.pdf - return iou # IoU - - -def box_iou(box1, box2, eps=1e-7): - # https://github.com/pytorch/vision/blob/master/torchvision/ops/boxes.py - """ - Return intersection-over-union (Jaccard index) of boxes. - Both sets of boxes are expected to be in (x1, y1, x2, y2) format. - Arguments: - box1 (Tensor[N, 4]) - box2 (Tensor[M, 4]) - Returns: - iou (Tensor[N, M]): the NxM matrix containing the pairwise - IoU values for every element in boxes1 and boxes2 - """ - - # inter(N,M) = (rb(N,M,2) - lt(N,M,2)).clamp(0).prod(2) - (a1, a2), (b1, b2) = box1.unsqueeze(1).chunk(2, 2), box2.unsqueeze(0).chunk(2, 2) - inter = (torch.min(a2, b2) - torch.max(a1, b1)).clamp(0).prod(2) - - # IoU = inter / (area1 + area2 - inter) - return inter / ((a2 - a1).prod(2) + (b2 - b1).prod(2) - inter + eps) - - -def bbox_ioa(box1, box2, eps=1e-7): - """ Returns the intersection over box2 area given box1, box2. Boxes are x1y1x2y2 - box1: np.array of shape(4) - box2: np.array of shape(nx4) - returns: np.array of shape(n) - """ - - # Get the coordinates of bounding boxes - b1_x1, b1_y1, b1_x2, b1_y2 = box1 - b2_x1, b2_y1, b2_x2, b2_y2 = box2.T - - # Intersection area - inter_area = (np.minimum(b1_x2, b2_x2) - np.maximum(b1_x1, b2_x1)).clip(0) * \ - (np.minimum(b1_y2, b2_y2) - np.maximum(b1_y1, b2_y1)).clip(0) - - # box2 area - box2_area = (b2_x2 - b2_x1) * (b2_y2 - b2_y1) + eps - - # Intersection over box2 area - return inter_area / box2_area - - -def wh_iou(wh1, wh2, eps=1e-7): - # Returns the nxm IoU matrix. wh1 is nx2, wh2 is mx2 - wh1 = wh1[:, None] # [N,1,2] - wh2 = wh2[None] # [1,M,2] - inter = torch.min(wh1, wh2).prod(2) # [N,M] - return inter / (wh1.prod(2) + wh2.prod(2) - inter + eps) # iou = inter / (area1 + area2 - inter) - - -# Plots ---------------------------------------------------------------------------------------------------------------- - - -@threaded -def plot_pr_curve(px, py, ap, save_dir=Path('pr_curve.png'), names=()): - # Precision-recall curve - fig, ax = plt.subplots(1, 1, figsize=(9, 6), tight_layout=True) - py = np.stack(py, axis=1) - - if 0 < len(names) < 21: # display per-class legend if < 21 classes - for i, y in enumerate(py.T): - ax.plot(px, y, linewidth=1, label=f'{names[i]} {ap[i, 0]:.3f}') # plot(recall, precision) - else: - ax.plot(px, py, linewidth=1, color='grey') # plot(recall, precision) - - ax.plot(px, py.mean(1), linewidth=3, color='blue', label='all classes %.3f mAP@0.5' % ap[:, 0].mean()) - ax.set_xlabel('Recall') - ax.set_ylabel('Precision') - ax.set_xlim(0, 1) - ax.set_ylim(0, 1) - ax.legend(bbox_to_anchor=(1.04, 1), loc='upper left') - ax.set_title('Precision-Recall Curve') - fig.savefig(save_dir, dpi=250) - plt.close(fig) - - -@threaded -def plot_mc_curve(px, py, save_dir=Path('mc_curve.png'), names=(), xlabel='Confidence', ylabel='Metric'): - # Metric-confidence curve - fig, ax = plt.subplots(1, 1, figsize=(9, 6), tight_layout=True) - - if 0 < len(names) < 21: # display per-class legend if < 21 classes - for i, y in enumerate(py): - ax.plot(px, y, linewidth=1, label=f'{names[i]}') # plot(confidence, metric) - else: - ax.plot(px, py.T, linewidth=1, color='grey') # plot(confidence, metric) - - y = smooth(py.mean(0), 0.05) - ax.plot(px, y, linewidth=3, color='blue', label=f'all classes {y.max():.2f} at {px[y.argmax()]:.3f}') - ax.set_xlabel(xlabel) - ax.set_ylabel(ylabel) - ax.set_xlim(0, 1) - ax.set_ylim(0, 1) - ax.legend(bbox_to_anchor=(1.04, 1), loc='upper left') - ax.set_title(f'{ylabel}-Confidence Curve') - fig.savefig(save_dir, dpi=250) - plt.close(fig) diff --git a/detection/thirdTool/yolo5x_qr/utils/plots.py b/detection/thirdTool/yolo5x_qr/utils/plots.py deleted file mode 100644 index deac127..0000000 --- a/detection/thirdTool/yolo5x_qr/utils/plots.py +++ /dev/null @@ -1,560 +0,0 @@ -# YOLOv5 🚀 by Ultralytics, GPL-3.0 license -""" -Plotting utils -""" - -import contextlib -import math -import os -from copy import copy -from pathlib import Path -from urllib.error import URLError - -import cv2 -import matplotlib -import matplotlib.pyplot as plt -import numpy as np -import pandas as pd -import seaborn as sn -import torch -from PIL import Image, ImageDraw, ImageFont - -from ..utils import TryExcept, threaded -from ..utils.general import (CONFIG_DIR, FONT, LOGGER, check_font, check_requirements, clip_boxes, increment_path, - is_ascii, xywh2xyxy, xyxy2xywh) -from ..utils.metrics import fitness -# from utils.segment.general import scale_image - -# Settings -RANK = int(os.getenv('RANK', -1)) -matplotlib.rc('font', **{'size': 11}) -matplotlib.use('Agg') # for writing to files only - - -class Colors: - # Ultralytics color palette https://ultralytics.com/ - def __init__(self): - # hex = matplotlib.colors.TABLEAU_COLORS.values() - hexs = ('FF3838', 'FF9D97', 'FF701F', 'FFB21D', 'CFD231', '48F90A', '92CC17', '3DDB86', '1A9334', '00D4BB', - '2C99A8', '00C2FF', '344593', '6473FF', '0018EC', '8438FF', '520085', 'CB38FF', 'FF95C8', 'FF37C7') - self.palette = [self.hex2rgb(f'#{c}') for c in hexs] - self.n = len(self.palette) - - def __call__(self, i, bgr=False): - c = self.palette[int(i) % self.n] - return (c[2], c[1], c[0]) if bgr else c - - @staticmethod - def hex2rgb(h): # rgb order (PIL) - return tuple(int(h[1 + i:1 + i + 2], 16) for i in (0, 2, 4)) - - -colors = Colors() # create instance for 'from utils.plots import colors' - - -def check_pil_font(font=FONT, size=10): - # Return a PIL TrueType Font, downloading to CONFIG_DIR if necessary - font = Path(font) - font = font if font.exists() else (CONFIG_DIR / font.name) - try: - return ImageFont.truetype(str(font) if font.exists() else font.name, size) - except Exception: # download if missing - try: - check_font(font) - return ImageFont.truetype(str(font), size) - except TypeError: - check_requirements('Pillow>=8.4.0') # known issue https://github.com/ultralytics/yolov5/issues/5374 - except URLError: # not online - return ImageFont.load_default() - - -class Annotator: - # YOLOv5 Annotator for train/val mosaics and jpgs and detect/hub inference annotations - def __init__(self, im, line_width=None, font_size=None, font='Arial.ttf', pil=False, example='abc'): - assert im.data.contiguous, 'Image not contiguous. Apply np.ascontiguousarray(im) to Annotator() input images.' - non_ascii = not is_ascii(example) # non-latin labels, i.e. asian, arabic, cyrillic - self.pil = pil or non_ascii - if self.pil: # use PIL - self.im = im if isinstance(im, Image.Image) else Image.fromarray(im) - self.draw = ImageDraw.Draw(self.im) - self.font = check_pil_font(font='Arial.Unicode.ttf' if non_ascii else font, - size=font_size or max(round(sum(self.im.size) / 2 * 0.035), 12)) - else: # use cv2 - self.im = im - self.lw = line_width or max(round(sum(im.shape) / 2 * 0.003), 2) # line width - - def box_label(self, box, label='', color=(128, 128, 128), txt_color=(255, 255, 255)): - # Add one xyxy box to image with label - if self.pil or not is_ascii(label): - self.draw.rectangle(box, width=self.lw, outline=color) # box - if label: - w, h = self.font.getsize(label) # text width, height (WARNING: deprecated) in 9.2.0 - # _, _, w, h = self.font.getbbox(label) # text width, height (New) - outside = box[1] - h >= 0 # label fits outside box - self.draw.rectangle( - (box[0], box[1] - h if outside else box[1], box[0] + w + 1, - box[1] + 1 if outside else box[1] + h + 1), - fill=color, - ) - # self.draw.text((box[0], box[1]), label, fill=txt_color, font=self.font, anchor='ls') # for PIL>8.0 - self.draw.text((box[0], box[1] - h if outside else box[1]), label, fill=txt_color, font=self.font) - else: # cv2 - p1, p2 = (int(box[0]), int(box[1])), (int(box[2]), int(box[3])) - cv2.rectangle(self.im, p1, p2, color, thickness=self.lw, lineType=cv2.LINE_AA) - if label: - tf = max(self.lw - 1, 1) # font thickness - w, h = cv2.getTextSize(label, 0, fontScale=self.lw / 3, thickness=tf)[0] # text width, height - outside = p1[1] - h >= 3 - p2 = p1[0] + w, p1[1] - h - 3 if outside else p1[1] + h + 3 - cv2.rectangle(self.im, p1, p2, color, -1, cv2.LINE_AA) # filled - cv2.putText(self.im, - label, (p1[0], p1[1] - 2 if outside else p1[1] + h + 2), - 0, - self.lw / 3, - txt_color, - thickness=tf, - lineType=cv2.LINE_AA) - - def masks(self, masks, colors, im_gpu, alpha=0.5, retina_masks=False): - """Plot masks at once. - Args: - masks (tensor): predicted masks on cuda, shape: [n, h, w] - colors (List[List[Int]]): colors for predicted masks, [[r, g, b] * n] - im_gpu (tensor): img is in cuda, shape: [3, h, w], range: [0, 1] - alpha (float): mask transparency: 0.0 fully transparent, 1.0 opaque - """ - if self.pil: - # convert to numpy first - self.im = np.asarray(self.im).copy() - if len(masks) == 0: - self.im[:] = im_gpu.permute(1, 2, 0).contiguous().cpu().numpy() * 255 - colors = torch.tensor(colors, device=im_gpu.device, dtype=torch.float32) / 255.0 - colors = colors[:, None, None] # shape(n,1,1,3) - masks = masks.unsqueeze(3) # shape(n,h,w,1) - masks_color = masks * (colors * alpha) # shape(n,h,w,3) - - inv_alph_masks = (1 - masks * alpha).cumprod(0) # shape(n,h,w,1) - mcs = (masks_color * inv_alph_masks).sum(0) * 2 # mask color summand shape(n,h,w,3) - - im_gpu = im_gpu.flip(dims=[0]) # flip channel - im_gpu = im_gpu.permute(1, 2, 0).contiguous() # shape(h,w,3) - im_gpu = im_gpu * inv_alph_masks[-1] + mcs - im_mask = (im_gpu * 255).byte().cpu().numpy() - self.im[:] = im_mask if retina_masks else scale_image(im_gpu.shape, im_mask, self.im.shape) - if self.pil: - # convert im back to PIL and update draw - self.fromarray(self.im) - - def rectangle(self, xy, fill=None, outline=None, width=1): - # Add rectangle to image (PIL-only) - self.draw.rectangle(xy, fill, outline, width) - - def text(self, xy, text, txt_color=(255, 255, 255), anchor='top'): - # Add text to image (PIL-only) - if anchor == 'bottom': # start y from font bottom - w, h = self.font.getsize(text) # text width, height - xy[1] += 1 - h - self.draw.text(xy, text, fill=txt_color, font=self.font) - - def fromarray(self, im): - # Update self.im from a numpy array - self.im = im if isinstance(im, Image.Image) else Image.fromarray(im) - self.draw = ImageDraw.Draw(self.im) - - def result(self): - # Return annotated image as array - return np.asarray(self.im) - - -def feature_visualization(x, module_type, stage, n=32, save_dir=Path('runs/detect/exp')): - """ - x: Features to be visualized - module_type: Module type - stage: Module stage within model - n: Maximum number of feature maps to plot - save_dir: Directory to save results - """ - if 'Detect' not in module_type: - batch, channels, height, width = x.shape # batch, channels, height, width - if height > 1 and width > 1: - f = save_dir / f"stage{stage}_{module_type.split('.')[-1]}_features.png" # filename - - blocks = torch.chunk(x[0].cpu(), channels, dim=0) # select batch index 0, block by channels - n = min(n, channels) # number of plots - fig, ax = plt.subplots(math.ceil(n / 8), 8, tight_layout=True) # 8 rows x n/8 cols - ax = ax.ravel() - plt.subplots_adjust(wspace=0.05, hspace=0.05) - for i in range(n): - ax[i].imshow(blocks[i].squeeze()) # cmap='gray' - ax[i].axis('off') - - LOGGER.info(f'Saving {f}... ({n}/{channels})') - plt.savefig(f, dpi=300, bbox_inches='tight') - plt.close() - np.save(str(f.with_suffix('.npy')), x[0].cpu().numpy()) # npy save - - -def hist2d(x, y, n=100): - # 2d histogram used in labels.png and evolve.png - xedges, yedges = np.linspace(x.min(), x.max(), n), np.linspace(y.min(), y.max(), n) - hist, xedges, yedges = np.histogram2d(x, y, (xedges, yedges)) - xidx = np.clip(np.digitize(x, xedges) - 1, 0, hist.shape[0] - 1) - yidx = np.clip(np.digitize(y, yedges) - 1, 0, hist.shape[1] - 1) - return np.log(hist[xidx, yidx]) - - -def butter_lowpass_filtfilt(data, cutoff=1500, fs=50000, order=5): - from scipy.signal import butter, filtfilt - - # https://stackoverflow.com/questions/28536191/how-to-filter-smooth-with-scipy-numpy - def butter_lowpass(cutoff, fs, order): - nyq = 0.5 * fs - normal_cutoff = cutoff / nyq - return butter(order, normal_cutoff, btype='low', analog=False) - - b, a = butter_lowpass(cutoff, fs, order=order) - return filtfilt(b, a, data) # forward-backward filter - - -def output_to_target(output, max_det=300): - # Convert model output to target format [batch_id, class_id, x, y, w, h, conf] for plotting - targets = [] - for i, o in enumerate(output): - box, conf, cls = o[:max_det, :6].cpu().split((4, 1, 1), 1) - j = torch.full((conf.shape[0], 1), i) - targets.append(torch.cat((j, cls, xyxy2xywh(box), conf), 1)) - return torch.cat(targets, 0).numpy() - - -@threaded -def plot_images(images, targets, paths=None, fname='images.jpg', names=None): - # Plot image grid with labels - if isinstance(images, torch.Tensor): - images = images.cpu().float().numpy() - if isinstance(targets, torch.Tensor): - targets = targets.cpu().numpy() - - max_size = 1920 # max image size - max_subplots = 16 # max image subplots, i.e. 4x4 - bs, _, h, w = images.shape # batch size, _, height, width - bs = min(bs, max_subplots) # limit plot images - ns = np.ceil(bs ** 0.5) # number of subplots (square) - if np.max(images[0]) <= 1: - images *= 255 # de-normalise (optional) - - # Build Image - mosaic = np.full((int(ns * h), int(ns * w), 3), 255, dtype=np.uint8) # init - for i, im in enumerate(images): - if i == max_subplots: # if last batch has fewer images than we expect - break - x, y = int(w * (i // ns)), int(h * (i % ns)) # block origin - im = im.transpose(1, 2, 0) - mosaic[y:y + h, x:x + w, :] = im - - # Resize (optional) - scale = max_size / ns / max(h, w) - if scale < 1: - h = math.ceil(scale * h) - w = math.ceil(scale * w) - mosaic = cv2.resize(mosaic, tuple(int(x * ns) for x in (w, h))) - - # Annotate - fs = int((h + w) * ns * 0.01) # font size - annotator = Annotator(mosaic, line_width=round(fs / 10), font_size=fs, pil=True, example=names) - for i in range(i + 1): - x, y = int(w * (i // ns)), int(h * (i % ns)) # block origin - annotator.rectangle([x, y, x + w, y + h], None, (255, 255, 255), width=2) # borders - if paths: - annotator.text((x + 5, y + 5), text=Path(paths[i]).name[:40], txt_color=(220, 220, 220)) # filenames - if len(targets) > 0: - ti = targets[targets[:, 0] == i] # image targets - boxes = xywh2xyxy(ti[:, 2:6]).T - classes = ti[:, 1].astype('int') - labels = ti.shape[1] == 6 # labels if no conf column - conf = None if labels else ti[:, 6] # check for confidence presence (label vs pred) - - if boxes.shape[1]: - if boxes.max() <= 1.01: # if normalized with tolerance 0.01 - boxes[[0, 2]] *= w # scale to pixels - boxes[[1, 3]] *= h - elif scale < 1: # absolute coords need scale if image scales - boxes *= scale - boxes[[0, 2]] += x - boxes[[1, 3]] += y - for j, box in enumerate(boxes.T.tolist()): - cls = classes[j] - color = colors(cls) - cls = names[cls] if names else cls - if labels or conf[j] > 0.25: # 0.25 conf thresh - label = f'{cls}' if labels else f'{cls} {conf[j]:.1f}' - annotator.box_label(box, label, color=color) - annotator.im.save(fname) # save - - -def plot_lr_scheduler(optimizer, scheduler, epochs=300, save_dir=''): - # Plot LR simulating training for full epochs - optimizer, scheduler = copy(optimizer), copy(scheduler) # do not modify originals - y = [] - for _ in range(epochs): - scheduler.step() - y.append(optimizer.param_groups[0]['lr']) - plt.plot(y, '.-', label='LR') - plt.xlabel('epoch') - plt.ylabel('LR') - plt.grid() - plt.xlim(0, epochs) - plt.ylim(0) - plt.savefig(Path(save_dir) / 'LR.png', dpi=200) - plt.close() - - -def plot_val_txt(): # from utils.plots import *; plot_val() - # Plot val.txt histograms - x = np.loadtxt('val.txt', dtype=np.float32) - box = xyxy2xywh(x[:, :4]) - cx, cy = box[:, 0], box[:, 1] - - fig, ax = plt.subplots(1, 1, figsize=(6, 6), tight_layout=True) - ax.hist2d(cx, cy, bins=600, cmax=10, cmin=0) - ax.set_aspect('equal') - plt.savefig('hist2d.png', dpi=300) - - fig, ax = plt.subplots(1, 2, figsize=(12, 6), tight_layout=True) - ax[0].hist(cx, bins=600) - ax[1].hist(cy, bins=600) - plt.savefig('hist1d.png', dpi=200) - - -def plot_targets_txt(): # from utils.plots import *; plot_targets_txt() - # Plot targets.txt histograms - x = np.loadtxt('targets.txt', dtype=np.float32).T - s = ['x targets', 'y targets', 'width targets', 'height targets'] - fig, ax = plt.subplots(2, 2, figsize=(8, 8), tight_layout=True) - ax = ax.ravel() - for i in range(4): - ax[i].hist(x[i], bins=100, label=f'{x[i].mean():.3g} +/- {x[i].std():.3g}') - ax[i].legend() - ax[i].set_title(s[i]) - plt.savefig('targets.jpg', dpi=200) - - -def plot_val_study(file='', dir='', x=None): # from utils.plots import *; plot_val_study() - # Plot file=study.txt generated by val.py (or plot all study*.txt in dir) - save_dir = Path(file).parent if file else Path(dir) - plot2 = False # plot additional results - if plot2: - ax = plt.subplots(2, 4, figsize=(10, 6), tight_layout=True)[1].ravel() - - fig2, ax2 = plt.subplots(1, 1, figsize=(8, 4), tight_layout=True) - # for f in [save_dir / f'study_coco_{x}.txt' for x in ['yolov5n6', 'yolov5s6', 'yolov5m6', 'yolov5l6', 'yolov5x6']]: - for f in sorted(save_dir.glob('study*.txt')): - y = np.loadtxt(f, dtype=np.float32, usecols=[0, 1, 2, 3, 7, 8, 9], ndmin=2).T - x = np.arange(y.shape[1]) if x is None else np.array(x) - if plot2: - s = ['P', 'R', 'mAP@.5', 'mAP@.5:.95', 't_preprocess (ms/img)', 't_inference (ms/img)', 't_NMS (ms/img)'] - for i in range(7): - ax[i].plot(x, y[i], '.-', linewidth=2, markersize=8) - ax[i].set_title(s[i]) - - j = y[3].argmax() + 1 - ax2.plot(y[5, 1:j], - y[3, 1:j] * 1E2, - '.-', - linewidth=2, - markersize=8, - label=f.stem.replace('study_coco_', '').replace('yolo', 'YOLO')) - - ax2.plot(1E3 / np.array([209, 140, 97, 58, 35, 18]), [34.6, 40.5, 43.0, 47.5, 49.7, 51.5], - 'k.-', - linewidth=2, - markersize=8, - alpha=.25, - label='EfficientDet') - - ax2.grid(alpha=0.2) - ax2.set_yticks(np.arange(20, 60, 5)) - ax2.set_xlim(0, 57) - ax2.set_ylim(25, 55) - ax2.set_xlabel('GPU Speed (ms/img)') - ax2.set_ylabel('COCO AP val') - ax2.legend(loc='lower right') - f = save_dir / 'study.png' - print(f'Saving {f}...') - plt.savefig(f, dpi=300) - - -@TryExcept() # known issue https://github.com/ultralytics/yolov5/issues/5395 -def plot_labels(labels, names=(), save_dir=Path('')): - # plot dataset labels - LOGGER.info(f"Plotting labels to {save_dir / 'labels.jpg'}... ") - c, b = labels[:, 0], labels[:, 1:].transpose() # classes, boxes - nc = int(c.max() + 1) # number of classes - x = pd.DataFrame(b.transpose(), columns=['x', 'y', 'width', 'height']) - - # seaborn correlogram - sn.pairplot(x, corner=True, diag_kind='auto', kind='hist', diag_kws=dict(bins=50), plot_kws=dict(pmax=0.9)) - plt.savefig(save_dir / 'labels_correlogram.jpg', dpi=200) - plt.close() - - # matplotlib labels - matplotlib.use('svg') # faster - ax = plt.subplots(2, 2, figsize=(8, 8), tight_layout=True)[1].ravel() - y = ax[0].hist(c, bins=np.linspace(0, nc, nc + 1) - 0.5, rwidth=0.8) - with contextlib.suppress(Exception): # color histogram bars by class - [y[2].patches[i].set_color([x / 255 for x in colors(i)]) for i in range(nc)] # known issue #3195 - ax[0].set_ylabel('instances') - if 0 < len(names) < 30: - ax[0].set_xticks(range(len(names))) - ax[0].set_xticklabels(list(names.values()), rotation=90, fontsize=10) - else: - ax[0].set_xlabel('classes') - sn.histplot(x, x='x', y='y', ax=ax[2], bins=50, pmax=0.9) - sn.histplot(x, x='width', y='height', ax=ax[3], bins=50, pmax=0.9) - - # rectangles - labels[:, 1:3] = 0.5 # center - labels[:, 1:] = xywh2xyxy(labels[:, 1:]) * 2000 - img = Image.fromarray(np.ones((2000, 2000, 3), dtype=np.uint8) * 255) - for cls, *box in labels[:1000]: - ImageDraw.Draw(img).rectangle(box, width=1, outline=colors(cls)) # plot - ax[1].imshow(img) - ax[1].axis('off') - - for a in [0, 1, 2, 3]: - for s in ['top', 'right', 'left', 'bottom']: - ax[a].spines[s].set_visible(False) - - plt.savefig(save_dir / 'labels.jpg', dpi=200) - matplotlib.use('Agg') - plt.close() - - -def imshow_cls(im, labels=None, pred=None, names=None, nmax=25, verbose=False, f=Path('images.jpg')): - # Show classification image grid with labels (optional) and predictions (optional) - from ..utils.augmentations import denormalize - - names = names or [f'class{i}' for i in range(1000)] - blocks = torch.chunk(denormalize(im.clone()).cpu().float(), len(im), - dim=0) # select batch index 0, block by channels - n = min(len(blocks), nmax) # number of plots - m = min(8, round(n ** 0.5)) # 8 x 8 default - fig, ax = plt.subplots(math.ceil(n / m), m) # 8 rows x n/8 cols - ax = ax.ravel() if m > 1 else [ax] - # plt.subplots_adjust(wspace=0.05, hspace=0.05) - for i in range(n): - ax[i].imshow(blocks[i].squeeze().permute((1, 2, 0)).numpy().clip(0.0, 1.0)) - ax[i].axis('off') - if labels is not None: - s = names[labels[i]] + (f'—{names[pred[i]]}' if pred is not None else '') - ax[i].set_title(s, fontsize=8, verticalalignment='top') - plt.savefig(f, dpi=300, bbox_inches='tight') - plt.close() - if verbose: - LOGGER.info(f'Saving {f}') - if labels is not None: - LOGGER.info('True: ' + ' '.join(f'{names[i]:3s}' for i in labels[:nmax])) - if pred is not None: - LOGGER.info('Predicted:' + ' '.join(f'{names[i]:3s}' for i in pred[:nmax])) - return f - - -def plot_evolve(evolve_csv='path/to/evolve.csv'): # from utils.plots import *; plot_evolve() - # Plot evolve.csv hyp evolution results - evolve_csv = Path(evolve_csv) - data = pd.read_csv(evolve_csv) - keys = [x.strip() for x in data.columns] - x = data.values - f = fitness(x) - j = np.argmax(f) # max fitness index - plt.figure(figsize=(10, 12), tight_layout=True) - matplotlib.rc('font', **{'size': 8}) - print(f'Best results from row {j} of {evolve_csv}:') - for i, k in enumerate(keys[7:]): - v = x[:, 7 + i] - mu = v[j] # best single result - plt.subplot(6, 5, i + 1) - plt.scatter(v, f, c=hist2d(v, f, 20), cmap='viridis', alpha=.8, edgecolors='none') - plt.plot(mu, f.max(), 'k+', markersize=15) - plt.title(f'{k} = {mu:.3g}', fontdict={'size': 9}) # limit to 40 characters - if i % 5 != 0: - plt.yticks([]) - print(f'{k:>15}: {mu:.3g}') - f = evolve_csv.with_suffix('.png') # filename - plt.savefig(f, dpi=200) - plt.close() - print(f'Saved {f}') - - -def plot_results(file='path/to/results.csv', dir=''): - # Plot training results.csv. Usage: from utils.plots import *; plot_results('path/to/results.csv') - save_dir = Path(file).parent if file else Path(dir) - fig, ax = plt.subplots(2, 5, figsize=(12, 6), tight_layout=True) - ax = ax.ravel() - files = list(save_dir.glob('results*.csv')) - assert len(files), f'No results.csv files found in {save_dir.resolve()}, nothing to plot.' - for f in files: - try: - data = pd.read_csv(f) - s = [x.strip() for x in data.columns] - x = data.values[:, 0] - for i, j in enumerate([1, 2, 3, 4, 5, 8, 9, 10, 6, 7]): - y = data.values[:, j].astype('float') - # y[y == 0] = np.nan # don't show zero values - ax[i].plot(x, y, marker='.', label=f.stem, linewidth=2, markersize=8) - ax[i].set_title(s[j], fontsize=12) - # if j in [8, 9, 10]: # share train and val loss y axes - # ax[i].get_shared_y_axes().join(ax[i], ax[i - 5]) - except Exception as e: - LOGGER.info(f'Warning: Plotting error for {f}: {e}') - ax[1].legend() - fig.savefig(save_dir / 'results.png', dpi=200) - plt.close() - - -def profile_idetection(start=0, stop=0, labels=(), save_dir=''): - # Plot iDetection '*.txt' per-image logs. from utils.plots import *; profile_idetection() - ax = plt.subplots(2, 4, figsize=(12, 6), tight_layout=True)[1].ravel() - s = ['Images', 'Free Storage (GB)', 'RAM Usage (GB)', 'Battery', 'dt_raw (ms)', 'dt_smooth (ms)', 'real-world FPS'] - files = list(Path(save_dir).glob('frames*.txt')) - for fi, f in enumerate(files): - try: - results = np.loadtxt(f, ndmin=2).T[:, 90:-30] # clip first and last rows - n = results.shape[1] # number of rows - x = np.arange(start, min(stop, n) if stop else n) - results = results[:, x] - t = (results[0] - results[0].min()) # set t0=0s - results[0] = x - for i, a in enumerate(ax): - if i < len(results): - label = labels[fi] if len(labels) else f.stem.replace('frames_', '') - a.plot(t, results[i], marker='.', label=label, linewidth=1, markersize=5) - a.set_title(s[i]) - a.set_xlabel('time (s)') - # if fi == len(files) - 1: - # a.set_ylim(bottom=0) - for side in ['top', 'right']: - a.spines[side].set_visible(False) - else: - a.remove() - except Exception as e: - print(f'Warning: Plotting error for {f}; {e}') - ax[1].legend() - plt.savefig(Path(save_dir) / 'idetection_profile.png', dpi=200) - - -def save_one_box(xyxy, im, file=Path('im.jpg'), gain=1.02, pad=10, square=False, BGR=False, save=True): - # Save image crop as {file} with crop size multiple {gain} and {pad} pixels. Save and/or return crop - xyxy = torch.tensor(xyxy).view(-1, 4) - b = xyxy2xywh(xyxy) # boxes - if square: - b[:, 2:] = b[:, 2:].max(1)[0].unsqueeze(1) # attempt rectangle to square - b[:, 2:] = b[:, 2:] * gain + pad # box wh * gain + pad - xyxy = xywh2xyxy(b).long() - clip_boxes(xyxy, im.shape) - crop = im[int(xyxy[0, 1]):int(xyxy[0, 3]), int(xyxy[0, 0]):int(xyxy[0, 2]), ::(1 if BGR else -1)] - if save: - file.parent.mkdir(parents=True, exist_ok=True) # make directory - f = str(increment_path(file).with_suffix('.jpg')) - # cv2.imwrite(f, crop) # save BGR, https://github.com/ultralytics/yolov5/issues/7007 chroma subsampling issue - Image.fromarray(crop[..., ::-1]).save(f, quality=95, subsampling=0) # save RGB - return crop diff --git a/detection/thirdTool/yolo5x_qr/utils/torch_utils.py b/detection/thirdTool/yolo5x_qr/utils/torch_utils.py deleted file mode 100644 index 0543a27..0000000 --- a/detection/thirdTool/yolo5x_qr/utils/torch_utils.py +++ /dev/null @@ -1,433 +0,0 @@ -# YOLOv5 🚀 by Ultralytics, GPL-3.0 license -""" -PyTorch utils -""" - -import math -import os -import platform -import subprocess -import time -import warnings -from contextlib import contextmanager -from copy import deepcopy -from pathlib import Path - -import torch -import torch.distributed as dist -import torch.nn as nn -import torch.nn.functional as F -from torch.nn.parallel import DistributedDataParallel as DDP - -from ..utils.general import LOGGER, check_version, colorstr, file_date, git_describe - -LOCAL_RANK = int(os.getenv('LOCAL_RANK', -1)) # https://pytorch.org/docs/stable/elastic/run.html -RANK = int(os.getenv('RANK', -1)) -WORLD_SIZE = int(os.getenv('WORLD_SIZE', 1)) - -try: - import thop # for FLOPs computation -except ImportError: - thop = None - -# Suppress PyTorch warnings -warnings.filterwarnings('ignore', message='User provided device_type of \'cuda\', but CUDA is not available. Disabling') -warnings.filterwarnings('ignore', category=UserWarning) - - -def smart_inference_mode(torch_1_9=check_version(torch.__version__, '1.9.0')): - # Applies torch.inference_mode() decorator if torch>=1.9.0 else torch.no_grad() decorator - def decorate(fn): - return (torch.inference_mode if torch_1_9 else torch.no_grad)()(fn) - - return decorate - - -def smartCrossEntropyLoss(label_smoothing=0.0): - # Returns nn.CrossEntropyLoss with label smoothing enabled for torch>=1.10.0 - if check_version(torch.__version__, '1.10.0'): - return nn.CrossEntropyLoss(label_smoothing=label_smoothing) - if label_smoothing > 0: - LOGGER.warning(f'WARNING ⚠️ label smoothing {label_smoothing} requires torch>=1.10.0') - return nn.CrossEntropyLoss() - - -def smart_DDP(model): - # Model DDP creation with checks - assert not check_version(torch.__version__, '1.12.0', pinned=True), \ - 'torch==1.12.0 torchvision==0.13.0 DDP training is not supported due to a known issue. ' \ - 'Please upgrade or downgrade torch to use DDP. See https://github.com/ultralytics/yolov5/issues/8395' - if check_version(torch.__version__, '1.11.0'): - return DDP(model, device_ids=[LOCAL_RANK], output_device=LOCAL_RANK, static_graph=True) - else: - return DDP(model, device_ids=[LOCAL_RANK], output_device=LOCAL_RANK) - - -def reshape_classifier_output(model, n=1000): - # Update a TorchVision classification model to class count 'n' if required - from ..models.common import Classify - name, m = list((model.model if hasattr(model, 'model') else model).named_children())[-1] # last module - if isinstance(m, Classify): # YOLOv5 Classify() head - if m.linear.out_features != n: - m.linear = nn.Linear(m.linear.in_features, n) - elif isinstance(m, nn.Linear): # ResNet, EfficientNet - if m.out_features != n: - setattr(model, name, nn.Linear(m.in_features, n)) - elif isinstance(m, nn.Sequential): - types = [type(x) for x in m] - if nn.Linear in types: - i = types.index(nn.Linear) # nn.Linear index - if m[i].out_features != n: - m[i] = nn.Linear(m[i].in_features, n) - elif nn.Conv2d in types: - i = types.index(nn.Conv2d) # nn.Conv2d index - if m[i].out_channels != n: - m[i] = nn.Conv2d(m[i].in_channels, n, m[i].kernel_size, m[i].stride, bias=m[i].bias is not None) - - -@contextmanager -def torch_distributed_zero_first(local_rank: int): - # Decorator to make all processes in distributed training wait for each local_master to do something - if local_rank not in [-1, 0]: - dist.barrier(device_ids=[local_rank]) - yield - if local_rank == 0: - dist.barrier(device_ids=[0]) - - -def device_count(): - # Returns number of CUDA devices available. Safe version of torch.cuda.device_count(). Supports Linux and Windows - assert platform.system() in ('Linux', 'Windows'), 'device_count() only supported on Linux or Windows' - try: - cmd = 'nvidia-smi -L | wc -l' if platform.system() == 'Linux' else 'nvidia-smi -L | find /c /v ""' # Windows - return int(subprocess.run(cmd, shell=True, capture_output=True, check=True).stdout.decode().split()[-1]) - except Exception: - return 0 - - -def select_device(device='', batch_size=0, newline=True,model_path=None): - # device = None or 'cpu' or 0 or '0' or '0,1,2,3' - model_name = model_path.split('/')[-1] - s = f'{model_name} 🚀 {git_describe() or file_date()} Python-{platform.python_version()} torch-{torch.__version__} ' - device = str(device).strip().lower().replace('cuda:', '').replace('none', '') # to string, 'cuda:0' to '0' - cpu = device == 'cpu' - mps = device == 'mps' # Apple Metal Performance Shaders (MPS) - if cpu or mps: - os.environ['CUDA_VISIBLE_DEVICES'] = '-1' # force torch.cuda.is_available() = False - elif device: # non-cpu device requested - os.environ['CUDA_VISIBLE_DEVICES'] = device # set environment variable - must be before assert is_available() - assert torch.cuda.is_available() and torch.cuda.device_count() >= len(device.replace(',', '')), \ - f"Invalid CUDA '--device {device}' requested, use '--device cpu' or pass valid CUDA device(s)" - - if not cpu and not mps and torch.cuda.is_available(): # prefer GPU if available - devices = device.split(',') if device else '0' # range(torch.cuda.device_count()) # i.e. 0,1,6,7 - n = len(devices) # device count - if n > 1 and batch_size > 0: # check batch_size is divisible by device_count - assert batch_size % n == 0, f'batch-size {batch_size} not multiple of GPU count {n}' - space = ' ' * (len(s) + 1) - for i, d in enumerate(devices): - p = torch.cuda.get_device_properties(i) - s += f"{'' if i == 0 else space}CUDA:{torch.version.cuda} ({p.name}, {p.total_memory / (1 << 20):.0f}MiB)\n" # bytes to MB - arg = 'cuda:0' - elif mps and getattr(torch, 'has_mps', False) and torch.backends.mps.is_available(): # prefer MPS if available - s += 'MPS\n' - arg = 'mps' - else: # revert to CPU - s += 'CPU\n' - arg = 'cpu' - - if not newline: - s = s.rstrip() - LOGGER.info(s) - return torch.device(arg) - - -def time_sync(): - # PyTorch-accurate time - if torch.cuda.is_available(): - torch.cuda.synchronize() - return time.time() - - -def profile(input, ops, n=10, device=None): - """ YOLOv5 speed/memory/FLOPs profiler - Usage: - input = torch.randn(16, 3, 640, 640) - m1 = lambda x: x * torch.sigmoid(x) - m2 = nn.SiLU() - profile(input, [m1, m2], n=100) # profile over 100 iterations - """ - results = [] - if not isinstance(device, torch.device): - device = select_device(device) - print(f"{'Params':>12s}{'GFLOPs':>12s}{'GPU_mem (GB)':>14s}{'forward (ms)':>14s}{'backward (ms)':>14s}" - f"{'input':>24s}{'output':>24s}") - - for x in input if isinstance(input, list) else [input]: - x = x.to(device) - x.requires_grad = True - for m in ops if isinstance(ops, list) else [ops]: - m = m.to(device) if hasattr(m, 'to') else m # device - m = m.half() if hasattr(m, 'half') and isinstance(x, torch.Tensor) and x.dtype is torch.float16 else m - tf, tb, t = 0, 0, [0, 0, 0] # dt forward, backward - try: - flops = thop.profile(m, inputs=(x,), verbose=False)[0] / 1E9 * 2 # GFLOPs - except Exception: - flops = 0 - - try: - for _ in range(n): - t[0] = time_sync() - y = m(x) - t[1] = time_sync() - try: - _ = (sum(yi.sum() for yi in y) if isinstance(y, list) else y).sum().backward() - t[2] = time_sync() - except Exception: # no backward method - # print(e) # for debug - t[2] = float('nan') - tf += (t[1] - t[0]) * 1000 / n # ms per op forward - tb += (t[2] - t[1]) * 1000 / n # ms per op backward - mem = torch.cuda.memory_reserved() / 1E9 if torch.cuda.is_available() else 0 # (GB) - s_in, s_out = (tuple(x.shape) if isinstance(x, torch.Tensor) else 'list' for x in (x, y)) # shapes - p = sum(x.numel() for x in m.parameters()) if isinstance(m, nn.Module) else 0 # parameters - print(f'{p:12}{flops:12.4g}{mem:>14.3f}{tf:14.4g}{tb:14.4g}{str(s_in):>24s}{str(s_out):>24s}') - results.append([p, flops, mem, tf, tb, s_in, s_out]) - except Exception as e: - print(e) - results.append(None) - torch.cuda.empty_cache() - return results - - -def is_parallel(model): - # Returns True if model is of type DP or DDP - return type(model) in (nn.parallel.DataParallel, nn.parallel.DistributedDataParallel) - - -def de_parallel(model): - # De-parallelize a model: returns single-GPU model if model is of type DP or DDP - return model.module if is_parallel(model) else model - - -def initialize_weights(model): - for m in model.modules(): - t = type(m) - if t is nn.Conv2d: - pass # nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu') - elif t is nn.BatchNorm2d: - m.eps = 1e-3 - m.momentum = 0.03 - elif t in [nn.Hardswish, nn.LeakyReLU, nn.ReLU, nn.ReLU6, nn.SiLU]: - m.inplace = True - - -def find_modules(model, mclass=nn.Conv2d): - # Finds layer indices matching module class 'mclass' - return [i for i, m in enumerate(model.module_list) if isinstance(m, mclass)] - - -def sparsity(model): - # Return global model sparsity - a, b = 0, 0 - for p in model.parameters(): - a += p.numel() - b += (p == 0).sum() - return b / a - - -def prune(model, amount=0.3): - # Prune model to requested global sparsity - import torch.nn.utils.prune as prune - for name, m in model.named_modules(): - if isinstance(m, nn.Conv2d): - prune.l1_unstructured(m, name='weight', amount=amount) # prune - prune.remove(m, 'weight') # make permanent - LOGGER.info(f'Model pruned to {sparsity(model):.3g} global sparsity') - - -def fuse_conv_and_bn(conv, bn): - # Fuse Conv2d() and BatchNorm2d() layers https://tehnokv.com/posts/fusing-batchnorm-and-conv/ - fusedconv = nn.Conv2d(conv.in_channels, - conv.out_channels, - kernel_size=conv.kernel_size, - stride=conv.stride, - padding=conv.padding, - dilation=conv.dilation, - groups=conv.groups, - bias=True).requires_grad_(False).to(conv.weight.device) - - # Prepare filters - w_conv = conv.weight.clone().view(conv.out_channels, -1) - w_bn = torch.diag(bn.weight.div(torch.sqrt(bn.eps + bn.running_var))) - fusedconv.weight.copy_(torch.mm(w_bn, w_conv).view(fusedconv.weight.shape)) - - # Prepare spatial bias - b_conv = torch.zeros(conv.weight.size(0), device=conv.weight.device) if conv.bias is None else conv.bias - b_bn = bn.bias - bn.weight.mul(bn.running_mean).div(torch.sqrt(bn.running_var + bn.eps)) - fusedconv.bias.copy_(torch.mm(w_bn, b_conv.reshape(-1, 1)).reshape(-1) + b_bn) - - return fusedconv - - -def model_info(model, verbose=False, imgsz=640): - # Model information. img_size may be int or list, i.e. img_size=640 or img_size=[640, 320] - n_p = sum(x.numel() for x in model.parameters()) # number parameters - n_g = sum(x.numel() for x in model.parameters() if x.requires_grad) # number gradients - if verbose: - print(f"{'layer':>5} {'name':>40} {'gradient':>9} {'parameters':>12} {'shape':>20} {'mu':>10} {'sigma':>10}") - for i, (name, p) in enumerate(model.named_parameters()): - name = name.replace('module_list.', '') - print('%5g %40s %9s %12g %20s %10.3g %10.3g' % - (i, name, p.requires_grad, p.numel(), list(p.shape), p.mean(), p.std())) - - try: # FLOPs - p = next(model.parameters()) - stride = max(int(model.stride.max()), 32) if hasattr(model, 'stride') else 32 # max stride - im = torch.empty((1, p.shape[1], stride, stride), device=p.device) # input image in BCHW format - flops = thop.profile(deepcopy(model), inputs=(im,), verbose=False)[0] / 1E9 * 2 # stride GFLOPs - imgsz = imgsz if isinstance(imgsz, list) else [imgsz, imgsz] # expand if int/float - fs = f', {flops * imgsz[0] / stride * imgsz[1] / stride:.1f} GFLOPs' # 640x640 GFLOPs - except Exception: - fs = '' - - name = Path(model.yaml_file).stem.replace('yolov5', 'YOLOv5') if hasattr(model, 'yaml_file') else 'Model' - LOGGER.info(f'model summary: {len(list(model.modules()))} layers, {n_p} parameters, {n_g} gradients{fs}') - - -def scale_img(img, ratio=1.0, same_shape=False, gs=32): # img(16,3,256,416) - # Scales img(bs,3,y,x) by ratio constrained to gs-multiple - if ratio == 1.0: - return img - h, w = img.shape[2:] - s = (int(h * ratio), int(w * ratio)) # new size - img = F.interpolate(img, size=s, mode='bilinear', align_corners=False) # resize - if not same_shape: # pad/crop img - h, w = (math.ceil(x * ratio / gs) * gs for x in (h, w)) - return F.pad(img, [0, w - s[1], 0, h - s[0]], value=0.447) # value = imagenet mean - - -def copy_attr(a, b, include=(), exclude=()): - # Copy attributes from b to a, options to only include [...] and to exclude [...] - for k, v in b.__dict__.items(): - if (len(include) and k not in include) or k.startswith('_') or k in exclude: - continue - else: - setattr(a, k, v) - - -def smart_optimizer(model, name='Adam', lr=0.001, momentum=0.9, decay=1e-5): - # YOLOv5 3-param group optimizer: 0) weights with decay, 1) weights no decay, 2) biases no decay - g = [], [], [] # optimizer parameter groups - bn = tuple(v for k, v in nn.__dict__.items() if 'Norm' in k) # normalization layers, i.e. BatchNorm2d() - for v in model.modules(): - for p_name, p in v.named_parameters(recurse=0): - if p_name == 'bias': # bias (no decay) - g[2].append(p) - elif p_name == 'weight' and isinstance(v, bn): # weight (no decay) - g[1].append(p) - else: - g[0].append(p) # weight (with decay) - - if name == 'Adam': - optimizer = torch.optim.Adam(g[2], lr=lr, betas=(momentum, 0.999)) # adjust beta1 to momentum - elif name == 'AdamW': - optimizer = torch.optim.AdamW(g[2], lr=lr, betas=(momentum, 0.999), weight_decay=0.0) - elif name == 'RMSProp': - optimizer = torch.optim.RMSprop(g[2], lr=lr, momentum=momentum) - elif name == 'SGD': - optimizer = torch.optim.SGD(g[2], lr=lr, momentum=momentum, nesterov=True) - else: - raise NotImplementedError(f'Optimizer {name} not implemented.') - - optimizer.add_param_group({'params': g[0], 'weight_decay': decay}) # add g0 with weight_decay - optimizer.add_param_group({'params': g[1], 'weight_decay': 0.0}) # add g1 (BatchNorm2d weights) - LOGGER.info(f"{colorstr('optimizer:')} {type(optimizer).__name__}(lr={lr}) with parameter groups " - f'{len(g[1])} weight(decay=0.0), {len(g[0])} weight(decay={decay}), {len(g[2])} bias') - return optimizer - - -def smart_hub_load(repo='ultralytics/yolov5', model='yolov5s', **kwargs): - # YOLOv5 torch.hub.load() wrapper with smart error/issue handling - if check_version(torch.__version__, '1.9.1'): - kwargs['skip_validation'] = True # validation causes GitHub API rate limit errors - if check_version(torch.__version__, '1.12.0'): - kwargs['trust_repo'] = True # argument required starting in torch 0.12 - try: - return torch.hub.load(repo, model, **kwargs) - except Exception: - return torch.hub.load(repo, model, force_reload=True, **kwargs) - - -def smart_resume(ckpt, optimizer, ema=None, weights='yolov5s.pt', epochs=300, resume=True): - # Resume training from a partially trained checkpoint - best_fitness = 0.0 - start_epoch = ckpt['epoch'] + 1 - if ckpt['optimizer'] is not None: - optimizer.load_state_dict(ckpt['optimizer']) # optimizer - best_fitness = ckpt['best_fitness'] - if ema and ckpt.get('ema'): - ema.ema.load_state_dict(ckpt['ema'].float().state_dict()) # EMA - ema.updates = ckpt['updates'] - if resume: - assert start_epoch > 0, f'{weights} training to {epochs} epochs is finished, nothing to resume.\n' \ - f"Start a new training without --resume, i.e. 'python train.py --weights {weights}'" - LOGGER.info(f'Resuming training from {weights} from epoch {start_epoch} to {epochs} total epochs') - if epochs < start_epoch: - LOGGER.info(f"{weights} has been trained for {ckpt['epoch']} epochs. Fine-tuning for {epochs} more epochs.") - epochs += ckpt['epoch'] # finetune additional epochs - return best_fitness, start_epoch, epochs - - -class EarlyStopping: - # YOLOv5 simple early stopper - def __init__(self, patience=30): - self.best_fitness = 0.0 # i.e. mAP - self.best_epoch = 0 - self.patience = patience or float('inf') # epochs to wait after fitness stops improving to stop - self.possible_stop = False # possible stop may occur next epoch - - def __call__(self, epoch, fitness): - if fitness >= self.best_fitness: # >= 0 to allow for early zero-fitness stage of training - self.best_epoch = epoch - self.best_fitness = fitness - delta = epoch - self.best_epoch # epochs without improvement - self.possible_stop = delta >= (self.patience - 1) # possible stop may occur next epoch - stop = delta >= self.patience # stop training if patience exceeded - if stop: - LOGGER.info(f'Stopping training early as no improvement observed in last {self.patience} epochs. ' - f'Best results observed at epoch {self.best_epoch}, best model saved as best.pt.\n' - f'To update EarlyStopping(patience={self.patience}) pass a new patience value, ' - f'i.e. `python train.py --patience 300` or use `--patience 0` to disable EarlyStopping.') - return stop - - -class ModelEMA: - """ Updated Exponential Moving Average (EMA) from https://github.com/rwightman/pytorch-image-models - Keeps a moving average of everything in the model state_dict (parameters and buffers) - For EMA details see https://www.tensorflow.org/api_docs/python/tf/train/ExponentialMovingAverage - """ - - def __init__(self, model, decay=0.9999, tau=2000, updates=0): - # Create EMA - self.ema = deepcopy(de_parallel(model)).eval() # FP32 EMA - self.updates = updates # number of EMA updates - self.decay = lambda x: decay * (1 - math.exp(-x / tau)) # decay exponential ramp (to help early epochs) - for p in self.ema.parameters(): - p.requires_grad_(False) - - def update(self, model): - # Update EMA parameters - self.updates += 1 - d = self.decay(self.updates) - - msd = de_parallel(model).state_dict() # model state_dict - for k, v in self.ema.state_dict().items(): - if v.dtype.is_floating_point: # true for FP16 and FP32 - v *= d - v += (1 - d) * msd[k].detach() - # assert v.dtype == msd[k].dtype == torch.float32, f'{k}: EMA {v.dtype} and model {msd[k].dtype} must be FP32' - - def update_attr(self, model, include=(), exclude=('process_group', 'reducer')): - # Update EMA attributes - copy_attr(self.ema, model, include, exclude) diff --git a/detection/utils.py b/detection/utils.py deleted file mode 100644 index 12ae3e8..0000000 --- a/detection/utils.py +++ /dev/null @@ -1,28 +0,0 @@ -import os -import oss2 - -def get_model(name): - os.makedirs(os.path.join(os.path.dirname(__file__), "model"), exist_ok=True) - fn = os.path.join(os.path.dirname(__file__), "model", name) - if os.path.exists(fn): - return fn - with open(fn + ".tmp", 'wb') as tf: - print(f"Downloading {name}...") - tf.write(oss_get(name, "emblem-models")) - print(f"Downloaded {name}") - os.rename(fn + ".tmp", fn) - return fn - -def oss_get(name, bucket=None): - try: - return oss_bucket(bucket).get_object(name).read() - except oss2.exceptions.NoSuchKey: - return None - -def oss_bucket(bucketname): - auth = oss2.Auth('LTAI5tC2qXGxwHZUZP7DoD1A', 'qPo9O6ZvEfqo4t8oflGEm0DoxLHJhm') - bucket = oss2.Bucket(auth, 'oss-rg-china-mainland.aliyuncs.com', bucketname) - return bucket - -if __name__ == "__main__": - print(get_model('sr.prototxt')) diff --git a/scanner/pages/camera/camera.js b/scanner/pages/camera/camera.js index d202c31..dcc404a 100644 --- a/scanner/pages/camera/camera.js +++ b/scanner/pages/camera/camera.js @@ -346,10 +346,10 @@ Page({ }); const ctx = wx.createCameraContext(); this.log(`camera set initial zoom to ${initial_zoom}x, will zoom in to ${rule.zoom}x when qr is found`); - ctx.setZoom({ zoom: initial_zoom }); + ctx.setZoom({ zoom: 2 }); this.on_qr_found = () => { this.log(`qr found, zoom to ${rule.zoom}x`); - ctx.setZoom({ zoom: rule.zoom }); + ctx.setZoom({ zoom: 6 }); this.setData({ zoom: rule.zoom, qrmarkers_class: "", diff --git a/scripts/entrypoint b/scripts/entrypoint index 0561f01..b60e374 100755 --- a/scripts/entrypoint +++ b/scripts/entrypoint @@ -36,9 +36,8 @@ def main(): '-t', '0', 'emblemapi.wsgi:application' ] + gunicorn_args, cwd=os.path.join(BASE_DIR, 'api')) - detection = subprocess.Popen(["python3", "app.py"], cwd=os.path.join(BASE_DIR, "detection")) - procs = [nginx, gunicorn, detection] + procs = [nginx, gunicorn] atexit.register(lambda: [x.kill() for x in procs]) while True: diff --git a/scripts/tmux.sh b/scripts/tmux.sh index bb7f00d..a41db4b 100755 --- a/scripts/tmux.sh +++ b/scripts/tmux.sh @@ -11,5 +11,4 @@ pip3 install -r requirements.txt tmux new-window -n serve "cd api && ./manage.py runserver; $SHELL -i" tmux split-window "cd web && npm run serve; $SHELL -i" -tmux split-window "cd detection && python3 app.py; $SHELL -i" $SHELL -i