OpenCV-Python实战(21)——OpenCV人脸检测项目在Web端的部署

x33g5p2x  于2022-03-01 转载在 Python  
字(10.3k)|赞(0)|评价(0)|浏览(311)

0. 前言

OpenCV 计算机视觉项目部署在 Web 端一个有趣的话题,部署在 Web 端的优势之一是不需要安装任何应用,只需要访问地址就可以访问应用。在本文中,我们使用 Python Web 框架创建并部署一个完整的 Web 人脸检测应用程序,在项目中我们将学习到如何处理来自浏览器的不同请求方式(例如 GETPOST 等),以及如何实战使用 OpenCVFlask 创建 Web 人脸检测 API

1. OpenCV 人脸检测项目在 Web 端的部署

本节中将使用 Python Web 框架创建并部署一个完整的 Web 人脸检测应用程序,此程序不仅可以处理本地图片(利用 request.files['image']),同时也可以用于处理来自网络中的图片(利用 request.args.get('url'))。

1.1 解析请求并构建响应

在此实战中,我们将看到如何使用 OpenCVFlask 创建一个 Web 人脸检测 API,我们将项目命名为 face_detection,项目目录结构如下所示:

face_detection
	|——server
	|	├─face_detection.py
	|	└─face_processing.py
	└─client
		├─request_test.py
		├─request_and_draw_rectangle.py
		└─test_example.png

其中 face_detection.py 脚本负责解析请求并构建对客户端的响应:

# face_detection.py
# 导入所需包
from flask import Flask, request, jsonify
import urllib.request
from face_processing import FaceProcessing

app = Flask(__name__)
fc = FaceProcessing()

@app.errorhandler(400)
def bad_request(e):
    # 返回代码错误 
    return jsonify({"status": 'Not ok', "message": "This server could not understand your request"}), 400

@app.errorhandler(404)
def not_found(e):
    # 返回代码错误
    return jsonify({"status": 'Not found', "message": "Route not found"}), 404

@app.errorhandler(500)
def internal_error(e):
    # 返回代码错误
    return jsonify({"status": "Internal Error", "message": "Internal error occurred in server"}), 500

@app.route('/detect', methods=['GET', 'POST', 'PUT'])
def detect_human_faces():
    if request.method == 'GET':
        if request.args.get('url'):
            with urllib.request.urlopen(request.args.get('url')) as url:
                return jsonify({"status": "Ok", "result": fc.face_detection(url.read())}), 200
        else:
            return jsonify({"status": "Bad request", "message": "Parameter url is not present"}), 400
    elif request.method == 'POST':
        if request.files.get('image'):
            return jsonify({"status": "Ok", "result": fc.face_detection(request.files['image'].read())}), 200
        else:
            return jsonify({"status": "Bad request", "message": "Parameter image is not present"}), 400
    else:
        return jsonify({"status": "Failure", "message": "PUT method not supported for API"}), 405

if __name__ == '__main__':
    app.run(host='0.0.0.0')

如上所示,使用 jsonify() 函数来创建给定参数的 JSON 表示,以返回 application/json MIME 类型。 JSON 是信息交换的事实标准,此项目将返回 JSON 响应,在项目的最后我们将了解如何对其进行修改以返回图像,此 Web 人脸检测 API 支持 GETPOST 请求;此外,在 face_detection 脚本中,我们还通过使用 errorhandler() 装饰函数来注册错误处理程序,用来响应出错时向客户端返回设置的错误代码。
人脸检测程序与负责响应的程序进行了分离,人脸检测程序在 face_processing.py 脚本中执行,其中编写了 FaceProcessing() 类:

# face_processing.py
import cv2
import numpy as np
import os
class FaceProcessing(object):
    def __init__(self):
        self.file = os.path.join(os.path.join(os.path.dirname(__file__), "data"), "haarcascade_frontalface_alt.xml")
        self.face_cascade = cv2.CascadeClassifier(self.file)

    def face_detection(self, image):
        # 将图像转换为 OpenCV 格式
        image_array = np.asarray(bytearray(image), dtype=np.uint8)
        img_opencv = cv2.imdecode(image_array, -1)
        output = []
        # 检测人脸并构建返回值
        gray = cv2.cvtColor(img_opencv, cv2.COLOR_BGR2GRAY)
        faces = self.face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(25, 25))
        for face in faces:
            # 返回检测框坐标
            x, y, w, h = face.tolist()
            face = {"box": [x, y, x + w, y + h]}
            output.append(face)
            print(face)
        # 返回结果
        return output

face_detection() 方法使用 OpenCVdetectMultiScale() 函数执行人脸检测,获得每个检测到的人脸的坐标 (x, y, w, h),并通过合适的格式对检测结果进行编码来构建返回检测框:

face = {"box": [x, y, x + w, y + h]}

最后,我们将编码完成的人脸检测框添加到 output 数组中,将所有检测到的人脸检测框都添加到 output 数组后,将其返回:

output.append(face)

1.2 构建请求进行测试

为了使用 Web 人脸检测 API,我们可以从浏览器执行 GET 请求;同时,此 API 还支持 POST 请求。接下来,我们构建测试脚本测试此 API ,此脚本可以执行 GETPOST 请求,以了解如何与人脸 API 进行交互,更具体的讲,测试脚本将对人脸 API 发送多个请求,以获得不同的响应,并查看错误处理的工作原理。
首先使用不正确的 URL 执行 GET 请求:

# request_test.py
import requests

FACE_DETECTION_REST_API_URL = "http://localhost:5000/detect"
FACE_DETECTION_REST_API_URL_WRONG = "http://localhost:5000/process"
IMAGE_PATH = "test_example.png"
URL_IMAGE = "https://imgs.mmkk.me/wmnv/img/20190625073459-5d11cea35c407.png"
# 提交 GET 请求
r = requests.get(FACE_DETECTION_REST_API_URL_WRONG)
# 查看响应
print("status code: {}".format(r.status_code))
print("headers: {}".format(r.headers))
print("content: {}".format(r.json()))

打印响应信息,可以看到:

status code: 404
headers: {'Content-Type': 'application/json', 'Content-Length': '51', 'Server': 'Werkzeug/1.0.1 Python/3.7.7', 'Date': 'Sat, 02 Oct 2021 01:45:19 GMT'}
content: {'message': 'Route not found', 'status': 'Not found'}

状态码 404 表示客户端可以与服务器通信,但服务器找不到请求的内容。这是因为请求的 URL (http://localhost:5000/process) 不正确。
执行的第二个请求是正确的 GET 请求:

# 提交 GET 请求
payload = {'url': URL_IMAGE}
r = requests.get(FACE_DETECTION_REST_API_URL, params=payload)
# 查看响应
print("status code: {}".format(r.status_code))
print("headers: {}".format(r.headers))
print("content: {}".format(r.json()))

打印响应信息,可以看到:

status code: 200
headers: {'Content-Type': 'application/json', 'Content-Length': '52', 'Server': 'Werkzeug/1.0.1 Python/3.7.7', 'Date': 'Sat, 02 Oct 2021 01:54:31 GMT'}
content: {'result': [{'box': [233, 77, 356, 252]}], 'status': 'Ok'}

状态码 200 表示请求已成功执行,还可以看到已检测到与人脸相对应的检测框坐标。
接下来执行缺少有效负载的 GET 请求:

# 提交 GET 请求
r = requests.get(FACE_DETECTION_REST_API_URL)
# 查看响应
print("status code: {}".format(r.status_code))
print("headers: {}".format(r.headers))
print("content: {}".format(r.json()))

打印响应信息,可以看到:

status code: 400
headers: {'Content-Type': 'application/json', 'Content-Length': '66', 'Server': 'Werkzeug/1.0.1 Python/3.7.7', 'Date': 'Sat, 02 Oct 2021 01:58:00 GMT'}
content: {'message': 'Parameter url is not present', 'status': 'Bad request'}

状态代码 400 表示错误请求,这是由于其缺少 url 参数。
接下来执行的第四个请求是具有正确负载的 POST 请求:

# 加载图像并构建有效负载
image = open(IMAGE_PATH, "rb").read()
payload = {"image": image}
# 提交 POST 请求
r = requests.post(FACE_DETECTION_REST_API_URL, files=payload)
# 查看响应
print("status code: {}".format(r.status_code))
print("headers: {}".format(r.headers))
print("content: {}".format(r.json()))

打印响应信息,可以看到:

status code: 200
headers: {'Content-Type': 'application/json', 'Content-Length': '52', 'Server': 'Werkzeug/1.0.1 Python/3.7.7', 'Date': 'Sat, 02 Oct 2021 02:03:26 GMT'}
content: {'result': [{'box': [193, 92, 355, 292]}], 'status': 'Ok'}

最后我们构造 PUT 请求:

# 提交 PUT 请求
r = requests.put(FACE_DETECTION_REST_API_URL, files=payload)
# 查看响应
print("status code: {}".format(r.status_code))
print("headers: {}".format(r.headers))
print("content: {}".format(r.json()))

打印响应信息,可以看到:

status code: 405
headers: {'Content-Type': 'application/json', 'Content-Length': '66', 'Server': 'Werkzeug/1.0.1 Python/3.7.7', 'Date': 'Sat, 02 Oct 2021 02:05:54 GMT'}
content: {'message': 'PUT method not supported for API', 'status': 'Failure'}

这是由于我们的 API 不支持 PUT 方法,仅支持 GET 和 POST 方法,因此返回状态码 405

2. 根据获得的响应信息在客户端绘制检测框

当请求成功执行时,将检测到的人脸作为 JSON 数据返回,接下来我们将编写程序了解如何解析响应并绘制检测到的人脸:

# request_and_draw_rectangle.py
import cv2
import numpy as np
import requests
from matplotlib import pyplot as plt

def show_img_with_matplotlib(color_img, title, pos):
    img_RGB = color_img[:, :, ::-1]
    ax = plt.subplot(1, 1, pos)
    plt.imshow(img_RGB)
    plt.title(title, fontsize=10)
    plt.axis('off')

FACE_DETECTION_REST_API_URL = "http://localhost:5000/detect"
IMAGE_PATH = "test_example.png"
# 加载图像构造有效负载
image = open(IMAGE_PATH, 'rb').read()
payload = {'image': image}
r = requests.post(FACE_DETECTION_REST_API_URL, files=payload)
# 打印响应信息
print("status code: {}".format(r.status_code))
print("headers: {}".format(r.headers))
print("content: {}".format(r.json()))
# 解析响应信息
json_data = r.json()
result = json_data['result']

image_array = np.asarray(bytearray(image), dtype=np.uint8)
img_opencv = cv2.imdecode(image_array, -1)
# 绘制检测框
for face in result:
    left, top, right, bottom = face['box']
    cv2.rectangle(img_opencv, (left, top), (right, bottom), (0, 255, 255), 2)
    cv2.circle(img_opencv, (left, top), 5, (0, 0, 255), -1)
    cv2.circle(img_opencv, (right, bottom), 5, (255, 0, 0), -1)

# 可视化
fig = plt.figure(figsize=(8, 6))
plt.suptitle("Using face API", fontsize=14, fontweight='bold')
show_img_with_matplotlib(img_opencv, "face detection", 1)

plt.show()

在上示代码中,首先加载图像并构建有效负载,然后,执行 POST 请求,从响应中获取 JSON 数据并进行解析:

# 解析响应信息
json_data = r.json()
result = json_data['result']

接下来,就可以利用返回的信息绘制检测到的人脸:

# 绘制检测框
for face in result:
    left, top, right, bottom = face['box']
    cv2.rectangle(img_opencv, (left, top), (right, bottom), (0, 255, 255), 2)
    cv2.circle(img_opencv, (left, top), 5, (0, 0, 255), -1)
    cv2.circle(img_opencv, (right, bottom), 5, (255, 0, 0), -1)

对于每个检测到的人脸,绘制矩形检测框以及左上角和右下角的点:

3. 在服务器端绘制检测框并返回

我们也可以直接在服务器端在图像中绘制检测框,然后将结果图像返回(相关讲解可以在《OpenCV计算机视觉项目在Web端的部署》中查看),我们需要做的仅仅是修改 face_detection.py,这就是代码分离的优势之一:

# 这里仅修改 GET 请求,对于 POST 的修改也是类似的,可以自行探索
@app.route('/detect', methods=['GET', 'POST', 'PUT'])
def detect_human_faces():
    if request.method == 'GET':
        if request.args.get('url'):
            with urllib.request.urlopen(request.args.get('url')) as url:
                image = url.read()
            result = fc.face_detection(image)
            image_array = np.asarray(bytearray(image), dtype=np.uint8)
            img_opencv = cv2.imdecode(image_array, -1)
            for face in result:
                left, top, right, bottom = face['box']

                cv2.rectangle(img_opencv, (left, top), (right, bottom), (0, 255, 255), 2)
                cv2.circle(img_opencv, (left, top), 5, (0, 0, 255), -1)
                cv2.circle(img_opencv, (right, bottom), 5, (255, 0, 0), -1)
            retval, buffer = cv2.imencode('.jpg', img_opencv)
            response = make_response(buffer.tobytes())
            response.headers['Content-Type'] = 'image'
            return response
        else:
            return jsonify({"status": "Bad request", "message": "Parameter url is not present"}), 400
    elif request.method == 'POST':
        if request.files.get('image'):
            return jsonify({"status": "Ok", "result": fc.face_detection(request.files['image'].read())}), 200
        else:
            return jsonify({"status": "Bad request", "message": "Parameter image is not present"}), 400
    else:
        return jsonify({"status": "Failure", "message": "PUT method not supported for API"}), 405

修改之后,我们就可以通过 GET 请求来查看程序效果:

http://10.140.12.255:5000/detect?url=https://imgs.mmkk.me/wmnv/img/20190625073459-5d11cea35c407.png

小结

在本文中,我们使用 Python Web 框架创建并部署了一个完整的 Web 人脸检测应用程序,同时在项目中我们处理了来自浏览器的不同请求方式(例如 GETPOST 等),并通过实战使用 OpenCVFlask 创建 Web 人脸检测 API,同时我们还使用了两种不同类型的响应结果提供不同的请求结果。

系列链接

OpenCV-Python实战(1)——OpenCV简介与图像处理基础
OpenCV-Python实战(2)——图像与视频文件的处理
OpenCV-Python实战(3)——OpenCV中绘制图形与文本
OpenCV-Python实战(4)——OpenCV常见图像处理技术
OpenCV-Python实战(5)——OpenCV图像运算
OpenCV-Python实战(6)——OpenCV中的色彩空间和色彩映射
OpenCV-Python实战(7)——直方图详解
OpenCV-Python实战(8)——直方图均衡化
OpenCV-Python实战(9)——OpenCV用于图像分割的阈值技术
OpenCV-Python实战(10)——OpenCV轮廓检测
OpenCV-Python实战(11)——OpenCV轮廓检测相关应用
OpenCV-Python实战(12)——一文详解AR增强现实
OpenCV-Python实战(13)——OpenCV与机器学习的碰撞
OpenCV-Python实战(14)——人脸检测详解
OpenCV-Python实战(15)——面部特征点检测详解
OpenCV-Python实战(16)——人脸追踪详解
OpenCV-Python实战(17)——人脸识别详解
OpenCV-Python实战(18)——深度学习简介与入门示例
OpenCV-Python实战(19)——OpenCV与深度学习的碰撞
OpenCV-Python实战(20)——OpenCV计算机视觉项目在Web端的部署

相关文章