总结:
- 根据上篇文章使用train.py训练好权重文件后,再使用flask搭建后台接口,把检测的数据和图片传给前端,目前只支持监测图片
1. 与官方提供的restapi.py类似,创建一个py文件
# YOLOv5 🚀 by Ultralytics, GPL-3.0 license"""Run a Flask REST API exposing one or more YOLOv5s models"""import argparseimport ioimport numpy as npfrom flask import Flask, request, jsonifyfrom PIL import Imagefrom utils.dataloaders import IMG_FORMATS, VID_FORMATS, LoadImages, LoadScreenshots, LoadStreamsimport osimport cv2import torchfrom pathlib import Pathimport sysfrom models.common import DetectMultiBackendfrom utils.general import (check_img_size,increment_path, non_max_suppression, scale_boxes)from utils.plots import Annotator, colorsfrom utils.torch_utils import select_device, time_syncimport jsonimport base64from lprr.plate import de_lpr,dr_platefrom flask_cors import CORSimport timeapp = Flask(__name__)# 解决前端跨域问题CORS(app, resources={ r"/yolo/*": { "origins": "http://localhost:8080", # 允许的域名 "methods": ["GET", "POST"], # 允许的 HTTP 方法 "allow_headers": ["Content-Type", "Authorization"], # 允许的请求头 "supports_credentials": True, # 允许携带 Cookie "allow_headers": ["Content-Type", "Authorization", "access-token"] # 添加 access-token }})models = {}DETECTION_URL = "/yolo/lprnet"FILE = Path(__file__).resolve()ROOT = FILE.parents[0] # YOLOv5 root directoryif str(ROOT) not in sys.path: sys.path.append(str(ROOT)) # add ROOT to PATHROOT = Path(os.path.relpath(ROOT, Path.cwd())) # relativedef save_base64_to_jpg(base64_string, output_path): # 1. 解码Base64字符串 image_data = base64.b64decode(base64_string) # 2. 转换为OpenCV图像 nparr = np.frombuffer(image_data, np.uint8) img = cv2.imdecode(nparr, cv2.IMREAD_COLOR) # 3. 保存为JPG cv2.imwrite(output_path, img)class Camera(): @staticmethod def frames(url): weights = ROOT / 'runs/train/exp9/weights/best.pt' # 训练好的权重路径 imgsz = 640 # 模型输入图像尺寸 device = select_device(0) # 0 or 0,1,2,3 or cpu 没有gpu的记得改为cpu model = DetectMultiBackend(weights, device=device, dnn=False, data=ROOT / 'data/plate.yaml') stride, names, pt, jit, onnx, engine = model.stride, model.names, model.pt, model.jit, model.onnx, model.engine # 检查输入尺寸是否满足模型要求 imgsz = check_img_size(imgsz, s=stride) # check image size vid_path, vid_writer = [None], [None] half = False # 半精度开关 # 仅在有GPU且格式支持时启用 half &= (pt or jit or onnx or engine) and device.type != 'cpu' # FP16 supported on limited backends with CUDA if pt or jit: # 转换模型精度 model.model.half() if half else model.model.float() dataset = LoadImages(url, img_size=imgsz) # #创建空白图像 img = torch.zeros((1, 3, imgsz, imgsz), device=device) # init img _ = model(img.half() if half else img) if device.type != 'cpu' else None # run once for path, img, im0s, vid_cap, s in dataset: img = torch.from_numpy(img).to(device) img = img.half() if half else img.float() # uint8 to fp16/32 img /= 255.0 # 0 - 255 to 0.0 - 1.0 if img.ndimension() == 3: img = img.unsqueeze(0) # Inference t1 = time_sync() pred = model(img, augment=False, visualize=False) # nms pred = non_max_suppression(pred, conf_thres=0.25, iou_thres=0.45, classes=None, agnostic=False, max_det=1000) t2 = time_sync() for i, det in enumerate(pred): # detections per image # p, s, im0 = path, '', im0s p, im0, frame = path, im0s.copy(), getattr(dataset, 'frame', 0) p = Path(p) # to Path save_path = str('./data/' + p.name) s += '%gx%g ' % img.shape[2:] gn = torch.tensor(im0.shape)[[1, 0, 1, 0]] annotator = Annotator(im0, line_width=3, example=str(names)) if det is not None and len(det): results = [] det[:, :4] = scale_boxes(img.shape[2:], det[:, :4], im0.shape).round() for c in det[:, -1].detach().unique(): n = (det[:, -1] == c).sum() s += '%g %s, ' % (n, names[int(c)]) for *xyxy, conf, cls in det: if float(conf) > 0.65: # 车牌识别函数 plate = de_lpr(xyxy, im0) # 画车牌边框 plateNum = dr_plate(im0, xyxy, plate) label = '%s %.2f' % (names[int(cls)], conf) class_name = model.names[int(cls)] results.append({ "xmin": int(xyxy[0]), "ymin": int(xyxy[1]), "xmax": int(xyxy[2]), "ymax": int(xyxy[3]), "confidence": float(conf), "class": class_name, "plate": plateNum, }) c = int(cls) # 这边是画出车牌边框和置信度 # annotator.box_label(xyxy, label, color=colors(c, True)) # cv2.imwrite(save_path, im0) # rendered_img = im0.render()[0] # 获取带标注的图片 # 3. 实时编码返回 _, buffer = cv2.imencode('.jpg', im0) imgData = base64.b64encode(buffer).decode('utf-8') results.append({ "imgUrl": imgData }) res = { "data": results, "code": 200 } # 传给前端的数据 return jsonify(res) # print('%sDone. (%.3fs)' % (s, t2 - t1)) # Save results (image with detections) # if True: # if dataset.mode == 'image': # cv2.imwrite(save_path, im0) # else: # 'video' or 'stream' # if vid_path[i] != save_path: # new video # vid_path[i] = save_path # if isinstance(vid_writer[i], cv2.VideoWriter): # vid_writer[i].release() # release previous video writer # if vid_cap: # video # fps = vid_cap.get(cv2.CAP_PROP_FPS) # w = int(vid_cap.get(cv2.CAP_PROP_FRAME_WIDTH)) # h = int(vid_cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) # else: # stream # fps, w, h = 30, im0.shape[1], im0.shape[0] # save_path = str( # Path(save_path).with_suffix('.mp4')) # force *.mp4 suffix on results videos # vid_writer[i] = cv2.VideoWriter(save_path, cv2.VideoWriter_fourcc(*'mp4v'), fps, # (w, h)) # vid_writer[i].write(im0) # cv2.namedWindow('img', cv2.WINDOW_NORMAL) # cv2.imshow('img', im0) # cv2.waitKey(0)@app.route(DETECTION_URL, methods=['POST'])def predict(): # print(model) if request.method != 'POST': return if request.data: camera_tool = Camera() json_str = request.data.decode('utf-8') # 或使用 response.content.decode('utf-8') # 步骤2:解析JSON imgFiles = json.loads(json_str).get("imgFiles")[0] timestamp_ms = int(time.time() * 1000) url = f"./uploadImg/{timestamp_ms}.jpg" save_base64_to_jpg(imgFiles, url) print(url,'request.data') return camera_tool.frames(url) if request.files.get('image'): # Method 1 # with request.files["image"] as f: # im = Image.open(io.BytesIO(f.read())) # Method 2 im_file = request.files['image'] im_bytes = im_file.read() im = Image.open(io.BytesIO(im_bytes)) # if model in models: # results = models[model](im, size=640) # reduce size=320 for faster inference # return results.pandas().xyxy[0].to_json(orient='records')if __name__ == '__main__': app.run(host='0.0.0.0', port=5000) # debug=True causes Restarting with stat
2. 使用vue3全家桶搭建的前端,前端页面简单写了一下,使用element-plus的upload组件封装的
<div class="container"> <div style="height: 200px;"> <UploadImgs ref="uploadImgsRef" @getImgs="getImgs" @delImg="delImg" :limitNum="1"></UploadImgs> <el-button @click="submitUpload" type="primary" size="small" style="margin-top: 12px">上传</el-button> </div> <div style="margin-top: 12px;border-top: 1px solid #000;padding-top: 12px;height: calc( 100% - 240px );" v-loading="loading" element-loading-text="图片检测中..." element-loading-background="rgba(122, 122, 122, 0.5)"> <span style="font-size: 16px;">检测结果:</span> <span>{{yoloRes}}</span> <div style="width: 100%;height: calc( 100% - 60px );margin-top: 20px" v-if="yoloResImg.length"> <el-image :src="yoloResImg[0]" :zoom-rate="1.2" :max-scale="7" :min-scale="0.2" :preview-src-list="yoloResImg" show-progress fit="cover" /> </div> </div> </div></template><script setup>import UploadImgs from "@/components/uploadImgs/index";import { reactive,ref,watch,nextTick,computed,onMounted,onBeforeUnmount } from "vue";import { uploadImg } from "@/apis/yoloImg"let imgFiles = ref([]);let yoloRes = ref(null)let yoloResImg = ref([])let loading = ref(false)//子组件uploadImgs触发const getImgs = async(imgs) => { let imgUid = imgs.file.uid //转为base64格式 let reader = new FileReader(); reader.readAsDataURL(imgs.file); reader.onload = async(file) => { let base64 = await blobToBase64(reader.result) const img = base64.replace(/^data:image\/\w+;base64,/, ""); let obj = { img, imgUid } imgFiles.value.push(obj); };};//删除图片const delImg = (uid) => { let index = imgFiles.value.findIndex( item => item.imgUid == uid) imgFiles.value.splice(index, 1)}// blob 转 base64const blobToBase64 = (url) => { return new Promise((resolve, reject) => { let image = new Image(); image.onload = function () { let canvas = document.createElement("canvas"); canvas.width = this.naturalWidth; canvas.height = this.naturalHeight; // 将图片插入画布并开始绘制 canvas.getContext("2d").drawImage(image, 0, 0); let result = canvas.toDataURL("image/jpeg", 0.3); resolve(result); }; image.setAttribute("crossOrigin", "Anonymous"); image.src = url; // 图片加载失败的错误处理 image.onerror = () => { reject(new Error("urlToBase64 error")); }; });};const submitUpload = () => { let uploadFiles = imgFiles.value.map( item => item.img ) loading.value = true uploadImg({ imgFiles: uploadFiles }).then( res =>{ console.log(res,'ssssssssssss'); loading.value = false yoloRes.value = res[0] yoloResImg.value = [`data:image/jpeg;base64,${res[1].imgUrl}`] })};</script><style lang="scss" scoped>.container{ width: 100%; height: 100%; padding: 20px;}</style>
- 这是前端获取的数据结构
- 前端axios请求示例,前端传过去的是一个base64格式的图片
- 前端展示效果