在这篇博文中,我们将使用 Flask 框架构建一个相机应用程序,我们可以在其中单击图片、录制视频、应用诸如灰度、负片和“仅人脸”之类的滤镜,就像出现在 Snapchat 上的滤镜一样。
文中为前端使用了一个非常基本的设计,因为该项目的主要动机是让自己熟悉 Flask 网络框架并在其中包括实时视频流。同样可以扩展以添加更多功能。
演示:
我们使用了线程、HTTP请求-响应、全局变量、错误处理和人脸检测等概念。让我们看看这一切是如何做到的。
前端
首先,前端是一个基本的 HTML 文件,带有用于获取输入的按钮和用于在后端进行预处理后显示输出帧的图像源标签。文件中的按钮将数据发布到服务器。该文件还显示了一些使用该应用程序的说明。该文件保存在项目目录的“模板”文件夹中。
<body> <div class="container"> <div class="row"> <div class="col-lg-8 offset-lg-2"> <h2 class="mt-5">CAMERA APP by Hemanth Nag</h2> <form method="post" action="{{ url_for('tasks') }}"> <input type="submit" value="Stop/Start" name="stop" /> <input type="submit" value="Capture" name="click"/> <input type="submit" value="Grey" name="grey" /> <input type="submit" value="Negative" name="neg" /> <input type="submit" value="Face Only" name="face" /> <input type="submit" value="Start/Stop Recording" name="rec" /> </form> <img class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="{{ url_for('video_feed') }}" height="80%"> <h3 style="font-family:courier;">Instructions:</h3> <ol style="font-family:courier;"> <li>Stop/Start--Toggle between stop or start live streaming</li> <li>Capture--Take still-shot and save in the 'shots' directory</li> <li>Grey--Toggle between grayscale and RGB output</li> <li>Negative--Toggle between negative and RGB output</li> <li>Face Only--Shows just your face if present(Toggle on/off)</li> <li>Start/Stop Recording--Toggle between starting and stopping video recording</li> </div> </div> </div> </body>
后端
至于后端,它是一个完成所有“魔法”的单个 python 脚本。它保存在项目目录中。
让我们分别查看文件的各个部分以了解它的工作原理。
初始化:
from flask import Flask, render_template, Response, request import cv2 import datetime, time import os, sys import numpy as np from threading import Thread global capture,rec_frame, grey, switch, neg, face, rec, out capture=0 grey=0 neg=0 face=0 switch=1 rec=0 #make shots directory to save pics try: os.mkdir('./shots') except OSError as error: pass #Load pretrained face detection model net = cv2.dnn.readNetFromCaffe('./saved_model/deploy.prototxt.txt', './saved_model/res10_300x300_ssd_iter_140000.caffemodel') #instatiate flask app app = Flask(__name__, template_folder='./templates') camera = cv2.VideoCapture(0)
在上面的代码中,我们导入了所有必要的模块。
Flask 是一个微型网络框架,它就像是前端和后端之间的桥梁。
我们从flask中导入’Response’和’request’模块来处理HTTP响应请求
’render_template ’ 用于渲染之前显示的 HTML 文件。
OpenCV是用于执行所有计算机视觉任务的模块。
“Thread”模块用于产生新线程。
然后我们声明所有全局变量,它们就像一个“切换开关”来执行不同的任务,比如捕获图像、开始/停止记录和应用滤镜。将变量初始化为 0 以将所有内容设置为 false。
在第 18 行,我们尝试创建一个名为“ shots ”的文件夹(如果它不存在)。这是保存所有捕获图像的位置。
第 24 行加载一个预训练的人脸检测模型以备将来使用,第 27 行创建 Flask 应用程序的实例。第 30 行为内置摄像头创建了一个视频捕获对象。
功能:
def record(out): global rec_frame while(rec): time.sleep(0.05) out.write(rec_frame)
’ record ’ 函数用于开始记录,即在 ’ rec ’ 变量设置为 true 时将帧写入 avi 文件。它使用“ out ”,这是稍后初始化的视频编写器的对象。
(注意:如果你觉得录制的视频快或慢,请修改 time.sleep 的值)。
def detect_face(frame): global net (h, w) = frame.shape[:2] blob = cv2.dnn.blobFromImage(cv2.resize(frame, (300, 300)), 1.0, (300, 300), (104.0, 177.0, 123.0)) net.setInput(blob) detections = net.forward() confidence = detections[0, 0, 0, 2] if confidence < 0.5: return frame box = detections[0, 0, 0, 3:7] * np.array([w, h, w, h]) (startX, startY, endX, endY) = box.astype("int") try: frame=frame[startY:endY, startX:endX] (h, w) = frame.shape[:2] r = 480 / float(h) dim = ( int(w * r), 480) frame=cv2.resize(frame,dim) except Exception as e: pass return frame
’ detect_face() ’ 将相机帧作为输入,并返回一个只包含在该帧中检测到的人脸的裁剪帧。它使用之前加载的预训练人脸检测模型。
可以访问此网站 www.pyimagesearch.com/2018/02/26/face-detection-with-opencv-and-deep-learning/ 以深入了解这是如何完成的。
def gen_frames(): # generate frame by frame from camera global out, capture,rec_frame while True: success, frame = camera.read() if success: if(face): frame= detect_face(frame) if(grey): frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) if(neg): frame=cv2.bitwise_not(frame) if(capture): capture=0 now = datetime.datetime.now() p = os.path.sep.join(['shots', "shot_{}.png".format(str(now).replace(":",''))]) cv2.imwrite(p, frame) if(rec): rec_frame=frame frame= cv2.putText(cv2.flip(frame,1),"Recording...", (0,25), cv2.FONT_HERSHEY_SIMPLEX, 1, (0,0,255),4) frame=cv2.flip(frame,1) try: ret, buffer = cv2.imencode('.jpg', cv2.flip(frame,1)) frame = buffer.tobytes() yield (b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n') except Exception as e: pass else: pass
’ gen_frames ’ 是一个重要的函数,完成实际的帧捕获(通过相机)和处理。它在无限循环中运行。
第 4 行从相机对象捕获帧。如果帧捕获成功,它会检查是否有任何滤镜开关为真。如果’ *face’、‘neg’*或’ gray '为真,则分别在读取帧上应用面部滤镜、负片滤镜和灰度滤镜。
如果 ’ capture ’ 变量设置为 true,则将其重置为 false(全局变量)并且当前帧以 ‘png’ 格式保存。如果“ rec ”为真,则将帧复制到“ rec_frame”全局变量,触发时将其保存到视频文件中。
第 25 行将帧编码到内存缓冲区中,然后转换为字节数组。第 27 行以需要作为 HTTP 响应发送的格式生成帧数据。
HTTP 路由:
这是前端与服务器通信的地方。通信通过 URL 路由在*'GET* ’ 和 ’ POST ’ 方法中发生。
app.route('/') def index(): return render_template('index.html')
‘@app. route(‘/’)’ 是一个 Python 装饰器,Flask 提供它来轻松地将 URL 分配给我们应用程序中的函数。
路由’/‘是根URL,输入根URL时调用’ index() '函数。*“index.html”*文件从函数呈现到网页中。
@app.route('/video_feed') def video_feed(): return Response(gen_frames(), mimetype='multipart/x-mixed-replace; boundary=frame')
路由 ’ /video_feed ’ 被设置为 html 文件中的图像源。此函数在循环中返回由“ gen_frames() ”产生的帧的响应块。
@app.route('/requests',methods=['POST','GET']) def tasks(): global switch,camera if request.method == 'POST': if request.form.get('click') == 'Capture': global capture capture=1 elif request.form.get('grey') == 'Grey': global grey grey=not grey elif request.form.get('neg') == 'Negative': global neg neg=not neg elif request.form.get('face') == 'Face Only': global face face=not face if(face): time.sleep(4) elif request.form.get('stop') == 'Stop/Start': if(switch==1): switch=0 camera.release() cv2.destroyAllWindows() else: camera = cv2.VideoCapture(0) switch=1 elif request.form.get('rec') == 'Start/Stop Recording': global rec, out rec= not rec if(rec): now=datetime.datetime.now() fourcc = cv2.VideoWriter_fourcc(*'XVID') out = cv2.VideoWriter('vid_{}.avi'.format(str(now).replace(":",'')), fourcc, 20.0, (640, 480)) #Start new thread for recording the video thread = Thread(target = record, args=[out,]) thread.start() elif(rec==False): out.release() elif request.method=='GET': return render_template('index.html') return render_template('index.html')
路由“ /requests ”被分配给“ tasks() ”函数,它处理所有的开关和视频录制。这个路由有’ POST '和*'GET* '方法,即它接受信息并发送信息。默认情况下,所有以前的路由都是“ GET ”。
如果来自客户端的 HTTP 方法是“POST”,则“ request.form.get ”接受来自用户按下的按钮的数据,并反转全局变量的先前状态,这些变量的作用类似于“ gen_frame ”函数中的开关。
例如,当用户按下“灰色”按钮时,“灰色”全局变量被设置为 True,从而在“ gen_frames ”函数中将帧转换为灰度。当再次按下“灰色”按钮时,“灰色”设置为假,使帧恢复正常。
在运行 Flask 应用程序的同时将帧录制到视频中非常棘手。最简单的解决方案是启动一个新线程。
线程是一个独立的执行流。这意味着你的程序将同时发生两件事。线程与其对等线程共享数据段、代码段、文件等信息,同时包含自己的寄存器、堆栈、计数器等。
在我们的例子中,’ record() ’ 函数有它自己的 while 循环,因此该循环在新线程中运行。
首先,当“ rec ”为真时,我们创建一个“ VideoWriter ”对象。在第 37 行,我们初始化一个新线程,目标是“ record() ”函数,第 38 行开始运行“ *record()”*函数的新线程。
当再次按下录制按钮时,*‘VideoWriter’*对象被释放并且停止录制以将视频保存在根目录中。
最后,如果来自客户端的 HTTP 方法是“ GET ”,则呈现“index.html”模板。
主函数:
if __name__ == '__main__': app.run()
*‘app.run()’*用于在其默认地址启动Flask应用程序: 127.0.0.1:5000/
通过向函数“run”添加“host”和“port”参数,可以设置不同的主机和端口号。将主机设置为广播地址0.0.0.0将使应用程序在整个局域网(wifi 等)中可见。
因此,如果你的移动设备连接到同一个 Wi-Fi,你就可以从它访问该应用程序。这是一个很好的“间谍摄像头”。
结论
要运行这个应用程序,你应该在你的 PC 上安装 python、flask 和 OpenCV。
要启动应用程序,请移至命令提示符中的项目目录。键入并输入
python camera_flask_app.py
现在,将127.0.0.1:5000/ 复制粘贴到你的浏览器中,就是这样。
你可以添加更多功能(例如 AI 滤镜)来构建 Snapchat 式应用程序。你还可以增强用户界面,使其更具交互性和色彩。你可以在这个 GitHub 帐户中获取该项目的源代码。
github.com/hemanth-nag/Camera_Flask_App
————————————————