一、創(chuàng)建一個(gè)flask項(xiàng)目
首先,開發(fā)工具我們選擇jetbrains公司的Pycharm,打開Pycharm,選擇new Project,flask,路徑根據(jù)自己的自身情況改,最好點(diǎn)擊create創(chuàng)建成功!
此時(shí),新建好的flask工程目錄長這樣
static文件夾下存放一些文件,比如css,js,images等,templates文件夾存放一些html的文件,便于日后flask部署。
app.py文件
from flask import Flask #導(dǎo)入項(xiàng)目庫
app = Flask(__name__) #實(shí)例化flask
@app.route('/') #flask的路由
def hello_world(): # put application's code here
return 'Hello World!'
if __name__ == '__main__': #主函數(shù)
app.run()
其中,app = Flask(__name__)用于實(shí)例化flask項(xiàng)目,@app.route('/')是flask的路由,括號(hào)中‘/’表示網(wǎng)址使用根目錄,app.run()用于啟動(dòng)flask項(xiàng)目。
二、解讀yolov5代碼
YOLOv5是一種計(jì)算機(jī)視覺的目標(biāo)檢測(cè)算法,全稱是"You Only Look Once version 5"。它是由美國加州大學(xué)伯克利分校的研究者開發(fā)的,相較于之前的YOLO版本,YOLOv5具有更快的檢測(cè)速度和更高的精度。其優(yōu)點(diǎn)有
- YOLOv5采用了一個(gè)名為CSPNet的新的骨干網(wǎng)絡(luò),可以更好地提取特征。
- YOLOv5使用了更高效的訓(xùn)練技術(shù),如自適應(yīng)訓(xùn)練、數(shù)據(jù)增強(qiáng)和多尺度訓(xùn)練,從而在實(shí)際應(yīng)用中具有更好的性能表現(xiàn)。
- YOLOv5是一種端到端的深度學(xué)習(xí)模型,可以直接從原始圖像中檢測(cè)和定位目標(biāo)。它使用卷積神經(jīng)網(wǎng)絡(luò)(CNN)來學(xué)習(xí)圖像中物體的特征,并使用多尺度預(yù)測(cè)和網(wǎng)格分割來檢測(cè)和定位目標(biāo),因此可以在高速運(yùn)行的同時(shí)保持高精度。
YOLOv5的代碼就在GitHub上,這是其地址GitHub - ultralytics/yolov5: YOLOv5 ?? in PyTorch > ONNX > CoreML > TFLite
在YOLOv5中,最核心的就是權(quán)重文件,當(dāng)模型訓(xùn)練完成之后,會(huì)在run目錄下的train文件夾里面的exp文件夾中的weights文件夾生成兩個(gè)pt文件,best.pt顧名思義就是最好的一次,last.pt就是最后一次
其中訓(xùn)練文件我就不多描述了,主要講解一下檢測(cè)文件
def parse_opt():
"""
weights: 訓(xùn)練的權(quán)重路徑,可以使用自己訓(xùn)練的權(quán)重,也可以使用官網(wǎng)提供的權(quán)重
默認(rèn)官網(wǎng)的權(quán)重yolov5s.pt(yolov5n.pt/yolov5s.pt/yolov5m.pt/yolov5l.pt/yolov5x.pt/區(qū)別在于網(wǎng)絡(luò)的寬度和深度以此增加)
source: 測(cè)試數(shù)據(jù),可以是圖片/視頻路徑,也可以是'0'(電腦自帶攝像頭),也可以是rtsp等視頻流, 默認(rèn)data/images
data: 配置數(shù)據(jù)文件路徑, 包括image/label/classes等信息, 訓(xùn)練自己的文件, 需要作相應(yīng)更改, 可以不用管
如果設(shè)置了只顯示個(gè)別類別即使用了--classes = 0 或二者1, 2, 3等, 則需要設(shè)置該文件,數(shù)字和類別相對(duì)應(yīng)才能只檢測(cè)某一個(gè)類
imgsz: 網(wǎng)絡(luò)輸入圖片大小, 默認(rèn)的大小是640
conf-thres: 置信度閾值, 默認(rèn)為0.25
iou-thres: 做nms的iou閾值, 默認(rèn)為0.45
max-det: 保留的最大檢測(cè)框數(shù)量, 每張圖片中檢測(cè)目標(biāo)的個(gè)數(shù)最多為1000類
device: 設(shè)置設(shè)備CPU/CUDA, 可以不用設(shè)置
view-img: 是否展示預(yù)測(cè)之后的圖片/視頻, 默認(rèn)False, --view-img 電腦界面出現(xiàn)圖片或者視頻檢測(cè)結(jié)果
save-txt: 是否將預(yù)測(cè)的框坐標(biāo)以txt文件形式保存, 默認(rèn)False, 使用--save-txt 在路徑runs/detect/exp*/labels/*.txt下生成每張圖片預(yù)測(cè)的txt文件
save-conf: 是否將置信度conf也保存到txt中, 默認(rèn)False
save-crop: 是否保存裁剪預(yù)測(cè)框圖片, 默認(rèn)為False, 使用--save-crop 在runs/detect/exp*/crop/剪切類別文件夾/ 路徑下會(huì)保存每個(gè)接下來的目標(biāo)
nosave: 不保存圖片、視頻, 要保存圖片,不設(shè)置--nosave 在runs/detect/exp*/會(huì)出現(xiàn)預(yù)測(cè)的結(jié)果
classes: 設(shè)置只保留某一部分類別, 形如0或者0 2 3, 使用--classes = n, 則在路徑runs/detect/exp*/下保存的圖片為n所對(duì)應(yīng)的類別, 此時(shí)需要設(shè)置data
agnostic-nms: 進(jìn)行NMS去除不同類別之間的框, 默認(rèn)False
augment: TTA測(cè)試時(shí)增強(qiáng)/多尺度預(yù)測(cè)
visualize: 是否可視化網(wǎng)絡(luò)層輸出特征
update: 如果為True,則對(duì)所有模型進(jìn)行strip_optimizer操作,去除pt文件中的優(yōu)化器等信息,默認(rèn)為False
project:保存測(cè)試日志的文件夾路徑
name:保存測(cè)試日志文件夾的名字, 所以最終是保存在project/name中
exist_ok: 是否重新創(chuàng)建日志文件, False時(shí)重新創(chuàng)建文件
line-thickness: 畫框的線條粗細(xì)
hide-labels: 可視化時(shí)隱藏預(yù)測(cè)類別
hide-conf: 可視化時(shí)隱藏置信度
half: 是否使用F16精度推理, 半進(jìn)度提高檢測(cè)速度
dnn: 用OpenCV DNN預(yù)測(cè)
"""
parser = argparse.ArgumentParser()
parser.add_argument('--weights', nargs='+', type=str, default=ROOT / 'weights/best.pt', help='model path(s)')
# parser.add_argument('--source', type=str, default=ROOT / 'data/images', help='file/dir/URL/glob, 0 for webcam') #圖片檢測(cè)
parser.add_argument('--source', type=str, default=ROOT / '0', help='file/dir/URL/glob, 0 for webcam') #視頻檢測(cè)
parser.add_argument('--data', type=str, default=ROOT / 'data/Amy.yaml', help='(optional) dataset.yaml path')
parser.add_argument('--imgsz', '--img', '--img-size', nargs='+', type=int, default=[640], help='inference size h,w')
parser.add_argument('--conf-thres', type=float, default=0.25, help='confidence threshold')
# conf-thres 置信度閾值,越低框越多
parser.add_argument('--iou-thres', type=float, default=0.45, help='NMS IoU threshold')
parser.add_argument('--max-det', type=int, default=1000, help='maximum detections per image')
parser.add_argument('--device', default='', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')
parser.add_argument('--view-img', action='store_true', help='show results')
parser.add_argument('--save-txt', action='store_true', help='save results to *.txt')
parser.add_argument('--save-conf', action='store_true', help='save confidences in --save-txt labels')
parser.add_argument('--save-crop', action='store_true', help='save cropped prediction boxes')
parser.add_argument('--nosave', action='store_true', help='do not save images/videos')
parser.add_argument('--classes', nargs='+', type=int, help='filter by class: --classes 0, or --classes 0 2 3')
parser.add_argument('--agnostic-nms', action='store_true', help='class-agnostic NMS')
parser.add_argument('--augment', action='store_true', help='augmented inference')
parser.add_argument('--visualize', action='store_true', help='visualize features')
parser.add_argument('--update', action='store_true', help='update all models')
parser.add_argument('--project', default=ROOT / 'runs/detect', help='save results to project/name')
parser.add_argument('--name', default='exp', help='save results to project/name')
parser.add_argument('--exist-ok', action='store_true', help='existing project/name ok, do not increment')
parser.add_argument('--line-thickness', default=3, type=int, help='bounding box thickness (pixels)')
parser.add_argument('--hide-labels', default=False, action='store_true', help='hide labels')
parser.add_argument('--hide-conf', default=False, action='store_true', help='hide confidences')
parser.add_argument('--half', action='store_true', help='use FP16 half-precision inference')
parser.add_argument('--dnn', action='store_true', help='use OpenCV DNN for ONNX inference')
opt = parser.parse_args()
opt.imgsz *= 2 if len(opt.imgsz) == 1 else 1 # expand
print_args(vars(opt))
return opt
檢測(cè)代碼中最重要的部分就是pare_opt函數(shù),各種參數(shù)的注釋我全部寫在代碼中,大家可以進(jìn)行參考,其中weights是訓(xùn)練出來的權(quán)重best.pt的路徑,source就是檢測(cè)數(shù)據(jù)的來源,default=ROOT/'0'代表調(diào)用本地?cái)z像頭,default=ROOT/'data/images'代表是檢測(cè)的data目錄下的images里面的圖片
使用flask肯定不能直接調(diào)用,需要根據(jù)需求來進(jìn)行調(diào)用
class VideoCamera(object):
def __init__(self):
# 通過opencv獲取實(shí)時(shí)視頻流
self.img_size = 640
self.threshold = 0.4
self.max_frame = 160
self.video = cap #視頻流
self.weights = 'weights/best.pt' #yolov5權(quán)重文件
self.device = 'cuda:0' if torch.cuda.is_available() else 'cpu'
self.device = select_device(self.device)
model = attempt_load(self.weights)
model.to(self.device).eval()
model.half()
# torch.save(model, 'test.pt')
self.m = model
self.names = model.module.names if hasattr(
model, 'module') else model.names
self.colors = [
(randint(0, 255), randint(0, 255), randint(0, 255)) for _ in self.names
]
def __del__(self):
if self.video is None:
# 沒有上傳視頻時(shí),不執(zhí)行讀取幀的操作
return None
self.video.release()
def get_frame(self):
if self.video is None:
# 沒有上傳視頻時(shí),不執(zhí)行讀取幀的操作
return None
ret, frame = self.video.read() # 讀視頻
if not ret:
# 視頻已經(jīng)播放完畢,沒有更多的幀可供處理
return None
im0, img = self.preprocess(frame) # 轉(zhuǎn)到處理函數(shù)
pred = self.m(img, augment=False)[0] # 輸入到模型
pred = pred.float()
pred = non_max_suppression(pred, self.threshold, 0.3)
pred_boxes = []
image_info = {}
count = 0
for det in pred:
if det is not None and len(det):
det[:, :4] = scale_coords(
img.shape[2:], det[:, :4], im0.shape).round()
for *x, conf, cls_id in det:
lbl = self.names[int(cls_id)]
x1, y1 = int(x[0]), int(x[1])
x2, y2 = int(x[2]), int(x[3])
pred_boxes.append(
(x1, y1, x2, y2, lbl, conf))
count += 1
key = '{}-{:02}'.format(lbl, count)
image_info[key] = ['{}×{}'.format(
x2 - x1, y2 - y1), np.round(float(conf), 3)]
frame = self.plot_bboxes(frame, pred_boxes)
ret, jpeg = cv2.imencode('.jpg', frame)
return jpeg.tobytes()
def preprocess(self, img):
img0 = img.copy()
img = letterbox(img, new_shape=self.img_size)[0]
img = img[:, :, ::-1].transpose(2, 0, 1)
img = np.ascontiguousarray(img)
img = torch.from_numpy(img).to(self.device)
img = img.half() # 半精度
img /= 255.0 # 圖像歸一化
if img.ndimension() == 3:
img = img.unsqueeze(0)
return img0, img
def plot_bboxes(self, image, bboxes, line_thickness=None):
tl = line_thickness or round(
0.002 * (image.shape[0] + image.shape[1]) / 2) + 1 # line/font thickness
for (x1, y1, x2, y2, cls_id, conf) in bboxes:
color = self.colors[self.names.index(cls_id)]
c1, c2 = (x1, y1), (x2, y2)
cv2.rectangle(image, c1, c2, color,
thickness=tl, lineType=cv2.LINE_AA)
tf = max(tl - 1, 1) # font thickness
t_size = cv2.getTextSize(
cls_id, 0, fontScale=tl / 3, thickness=tf)[0]
c2 = c1[0] + t_size[0], c1[1] - t_size[1] - 3
cv2.rectangle(image, c1, c2, color, -1, cv2.LINE_AA) # filled
cv2.putText(image, '{}-{:.2f} '.format(cls_id, conf), (c1[0], c1[1] - 2), 0, tl / 3,
[225, 255, 255], thickness=tf, lineType=cv2.LINE_AA)
return image
這里是封裝了一個(gè)類名為VideoCamera,其中__init__(self)方法用于初始化類的實(shí)例,里面設(shè)置了一些參數(shù)包括圖片的大小,權(quán)重的路徑,視頻的地址,是否使用GPU等,__del__(self)方法用于釋放資源,如果視頻播放完畢,則關(guān)閉視頻流,get_frame(self)方法獲取視頻流的幀,對(duì)幀進(jìn)行預(yù)處理,使用YOLOv5進(jìn)行目標(biāo)檢測(cè),處理檢測(cè)結(jié)果,提取預(yù)測(cè)框的信息,繪制帶有預(yù)測(cè)框的圖像并返回其 JPEG 編碼,preprocess(self, img) 方法對(duì)圖像進(jìn)行預(yù)處理,復(fù)制原始圖像,調(diào)整圖像尺寸、通道順序,將圖像轉(zhuǎn)換為 PyTorch 張量,并移動(dòng)到設(shè)備上,對(duì)圖像進(jìn)行歸一化處理,plot_bboxes(self, image, bboxes, line_thickness=None) 方法在圖像上繪制檢測(cè)框,為每個(gè)類別進(jìn)行隨機(jī)分配顏色。
三、flask后端代碼
?flask項(xiàng)目錄
其中data目錄,models目錄,runs目錄,weights目錄,utils目錄是從yolov5的工程目錄中copy過來的,其他的都是flask項(xiàng)目自帶的。
首先導(dǎo)入相關(guān)庫
from flask import *
from flask_cors import CORS
import torch
import numpy as np
from models.experimental import attempt_load
from utils.general import non_max_suppression, scale_coords
from utils.augmentations import letterbox
from utils.torch_utils import select_device
import cv2
from random import randint
然后
@app.route('/')
def index():
return render_template('index.html') #template文件夾下的index.html
def gen(camera):
while True:
frame = camera.get_frame()
# 使用generator函數(shù)輸出視頻流, 每次請(qǐng)求輸出的content類型是image/jpeg
if frame is None:
break
yield (b'--frame\r\n'
b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n\r\n')
@app.route('/video_feed') # 這個(gè)地址返回視頻流響應(yīng)
def video_feed():
return Response(gen(VideoCamera()),
mimetype='multipart/x-mixed-replace; boundary=frame')
@app.route('/detect',methods=['POST'])
def vedio_stream():
global cap
if request.method == 'POST':
stream = request.files['videoFile'].stream
# 將文件流寫入臨時(shí)文件
temp_video_path = 'temp_video.mp4'
with open(temp_video_path, 'wb') as temp_video_file:
temp_video_file.write(stream.read())
cap = cv2.VideoCapture(temp_video_path)
else:
return 'request method error!'
return redirect('/')
index()函數(shù)用于當(dāng)訪問url(‘/’)時(shí),將渲染成index.html的模板,gen() 函數(shù)是一個(gè)生成器函數(shù),用于不斷地獲取視頻幀并生成對(duì)應(yīng)的MJPEG格式數(shù)據(jù)。這個(gè)函V數(shù)作為視頻流的生成器,/video_feed 路由返回一個(gè)包含視頻流響應(yīng)的 Response 對(duì)象。 是一個(gè)用于獲取視頻幀的相機(jī)類,該類的 get_frame 方法用于獲取視頻幀數(shù)據(jù)。
四、前端代碼
我藝術(shù)細(xì)胞太少,前端很簡陋,之所以寫出來是因?yàn)橛袔讉€(gè)重點(diǎn)要強(qiáng)調(diào)一下
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>視頻檢測(cè)</title>
<style>
div{
margin: 0 auto;
text-align: center;
width: 1200px;
height: 800px;
}
img{
width: 75%;
height: 75%;
border: 1px solid black;
}
</style>
</head>
<body>
<div>
<h1>視頻檢測(cè)</h1>
<form id="uploadForm" enctype="multipart/form-data" method="post" action="http://127.0.0.1:5000/detect">
<input type="file" name="videoFile" accept="video/*">
<button type="submit">開始檢測(cè)</button>
</form>
<img src="{{ url_for('video_feed') }}">
</div>
</body>
</html>
?其中{{url_for('video_feed')}}對(duì)應(yīng)的就是上面的video_feed函數(shù),因?yàn)関ideo_feed函數(shù)的路由是video_feed,這樣返回的視頻以每幀的方式呈現(xiàn),action中的網(wǎng)址寫的就是video_steam函數(shù),使用的是post方法,input標(biāo)簽里面的name="videoFile"就是對(duì)應(yīng)下面
stream = request.files['videoFile'].stream
?
運(yùn)行結(jié)果
最后看一下運(yùn)行結(jié)果
下面是本帥哥的帥哥,哈哈,自戀一下,大家不要把重點(diǎn)放在本帥哥身上哈。 ?文章來源:http://www.zghlxwxcb.cn/news/detail-821557.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-821557.html
到了這里,關(guān)于使用flask將Yolov5部署到前端頁面實(shí)現(xiàn)視頻檢測(cè)(保姆級(jí))的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!