[Day9] Face Detection - 使用OpenCV & Dlib:Haar cascades

了解一套工具最好的方法是:动手完成一个现有的范例
了解一门技术最好的方法是:用那套技术完整做出一个自己的应用

Get your hands dirty!

介绍完目前主流的API与相关平台後,还是要回头来看看要如何自行写程序完成一样的功能。

本文开始

前面提到过,使用OpenCV & Dlib来做人脸侦测,大概可以分为四种方式:

  1. OpenCV Haar cascades <-- 今天说这个
  2. OpenCV deep neural networks (DNNs)
  3. Dlib HOG + Linear SVM
  4. Dlib max-margin object detector (MMOD)

接下来就依序介绍各个方式吧。

:如同在第一天提到的,这个系列使用的主要语言是Python,对Python不了解或有疑问的可以参考其他邦友发表的优质文章

环境准备

这边推荐两种Python开发环境使用:

  1. Google Colab:只要有浏览器+网路就可以使用、预先就安装好常用套件、需要有Google帐号
  2. PyCharm:整合环境方便管理大型与复杂的专案、提供几种常见的虚拟环境方便切换使用(Virtualenv、Conda、Pipenv)

由於接下来的实作大部分都会围绕在自行开发上,为了能够更方便的整合专案,後续的开发环境都会使用PyCharm来作范例说明与实作,关於PyCharm的安装与使用请参考邦帮忙其他相关文章

OpenCV Haar cascades

习惯上我喜欢称呼这个方法叫哈尔特徵检测

它以辨识速度极快、硬体需求低,常常会被应用在各种行动装置或是树莓派(Raspberry Pi)等硬体规格相对低阶的装置上。

这个方法主要是透过滑动检测事先定义好的小矩形,由左上至右下,找寻人脸上明显的特徵:如两眼之间的鼻子区域通常比眼睛更亮,然後慢慢把非人脸区域排除。从而检测出人脸真正区域

这个技术相关的原理在网路上都可以搜寻的到,这边就让我们直接开始实作吧!

  1. 打开PyCharm IDE,新增一个专案

  2. (可不用) 点击上方工具栏的「File」->「Settings」->「Project: your_project_name」->「Python Interpreter」,新增一个Python的虚拟运作环境
    haar_ide_1

  3. 点选上方的"+"符号安装套件
    haar_ide_2

  4. 依序在搜寻框输入下面的套件名称,确认版本相同後,按下方的「Install Package」安装套件

    • opencv-contrib-python (版本:4.1.2.30)
    • cmake (版本:3.21.2)
    • dlib (版本:19.22.1)
    • imutils (版本:0.5.4)
      haar_ide_3
      haar_ide_5
      haar_ide_4
      imutils
  5. 安装需要一点时间...等待安装都成功後,关闭视窗。在主视窗开启下方的「Terminal」,输入pip freeze
    haar_ide_6

    > pip freeze
    

    确认你安装的套件与版本是否与下面相同

    cmake==3.21.2
    dlib==19.22.1
    imutils==0.5.4
    numpy==1.21.2
    opencv-contrib-python==4.5.3.56
    
  6. 接下来在新增的专案目录下新增目录:face_detection,专门放人脸侦测相关档案用
    haar_ide_7

  7. 再来新增一个Python档案:opencv_haar_cascade.py,撰写今天主程序
    haar_ide_8

  8. 在新增的Python档案内输入以下内容 (相关程序码说明在注解内):

    # 汇入必要套件
    import argparse
    import ntpath
    import os
    import time
    
    import cv2
    import imutils
    from imutils.video import WebcamVideoStream
    
    detectors = {
        "eye": os.path.sep.join([ntpath.dirname(cv2.__file__), 'data', 'haarcascade_eye.xml']),
        "face": os.path.sep.join([ntpath.dirname(cv2.__file__), 'data', 'haarcascade_frontalface_default.xml'])
    }
    
    
    # 定义人脸侦测函数方便重复使用
    def detect(gray, part="face"):
        # 初始化Haar cascades函数
        detector = cv2.CascadeClassifier(detectors[part])
        # 根据选择的模型侦测
        rects = detector.detectMultiScale(gray, scaleFactor=1.05, minNeighbors=5, minSize=(15, 15),
                                          flags=cv2.CASCADE_SCALE_IMAGE)
        return rects
    
    
    def main():
        # 初始化arguments
        ap = argparse.ArgumentParser()
        ap.add_argument("-p", "--part", type=str, choices=["eye", "face"], default="face", help="detect which part of face")
        args = vars(ap.parse_args())
    
        # 启动WebCam
        vs = WebcamVideoStream().start()
        time.sleep(2.0)
        start = time.time()
        fps = vs.stream.get(cv2.CAP_PROP_FPS)
        print("Frames per second using cv2.CAP_PROP_FPS : {0}".format(fps))
    
        while True:
            # 取得当前的frame,变更比例为宽300,并且转成灰阶图片
            frame = vs.read()
            img = frame.copy()
            img = imutils.resize(img, width=300)
            gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    
            # 呼叫侦测函数,取得结果
            rects = detect(gray, args["part"])
    
            # 绘出侦测结果 (记得将侦测的座标转回原本的frame大小)
            ratio = frame.shape[1] / img.shape[1]
            for rect in rects:
                rect = rect * ratio
                (x, y, w, h) = rect.astype("int")
                cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
    
            # 标示FPS
            end = time.time()
            text = f"FPS: {str(int(1 / (end - start)))}"
            cv2.putText(frame, text, (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
            start = end
    
            # 显示影像
            cv2.imshow("Frame", frame)
    
            # 判断是否案下"q";跳离回圈
            key = cv2.waitKey(1) & 0xff
            if key == ord('q'):
                break
    
        #  清除画面与结束WebCam
        cv2.destroyAllWindows()
        vs.stop()
    
    
    if __name__ == '__main__':
        main()
    
    
  9. 运行程序:

    • 在Terminal中输入python face_detection/opencv_haar_cascade.py
    • 直接点程序码第61行前的"启动"icon
      haar_ide_10
  10. 执行後,会开启WebCam,并且显示类似下面的节果:
    haar_ide_9

你可以试着将启动指令改成python face_detection/opencv_haar_cascade.py -p eye跑看看会有什麽结果。

结论

  1. 戴口罩基本上无法成功辨识,这是由於前面提到哈尔特徵检测的作法是滑动视窗的限制,你可以试着比对戴上口罩前与戴上口罩後玩看看

  2. 一般WebCam的FPS为30 (你可以透过print(vs.stream.get(cv2.CAP_PROP_FPS))检查看看),透过哈尔特徵检测处理宽为300px的影像还是可以有约28 ~ 32 fps;後续可以跟其他方法比较看看,这个速度算是非常快了 (在实际使用下几乎感受不到延迟)

  3. 哈尔特徵检测的false-positive (失败侦测)结果会受程序码39行的detectMultiScale()参数影响:

    • scaleFactor:指定原始图片在进行检测时,要缩放的比例为多少;1.05表示每次以95%的比例进行缩放;值的范围为[1.0, MAX_VALUE],越小检测出的结果越多 (也需花费更多时间),通常会使用1.05 ~ 1.1之间的数值
    • minNeighbors:指定在检测结果中需要多少个相邻的检测框才认定检测成功;5表示需要5个相邻检测框;值必须为正整数,越小检测的结果越多 (但失败侦测的可能性也变大),通常会使用3 ~ 10之间的数值
    • minSize:指定最小可以接受(宽, 高)的检测框,小於这个大小的结果会被丢弃;(30, 30)表示使用宽高皆至少为30px的检测结果;值必须为(正整数, 正整数)的格式,通常会从(30, 30)开始使用,根据结果再调整
    • flags:目前版本中无作用,可以不用写或是给cv2.CASCADE_SCALE_IMAGE

    以上的参数的调整会非常容易改变最终的结果,这边建议根据你实际执行的情况来做调整,顺序建议为:
    minSize -> minNeighbors -> scaleFactor

  4. OpenCV内建的检测模型还有其他很多可以使用,如侦测眼睛微笑上半身全身等等,可以参考程序码20-21行的路径目录;有兴趣可以到该目录下看看

参考程序码在这
就这样,想到再补充。

明天见!


<<:  简单的先做 VS 技术难题先做

>>:  尝试的结果

LeetCode解题 Day07

206. Reverse Linked List https://leetcode.com/prob...

怎麽复制TABLE_SCHEMA里的table

SQL Server 1.一个一个TABLE去点,汇出Scripts 2.SQL Server 汇出...

Day 10 实用的 let 方法以及客制化错误讯息!

该文章同步发布於:我的部落格 改变数值的时候 昨天提到变动性的问题是什麽呢? 我们到现在的测试都是...

Azure - Day2 以考取AZ204为目标

小弟近日工作刚好会用到Azure,公司希望我们可以以考取AZ204为目标,所以接下来会照着这个目标发...

「Android 必看」如何救回 WhatsApp 聊天记录?

WhatsApp 聊天记录消失了? 如何恢复 WhatsApp 照片和信息? 我们都有过 Whats...