commit a882a63b6da885d7605299d4626987bdb80fd962 Author: ybyang7 Date: Sat Mar 25 16:04:55 2023 +0000 Duplicate from Gradio-Blocks/Gradio_YOLOv5_Det Co-authored-by: ZengYifu diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..ac481c8 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,27 @@ +*.7z filter=lfs diff=lfs merge=lfs -text +*.arrow filter=lfs diff=lfs merge=lfs -text +*.bin filter=lfs diff=lfs merge=lfs -text +*.bz2 filter=lfs diff=lfs merge=lfs -text +*.ftz filter=lfs diff=lfs merge=lfs -text +*.gz filter=lfs diff=lfs merge=lfs -text +*.h5 filter=lfs diff=lfs merge=lfs -text +*.joblib filter=lfs diff=lfs merge=lfs -text +*.lfs.* filter=lfs diff=lfs merge=lfs -text +*.model filter=lfs diff=lfs merge=lfs -text +*.msgpack filter=lfs diff=lfs merge=lfs -text +*.onnx filter=lfs diff=lfs merge=lfs -text +*.ot filter=lfs diff=lfs merge=lfs -text +*.parquet filter=lfs diff=lfs merge=lfs -text +*.pb filter=lfs diff=lfs merge=lfs -text +*.pt filter=lfs diff=lfs merge=lfs -text +*.pth filter=lfs diff=lfs merge=lfs -text +*.rar filter=lfs diff=lfs merge=lfs -text +saved_model/**/* filter=lfs diff=lfs merge=lfs -text +*.tar.* filter=lfs diff=lfs merge=lfs -text +*.tflite filter=lfs diff=lfs merge=lfs -text +*.tgz filter=lfs diff=lfs merge=lfs -text +*.wasm filter=lfs diff=lfs merge=lfs -text +*.xz filter=lfs diff=lfs merge=lfs -text +*.zip filter=lfs diff=lfs merge=lfs -text +*.zstandard filter=lfs diff=lfs merge=lfs -text +*tfevents* filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d2456d5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,51 @@ +# 图片格式 +*.jpg +*.jpeg +*.png +*.svg +*.gif + +# 视频格式 +*.mp4 +*.avi +.ipynb_checkpoints +*/__pycache__ + +# 日志格式 +*.log +*.datas +*.txt + +# 生成文件 +*.pdf +*.xlsx +*.csv + +# 参数文件 +*.yaml +*.json + +# 压缩文件格式 +*.zip +*.tar +*.tar.gz +*.rar + +# 字体格式 +*.ttc +*.ttf +*.otf + +# 模型文件 +*.pt +*.db + +/flagged +/run +!requirements.txt +!cls_name/* +!model_config/* +!img_example/* +!packages.txt + +app copy.py \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..aaf2062 --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +--- +title: Gradio_YOLOv5_Det +emoji: 🚀 +colorFrom: red +colorTo: red +sdk: gradio +sdk_version: 3.0.9 +app_file: app.py +pinned: true +license: gpl-3.0 +duplicated_from: Gradio-Blocks/Gradio_YOLOv5_Det +--- + +Check out the configuration reference at https://huggingface.co/docs/hub/spaces#reference + +🚀 Project homepage:https://gitee.com/CV_Lab/gradio_yolov5_det diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..a8a98f8 --- /dev/null +++ b/__init__.py @@ -0,0 +1,2 @@ +__author__ = "曾逸夫(Zeng Yifu)" +__email__ = "zyfiy1314@163.com" diff --git a/app.py b/app.py new file mode 100644 index 0000000..5e61053 --- /dev/null +++ b/app.py @@ -0,0 +1,682 @@ +# Gradio YOLOv5 Det v0.4 +# author: Zeng Yifu(曾逸夫) +# creation time: 2022-05-28 +# email: zyfiy1314@163.com +# project homepage: https://gitee.com/CV_Lab/gradio_yolov5_det + +import argparse +import csv +import gc +import json +import os +import sys +from collections import Counter +from pathlib import Path + +import cv2 +import gradio as gr +import numpy as np +import pandas as pd +import torch +import yaml +from PIL import Image, ImageDraw, ImageFont + +from util.fonts_opt import is_fonts +from util.pdf_opt import pdf_generate + +ROOT_PATH = sys.path[0] # root directory + +# model path +model_path = "ultralytics/yolov5" + +# Gradio YOLOv5 Det version +GYD_VERSION = "Gradio YOLOv5 Det v0.4" + +# model name temporary variable +model_name_tmp = "" + +# Device temporary variables +device_tmp = "" + +# File extension +suffix_list = [".csv", ".yaml"] + +# font size +FONTSIZE = 25 + +# object style +obj_style = ["Small Object", "Medium Object", "Large Object"] + + +def parse_args(known=False): + parser = argparse.ArgumentParser(description="Gradio YOLOv5 Det v0.4") + parser.add_argument("--source", "-src", default="upload", type=str, help="input source") + parser.add_argument("--source_video", "-src_v", default="webcam", type=str, help="video input source") + parser.add_argument("--img_tool", "-it", default="editor", type=str, help="input image tool") + parser.add_argument("--model_name", "-mn", default="yolov5s", type=str, help="model name") + parser.add_argument( + "--model_cfg", + "-mc", + default="./model_config/model_name_p5_p6_all.yaml", + type=str, + help="model config", + ) + parser.add_argument( + "--cls_name", + "-cls", + default="./cls_name/cls_name_en.yaml", + type=str, + help="cls name", + ) + parser.add_argument( + "--nms_conf", + "-conf", + default=0.5, + type=float, + help="model NMS confidence threshold", + ) + parser.add_argument("--nms_iou", "-iou", default=0.45, type=float, help="model NMS IoU threshold") + parser.add_argument( + "--device", + "-dev", + default="cpu", + type=str, + help="cuda or cpu", + ) + parser.add_argument("--inference_size", "-isz", default=640, type=int, help="model inference size") + parser.add_argument("--max_detnum", "-mdn", default=50, type=float, help="model max det num") + parser.add_argument("--slider_step", "-ss", default=0.05, type=float, help="slider step") + parser.add_argument( + "--is_login", + "-isl", + action="store_true", + default=False, + help="is login", + ) + parser.add_argument('--usr_pwd', + "-up", + nargs='+', + type=str, + default=["admin", "admin"], + help="user & password for login") + parser.add_argument( + "--is_share", + "-is", + action="store_true", + default=False, + help="is login", + ) + + args = parser.parse_known_args()[0] if known else parser.parse_args() + return args + + +# yaml file parsing +def yaml_parse(file_path): + return yaml.safe_load(open(file_path, encoding="utf-8").read()) + + +# yaml csv file parsing +def yaml_csv(file_path, file_tag): + file_suffix = Path(file_path).suffix + if file_suffix == suffix_list[0]: + # model name + file_names = [i[0] for i in list(csv.reader(open(file_path)))] # csv version + elif file_suffix == suffix_list[1]: + # model name + file_names = yaml_parse(file_path).get(file_tag) # yaml version + else: + print(f"{file_path} is not in the correct format! Program exits!") + sys.exit() + + return file_names + + +# model loading +def model_loading(model_name, device, opt=[]): + + # 加载本地模型 + try: + # load model + model = torch.hub.load(model_path, + model_name, + force_reload=[True if "refresh_yolov5" in opt else False][0], + device=device, + _verbose=False) + except Exception as e: + print(e) + else: + print(f"🚀 welcome to {GYD_VERSION},{model_name} loaded successfully!") + + return model + + +# check information +def export_json(results, img_size): + + return [[{ + "ID": i, + "CLASS": int(result[i][5]), + "CLASS_NAME": model_cls_name_cp[int(result[i][5])], + "BOUNDING_BOX": { + "XMIN": round(result[i][:4].tolist()[0], 6), + "YMIN": round(result[i][:4].tolist()[1], 6), + "XMAX": round(result[i][:4].tolist()[2], 6), + "YMAX": round(result[i][:4].tolist()[3], 6),}, + "CONF": round(float(result[i][4]), 2), + "FPS": round(1000 / float(results.t[1]), 2), + "IMG_WIDTH": img_size[0], + "IMG_HEIGHT": img_size[1],} for i in range(len(result))] for result in results.xyxyn] + + +# frame conversion +def pil_draw(img, countdown_msg, textFont, xyxy, font_size, opt, obj_cls_index, color_list): + + img_pil = ImageDraw.Draw(img) + + img_pil.rectangle(xyxy, fill=None, outline=color_list[obj_cls_index]) # bounding box + + if "label" in opt: + text_w, text_h = textFont.getsize(countdown_msg) # Label size + + img_pil.rectangle( + (xyxy[0], xyxy[1], xyxy[0] + text_w, xyxy[1] + text_h), + fill=color_list[obj_cls_index], + outline=color_list[obj_cls_index], + ) # label background + + img_pil.multiline_text( + (xyxy[0], xyxy[1]), + countdown_msg, + fill=(255, 255, 255), + font=textFont, + align="center", + ) + + return img + + +# Label and bounding box color settings +def color_set(cls_num): + color_list = [] + for i in range(cls_num): + color = tuple(np.random.choice(range(256), size=3)) + # color = ["#"+''.join([random.choice('0123456789ABCDEF') for j in range(6)])] + color_list.append(color) + + return color_list + + +# YOLOv5 image detection function +def yolo_det_img(img, device, model_name, infer_size, conf, iou, max_num, model_cls, opt): + + global model, model_name_tmp, device_tmp + + # object size num + s_obj, m_obj, l_obj = 0, 0, 0 + # object area list + area_obj_all = [] + # cls num stat + cls_det_stat = [] + + if model_name_tmp != model_name: + # Model judgment to avoid repeated loading + model_name_tmp = model_name + print(f"Loading model {model_name_tmp}......") + model = model_loading(model_name_tmp, device, opt) + elif device_tmp != device: + # Device judgment to avoid repeated loading + device_tmp = device + print(f"Loading model {model_name_tmp}......") + model = model_loading(model_name_tmp, device, opt) + else: + print(f"Loading model {model_name_tmp}......") + model = model_loading(model_name_tmp, device, opt) + + # -------------Model tuning ------------- + model.conf = conf # NMS confidence threshold + model.iou = iou # NMS IoU threshold + model.max_det = int(max_num) # Maximum number of detection frames + model.classes = model_cls # model classes + + color_list = color_set(len(model_cls_name_cp)) # 设置颜色 + + img_size = img.size # frame size + + results = model(img, size=infer_size) # detection + + # ----------------目标裁剪---------------- + crops = results.crop(save=False) + img_crops = [] + for i in range(len(crops)): + img_crops.append(crops[i]["im"][..., ::-1]) + + # Data Frame + dataframe = results.pandas().xyxy[0].round(2) + + det_csv = "./Det_Report.csv" + det_excel = "./Det_Report.xlsx" + + if "csv" in opt: + dataframe.to_csv(det_csv, index=False) + else: + det_csv = None + + if "excel" in opt: + dataframe.to_excel(det_excel, sheet_name='sheet1', index=False) + else: + det_excel = None + + # ----------------Load fonts---------------- + yaml_index = cls_name.index(".yaml") + cls_name_lang = cls_name[yaml_index - 2:yaml_index] + + if cls_name_lang == "zh": + # Chinese + textFont = ImageFont.truetype(str(f"{ROOT_PATH}/fonts/SimSun.ttf"), size=FONTSIZE) + elif cls_name_lang in ["en", "ru", "es", "ar"]: + # English, Russian, Spanish, Arabic + textFont = ImageFont.truetype(str(f"{ROOT_PATH}/fonts/TimesNewRoman.ttf"), size=FONTSIZE) + elif cls_name_lang == "ko": + # Korean + textFont = ImageFont.truetype(str(f"{ROOT_PATH}/fonts/malgun.ttf"), size=FONTSIZE) + + for result in results.xyxyn: + for i in range(len(result)): + id = int(i) # instance ID + obj_cls_index = int(result[i][5]) # category index + obj_cls = model_cls_name_cp[obj_cls_index] # category + cls_det_stat.append(obj_cls) + + # ------------ border coordinates ------------ + x0 = float(result[i][:4].tolist()[0]) + y0 = float(result[i][:4].tolist()[1]) + x1 = float(result[i][:4].tolist()[2]) + y1 = float(result[i][:4].tolist()[3]) + + # ------------ Actual coordinates of the border ------------ + x0 = int(img_size[0] * x0) + y0 = int(img_size[1] * y0) + x1 = int(img_size[0] * x1) + y1 = int(img_size[1] * y1) + + conf = float(result[i][4]) # confidence + # fps = f"{(1000 / float(results.t[1])):.2f}" # FPS + + det_img = pil_draw( + img, + f"{id}-{obj_cls}:{conf:.2f}", + textFont, + [x0, y0, x1, y1], + FONTSIZE, + opt, + obj_cls_index, + color_list, + ) + + # ----------add object size---------- + w_obj = x1 - x0 + h_obj = y1 - y0 + area_obj = w_obj * h_obj + area_obj_all.append(area_obj) + + # ------------JSON generate------------ + det_json = export_json(results, img.size)[0] # Detection information + det_json_format = json.dumps(det_json, sort_keys=False, indent=4, separators=(",", ":"), + ensure_ascii=False) # JSON formatting + + if "json" not in opt: + det_json = None + + # -------PDF generate------- + report = "./Det_Report.pdf" + if "pdf" in opt: + pdf_generate(f"{det_json_format}", report, GYD_VERSION) + else: + report = None + + # --------------object size compute-------------- + for i in range(len(area_obj_all)): + if (0 < area_obj_all[i] <= 32 ** 2): + s_obj = s_obj + 1 + elif (32 ** 2 < area_obj_all[i] <= 96 ** 2): + m_obj = m_obj + 1 + elif (area_obj_all[i] > 96 ** 2): + l_obj = l_obj + 1 + + sml_obj_total = s_obj + m_obj + l_obj + + objSize_dict = {obj_style[i]: [s_obj, m_obj, l_obj][i] / sml_obj_total for i in range(3)} + + # ------------cls stat------------ + clsRatio_dict = {} + clsDet_dict = Counter(cls_det_stat) + clsDet_dict_sum = sum(clsDet_dict.values()) + + for k, v in clsDet_dict.items(): + clsRatio_dict[k] = v / clsDet_dict_sum + + return det_img, img_crops, objSize_dict, clsRatio_dict, dataframe, det_json, report, det_csv, det_excel + + +# YOLOv5 video detection function +def yolo_det_video(video, device, model_name, infer_size, conf, iou, max_num, model_cls, opt): + + global model, model_name_tmp, device_tmp + + os.system(""" + if [ -e './output.mp4' ]; then + rm ./output.mp4 + fi + """) + + if model_name_tmp != model_name: + # Model judgment to avoid repeated loading + model_name_tmp = model_name + print(f"Loading model {model_name_tmp}......") + model = model_loading(model_name_tmp, device, opt) + elif device_tmp != device: + # Device judgment to avoid repeated loading + device_tmp = device + print(f"Loading model {model_name_tmp}......") + model = model_loading(model_name_tmp, device, opt) + else: + print(f"Loading model {model_name_tmp}......") + model = model_loading(model_name_tmp, device, opt) + + # -------------Model tuning ------------- + model.conf = conf # NMS confidence threshold + model.iou = iou # NMS IOU threshold + model.max_det = int(max_num) # Maximum number of detection frames + model.classes = model_cls # model classes + + color_list = color_set(len(model_cls_name_cp)) # 设置颜色 + + # ----------------Load fonts---------------- + yaml_index = cls_name.index(".yaml") + cls_name_lang = cls_name[yaml_index - 2:yaml_index] + + if cls_name_lang == "zh": + # Chinese + textFont = ImageFont.truetype(str(f"{ROOT_PATH}/fonts/SimSun.ttf"), size=FONTSIZE) + elif cls_name_lang in ["en", "ru", "es", "ar"]: + # English, Russian, Spanish, Arabic + textFont = ImageFont.truetype(str(f"{ROOT_PATH}/fonts/TimesNewRoman.ttf"), size=FONTSIZE) + elif cls_name_lang == "ko": + # Korean + textFont = ImageFont.truetype(str(f"{ROOT_PATH}/fonts/malgun.ttf"), size=FONTSIZE) + + # video->frame + gc.collect() + output_video_path = "./output.avi" + cap = cv2.VideoCapture(video) + fourcc = cv2.VideoWriter_fourcc(*"I420") # encoder + + out = cv2.VideoWriter(output_video_path, fourcc, 30.0, (int(cap.get(3)), int(cap.get(4)))) + while cap.isOpened(): + ret, frame = cap.read() + # Determine empty frame + if not ret: + break + + results = model(frame, size=infer_size) # detection + h, w, _ = frame.shape # frame size + img_size = (w, h) # frame size + + for result in results.xyxyn: + for i in range(len(result)): + id = int(i) # instance ID + obj_cls_index = int(result[i][5]) # category index + obj_cls = model_cls_name_cp[obj_cls_index] # category + + # ------------ border coordinates ------------ + x0 = float(result[i][:4].tolist()[0]) + y0 = float(result[i][:4].tolist()[1]) + x1 = float(result[i][:4].tolist()[2]) + y1 = float(result[i][:4].tolist()[3]) + + # ------------ Actual coordinates of the border ------------ + x0 = int(img_size[0] * x0) + y0 = int(img_size[1] * y0) + x1 = int(img_size[0] * x1) + y1 = int(img_size[1] * y1) + + conf = float(result[i][4]) # confidence + # fps = f"{(1000 / float(results.t[1])):.2f}" # FPS + + frame = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)) + frame = pil_draw( + frame, + f"{id}-{obj_cls}:{conf:.2f}", + textFont, + [x0, y0, x1, y1], + FONTSIZE, + opt, + obj_cls_index, + color_list, + ) + + frame = cv2.cvtColor(np.asarray(frame), cv2.COLOR_RGB2BGR) + + # frame->video + out.write(frame) + out.release() + cap.release() + # cv2.destroyAllWindows() + + return output_video_path + + +def main(args): + gr.close_all() + + global model, model_cls_name_cp, cls_name + + source = args.source + source_video = args.source_video + img_tool = args.img_tool + nms_conf = args.nms_conf + nms_iou = args.nms_iou + model_name = args.model_name + model_cfg = args.model_cfg + cls_name = args.cls_name + device = args.device + inference_size = args.inference_size + max_detnum = args.max_detnum + slider_step = args.slider_step + is_login = args.is_login + usr_pwd = args.usr_pwd + is_share = args.is_share + + is_fonts(f"{ROOT_PATH}/fonts") # Check font files + + # model loading + model = model_loading(model_name, device) + + model_names = yaml_csv(model_cfg, "model_names") # model names + model_cls_name = yaml_csv(cls_name, "model_cls_name") # class name + + model_cls_name_cp = model_cls_name.copy() # class name + + # ------------------- Input Components ------------------- + inputs_img = gr.Image(image_mode="RGB", source=source, tool=img_tool, type="pil", label="original image") + inputs_device01 = gr.Radio(choices=["cuda:0", "cpu"], value=device, label="device") + inputs_model01 = gr.Dropdown(choices=model_names, value=model_name, type="value", label="model") + inputs_size01 = gr.Radio(choices=[320, 640, 1280], value=inference_size, label="inference size") + input_conf01 = gr.Slider(0, 1, step=slider_step, value=nms_conf, label="confidence threshold") + inputs_iou01 = gr.Slider(0, 1, step=slider_step, value=nms_iou, label="IoU threshold") + inputs_maxnum01 = gr.Number(value=max_detnum, label="Maximum number of detections") + inputs_clsName01 = gr.CheckboxGroup(choices=model_cls_name, value=model_cls_name, type="index", label="category") + inputs_opt01 = gr.CheckboxGroup(choices=["refresh_yolov5", "label", "pdf", "json", "csv", "excel"], + value=["label", "pdf"], + type="value", + label="operate") + + # ------------------- Input Components ------------------- + inputs_video = gr.Video(format="mp4", source=source_video, label="original video") # webcam + inputs_device02 = gr.Radio(choices=["cuda:0", "cpu"], value=device, label="device") + inputs_model02 = gr.Dropdown(choices=model_names, value=model_name, type="value", label="model") + inputs_size02 = gr.Radio(choices=[320, 640, 1280], value=inference_size, label="inference size") + input_conf02 = gr.Slider(0, 1, step=slider_step, value=nms_conf, label="confidence threshold") + inputs_iou02 = gr.Slider(0, 1, step=slider_step, value=nms_iou, label="IoU threshold") + inputs_maxnum02 = gr.Number(value=max_detnum, label="Maximum number of detections") + inputs_clsName02 = gr.CheckboxGroup(choices=model_cls_name, value=model_cls_name, type="index", label="category") + inputs_opt02 = gr.CheckboxGroup(choices=["refresh_yolov5", "label"], value=["label"], type="value", label="operate") + + # Input parameters + inputs_img_list = [ + inputs_img, # input image + inputs_device01, # device + inputs_model01, # model + inputs_size01, # inference size + input_conf01, # confidence threshold + inputs_iou01, # IoU threshold + inputs_maxnum01, # maximum number of detections + inputs_clsName01, # category + inputs_opt01, # detect operations + ] + + inputs_video_list = [ + inputs_video, # input image + inputs_device02, # device + inputs_model02, # model + inputs_size02, # inference size + input_conf02, # confidence threshold + inputs_iou02, # IoU threshold + inputs_maxnum02, # maximum number of detections + inputs_clsName02, # category + inputs_opt02, # detect operation + ] + + # -------------------output component------------------- + outputs_img = gr.Image(type="pil", label="Detection image") + outputs_crops = gr.Gallery(label="Object crop") + outputs_df = gr.Dataframe(max_rows=5, + overflow_row_behaviour="paginate", + type="pandas", + label="List of detection information") + outputs_objSize = gr.Label(label="Object size ratio statistics") + outputs_clsSize = gr.Label(label="Category detection proportion statistics") + outputs_json = gr.JSON(label="Detection information") + outputs_pdf = gr.File(label="pdf detection report") + outputs_csv = gr.File(label="csv detection report") + outputs_excel = gr.File(label="xlsx detection report") + + # -------------------output component------------------- + outputs_video = gr.Video(format='mp4', label="Detection video") + + # output parameters + outputs_img_list = [ + outputs_img, outputs_crops, outputs_objSize, outputs_clsSize, outputs_df, outputs_json, outputs_pdf, + outputs_csv, outputs_excel] + outputs_video_list = [outputs_video] + + # title + title = "Gradio YOLOv5 Det v0.4" + + # describe + description = "Author: 曾逸夫(Zeng Yifu), Project Address: https://gitee.com/CV_Lab/gradio_yolov5_det, Github: https://github.com/Zengyf-CVer, thanks to [Gradio](https://github.com/gradio-app/gradio) & [YOLOv5](https://github.com/ultralytics/yolov5)" + # article="https://gitee.com/CV_Lab/gradio_yolov5_det" + + # example image + examples = [ + [ + "./img_example/bus.jpg", + "cpu", + "yolov5s", + 640, + 0.6, + 0.5, + 10, + ["person", "bus"], + ["label", "pdf"],], + [ + "./img_example/giraffe.jpg", + "cpu", + "yolov5l", + 320, + 0.5, + 0.45, + 12, + ["giraffe"], + ["label", "pdf"],], + [ + "./img_example/zidane.jpg", + "cpu", + "yolov5m", + 640, + 0.6, + 0.5, + 15, + ["person", "tie"], + ["pdf", "json"],], + [ + "./img_example/Millenial-at-work.jpg", + "cpu", + "yolov5s6", + 1280, + 0.5, + 0.5, + 20, + ["person", "chair", "cup", "laptop"], + ["label", "pdf"],],] + + # interface + gyd_img = gr.Interface( + fn=yolo_det_img, + inputs=inputs_img_list, + outputs=outputs_img_list, + title=title, + description=description, + # article=article, + examples=examples, + cache_examples=False, + # theme="seafoam", + # live=True, # Change output in real time + flagging_dir="run", # output directory + # allow_flagging="manual", + # flagging_options=["good", "generally", "bad"], + ) + + gyd_video = gr.Interface( + # fn=yolo_det_video_test, + fn=yolo_det_video, + inputs=inputs_video_list, + outputs=outputs_video_list, + title=title, + description=description, + # article=article, + # examples=examples, + # theme="seafoam", + # live=True, # Change output in real time + flagging_dir="run", # output directory + allow_flagging="never", + # flagging_options=["good", "generally", "bad"], + ) + + gyd = gr.TabbedInterface(interface_list=[gyd_img, gyd_video], tab_names=["Image Mode", "Video Mode"]) + + if not is_login: + gyd.launch( + inbrowser=True, # Automatically open default browser + show_tips=True, # Automatically display the latest features of gradio + share=is_share, # Project sharing, other devices can access + favicon_path="./icon/logo.ico", # web icon + show_error=True, # Display error message in browser console + quiet=True, # Suppress most print statements + ) + else: + gyd.launch( + inbrowser=True, # Automatically open default browser + show_tips=True, # Automatically display the latest features of gradio + auth=usr_pwd, # login interface + share=is_share, # Project sharing, other devices can access + favicon_path="./icon/logo.ico", # web icon + show_error=True, # Display error message in browser console + quiet=True, # Suppress most print statements + ) + + +if __name__ == "__main__": + args = parse_args() + main(args) diff --git a/cls_name/cls_name.csv b/cls_name/cls_name.csv new file mode 100644 index 0000000..183a596 --- /dev/null +++ b/cls_name/cls_name.csv @@ -0,0 +1,80 @@ +人 +自行车 +汽车 +摩托车 +飞机 +公交车 +火车 +卡车 +船 +红绿灯 +消防栓 +停止标志 +停车收费表 +长凳 +鸟 +猫 +狗 +马 +羊 +牛 +象 +熊 +斑马 +长颈鹿 +背包 +雨伞 +手提包 +领带 +手提箱 +飞盘 +滑雪板 +单板滑雪 +运动球 +风筝 +棒球棒 +棒球手套 +滑板 +冲浪板 +网球拍 +瓶子 +红酒杯 +杯子 +叉子 +刀 +勺 +碗 +香蕉 +苹果 +三明治 +橙子 +西兰花 +胡萝卜 +热狗 +比萨 +甜甜圈 +蛋糕 +椅子 +长椅 +盆栽 +床 +餐桌 +马桶 +电视 +笔记本电脑 +鼠标 +遥控器 +键盘 +手机 +微波炉 +烤箱 +烤面包机 +洗碗槽 +冰箱 +书 +时钟 +花瓶 +剪刀 +泰迪熊 +吹风机 +牙刷 diff --git a/cls_name/cls_name.yaml b/cls_name/cls_name.yaml new file mode 100644 index 0000000..9c5dd35 --- /dev/null +++ b/cls_name/cls_name.yaml @@ -0,0 +1,7 @@ +model_cls_name: ['人', '自行车', '汽车', '摩托车', '飞机', '公交车', '火车', '卡车', '船', '红绿灯', '消防栓', '停止标志', + '停车收费表', '长凳', '鸟', '猫', '狗', '马', '羊', '牛', '象', '熊', '斑马', '长颈鹿', '背包', '雨伞', '手提包', '领带', + '手提箱', '飞盘', '滑雪板', '单板滑雪', '运动球', '风筝', '棒球棒', '棒球手套', '滑板', '冲浪板', '网球拍', '瓶子', '红酒杯', + '杯子', '叉子', '刀', '勺', '碗', '香蕉', '苹果', '三明治', '橙子', '西兰花', '胡萝卜', '热狗', '比萨', '甜甜圈', '蛋糕', + '椅子', '长椅', '盆栽', '床', '餐桌', '马桶', '电视', '笔记本电脑', '鼠标', '遥控器', '键盘', '手机', '微波炉', '烤箱', + '烤面包机', '洗碗槽', '冰箱', '书', '时钟', '花瓶', '剪刀', '泰迪熊', '吹风机', '牙刷' + ] diff --git a/cls_name/cls_name_ar.yaml b/cls_name/cls_name_ar.yaml new file mode 100644 index 0000000..9ce53d7 --- /dev/null +++ b/cls_name/cls_name_ar.yaml @@ -0,0 +1,9 @@ +model_cls_name: [" الناس " , " الدراجات " , " السيارات " , " الدراجات النارية " , " الطائرات " , " الحافلات " , " القطارات " , " الشاحنات " , " السفن " , " إشارات المرور " , +" صنبور " , " علامة " , " موقف سيارات " , " الجدول " , " مقعد " , " الطيور " , " القط " , " الكلب " , " الحصان " , " الأغنام " , " الثور " , " الفيل " , +" الدب " , " حمار وحشي " , " الزرافة " , " حقيبة " , " مظلة " , " حقيبة يد " , " ربطة عنق " , " حقيبة " , " الفريسبي " , " الزلاجات " , " الزلاجات " , +" الكرة الرياضية " , " طائرة ورقية " , " مضرب بيسبول " , " قفازات البيسبول " , " لوح التزلج " , " ركوب الأمواج " , " مضرب تنس " , " زجاجة " , +" كأس " , " كأس " , " شوكة " , " سكين " , " ملعقة " , " وعاء " , " الموز " , " التفاح " , " ساندويتش " , " البرتقال " , " القرنبيط " , +" الجزر " , " الكلاب الساخنة " , " البيتزا " , " دونات " , " كعكة " , " كرسي " , " أريكة " , " بوعاء " , " السرير " , " طاولة الطعام " , " المرحاض " , +التلفزيون , الكمبيوتر المحمول , الفأرة , وحدة تحكم عن بعد , لوحة المفاتيح , الهاتف المحمول , فرن الميكروويف , محمصة خبز كهربائية , بالوعة , ثلاجة , +" كتاب " , " ساعة " , " زهرية " , " مقص " , " دمية دب " , " مجفف الشعر " , " فرشاة الأسنان " +] diff --git a/cls_name/cls_name_en.yaml b/cls_name/cls_name_en.yaml new file mode 100644 index 0000000..b64a483 --- /dev/null +++ b/cls_name/cls_name_en.yaml @@ -0,0 +1,9 @@ +model_cls_name: ['person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train', 'truck', 'boat', 'traffic light', + 'fire hydrant', 'stop sign', 'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep', 'cow', 'elephant', + 'bear', 'zebra', 'giraffe', 'backpack', 'umbrella', 'handbag', 'tie', 'suitcase', 'frisbee', 'skis', 'snowboard', + 'sports ball', 'kite', 'baseball bat', 'baseball glove', 'skateboard', 'surfboard', 'tennis racket', 'bottle', + 'wine glass', 'cup', 'fork', 'knife', 'spoon', 'bowl', 'banana', 'apple', 'sandwich', 'orange', 'broccoli', + 'carrot', 'hot dog', 'pizza', 'donut', 'cake', 'chair', 'couch', 'potted plant', 'bed', 'dining table', 'toilet', + 'tv', 'laptop', 'mouse', 'remote', 'keyboard', 'cell phone', 'microwave', 'oven', 'toaster', 'sink', 'refrigerator', + 'book', 'clock', 'vase', 'scissors', 'teddy bear', 'hair drier', 'toothbrush' +] diff --git a/cls_name/cls_name_es.yaml b/cls_name/cls_name_es.yaml new file mode 100644 index 0000000..745ceb8 --- /dev/null +++ b/cls_name/cls_name_es.yaml @@ -0,0 +1,9 @@ +model_cls_name: ['persona', 'bicicleta', 'coche', 'motocicleta', 'avión', 'autobús', 'tren', 'camión', 'barco', 'semáforo', + 'boca de incendios', 'señal de alto', 'parquímetro', 'banco', 'pájaro', 'gato', 'perro', 'caballo', 'oveja', 'vaca', 'elefante', + 'oso', 'cebra', 'jirafa', 'mochila', 'paraguas', 'bolso', 'corbata', 'maleta', 'frisbee', 'esquís', 'snowboard', + 'pelota deportiva', 'cometa', 'bate de béisbol', 'guante de béisbol', 'monopatín', 'tabla de surf', 'raqueta de tenis', 'botella', + 'copa de vino', 'taza', 'tenedor', 'cuchillo', 'cuchara', 'tazón', 'plátano', 'manzana', 'sándwich', 'naranja', 'brócoli', + 'zanahoria', 'perrito caliente', 'pizza', 'rosquilla', 'pastel', 'silla', 'sofá', 'planta en maceta', 'cama', 'mesa de comedor', 'inodoro', + 'tv', 'laptop', 'ratón', 'control remoto', 'teclado', 'celular', 'microondas', 'horno', 'tostadora', 'fregadero', 'nevera', + 'libro', 'reloj', 'jarrón', 'tijeras', 'oso de peluche', 'secador de pelo', 'cepillo de dientes' +] diff --git a/cls_name/cls_name_ko.yaml b/cls_name/cls_name_ko.yaml new file mode 100644 index 0000000..9e09724 --- /dev/null +++ b/cls_name/cls_name_ko.yaml @@ -0,0 +1,9 @@ +model_cls_name: ['사람', '자전거', '자동차', '오토바이', '비행기', '버스', '기차', '트럭', '보트', '신호등', + '소화전', '정지 신호', '주차 미터기', '벤치', '새', '고양이', '개', '말', '양', '소', '코끼리', + '곰', '얼룩말', '기린', '배낭', '우산', '핸드백', '타이', '여행가방', '프리스비', '스키', '스노우보드', + '스포츠 공', '연', '야구 방망이', '야구 글러브', '스케이트보드', '서프보드', '테니스 라켓', '병', + '와인잔', '컵', '포크', '나이프', '숟가락', '그릇', '바나나', '사과', '샌드위치', '오렌지', '브로콜리', + '당근', '핫도그', '피자', '도넛', '케이크', '의자', '소파', '화분', '침대', '식탁', '화장실', + 'tv', '노트북', '마우스', '리모컨', '키보드', '휴대전화', '전자레인지', '오븐', '토스터', '싱크대', '냉장고', + '책', '시계', '꽃병', '가위', '테디베어', '드라이기', '칫솔' +] diff --git a/cls_name/cls_name_ru.yaml b/cls_name/cls_name_ru.yaml new file mode 100644 index 0000000..f7a14d1 --- /dev/null +++ b/cls_name/cls_name_ru.yaml @@ -0,0 +1,9 @@ +model_cls_name: ['человек', 'велосипед', 'автомобиль', 'мотоцикл', 'самолет', 'автобус', 'поезд', 'грузовик', 'лодка', 'светофор', + 'пожарный гидрант', 'стоп', 'паркомат', 'скамейка', 'птица', 'кошка', 'собака', 'лошадь', 'овца', 'корова', 'слон', + 'медведь', 'зебра', 'жираф', 'рюкзак', 'зонт', 'сумочка', 'галстук', 'чемодан', 'фрисби', 'лыжи', 'сноуборд', + 'спортивный мяч', 'воздушный змей', 'бейсбольная бита', 'бейсбольная перчатка', 'скейтборд', 'доска для серфинга', 'теннисная ракетка', 'бутылка', + 'бокал', 'чашка', 'вилка', 'нож', 'ложка', 'миска', 'банан', 'яблоко', 'бутерброд', 'апельсин', 'брокколи', + 'морковь', 'хот-дог', 'пицца', 'пончик', 'торт', 'стул', 'диван', 'растение в горшке', 'кровать', 'обеденный стол', 'туалет', + 'телевизор', 'ноутбук', 'мышь', 'пульт', 'клавиатура', 'мобильный телефон', 'микроволновая печь', 'духовка', 'тостер', 'раковина', 'холодильник', + 'книга', 'часы', 'ваза', 'ножницы', 'плюшевый мишка', 'фен', 'зубная щетка' +] diff --git a/cls_name/cls_name_zh.yaml b/cls_name/cls_name_zh.yaml new file mode 100644 index 0000000..9c5dd35 --- /dev/null +++ b/cls_name/cls_name_zh.yaml @@ -0,0 +1,7 @@ +model_cls_name: ['人', '自行车', '汽车', '摩托车', '飞机', '公交车', '火车', '卡车', '船', '红绿灯', '消防栓', '停止标志', + '停车收费表', '长凳', '鸟', '猫', '狗', '马', '羊', '牛', '象', '熊', '斑马', '长颈鹿', '背包', '雨伞', '手提包', '领带', + '手提箱', '飞盘', '滑雪板', '单板滑雪', '运动球', '风筝', '棒球棒', '棒球手套', '滑板', '冲浪板', '网球拍', '瓶子', '红酒杯', + '杯子', '叉子', '刀', '勺', '碗', '香蕉', '苹果', '三明治', '橙子', '西兰花', '胡萝卜', '热狗', '比萨', '甜甜圈', '蛋糕', + '椅子', '长椅', '盆栽', '床', '餐桌', '马桶', '电视', '笔记本电脑', '鼠标', '遥控器', '键盘', '手机', '微波炉', '烤箱', + '烤面包机', '洗碗槽', '冰箱', '书', '时钟', '花瓶', '剪刀', '泰迪熊', '吹风机', '牙刷' + ] diff --git a/img_example/Millenial-at-work.jpg b/img_example/Millenial-at-work.jpg new file mode 100644 index 0000000..78cb50b Binary files /dev/null and b/img_example/Millenial-at-work.jpg differ diff --git a/img_example/bus.jpg b/img_example/bus.jpg new file mode 100644 index 0000000..b43e311 Binary files /dev/null and b/img_example/bus.jpg differ diff --git a/img_example/giraffe.jpg b/img_example/giraffe.jpg new file mode 100644 index 0000000..c819e3a Binary files /dev/null and b/img_example/giraffe.jpg differ diff --git a/img_example/read.txt b/img_example/read.txt new file mode 100644 index 0000000..e69de29 diff --git a/img_example/zidane.jpg b/img_example/zidane.jpg new file mode 100644 index 0000000..92d72ea Binary files /dev/null and b/img_example/zidane.jpg differ diff --git a/model_config/model_name_p5_all.csv b/model_config/model_name_p5_all.csv new file mode 100644 index 0000000..d03afe8 --- /dev/null +++ b/model_config/model_name_p5_all.csv @@ -0,0 +1,5 @@ +yolov5n +yolov5s +yolov5m +yolov5l +yolov5x diff --git a/model_config/model_name_p5_all.yaml b/model_config/model_name_p5_all.yaml new file mode 100644 index 0000000..b178207 --- /dev/null +++ b/model_config/model_name_p5_all.yaml @@ -0,0 +1 @@ +model_names: ["yolov5n", "yolov5s", "yolov5m", "yolov5l", "yolov5x"] diff --git a/model_config/model_name_p5_n.csv b/model_config/model_name_p5_n.csv new file mode 100644 index 0000000..3c618e3 --- /dev/null +++ b/model_config/model_name_p5_n.csv @@ -0,0 +1 @@ +yolov5n diff --git a/model_config/model_name_p5_n.yaml b/model_config/model_name_p5_n.yaml new file mode 100644 index 0000000..b222f3c --- /dev/null +++ b/model_config/model_name_p5_n.yaml @@ -0,0 +1 @@ +model_names: ["yolov5n"] diff --git a/model_config/model_name_p5_p6_all.yaml b/model_config/model_name_p5_p6_all.yaml new file mode 100644 index 0000000..e18f6a6 --- /dev/null +++ b/model_config/model_name_p5_p6_all.yaml @@ -0,0 +1 @@ +model_names: ["yolov5n", "yolov5s", "yolov5m", "yolov5l", "yolov5x", "yolov5n6", "yolov5s6", "yolov5m6", "yolov5l6", "yolov5x6"] diff --git a/model_config/model_name_p6_all.csv b/model_config/model_name_p6_all.csv new file mode 100644 index 0000000..7b255af --- /dev/null +++ b/model_config/model_name_p6_all.csv @@ -0,0 +1,5 @@ +yolov5n6 +yolov5s6 +yolov5m6 +yolov5l6 +yolov5x6 diff --git a/model_config/model_name_p6_all.yaml b/model_config/model_name_p6_all.yaml new file mode 100644 index 0000000..27143f7 --- /dev/null +++ b/model_config/model_name_p6_all.yaml @@ -0,0 +1 @@ +model_names: ["yolov5n6", "yolov5s6", "yolov5m6", "yolov5l6", "yolov5x6"] diff --git a/model_download/yolov5_model_p5_all.sh b/model_download/yolov5_model_p5_all.sh new file mode 100644 index 0000000..ab68c26 --- /dev/null +++ b/model_download/yolov5_model_p5_all.sh @@ -0,0 +1,8 @@ +cd ./yolov5 + +# 下载YOLOv5模型 +wget -c -t 0 https://github.com/ultralytics/yolov5/releases/download/v6.1/yolov5n.pt +wget -c -t 0 https://github.com/ultralytics/yolov5/releases/download/v6.1/yolov5s.pt +wget -c -t 0 https://github.com/ultralytics/yolov5/releases/download/v6.1/yolov5m.pt +wget -c -t 0 https://github.com/ultralytics/yolov5/releases/download/v6.1/yolov5l.pt +wget -c -t 0 https://github.com/ultralytics/yolov5/releases/download/v6.1/yolov5x.pt diff --git a/model_download/yolov5_model_p5_n.sh b/model_download/yolov5_model_p5_n.sh new file mode 100644 index 0000000..5fc6d09 --- /dev/null +++ b/model_download/yolov5_model_p5_n.sh @@ -0,0 +1,4 @@ +cd ./yolov5 + +# 下载YOLOv5模型 +wget -c -t 0 https://github.com/ultralytics/yolov5/releases/download/v6.1/yolov5n.pt diff --git a/model_download/yolov5_model_p6_all.sh b/model_download/yolov5_model_p6_all.sh new file mode 100644 index 0000000..514a047 --- /dev/null +++ b/model_download/yolov5_model_p6_all.sh @@ -0,0 +1,8 @@ +cd ./yolov5 + +# 下载YOLOv5模型 +wget -c -t 0 https://github.com/ultralytics/yolov5/releases/download/v6.1/yolov5n6.pt +wget -c -t 0 https://github.com/ultralytics/yolov5/releases/download/v6.1/yolov5s6.pt +wget -c -t 0 https://github.com/ultralytics/yolov5/releases/download/v6.1/yolov5m6.pt +wget -c -t 0 https://github.com/ultralytics/yolov5/releases/download/v6.1/yolov5l6.pt +wget -c -t 0 https://github.com/ultralytics/yolov5/releases/download/v6.1/yolov5x6.pt diff --git a/packages.txt b/packages.txt new file mode 100644 index 0000000..de4a508 --- /dev/null +++ b/packages.txt @@ -0,0 +1,3 @@ +ffmpeg +x264 +libx264-dev diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..41d8cca --- /dev/null +++ b/requirements.txt @@ -0,0 +1,45 @@ +# Base ---------------------------------------- +matplotlib>=3.2.2 +numpy>=1.22.3 +opencv-python-headless>=4.5.5.64 +Pillow>=7.1.2 +PyYAML>=5.3.1 +requests>=2.23.0 +scipy>=1.4.1 # Google Colab version +torch>=1.7.0 +torchvision>=0.8.1 +tqdm>=4.41.0 + +# Gradio YOLOv5 Det ---------------------------------------- +gradio>=3.0.3 +wget>=3.2 +rich>=12.2.0 +fpdf>=1.7.2 +plotly>=5.7.0 +bokeh>=2.4.2 +openpyxl>=3.0.10 + +# Logging ------------------------------------- +tensorboard>=2.4.1 +# wandb + +# Plotting ------------------------------------ +pandas>=1.1.4 +seaborn>=0.11.0 + +# Export -------------------------------------- +# coremltools>=4.1 # CoreML export +# onnx>=1.9.0 # ONNX export +# onnx-simplifier>=0.3.6 # ONNX simplifier +# scikit-learn==0.19.2 # CoreML quantization +# tensorflow>=2.4.1 # TFLite export +# tensorflowjs>=3.9.0 # TF.js export +# openvino-dev # OpenVINO export + +# Extras -------------------------------------- +ipython # interactive notebook +psutil # system utilization +thop # FLOPs computation +# albumentations>=1.0.3 +# pycocotools>=2.0 # COCO mAP +# roboflow \ No newline at end of file diff --git a/util/fonts_opt.py b/util/fonts_opt.py new file mode 100644 index 0000000..ccea77a --- /dev/null +++ b/util/fonts_opt.py @@ -0,0 +1,69 @@ +# font management +# author: Zeng Yifu(曾逸夫) +# creation time: 2022-05-01 +# email: zyfiy1314@163.com +# project homepage: https://gitee.com/CV_Lab/gradio_yolov5_det + +import os +import sys +from pathlib import Path + +import wget +from rich.console import Console + +ROOT_PATH = sys.path[0] # Project root directory + +# Chinese, English, Russian, Spanish, Arabic, Korean +fonts_list = ["SimSun.ttf", "TimesNewRoman.ttf", "malgun.ttf"] # font list +fonts_suffix = ["ttc", "ttf", "otf"] # font suffix + +data_url_dict = { + "SimSun.ttf": "https://gitee.com/CV_Lab/gradio_yolov5_det/attach_files/1053539/download/SimSun.ttf", + "TimesNewRoman.ttf": "https://gitee.com/CV_Lab/gradio_yolov5_det/attach_files/1053537/download/TimesNewRoman.ttf", + "malgun.ttf": "https://gitee.com/CV_Lab/gradio_yolov5_det/attach_files/1053538/download/malgun.ttf",} + +console = Console() + + +# create font library +def add_fronts(font_diff): + + global font_name + + for k, v in data_url_dict.items(): + if k in font_diff: + font_name = v.split("/")[-1] # font name + Path(f"{ROOT_PATH}/fonts").mkdir(parents=True, exist_ok=True) # Create a directory + + file_path = f"{ROOT_PATH}/fonts/{font_name}" # font path + + try: + # Download font file + wget.download(v, file_path) + except Exception as e: + print("Path error! Program ended!") + print(e) + sys.exit() + else: + print() + console.print(f"{font_name} [bold green]font file download complete![/bold green] has been saved to: {file_path}") + + +# Determine the font file +def is_fonts(fonts_dir): + if os.path.isdir(fonts_dir): + # if the font library exists + f_list = os.listdir(fonts_dir) # local font library + + font_diff = list(set(fonts_list).difference(set(f_list))) + + if font_diff != []: + # font does not exist + console.print("[bold red] font does not exist, loading...[/bold red]") + add_fronts(font_diff) # Create a font library + else: + console.print(f"{fonts_list}[bold green]font already exists![/bold green]") + else: + # The font library does not exist, create a font library + console.print("[bold red]font library does not exist, creating...[/bold red]") + add_fronts(fonts_list) # Create a font library \ No newline at end of file diff --git a/util/pdf_opt.py b/util/pdf_opt.py new file mode 100644 index 0000000..19a2524 --- /dev/null +++ b/util/pdf_opt.py @@ -0,0 +1,78 @@ +# PDF management +# author: Zeng Yifu +# creation time: 2022-05-05 + +from fpdf import FPDF + + +# PDF generation class +class PDF(FPDF): + # Reference: https://pyfpdf.readthedocs.io/en/latest/Tutorial/index.html + def header(self): + # Set Chinese font + self.add_font("SimSun", "", "./fonts/SimSun.ttf", uni=True) + self.set_font("SimSun", "", 16) + # Calculate width of title and position + w = self.get_string_width(title) + 6 + self.set_x((210 - w) / 2) + # Colors of frame, background and text + self.set_draw_color(255, 255, 255) + self.set_fill_color(255, 255, 255) + self.set_text_color(0, 0, 0) + # Thickness of frame (1 mm) + # self.set_line_width(1) + # Title + self.cell(w, 9, title, 1, 1, "C", 1) + # Line break + self.ln(10) + + def footer(self): + # Position at 1.5 cm from bottom + self.set_y(-15) + # Set Chinese font + self.add_font("SimSun", "", "./fonts/SimSun.ttf", uni=True) + self.set_font("SimSun", "", 12) + # Text color in gray + self.set_text_color(128) + # Page number + self.cell(0, 10, "Page " + str(self.page_no()), 0, 0, "C") + + def chapter_title(self, num, label): + # Set Chinese font + self.add_font("SimSun", "", "./fonts/SimSun.ttf", uni=True) + self.set_font("SimSun", "", 12) + # Background color + self.set_fill_color(200, 220, 255) + # Title + # self.cell(0, 6, 'Chapter %d : %s' % (num, label), 0, 1, 'L', 1) + self.cell(0, 6, "Detection Result:", 0, 1, "L", 1) + # Line break + self.ln(4) + + def chapter_body(self, name): + + # Set Chinese font + self.add_font("SimSun", "", "./fonts/SimSun.ttf", uni=True) + self.set_font("SimSun", "", 12) + # Output justified text + self.multi_cell(0, 5, name) + # Line break + self.ln() + self.cell(0, 5, "--------------------------------------") + + def print_chapter(self, num, title, name): + self.add_page() + self.chapter_title(num, title) + self.chapter_body(name) + + +# pdf generation function +def pdf_generate(input_file, output_file, title_): + global title + + title = title_ + pdf = PDF() + pdf.set_title(title) + pdf.set_author("Zeng Yifu") + pdf.print_chapter(1, "A RUNAWAY REEF", input_file) + pdf.output(output_file) \ No newline at end of file