Python人脸检测(初级)

前言

忙了一段时间,少有时间写文,前两天公司的一个web项目需要用户上传自拍照然后检查自拍照里面的人脸状态(人脸数量,是否正脸,是否戴眼镜,是否戴帽子等等),如果状态合适就将人脸提取出来,最后与设置的前景图片进行融合。

这其中涉及到的关键点在于人脸的检测,人脸的提取,至于人脸与前景的融合则可以使用前端js实现,对人脸的处理一般使用滤镜处理,前端可以使用腾讯AlloyTeam团队开源的项目AlloyImage()处理图片,拥有丰富的滤镜。当然也可以在后端处理好传给前端,后端处理可以选择使用OpenCV,后端处理的缺点在于多用户同时上传的时候服务器压力较大,交给前端处理比较妥当。

关于滤镜处理图片这部分不多说了,仔细研读腾讯的开源项目文档就能搞定,今天说说人脸检测和提取部分,注意是“检测”,不是“识别”。

检测的意思是只需要检查是人脸和人脸的状态以及人脸的位置,识别则是根据人脸特征匹配到人的姓名,这是两种不同的概念,千万不要混淆。

分析

既然是入门级的人脸检测,当然是从易到难,首先考虑的是有没有第三方人脸检测API,直接调用第三方接口,也是一种快速实现的方法。说到第三方接口,自然要货比三家,谁好用用谁。

经过搜索引擎的筛选,博主选择了四家服务商的接口,分别是百度、腾讯、阿里云、Face++。当然了,如果这篇文章只是单纯的描述如何调用第三方接口的话,估计各位看官也不会买账,不就是一个requests请求的事,还需要写个文?

为了体现从易到难循序渐进的思想,博主增加了本地检测的方式进行对比,本地Python配合OpenCV也可以实现人脸的检测,把这几种进行对比,自然就能看出各自的优劣。

当然了,本地OpenCV检测的速度和效率是第三方服务商接口调用无法比的,这也是为下一篇进阶(监控流媒体画面实时检测)打基础。

好了,废话不多说,开始几种方案的对比。

接口

首先对四家的接口文档进行分析,对接难度从易到难分别是 Face++、百度、腾讯、阿里云,参考标准是实现相同功能需要的代码行数(手动滑稽!)。

给出四家服务商的接口文档地址供大家自行参考:

  • Face++ 文档地址:https://console.faceplusplus.com.cn/documents/4888373
  • 腾讯人脸检测 文档地址:https://cloud.tencent.com/document/product/867/17588
  • 百度 人脸检测 文档地址:http://ai.baidu.com/docs#/Face-Detect-V3/top
  • 阿里云 人脸检测 文档地址:https://help.aliyun.com/knowledge_detail/53399.html

Face++和百度云不需要生成签名,只需要传入key和id即可,腾讯和阿里云需要生成对应的签名做认证才能调用成功,其中阿里云的签名生成是对所有的请求内容进行签名,稍有错误就导致签名失败,所以调试起来稍微有点复杂,博主也是在这里被坑的很惨。

Face++ 支持三种图片的传递方式,url、二进制、base64,文件大小为最大2M。

腾讯 支持两种图片的传递方式,url和二进制。

百度 支持两种图片的传递方式,url和Base64。

阿里云 支持两种图片的传递方式,url和Base64。

阿里云返回的结果中数据量最小,很多检测结果是没有的,比如是否戴眼镜,戴帽子等,当然了,这里的重点是人脸检测,只需要检测到人脸的位置信息就OK了,其他的都是次要信息。

针对项目需求,url传图的方式因为有图片下载的时间不确定性,而且用户上传图片后台转换url也是一道程序,还不如直接传图,四家服务商除了腾讯不支持base64以外,其他三家都支持base64,而且前端调用微信的图片上传方法可以直接将图片在前端就转换成base64,通过接口上传给服务端处理,调用腾讯云的时候可以将base64编码的图片直接decode成二进制文件,这样腾讯云就可以支持了。

实战

(ps:请忽略博客本身的水印

博主首选选取一张带有明显人脸标志的图片进行测试,上测试图(在此感谢迪丽热巴)

reba.jpg - 大小: 25.18 KB - 尺寸: 550 x 366 - 点击打开新窗口浏览全图

然后根据需求编写代码,先给出测试结果的对比图,再来根据各自的结果进行分析

result.jpg - 大小: 152.62 KB - 尺寸: 1106 x 566 - 点击打开新窗口浏览全图
从对比图可以看出,Face++和百度的识别结果基本一致,腾讯和本地OpenCV识别出来的结果非常接近,而阿里云识别出来的结果则是相对比较完整的人脸。

当然了,仅仅依靠这一张图很难判断出来谁更厉害,那咱们多来几个测试,首先来一张胡歌的侧脸特写(在此感谢胡歌)

huge.jpg - 大小: 116.21 KB - 尺寸: 1106 x 566 - 点击打开新窗口浏览全图
这组检测结果里面依然是阿里云表现最佳,而百度则有点离谱,OpenCV虽然检测到的位置是准确的,但是检测人脸的范围出现了失误,显示为正方形。

下面再来一张,这张测试图是一张面部被头发遮挡并且不是正面,看下各自的识别结果(在此感谢袁姗姗)

shanshan.jpg - 大小: 92.41 KB - 尺寸: 1106 x 566 - 点击打开新窗口浏览全图
从这组测试结果来看还是阿里云最佳,其次是face++,而OpenCV直接识别失败了!!

这里得说明一下,OpenCV识别失败并不是因为它就一定很垃圾,OpenCV的识别率是根据给定的人脸模型而来的,OpenCV自带了很多识别的模型,这些模型都是经过多次卷积网络算法训练得来的,而博主这里使用的是人脸最为严格的模型,也就是说识别的条件更为苛刻,所以在这次测试中OpenCV没有检测到人脸,比较尴尬。

所以,综上一些简单的测试后发现如果要做人脸的提取,还是选择阿里云的人脸检测接口比较合适,下面来看看各家API的收费情况,这里仅仅比对免费额度。

Face++提供免费的使用API key,不限制调用次数,但是一些高级的功能无法使用。

百度 对于个人用户提供QOS 为2 企业用户QOS 为10,调用次数不限的免费服务。

腾讯 提供不限次数的调用请求,但是高级功能需要付费。

阿里云 提供免费5000次的免费调用额度,5000次用完自动切换到付费模式。

这里放上一张前两天项目中的效果图吧

shibie.jpg - 大小: 266.2 KB - 尺寸: 1080 x 1707 - 点击打开新窗口浏览全图

代码

根据各家提供的API文档和测试demo,编写各自的测试代码,下面提供代码供大家参考。

注意:这里代码仅仅作为快速调试的参考,没有做异常处理,正式开发请根据自身开发环境和生产环境自行考虑各种异常处理,请不要直接使用我的代码。

如果有不懂的地方可以发邮件询问。

Face++(图片以二进制上传)

# -*- coding: utf-8 -*-

import requests, sys
from PIL import Image, ImageDraw

source_path = sys.path[0] + '/shanshan.jpg'

with open(source_path, "rb") as f:
    file_data = f.read()
params = {
    "api_key": "xxxxxx",
    "api_secret": "xxxxxxx"
}

f = {"image_file": file_data}

request_url = "https://api-cn.faceplusplus.com/facepp/v3/detect"

decode_result = requests.post(url=request_url, data=params, files=f, timeout=30).json()

current_face = decode_result.get('faces')[0]
face_location = current_face.get('face_rectangle')

face_x = int(face_location.get("left"))
face_y = int(face_location.get("top"))
face_width = face_x + int(face_location.get("width"))
face_height = face_y + int(face_location.get("height"))

old_img = Image.open(source_path)
draw_instance = ImageDraw.Draw(old_img)
draw_instance.rectangle((face_x, face_y, face_width, face_height), outline=(255, 0, 0))
old_img.save(sys.path[0] + '/i-face++.jpg')

百度(图片以base64上传)

# -*- coding: utf-8 -*-

import requests, sys, base64
from PIL import Image, ImageDraw

source_path = sys.path[0] + '/shanshan.jpg'

# client_id 为官网获取的AK, client_secret 为官网获取的SK
host = 'https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=*********&client_secret=**********'
session = requests.get(url=host, timeout=30).json()
access_token = session.get('access_token')

with open(source_path, "rb") as f:
    base64_data = base64.b64encode(f.read())
params = {
    "image": base64_data,
    "image_type": "BASE64",
    "face_field": "beauty,age,glasses,face_type,quality"
}

request_url = "https://aip.baidubce.com/rest/2.0/face/v3/detect"
request_url = request_url + "?access_token=" + access_token
decode_result = requests.post(url=request_url, data=params, timeout=30).json()

face_num = decode_result.get('result').get('face_num')
current_face = decode_result.get('result').get('face_list')[0]
face_location = current_face.get('location')
face_glasses = current_face.get('glasses')
face_real = current_face.get('face_type')
face_quality = current_face.get('quality')
face_probability = current_face.get('face_probability')
face_angel = current_face.get('angel')

face_x = int(face_location.get("left"))
face_y = int(face_location.get("top"))
face_width = face_x + int(face_location.get("width"))
face_height = face_y + int(face_location.get("height"))

old_img = Image.open(source_path)
draw_instance = ImageDraw.Draw(old_img)
draw_instance.rectangle((face_x, face_y, face_width, face_height), outline=(255, 0, 0))
old_img.save(sys.path[0] + '/i-baidu.jpg')

腾讯(图片以二进制上传)

# -*- coding: utf-8 -*-

import requests, sys, base64, time, random, hashlib, hmac
from PIL import Image, ImageDraw

source_path = sys.path[0] + '/shanshan.jpg'
orignal = "a=******&b=&k=*******&e=******&t=%d&r=%d&f=" % (int(time.time()), random.randint(1, 1000))

SignTmp = hmac.new("**********".encode('utf8'), orignal.encode('utf8'), hashlib.sha1).digest()

before_string = SignTmp + orignal.encode('utf8')
Sign = base64.b64encode(before_string).decode('utf8')

request_url = "https://recognition.image.myqcloud.com/face/detect"

headers = {"authorization": Sign, "host": "recognition.image.myqcloud.com"}

s = requests.session()


with open(source_path, "rb") as f:
    img_file = f.read()

params = {"appid": "*********", "mode": 1}

f = {"image": open(source_path, "rb").read()}

decode_result = s.post(url=request_url, headers=headers, data=params, files=f, timeout=30).json()

if decode_result.get('code') == 0:
    face_data = decode_result.get('data')

    current_face = face_data.get('face')[0]

    face_x = int(current_face.get("x"))
    face_y = int(current_face.get("y"))
    face_width = face_x + int(current_face.get("width"))
    face_height = face_y + int(current_face.get("height"))

    old_img = Image.open(source_path)
    draw_instance = ImageDraw.Draw(old_img)
    draw_instance.rectangle((face_x, face_y, face_width, face_height), outline=(255, 0, 0))
    old_img.save(sys.path[0] + '/i-tencent.jpg')

阿里云(图片以base64上传)

# -*- coding: utf-8 -*-

import requests, sys, base64, hashlib, hmac, json, datetime
from PIL import Image, ImageDraw

source_path = sys.path[0] + '/shanshan.jpg'

id = "*******"
key = "***********"

request_url = "https://dtplus-cn-shanghai.data.aliyuncs.com/face/detect"

with open(source_path, "rb") as f:
    base64_data = base64.b64encode(f.read()).decode('utf8')

params = {"type": 1, "content": base64_data}


def get_current_date():
    date = datetime.datetime.strftime(datetime.datetime.utcnow(), "%a, %d %b %Y %H:%M:%S GMT")
    return date


def to_md5_base64(string_body):
    hash_obj = hashlib.md5()
    hash_obj.update(string_body.encode('utf8'))
    return base64.b64encode(hash_obj.digest()).decode('utf8')


def to_sha1_base64(string_to_sign, secret):
    hmac_sha1 = hmac.new(secret.encode('utf8'), string_to_sign.encode('utf8'), hashlib.sha1)
    return base64.b64encode(hmac_sha1.digest()).decode('utf8')


options = {
    'method': 'POST',
    'body': json.dumps(params),
    'headers': {
        'accept': 'application/json',
        'content-type': 'application/json',
        'date':  get_current_date()
    }
}

body_md5 = to_md5_base64(options.get('body'))


string_sign = '%s\n%s\n%s\n%s\n%s\n%s' % (
    options.get('method'),
    options.get('headers').get('accept'),
    body_md5,
    options.get('headers').get('content-type'),
    options.get('headers').get('date'),
    "/face/detect"
)

signature = to_sha1_base64(string_sign, key)


headers = {
    'Accept': 'application/json',
    'Content-type': 'application/json',
    "Date": options.get('headers').get('date'),
    "Authorization": "Dataplus %s:%s" % (id, signature)
}

s = requests.session()

decode_result = s.post(url=request_url, headers=headers, data=json.dumps(params), timeout=30).json()

if decode_result.get('errno') == 0:
    face_data = decode_result.get('face_rect')

    face_x = int(face_data[0])
    face_y = int(face_data[1])
    face_width = face_x + int(face_data[2])
    face_height = face_y + int(face_data[3])

    old_img = Image.open(source_path)
    draw_instance = ImageDraw.Draw(old_img)
    draw_instance.rectangle((face_x, face_y, face_width, face_height), outline=(255, 0, 0))
    old_img.save(sys.path[0] + '/i-aliyun.jpg')

OpenCV

# -*- coding: utf-8 -*-

import cv2, sys
from PIL import Image, ImageDraw


def test_face():
    source_path = sys.path[0] + '/shanshan.jpg'
    img = cv2.imread(source_path)
    face_cascade = cv2.CascadeClassifier("/usr/share/opencv/haarcascades/haarcascade_frontalface_default.xml")
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    faces = face_cascade.detectMultiScale(gray, 1.3, 4)
    if len(faces) > 1:
        print("不止一个人脸")
    else:
        print("一个人脸")

        face_x = faces[0][0]
        face_y = faces[0][1]
        face_width = face_x + faces[0][2]
        face_height = face_y + faces[0][3]

        old_img = Image.open(source_path)
        draw_instance = ImageDraw.Draw(old_img)
        draw_instance.rectangle((face_x, face_y, face_width, face_height), outline=(255, 0, 0))
        old_img.save(sys.path[0] + '/i-opencv.jpg')

if __name__ == '__main__':
    test_face()
请根据自己的环境自行替换代码中的参数进行测试。

如果需要对图片中的人脸进行提取也非常简单,增加下面的代码即可

image_name = sys.path[0] + '/cut-opencv.jpg'
Image.open(source_path).crop((face_x, face_y, face_width, face_height)).save(image_name)

用途

写了这么多,到底都有啥用途呢,很常见的那种颜值测试就可以用这种api直接实现,api里面都可以直接返回颜值分数,非常简单。

下一篇咱们讲解如何使用OpenCV实现实时视频监控流媒体的人脸检测与提取,之后还有结合阿里云oss存储实现的大量图片视频混合识别去特征提取以及人脸特征自动判断以及动物识别(比如猫?),还有身体姿态识别等等内容都会一一更新上去。

哈哈,当然了,都是实战!源码什么的都会公开。

本文链接:https://www.92ez.com/?action=show&id=23472
!!! 转载请先联系non3gov@gmail.com授权并在显著位置注明作者和原文链接 !!! 小黑屋
提示:技术文章有一定的时效性,请先确认是否适用你当前的系统环境。

上一篇: IP为113.116.6.6的同学,我有话想对你说
下一篇: 手指肌腱断裂无法修复是一种怎样的体验

访客评论
目前还没有人评论,您发表点看法?
发表评论

评论内容 (必填):