2 Commits

Author SHA1 Message Date
cf88a17a47 no "out of range" warning 2019-03-26 10:49:22 +08:00
cbb8b935ce print the shape of image captured 2019-03-26 10:42:15 +08:00
113 changed files with 1043 additions and 71164 deletions

5
.idea/.gitignore generated vendored
View File

@ -1,5 +0,0 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/

View File

@ -2,9 +2,13 @@
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.venv" />
<sourceFolder url="file://$MODULE_DIR$/data" isTestSource="false" />
</content>
<orderEntry type="jdk" jdkName="Python 3.10 (Dlib_face_recognition_from_camera) (2)" jdkType="Python SDK" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="TestRunnerService">
<option name="projectConfiguration" value="pytest" />
<option name="PROJECT_TEST_RUNNER" value="py.test" />
</component>
</module>

4
.idea/encodings.xml generated Normal file
View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with NO BOM" />
</project>

View File

@ -0,0 +1,12 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PyPep8Inspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="ignoredErrors">
<list>
<option value="E501" />
</list>
</option>
</inspection_tool>
</profile>
</component>

View File

@ -1,6 +0,0 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

5
.idea/misc.xml generated
View File

@ -1,7 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.10 (Dlib_face_recognition_from_camera) (2)" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (Dlib_face_recognition_from_camera) (2)" project-jdk-type="Python SDK" />
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.6" project-jdk-type="Python SDK" />
</project>

1
.idea/vcs.xml generated
View File

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

357
.idea/workspace.xml generated Normal file
View File

@ -0,0 +1,357 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ChangeListManager">
<list default="true" id="e58b655a-3a9b-4001-b4da-39e07ab46629" name="Default Changelist" comment="">
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/README.rst" beforeDir="false" afterPath="$PROJECT_DIR$/README.rst" afterDir="false" />
</list>
<option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
<option name="LAST_RESOLUTION" value="IGNORE" />
</component>
<component name="FUSProjectUsageTrigger">
<session id="2127136746">
<usages-collector id="statistics.lifecycle.project">
<counts>
<entry key="project.closed" value="1" />
<entry key="project.open.time.4" value="1" />
<entry key="project.open.time.5" value="1" />
<entry key="project.opened" value="2" />
</counts>
</usages-collector>
<usages-collector id="statistics.file.extensions.open">
<counts>
<entry key="png" value="1" />
<entry key="txt" value="1" />
</counts>
</usages-collector>
<usages-collector id="statistics.file.types.open">
<counts>
<entry key="Image" value="1" />
<entry key="PLAIN_TEXT" value="1" />
</counts>
</usages-collector>
<usages-collector id="statistics.file.extensions.edit">
<counts>
<entry key="rst" value="2" />
</counts>
</usages-collector>
<usages-collector id="statistics.file.types.edit">
<counts>
<entry key="ReST" value="2" />
</counts>
</usages-collector>
</session>
</component>
<component name="FileEditorManager">
<leaf SIDE_TABS_SIZE_LIMIT_KEY="300">
<file pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/get_faces_from_camera.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="67">
<caret line="12" column="34" selection-start-line="12" selection-start-column="34" selection-end-line="12" selection-end-column="34" />
</state>
</provider>
</entry>
</file>
<file pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/get_features_into_CSV.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="83">
<caret line="16" lean-forward="true" selection-start-line="16" selection-end-line="16" />
<folding>
<element signature="e#491#501#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
</file>
<file pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/face_reco_from_camera.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="127">
<caret line="89" column="22" lean-forward="true" selection-start-line="89" selection-start-column="22" selection-end-line="89" selection-end-column="22" />
<folding>
<element signature="e#230#264#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
</file>
<file pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/requirements.txt">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="76">
<caret line="4" column="16" lean-forward="true" selection-start-line="4" selection-start-column="16" selection-end-line="4" selection-end-column="16" />
</state>
</provider>
</entry>
</file>
<file pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/introduction/face_reco_two_people_in_database.png">
<provider selected="true" editor-type-id="images" />
</entry>
</file>
<file pinned="false" current-in-tab="true">
<entry file="file://$PROJECT_DIR$/README.rst">
<provider selected="true" editor-type-id="restructured-text-editor" />
</entry>
</file>
<file pinned="false" current-in-tab="false">
<entry file="file://$PROJECT_DIR$/how_to_use_camera.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="513">
<caret line="27" column="13" selection-start-line="27" selection-start-column="13" selection-end-line="27" selection-end-column="13" />
</state>
</provider>
</entry>
</file>
</leaf>
</component>
<component name="Git.Settings">
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
</component>
<component name="IdeDocumentHistory">
<option name="CHANGED_PATHS">
<list>
<option value="$PROJECT_DIR$/requirements.txt" />
<option value="$PROJECT_DIR$/README.rst" />
</list>
</option>
</component>
<component name="ProjectFrameBounds">
<option name="x" value="740" />
<option name="y" value="86" />
<option name="width" value="1194" />
<option name="height" value="987" />
</component>
<component name="ProjectLevelVcsManager" settingsEditedManually="true" />
<component name="ProjectView">
<navigator proportions="" version="1">
<foldersAlwaysOnTop value="true" />
</navigator>
<panes>
<pane id="ProjectPane">
<subPane>
<expand>
<path>
<item name="Dlib_face_recognition_from_camera" type="b2602c69:ProjectViewProjectNode" />
<item name="Dlib_face_recognition_from_camera" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="Dlib_face_recognition_from_camera" type="b2602c69:ProjectViewProjectNode" />
<item name="Dlib_face_recognition_from_camera" type="462c0819:PsiDirectoryNode" />
<item name="data" type="462c0819:PsiDirectoryNode" />
</path>
<path>
<item name="Dlib_face_recognition_from_camera" type="b2602c69:ProjectViewProjectNode" />
<item name="Dlib_face_recognition_from_camera" type="462c0819:PsiDirectoryNode" />
<item name="introduction" type="462c0819:PsiDirectoryNode" />
</path>
</expand>
<select />
</subPane>
</pane>
<pane id="Scope" />
</panes>
</component>
<component name="RunDashboard">
<option name="ruleStates">
<list>
<RuleState>
<option name="name" value="ConfigurationTypeDashboardGroupingRule" />
</RuleState>
<RuleState>
<option name="name" value="StatusDashboardGroupingRule" />
</RuleState>
</list>
</option>
</component>
<component name="RunManager" selected="Python.face_reco_from_camera">
<configuration name="face_reco_from_camera" type="PythonConfigurationType" factoryName="Python" temporary="true">
<module name="Dlib_face_recognition_from_camera" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/face_reco_from_camera.py" />
<option name="PARAMETERS" value="" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="false" />
<option name="MODULE_MODE" value="false" />
<option name="REDIRECT_INPUT" value="false" />
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
<configuration name="get_faces_from_camera" type="PythonConfigurationType" factoryName="Python" temporary="true">
<module name="Dlib_face_recognition_from_camera" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/get_faces_from_camera.py" />
<option name="PARAMETERS" value="" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="false" />
<option name="MODULE_MODE" value="false" />
<option name="REDIRECT_INPUT" value="false" />
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
<configuration name="get_features_into_CSV" type="PythonConfigurationType" factoryName="Python" temporary="true">
<module name="Dlib_face_recognition_from_camera" />
<option name="INTERPRETER_OPTIONS" value="" />
<option name="PARENT_ENVS" value="true" />
<envs>
<env name="PYTHONUNBUFFERED" value="1" />
</envs>
<option name="SDK_HOME" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
<option name="IS_MODULE_SDK" value="true" />
<option name="ADD_CONTENT_ROOTS" value="true" />
<option name="ADD_SOURCE_ROOTS" value="true" />
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/get_features_into_CSV.py" />
<option name="PARAMETERS" value="" />
<option name="SHOW_COMMAND_LINE" value="false" />
<option name="EMULATE_TERMINAL" value="false" />
<option name="MODULE_MODE" value="false" />
<option name="REDIRECT_INPUT" value="false" />
<option name="INPUT_FILE" value="" />
<method v="2" />
</configuration>
<list>
<item itemvalue="Python.get_faces_from_camera" />
<item itemvalue="Python.get_features_into_CSV" />
<item itemvalue="Python.face_reco_from_camera" />
</list>
<recent_temporary>
<list>
<item itemvalue="Python.face_reco_from_camera" />
<item itemvalue="Python.get_features_into_CSV" />
<item itemvalue="Python.get_faces_from_camera" />
</list>
</recent_temporary>
</component>
<component name="SvnConfiguration">
<configuration />
</component>
<component name="TaskManager">
<task active="true" id="Default" summary="Default task">
<changelist id="e58b655a-3a9b-4001-b4da-39e07ab46629" name="Default Changelist" comment="" />
<created>1538622047029</created>
<option name="number" value="Default" />
<option name="presentableId" value="Default" />
<updated>1538622047029</updated>
</task>
<servers />
</component>
<component name="ToolWindowManager">
<frame x="842" y="68" width="1194" height="987" extended-state="0" />
<editor active="true" />
<layout>
<window_info active="true" content_ui="combo" id="Project" order="0" visible="true" weight="0.28200692" />
<window_info id="Structure" order="1" weight="0.25" />
<window_info id="Favorites" order="2" side_tool="true" />
<window_info anchor="bottom" id="Message" order="0" />
<window_info anchor="bottom" id="Find" order="1" />
<window_info anchor="bottom" id="Run" order="2" visible="true" weight="0.4976526" />
<window_info anchor="bottom" id="Debug" order="3" weight="0.39952996" />
<window_info anchor="bottom" id="Cvs" order="4" weight="0.25" />
<window_info anchor="bottom" id="Inspection" order="5" weight="0.4" />
<window_info anchor="bottom" id="TODO" order="6" />
<window_info anchor="bottom" id="Version Control" order="7" weight="0.32983682" />
<window_info anchor="bottom" id="Terminal" order="8" weight="0.39976552" />
<window_info anchor="bottom" id="Event Log" order="9" side_tool="true" />
<window_info anchor="bottom" id="Python Console" order="10" />
<window_info anchor="right" id="Commander" order="0" weight="0.4" />
<window_info anchor="right" id="Ant Build" order="1" weight="0.25" />
<window_info anchor="right" content_ui="combo" id="Hierarchy" order="2" weight="0.25" />
</layout>
</component>
<component name="VcsContentAnnotationSettings">
<option name="myLimit" value="2678400000" />
</component>
<component name="XDebuggerManager">
<breakpoint-manager>
<default-breakpoints>
<breakpoint type="python-exception">
<properties notifyOnTerminate="true" exception="BaseException">
<option name="notifyOnTerminate" value="true" />
</properties>
</breakpoint>
</default-breakpoints>
</breakpoint-manager>
</component>
<component name="editorHistoryManager">
<entry file="file://$PROJECT_DIR$/use_camera.py" />
<entry file="file://$PROJECT_DIR$/patch" />
<entry file="file://$PROJECT_DIR$/README.md" />
<entry file="file://$PROJECT_DIR$/data/data_csvs_from_camera/person_1.csv" />
<entry file="file://$PROJECT_DIR$/data/data_csvs_from_camera/person_2.csv" />
<entry file="file://$PROJECT_DIR$/data/data_faces_from_camera/person_6/img_face_1.jpg" />
<entry file="file://$PROJECT_DIR$/test.py" />
<entry file="file://$PROJECT_DIR$/data/features_all.csv" />
<entry file="file://$PROJECT_DIR$/introduction/face_reco_single_person_custmize_name.png" />
<entry file="file://$PROJECT_DIR$/how_to_use_camera.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="513">
<caret line="27" column="13" selection-start-line="27" selection-start-column="13" selection-end-line="27" selection-end-column="13" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/get_features_into_CSV.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="83">
<caret line="16" lean-forward="true" selection-start-line="16" selection-end-line="16" />
<folding>
<element signature="e#491#501#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/get_faces_from_camera.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="67">
<caret line="12" column="34" selection-start-line="12" selection-start-column="34" selection-end-line="12" selection-end-column="34" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/face_reco_from_camera.py">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="127">
<caret line="89" column="22" lean-forward="true" selection-start-line="89" selection-start-column="22" selection-end-line="89" selection-end-column="22" />
<folding>
<element signature="e#230#264#0" expanded="true" />
</folding>
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/requirements.txt">
<provider selected="true" editor-type-id="text-editor">
<state relative-caret-position="76">
<caret line="4" column="16" lean-forward="true" selection-start-line="4" selection-start-column="16" selection-end-line="4" selection-end-column="16" />
</state>
</provider>
</entry>
<entry file="file://$PROJECT_DIR$/introduction/face_reco_two_people_in_database.png">
<provider selected="true" editor-type-id="images" />
</entry>
<entry file="file://$PROJECT_DIR$/README.rst">
<provider selected="true" editor-type-id="restructured-text-editor" />
</entry>
</component>
</project>

21
LICENSE
View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2018-2021 coneypo
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

105
README.md
View File

@ -1,105 +0,0 @@
# Face Recognition 项目说明
> 本项目使用`OpenCV`+`Dlib`+`Yolov4-tiny`+`Python` 实现2个主要功能:
> 1. 通过摄像头捕获人脸数据录入数据库,通过摄像头获取的实时视频流识别人脸
> 2. 通过获取的视频捕获人脸数据,和已经录入数据库的人脸数据进行比较,识别出对应的人脸
项目结构说明如下:
```shell
.
├── data
│   ├── data_faces # 人脸数据库或通过摄像头捕获人脸存放位置
├── face_check_from_data.py # 人脸对比
├── face_reco_from_camera_ot.py # 带OT人脸追踪的视频流人脸识别
├── face_reco_from_camera.py # 视频流人脸识别
├── face_reco_from_video.py # 监控视频下的人脸捕获
├── faces_output # 监控视频下的人脸捕获数据存放位置
│   ├── all_faces
│   ├── captured_faces
│   └── result_video.avi
├── features_extraction_to_csv.py # 支持DNN检测器的数据转码
├── features.py # 基础的数据转码
├── get_faces_dnn.py # 支持OpenCV DNN检测器的摄像头人脸获取
├── get_faces.py # 摄像头人脸获取
├── get_faces_UI.py # UI摄像头人脸获取
├── LICENSE
├── models # 模型存放位置
│   ├── deploy.prototxt
│   ├── dlib
│   │   ├── dlib_face_recognition_resnet_model_v1.dat
│   │   ├── mmod_human_face_detector.dat
│   │   └── shape_predictor_68_face_landmarks.dat
│   ├── face_embeddings.pkl
│   ├── face_model_trained.xml
│   ├── haar
│   │   ├── haarcascade_eye.xml
│   │   └── haarcascade_frontalface_default.xml
│   ├── opencv_face_detector.pbtxt
│   ├── opencv_face_detector_uint8.pb
│   ├── openface_nn4.small2.v1.t7
│   ├── res10_300x300_ssd_iter_140000.caffemodel
│   ├── yolov4-tiny.cfg
│   └── yolov4-tiny.weights
├── README.md
├── requirements.txt
├── shutil
└── simsun.ttc
```
## 识别实时视频流使用方法:
1. 安装依赖
```shell
pip install -r requirements.txt
```
2. 通过摄像头捕获人脸数据,或导入人脸数据,本项目提供三种方式获取人脸数据:
```shell
python3 get_faces.py # 基础的人脸捕获,流畅度一般
python3 get_faces_UI.py # 基础人脸捕获方式+UI支持
python3 get_faces_dnn.py # 使用OpenCV DNN人脸检测器,大幅提高视频流畅度
```
3. 转码,将捕获的人脸数据转码
```shell
python3 features.py # 使用基础人脸捕获方式和UI支持的转码方式
python3 features_extraction_to_csv.py # 使用DNN方式识别的转码方式
```
4. 人脸识别
```shell
python3 face_reco_from_camera.py # 人脸识别
python3 face_reco_from_camera_ot.py # 带OT追踪的人脸识别
```
## 识别监控视频的使用方法:
1. 安装依赖
```shell
pip install -r requirements.txt
```
2. 通过摄像头捕获人脸数据,或导入人脸数据,本项目提供三种方式获取人脸数据:
```shell
python3 get_faces.py # 基础的人脸捕获,流畅度一般
python3 get_faces_UI.py # 基础人脸捕获方式+UI支持
python3 get_faces_dnn.py # 使用OpenCV DNN人脸检测器,大幅提高视频流畅度
```
3. 监控视频中捕获人脸数据
```shell
python3 face_reco_from_video.py
```
4. 人脸对比
```shell
python3 face_check_from_data.py
```

140
README.rst Normal file
View File

@ -0,0 +1,140 @@
Face recognition from camera
############################
Introduction
************
Detect and recognize single/multi-faces from camera;
调用摄像头进行人脸识别,支持多张人脸同时识别;
#. 摄像头人脸录入 / Face register
.. image:: introduction/get_face_from_camera.png
:align: center
请不要离摄像头过近,人脸超出摄像头范围时会有 "OUT OF RANGE" 提醒 /
Please do not too close to the camera, or you can't save faces with "OUT OF RANGE" warning;
.. image:: introduction/get_face_from_camera_out_of_range.png
:align: center
#. 提取特征建立人脸数据库 / Generate database from images captured
#. 利用摄像头进行人脸识别 / Face recognizer
当单张人脸 / When single-face:
.. image:: introduction/face_reco_single_person.png
:align: center
当多张人脸 / When multi-faces:
一张已录入人脸 + 未录入 unknown 人脸:
.. image:: introduction/face_reco_two_people.png
:align: center
同时识别多张已录入人脸:
.. image:: introduction/face_reco_two_people_in_database.png
:align: center
** 关于精度 / About accuracy:
* When using a distance threshold of ``0.6``, the dlib model obtains an accuracy of ``99.38%`` on the standard LFW face recognition benchmark.
Overview
********
此项目中人脸识别的实现流程 / The design of this repo:
.. image:: introduction/overview.png
:align: center
Steps
*****
#. 下载源码 / Download from website or via GitHub Desktop in windows, or clone repo in Ubuntu
.. code-block:: bash
git clone https://github.com/coneypo/Dlib_face_recognition_from_camera
#. 进行 face register / 人脸信息采集录入
.. code-block:: bash
python3 get_face_from_camera.py
#. 提取所有录入人脸数据存入 features_all.csv
.. code-block:: bash
python3 get_features_into_CSV.py
#. 调用摄像头进行实时人脸识别
.. code-block:: bash
python3 face_reco_from_camera.py
About Source Code
*****************
Python 源码介绍如下:
#. get_face_from_camera.py:
进行 Face register / 人脸信息采集录入
* 请注意存储人脸图片时,矩形框不要超出摄像头范围,要不然无法保存到本地;
* 超出会有 "out of range" 的提醒;
#. get_features_into_CSV.py:
从上一步存下来的图像文件中,提取人脸数据存入CSV;
* 会生成一个存储所有特征人脸数据的 "features_all.csv";
* size: n*128 , n means n people you registered and 128 means 128D features of the face
#. face_reco_from_camera.py:
这一步将调用摄像头进行实时人脸识别; / This part will implement real-time face recognition;
* Compare the faces captured from camera with the faces you have registered which are saved in "features_all.csv"
* 将捕获到的人脸数据和之前存的人脸数据进行对比计算欧式距离, 由此判断是否是同一个人;
修改显示的人名 / If you want customize the names shown, please refer to this patch and modify the code: https://github.com/coneypo/Dlib_face_recognition_from_camera/commit/58466ce87bf3a42ac5ef855b791bf8c658d408df?diff=unified
.. image:: introduction/face_reco_single_person_customize_name.png
:align: center
More
****
Tips:
1. Windows下建议不要把代码放到 ``C:\``, 可能会出现权限读取问题
2. 代码最好不要有中文路径
3. 人脸录入的时候先建文件夹再保存图片, 先 ``N````S``
For more details, please refer to my blog (in chinese) or mail to me /
可以访问我的博客获取本项目的更详细介绍,如有问题可以邮件联系我:
* Blog: https://www.cnblogs.com/AdaminXie/p/9010298.html
* Mail: coneypo@foxmail.com
仅限于交流学习, 商业合作勿扰;
Thanks for your support.

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@ -1,810 +0,0 @@
# 使用yolov3-face模型人脸识别方法
import cv2
import numpy as np
import os
import glob
import json
from datetime import datetime
from collections import defaultdict
import shutil
import dlib
class UnifiedFaceRecognizer:
def __init__(self):
# 数据源路径
self.data_sources = {
'database': "./data/data_faces/person_mabinhao/",
'labeled': "./labeled_faces/",
'query': "./faces_output/all_faces/"
}
# 输出路径
self.output_folder = "./unified_face_results/"
self.features_folder = "./face_features/"
# 模型路径 - 更新为YOLOv3-face模型
self.model_paths = {
'yolov3_face_cfg': './models/yolov3-face.cfg',
'yolov3_face_weights': './models/yolov3-wider_16000.weights',
'face_recognizer': './models/openface_nn4.small2.v1.t7',
'shape_predictor': './models/shape_predictor_68_face_landmarks.dat'
}
# 人脸检测模型
self.yolov3_face_detector = None
self.face_recognizer = None
self.shape_predictor = None
# 识别阈值
self.recognition_threshold = 0.65
self.face_confidence_threshold = 0.8 # YOLOv3置信度阈值
# 人脸特征数据库
self.face_database = {}
self.feature_vectors = {}
# 创建必要的目录
self.create_directories()
# 加载模型
self.load_models()
def create_directories(self):
"""创建必要的目录"""
for folder in [self.output_folder, self.features_folder]:
os.makedirs(folder, exist_ok=True)
print(f"✅ 创建目录: {folder}")
def load_models(self):
"""加载YOLOv3人脸检测和人脸识别模型"""
print("加载人脸识别模型...")
# 加载YOLOv3-face人脸检测器
try:
if os.path.exists(self.model_paths['yolov3_face_cfg']) and \
os.path.exists(self.model_paths['yolov3_face_weights']):
self.yolov3_face_detector = cv2.dnn.readNetFromDarknet(
self.model_paths['yolov3_face_cfg'],
self.model_paths['yolov3_face_weights']
)
# 使用CPU确保兼容性
self.yolov3_face_detector.setPreferableBackend(cv2.dnn.DNN_BACKEND_OPENCV)
self.yolov3_face_detector.setPreferableTarget(cv2.dnn.DNN_TARGET_CPU)
print("✅ YOLOv3-face人脸检测器加载成功")
else:
print("⚠️ YOLOv3-face模型文件不存在,请下载 yolov3-face.cfg 和 yolov3-face.weights")
except Exception as e:
print(f"❌ YOLOv3-face人脸检测器加载失败: {e}")
# 加载dlib关键点检测器
try:
if os.path.exists(self.model_paths['shape_predictor']):
self.shape_predictor = dlib.shape_predictor(self.model_paths['shape_predictor'])
print("✅ dlib关键点检测器加载成功")
else:
print("⚠️ dlib关键点检测器模型文件不存在,请下载 shape_predictor_68_face_landmarks.dat")
except Exception as e:
print(f"❌ dlib关键点检测器加载失败: {e}")
# 加载人脸识别器
try:
if os.path.exists(self.model_paths['face_recognizer']):
self.face_recognizer = cv2.dnn.readNetFromTorch(
self.model_paths['face_recognizer']
)
print("✅ 人脸识别器加载成功")
else:
print("⚠️ 人脸识别器模型文件不存在")
except Exception as e:
print(f"❌ 人脸识别器加载失败: {e}")
def detect_faces_yolov3(self, image):
"""使用YOLOv3检测人脸"""
if image is None or image.size == 0:
return []
faces = []
h, w = image.shape[:2]
try:
# 准备输入blob
blob = cv2.dnn.blobFromImage(image, 1/255.0, (416, 416), (0, 0, 0), swapRB=True, crop=False)
self.yolov3_face_detector.setInput(blob)
# 获取输出层名称
layer_names = self.yolov3_face_detector.getLayerNames()
try:
# OpenCV 4.x
output_layers = [layer_names[i - 1] for i in self.yolov3_face_detector.getUnconnectedOutLayers()]
except:
# OpenCV 3.x
output_layers = [layer_names[i[0] - 1] for i in self.yolov3_face_detector.getUnconnectedOutLayers()]
# 前向传播
outputs = self.yolov3_face_detector.forward(output_layers)
boxes = []
confidences = []
for output in outputs:
for detection in output:
scores = detection[5:]
confidence = scores[0] # 人脸置信度
if confidence > self.face_confidence_threshold:
center_x = int(detection[0] * w)
center_y = int(detection[1] * h)
box_width = int(detection[2] * w)
box_height = int(detection[3] * h)
x = int(center_x - box_width / 2)
y = int(center_y - box_height / 2)
boxes.append([x, y, box_width, box_height])
confidences.append(float(confidence))
# 应用非极大值抑制
indices = cv2.dnn.NMSBoxes(boxes, confidences, self.face_confidence_threshold, 0.4)
if len(indices) > 0:
for i in indices.flatten():
x, y, box_w, box_h = boxes[i]
x1, y1, x2, y2 = x, y, x + box_w, y + box_h
# 确保坐标在图像范围内
x1, y1 = max(0, x1), max(0, y1)
x2, y2 = min(w, x2), min(h, y2)
# 检查人脸区域是否有效
if x2 > x1 and y2 > y1:
face_roi = image[y1:y2, x1:x2]
if face_roi.size > 0 and face_roi.shape[0] > 20 and face_roi.shape[1] > 20:
# 检测关键点
landmarks = self.detect_landmarks_dlib(image, (x1, y1, x2, y2))
# 扩展人脸区域以获得更大的图像
expanded_face = self.expand_face_region(image, (x1, y1, x2, y2))
faces.append({
'roi': expanded_face['image'],
'bbox': expanded_face['bbox'],
'original_bbox': (x1, y1, x2, y2),
'confidence': confidences[i],
'landmarks': landmarks,
'detector': 'yolov3'
})
except Exception as e:
print(f"YOLOv3人脸检测错误: {e}")
return faces
def expand_face_region(self, image, bbox, expansion_ratio=0.3):
"""
扩展人脸区域以获得更大的图像
"""
x1, y1, x2, y2 = bbox
width = x2 - x1
height = y2 - y1
# 计算扩展量
expand_w = int(width * expansion_ratio)
expand_h = int(height * expansion_ratio)
# 计算扩展后的边界框
new_x1 = max(0, x1 - expand_w)
new_y1 = max(0, y1 - expand_h)
new_x2 = min(image.shape[1], x2 + expand_w)
new_y2 = min(image.shape[0], y2 + expand_h)
# 提取扩展后的人脸区域
expanded_face = image[new_y1:new_y2, new_x1:new_x2]
return {
'bbox': (new_x1, new_y1, new_x2, new_y2),
'image': expanded_face
}
def detect_landmarks_dlib(self, image, bbox):
"""使用dlib检测68个人脸关键点"""
try:
# 转换为RGB格式
rgb_image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# 创建dlib矩形对象
x1, y1, x2, y2 = bbox
dlib_rect = dlib.rectangle(x1, y1, x2, y2)
# 预测关键点
shape = self.shape_predictor(rgb_image, dlib_rect)
# 提取68个关键点坐标
keypoints = []
for i in range(68):
point = shape.part(i)
keypoints.append((point.x, point.y))
# 组织关键点结构
landmarks = {
'jaw': keypoints[0:17],
'right_eyebrow': keypoints[17:22],
'left_eyebrow': keypoints[22:27],
'nose': keypoints[27:36],
'right_eye': keypoints[36:42],
'left_eye': keypoints[42:48],
'mouth': keypoints[48:68],
'all_points': keypoints,
# 重要特征点
'left_eye_center': self.get_eye_center(keypoints[36:42]),
'right_eye_center': self.get_eye_center(keypoints[42:48]),
'nose_tip': keypoints[30],
'mouth_left': keypoints[48],
'mouth_right': keypoints[54],
'mouth_center': keypoints[66]
}
return landmarks
except Exception as e:
print(f"dlib关键点检测错误: {e}")
return self.get_fallback_landmarks(bbox)
def get_eye_center(self, eye_points):
"""计算眼睛中心点"""
if not eye_points:
return (0, 0)
x_coords = [p[0] for p in eye_points]
y_coords = [p[1] for p in eye_points]
center_x = sum(x_coords) // len(x_coords)
center_y = sum(y_coords) // len(y_coords)
return (center_x, center_y)
def get_fallback_landmarks(self, bbox):
"""备用关键点检测(当dlib失败时使用)"""
x1, y1, x2, y2 = bbox
w, h = x2 - x1, y2 - y1
return {
'left_eye_center': (x1 + w//3, y1 + h//3),
'right_eye_center': (x1 + 2*w//3, y1 + h//3),
'nose_tip': (x1 + w//2, y1 + h//2),
'mouth_left': (x1 + w//3, y1 + 2*h//3),
'mouth_right': (x1 + 2*w//3, y1 + 2*h//3),
'mouth_center': (x1 + w//2, y1 + 2*h//3)
}
def detect_faces_ensemble(self, image):
"""多模型融合的人脸检测(主要使用YOLOv3)"""
if image is None or image.size == 0:
return []
all_faces = []
# 1. 使用YOLOv3检测(主要)
if self.yolov3_face_detector is not None:
try:
yolov3_faces = self.detect_faces_yolov3(image)
all_faces.extend(yolov3_faces)
print(f" YOLOv3检测到 {len(yolov3_faces)} 个人脸")
except Exception as e:
print(f"YOLOv3检测失败: {e}")
# 非极大值抑制去除重复检测
if all_faces:
return self.non_max_suppression(all_faces)
else:
return []
def non_max_suppression(self, faces, overlap_threshold=0.5):
"""非极大值抑制去除重复检测"""
if len(faces) == 0:
return []
# 按置信度排序
faces.sort(key=lambda x: x['confidence'], reverse=True)
pick = []
boxes = [face['bbox'] for face in faces]
x1 = np.array([box[0] for box in boxes])
y1 = np.array([box[1] for box in boxes])
x2 = np.array([box[2] for box in boxes])
y2 = np.array([box[3] for box in boxes])
area = (x2 - x1 + 1) * (y2 - y1 + 1)
idxs = np.arange(len(faces))
while len(idxs) > 0:
i = idxs[0]
pick.append(i)
xx1 = np.maximum(x1[i], x1[idxs[1:]])
yy1 = np.maximum(y1[i], y1[idxs[1:]])
xx2 = np.minimum(x2[i], x2[idxs[1:]])
yy2 = np.minimum(y2[i], y2[idxs[1:]])
w = np.maximum(0, xx2 - xx1 + 1)
h = np.maximum(0, yy2 - yy1 + 1)
overlap = (w * h) / area[idxs[1:]]
idxs = np.delete(idxs, np.concatenate(([0], np.where(overlap > overlap_threshold)[0] + 1)))
return [faces[i] for i in pick]
def process_single_image(self, image_path, person_name="unknown", save_aligned=False, save_landmarks=False):
"""处理单张图片:检测→对齐→特征提取"""
results = []
# 读取图片
image = cv2.imread(image_path)
if image is None:
print(f"❌ 无法读取图片: {image_path}")
return results
# 人脸检测(使用YOLOv3)
faces = self.detect_faces_ensemble(image)
if not faces:
print(f"❌ 未检测到人脸: {os.path.basename(image_path)}")
return results
print(f"✅ 检测到 {len(faces)} 个人脸: {os.path.basename(image_path)}")
for i, face in enumerate(faces):
try:
# 人脸对齐
aligned_face = self.align_face_procrustes(face['roi'], face['landmarks'])
# 特征提取
features = self.extract_features(aligned_face)
if features is not None:
result = {
'image_path': image_path,
'person_name': person_name,
'features': features,
'bbox': face['bbox'],
'confidence': face['confidence'],
'detector': face.get('detector', 'unknown'),
'landmarks_count': len(face['landmarks'].get('all_points', [])),
'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
}
results.append(result)
print(f" ✅ 人脸 {i+1}: 对齐和特征提取成功 ({result['landmarks_count']} 个关键点)")
else:
print(f" ❌ 人脸 {i+1}: 特征提取失败")
except Exception as e:
print(f" ❌ 人脸 {i+1} 处理失败: {e}")
return results
def align_face_procrustes(self, face_roi, landmarks):
"""使用Procrustes分析进行精确人脸对齐"""
try:
# 获取眼睛中心点
left_eye = landmarks['left_eye_center']
right_eye = landmarks['right_eye_center']
# 计算眼睛连线角度
dx = right_eye[0] - left_eye[0]
dy = right_eye[1] - left_eye[1]
angle = np.degrees(np.arctan2(dy, dx))
# 计算眼睛中心点
eyes_center = (
(left_eye[0] + right_eye[0]) // 2,
(left_eye[1] + right_eye[1]) // 2
)
# 计算缩放比例(基于眼睛距离)
eye_distance = np.sqrt(dx**2 + dy**2)
desired_eye_distance = 50 # 目标眼睛距离
scale = desired_eye_distance / eye_distance
# 旋转矩阵
rotation_matrix = cv2.getRotationMatrix2D(eyes_center, angle, scale)
# 调整平移分量,使眼睛中心位于图像中心
h, w = face_roi.shape[:2]
rotation_matrix[0, 2] += w * 0.5 - eyes_center[0]
rotation_matrix[1, 2] += h * 0.5 - eyes_center[1]
# 应用仿射变换
aligned_face = cv2.warpAffine(face_roi, rotation_matrix, (w, h),
flags=cv2.INTER_CUBIC)
return aligned_face
except Exception as e:
print(f"Procrustes对齐失败: {e}")
return self.align_face_simple(face_roi, landmarks)
def align_face_simple(self, face_roi, landmarks):
"""简化版人脸对齐"""
try:
left_eye = landmarks['left_eye_center']
right_eye = landmarks['right_eye_center']
# 计算眼睛连线角度
dx = right_eye[0] - left_eye[0]
dy = right_eye[1] - left_eye[1]
angle = np.degrees(np.arctan2(dy, dx))
# 计算旋转中心
h, w = face_roi.shape[:2]
center = (w // 2, h // 2)
# 旋转矩阵
rotation_matrix = cv2.getRotationMatrix2D(center, angle, 1.0)
# 应用旋转
aligned_face = cv2.warpAffine(face_roi, rotation_matrix, (w, h),
flags=cv2.INTER_CUBIC)
return aligned_face
except Exception as e:
print(f"简化对齐失败: {e}")
return face_roi
def extract_features(self, aligned_face):
"""提取人脸特征"""
if aligned_face is None or aligned_face.size == 0:
return None
try:
# 调整尺寸为模型输入大小
input_size = (96, 96)
resized_face = cv2.resize(aligned_face, input_size)
# 预处理
blob = cv2.dnn.blobFromImage(
resized_face,
1.0 / 255,
input_size,
(0, 0, 0),
swapRB=True,
crop=False
)
# 前向传播获取特征
self.face_recognizer.setInput(blob)
vec = self.face_recognizer.forward()
# 归一化特征向量
embedding = vec.flatten()
norm = np.linalg.norm(embedding)
if norm > 0:
embedding = embedding / norm
return embedding
else:
return None
except Exception as e:
print(f"特征提取失败: {e}")
return None
def draw_landmarks(self, image, landmarks, color=(0, 255, 0), radius=2):
"""在图像上绘制关键点"""
try:
for point in landmarks.get('all_points', []):
x, y = int(point[0]), int(point[1])
cv2.circle(image, (x, y), radius, color, -1)
# 绘制眼睛中心
left_eye = landmarks.get('left_eye_center')
right_eye = landmarks.get('right_eye_center')
if left_eye:
cv2.circle(image, (int(left_eye[0]), int(left_eye[1])), radius, (255, 0, 0), -1)
if right_eye:
cv2.circle(image, (int(right_eye[0]), int(right_eye[1])), radius, (255, 0, 0), -1)
except Exception as e:
print(f"绘制关键点失败: {e}")
def scan_data_source(self, source_name, source_path):
"""扫描数据源并处理所有图片"""
print(f"\n扫描数据源: {source_name} -> {source_path}")
if not os.path.exists(source_path):
print(f"❌ 数据源不存在: {source_path}")
return []
all_results = []
# 根据数据源类型采用不同的处理方式
if source_name == 'labeled':
# labeled_faces: 按人物分目录
person_dirs = [d for d in os.listdir(source_path)
if os.path.isdir(os.path.join(source_path, d))]
for person_dir in person_dirs:
person_path = os.path.join(source_path, person_dir)
image_files = self.find_image_files(person_path)
print(f"处理标注人物: {person_dir} ({len(image_files)} 张图片)")
for image_file in image_files:
results = self.process_single_image(image_file, person_dir, save_aligned=True)
all_results.extend(results)
elif source_name == 'database':
# database: 图片文件名即人物名
image_files = self.find_image_files(source_path)
# 按人物分组
person_images = defaultdict(list)
for image_file in image_files:
person_name = os.path.splitext(os.path.basename(image_file))[0]
person_images[person_name].append(image_file)
for person_name, images in person_images.items():
print(f"处理数据库人物: {person_name} ({len(images)} 张图片)")
for image_file in images:
results = self.process_single_image(image_file, person_name, save_aligned=True)
all_results.extend(results)
elif source_name == 'query':
# query: 未知人物,需要识别
image_files = self.find_image_files(source_path)
print(f"处理查询图片: {len(image_files)}")
for image_file in image_files:
results = self.process_single_image(image_file, "unknown", save_aligned=True)
all_results.extend(results)
print(f"{source_name} 处理完成: {len(all_results)} 个人脸结果")
return all_results
def find_image_files(self, folder_path):
"""查找文件夹中的所有图片文件"""
image_extensions = ['*.jpg', '*.jpeg', '*.png', '*.bmp', '*.tiff']
image_files = []
for extension in image_extensions:
pattern = os.path.join(folder_path, extension)
image_files.extend(glob.glob(pattern))
pattern = os.path.join(folder_path, extension.upper())
image_files.extend(glob.glob(pattern))
return sorted(list(set(image_files)))
def build_face_database(self):
"""构建人脸特征数据库"""
print("\n构建人脸特征数据库...")
# 处理数据库和标注数据来构建数据库
database_results = self.scan_data_source('database', self.data_sources['database'])
labeled_results = self.scan_data_source('labeled', self.data_sources['labeled'])
# 合并结果
known_results = database_results + labeled_results
# 构建特征数据库
self.face_database = {}
self.feature_vectors = {}
for result in known_results:
person_name = result['person_name']
features = result['features']
if person_name not in self.face_database:
self.face_database[person_name] = {
'features_list': [features],
'sample_count': 1,
'image_paths': [result['image_path']]
}
self.feature_vectors[person_name] = features
else:
self.face_database[person_name]['features_list'].append(features)
self.face_database[person_name]['sample_count'] += 1
self.face_database[person_name]['image_paths'].append(result['image_path'])
# 更新特征向量(平均特征)
old_features = self.feature_vectors[person_name]
new_features = (old_features + features) / 2
self.feature_vectors[person_name] = new_features
print(f"✅ 人脸数据库构建完成: {len(self.face_database)} 个已知人物")
return len(self.face_database)
def recognize_face(self, query_features):
"""识别人脸身份"""
if not self.feature_vectors or query_features is None:
return "unknown", 1.0, None
best_name = "unknown"
best_distance = float('inf')
best_similarity = 0.0
for person_name, db_features in self.feature_vectors.items():
# 计算余弦相似度
similarity = np.dot(query_features, db_features)
distance = 1 - similarity
if distance < best_distance:
best_distance = distance
best_similarity = similarity
best_name = person_name
# 判断是否识别成功
if best_similarity > self.recognition_threshold:
return best_name, best_similarity, best_distance
else:
return "unknown", best_similarity, best_distance
def process_all_sources(self):
"""处理所有数据源并进行统一识别"""
print("开始统一处理所有数据源...")
# 1. 构建人脸数据库
if not self.build_face_database():
print("❌ 人脸数据库构建失败")
return None
# 2. 处理查询图片并进行识别
query_results = self.scan_data_source('query', self.data_sources['query'])
# 3. 对查询结果进行识别
recognition_results = []
for result in query_results:
query_image = result['image_path']
query_features = result['features']
recognized_name, similarity, distance = self.recognize_face(query_features)
recognition_result = {
**result,
'recognized_name': recognized_name,
'similarity': similarity,
'distance': distance,
'is_recognized': recognized_name != "unknown"
}
recognition_results.append(recognition_result)
status = "" if recognized_name != "unknown" else ""
print(f"{status} 被识别照片: {query_image} 识别结果: {recognized_name} (相似度: {similarity:.3f})")
# 4. 生成报告
self.generate_comprehensive_report(recognition_results)
return recognition_results
def generate_comprehensive_report(self, results):
"""生成综合报告"""
try:
report_path = os.path.join(self.output_folder, "unified_face_recognition_report.txt")
total_faces = len(results)
recognized_faces = len([r for r in results if r['is_recognized']])
with open(report_path, 'w', encoding='utf-8') as f:
f.write("统一人脸识别系统报告 (使用YOLOv3-face)\n")
f.write("=" * 60 + "\n")
f.write(f"处理时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
f.write(f"识别阈值: {self.recognition_threshold}\n")
f.write(f"检测阈值: {self.face_confidence_threshold}\n\n")
# 数据库信息
f.write("人脸数据库信息:\n")
f.write("-" * 30 + "\n")
f.write(f"已知人物数量: {len(self.face_database)}\n")
for person_name, info in self.face_database.items():
f.write(f" - {person_name}: {info['sample_count']} 个样本\n")
f.write("\n")
# 识别统计
f.write("识别结果统计:\n")
f.write("-" * 30 + "\n")
f.write(f"总检测人脸: {total_faces}\n")
f.write(f"成功识别: {recognized_faces}\n")
f.write(f"识别率: {recognized_faces/max(total_faces,1)*100:.1f}%\n\n")
# 详细结果
f.write("详细识别结果:\n")
f.write("-" * 40 + "\n")
for result in results:
status = "成功" if result['is_recognized'] else "失败"
f.write(f"图片: {os.path.basename(result['image_path'])}\n")
f.write(f" 原始标签: {result['person_name']}\n")
f.write(f" 识别结果: {result['recognized_name']} ({status})\n")
f.write(f" 相似度: {result['similarity']:.3f}\n")
f.write(f" 距离: {result['distance']:.3f}\n")
f.write(f" 检测置信度: {result['confidence']:.3f}\n\n")
print(f"✅ 综合报告已保存: {report_path}")
print(f"识别统计: {recognized_faces}/{total_faces} ({recognized_faces/max(total_faces,1)*100:.1f}%)")
except Exception as e:
print(f"生成报告失败: {e}")
def interactive_mode(self):
while True:
print("\n" + "="*50)
print("统一人脸识别系统 - 交互模式 (使用YOLOv3)")
print("="*50)
print("1. 处理所有数据源并识别")
print("2. 仅构建人脸数据库")
print("3. 仅处理查询图片")
print("4. 查看当前数据库")
print("5. 重新识别查询图片")
print("6. 退出")
print("-"*50)
choice = input("请选择操作 (1-6): ").strip()
if choice == '1':
self.process_all_sources()
elif choice == '2':
self.build_face_database()
self.show_database_info()
elif choice == '3':
query_results = self.scan_data_source('query', self.data_sources['query'])
print(f"处理完成: {len(query_results)} 个人脸")
elif choice == '4':
self.show_database_info()
elif choice == '5':
if not self.face_database:
print("❌ 请先构建人脸数据库")
else:
query_results = self.scan_data_source('query', self.data_sources['query'])
elif choice == '6':
print("再见!")
break
else:
print("无效选择,请重新输入!")
def show_database_info(self):
"""显示数据库信息"""
if not self.face_database:
print("❌ 数据库为空")
return
print(f"\n当前人脸数据库包含 {len(self.face_database)} 个已知人物:")
print("-" * 40)
for i, (person_name, info) in enumerate(self.face_database.items(), 1):
print(f"{i:2d}. {person_name}: {info['sample_count']} 个样本")
print("-" * 40)
def run(self):
"""运行统一人脸识别系统"""
print("=== 统一人脸识别系统启动 (使用YOLOv3-face) ===")
print("将对三个数据源进行统一处理:")
print(" 1. data/data_faces/ - 已知人物数据库")
print(" 2. labeled_faces/ - 手动标注数据")
print(" 3. all_faces/ - 待识别查询数据")
print("="*60)
# 检查模型
if self.yolov3_face_detector is None or self.face_recognizer is None:
print("❌ 模型加载失败,无法继续")
return False
self.interactive_mode()
def main():
# 创建识别器实例
recognizer = UnifiedFaceRecognizer()
# 运行识别系统
success = recognizer.run()
if success:
print("\n🎉 人脸识别任务完成!")
print(f"结果保存在: {recognizer.output_folder}")
else:
print("\n❌ 人脸识别任务失败!")
if __name__ == '__main__':
main()

View File

@ -1,425 +0,0 @@
import cv2
import numpy as np
import os
import pickle
import glob
from datetime import datetime
import logging
from collections import defaultdict
# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
class SimpleFaceComparator:
def __init__(self):
# 模型路径
self.face_recognizer_model = './models/openface_nn4.small2.v1.t7'
# 文件夹路径
self.database_folder = './data/data_faces/person_mabinhao/' # 数据库人脸文件夹
self.query_folder = './faces_output/all_faces/' # 待识别人脸文件夹
# 输出路径
self.output_folder = './comparison_results/'
self.embeddings_file = './models/face_embeddings.pkl'
# 阈值设置
self.recognition_threshold = 0.4 # 人脸识别阈值
# 加载模型
self.load_models()
# 加载数据库
self.face_database = self.load_or_create_database()
def load_models(self):
"""加载人脸识别模型"""
try:
self.face_recognizer = cv2.dnn.readNetFromTorch(self.face_recognizer_model)
# 尝试使用GPU加速
if cv2.cuda.getCudaEnabledDeviceCount() > 0:
self.face_recognizer.setPreferableBackend(cv2.dnn.DNN_BACKEND_CUDA)
self.face_recognizer.setPreferableTarget(cv2.dnn.DNN_TARGET_CUDA)
print("✅ 使用CUDA加速")
else:
print("✅ 使用CPU")
print("✅ 模型加载成功")
except Exception as e:
print(f"❌ 模型加载失败: {e}")
raise
def extract_face_embedding(self, image):
"""提取人脸特征向量"""
if image is None or image.size == 0:
return None
# 调整大小并预处理
face_blob = cv2.dnn.blobFromImage(
image,
1.0 / 255,
(96, 96),
(0, 0, 0),
swapRB=True,
crop=False
)
# 提取特征
self.face_recognizer.setInput(face_blob)
vec = self.face_recognizer.forward()
# 归一化
embedding = vec.flatten()
embedding /= np.linalg.norm(embedding)
return embedding
def load_or_create_database(self):
"""加载或创建人脸数据库"""
if os.path.exists(self.embeddings_file):
print("加载已有的人脸数据库...")
with open(self.embeddings_file, 'rb') as f:
database = pickle.load(f)
print(f"✅ 数据库加载成功: {len(database)} 个人物")
return database
else:
print("创建新的人脸数据库")
return self.build_database_from_folder()
def build_database_from_folder(self):
"""从数据库文件夹构建人脸数据库"""
print(f"从文件夹构建数据库: {self.database_folder}")
if not os.path.exists(self.database_folder):
os.makedirs(self.database_folder)
print(f"✅ 创建数据库文件夹: {self.database_folder}")
return {}
database = {}
# 获取所有图片文件
image_files = glob.glob(os.path.join(self.database_folder, "*.jpg")) + \
glob.glob(os.path.join(self.database_folder, "*.png")) + \
glob.glob(os.path.join(self.database_folder, "*.jpeg"))
if not image_files:
print("❌ 数据库文件夹中没有图片")
return database
# 按人物名称分组(文件名作为人物名)
person_images = defaultdict(list)
for image_file in image_files:
# 从文件名提取人物名字(去掉扩展名)
person_name = os.path.splitext(os.path.basename(image_file))[0]
person_images[person_name].append(image_file)
# 为每个人物提取特征
for person_name, image_paths in person_images.items():
print(f"处理数据库人物: {person_name} ({len(image_paths)} 张图片)")
embeddings = []
valid_count = 0
for image_path in image_paths:
image = cv2.imread(image_path)
if image is None:
print(f" ❌ 无法读取图片: {image_path}")
continue
# 提取特征
embedding = self.extract_face_embedding(image)
if embedding is not None:
embeddings.append(embedding)
valid_count += 1
print(f" ✅ 成功提取特征: {os.path.basename(image_path)}")
else:
print(f" ❌ 特征提取失败: {os.path.basename(image_path)}")
if embeddings:
# 计算平均特征向量
avg_embedding = np.mean(embeddings, axis=0)
avg_embedding /= np.linalg.norm(avg_embedding)
# 保存到数据库
database[person_name] = {
'embedding': avg_embedding,
'samples': len(embeddings),
'image_count': len(image_paths),
'image_paths': image_paths,
'registered_time': datetime.now().strftime("%Y-%m-%d %H:%M:%S")
}
print(f"{person_name} 注册成功! 使用 {valid_count}/{len(image_paths)} 张有效样本")
else:
print(f"{person_name} 注册失败,无有效特征")
# 保存数据库
if database:
self.save_database(database)
return database
def save_database(self, database=None):
"""保存人脸数据库"""
if database is None:
database = self.face_database
with open(self.embeddings_file, 'wb') as f:
pickle.dump(database, f)
print(f"✅ 数据库已保存: {len(database)} 个人物")
def recognize_face(self, embedding):
"""识别人脸"""
if not self.face_database:
return "未知", 1.0, None
best_name = "未知"
best_distance = float('inf')
best_person_data = None
for name, data in self.face_database.items():
db_embedding = data['embedding']
# 计算余弦距离
distance = 1 - np.dot(embedding, db_embedding)
if distance < best_distance:
best_distance = distance
best_name = name
best_person_data = data
# 检查是否超过阈值
if best_distance > self.recognition_threshold:
return "未知", best_distance, None
else:
return best_name, best_distance, best_person_data
def compare_faces(self):
"""对比两个文件夹中的人脸"""
print(f"\n开始人脸对比检测...")
print(f"数据库文件夹: {self.database_folder}")
print(f"查询文件夹: {self.query_folder}")
# 检查文件夹是否存在
if not os.path.exists(self.query_folder):
print(f"❌ 查询文件夹不存在: {self.query_folder}")
return None
if not self.face_database:
print("❌ 数据库为空,无法进行对比")
return None
# 获取查询文件夹中的所有图片
query_files = glob.glob(os.path.join(self.query_folder, "*.jpg")) + \
glob.glob(os.path.join(self.query_folder, "*.png")) + \
glob.glob(os.path.join(self.query_folder, "*.jpeg"))
if not query_files:
print(f"❌ 查询文件夹中没有图片: {self.query_folder}")
return None
print(f"找到 {len(query_files)} 张待识别人脸图片")
# 创建输出文件夹
if not os.path.exists(self.output_folder):
os.makedirs(self.output_folder)
all_results = []
# 处理每张查询图片
for query_file in query_files:
print(f"\n处理查询图片: {os.path.basename(query_file)}")
# 读取图片
query_image = cv2.imread(query_file)
if query_image is None:
print(f" ❌ 无法读取图片: {query_file}")
continue
# 提取特征
query_embedding = self.extract_face_embedding(query_image)
if query_embedding is None:
print(f" ❌ 无法提取人脸特征: {os.path.basename(query_file)}")
continue
# 识别
name, distance, person_data = self.recognize_face(query_embedding)
recognition_confidence = max(0, 1 - distance)
# 保存结果
result = {
'query_file': os.path.basename(query_file),
'query_path': query_file,
'recognized_name': name,
'recognition_confidence': recognition_confidence,
'distance': distance,
'person_data': person_data,
'timestamp': datetime.now().strftime("%Y-%m-%d %H:%M:%S")
}
all_results.append(result)
# 输出结果
status = "" if name != "未知" else ""
print(f" {status} 识别结果: {name} (置信度: {recognition_confidence:.3f})")
# 可视化结果
self.visualize_comparison(result, query_image)
# 生成报告
if all_results:
self.generate_comparison_report(all_results)
return all_results
def visualize_comparison(self, result, query_image):
"""可视化对比结果"""
output_image = query_image.copy()
# 获取图片基本信息
height, width = output_image.shape[:2]
query_filename = result['query_file']
recognized_name = result['recognized_name']
confidence = result['recognition_confidence']
# 设置颜色
color = (0, 255, 0) if recognized_name != "未知" else (0, 0, 255)
# 添加边框
cv2.rectangle(output_image, (0, 0), (width, height), color, 8)
# 添加识别结果文本
result_text = f"{recognized_name} ({confidence:.3f})"
cv2.putText(output_image, result_text, (20, 40),
cv2.FONT_HERSHEY_SIMPLEX, 1.2, color, 3)
# 添加文件名
cv2.putText(output_image, f"File: {query_filename}", (20, height - 20),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2)
# 如果有匹配的数据库人物,显示数据库信息
if result['person_data']:
person_data = result['person_data']
sample_text = f"Samples: {person_data['samples']}"
cv2.putText(output_image, sample_text, (20, 80),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
# 保存结果图片
output_filename = f"result_{recognized_name}_{confidence:.3f}_{query_filename}"
output_path = os.path.join(self.output_folder, output_filename)
cv2.imwrite(output_path, output_image)
print(f" 💾 结果图片已保存: {output_filename}")
def generate_comparison_report(self, results):
"""生成对比报告"""
report_path = os.path.join(self.output_folder, "face_comparison_report.txt")
# 统计结果
total_queries = len(results)
recognized_queries = [r for r in results if r['recognized_name'] != "未知"]
unrecognized_queries = [r for r in results if r['recognized_name'] == "未知"]
# 按人物统计
person_stats = defaultdict(lambda: {'count': 0, 'total_confidence': 0, 'files': []})
for result in recognized_queries:
name = result['recognized_name']
person_stats[name]['count'] += 1
person_stats[name]['total_confidence'] += result['recognition_confidence']
person_stats[name]['files'].append(result['query_file'])
# 计算平均置信度
for name in person_stats:
person_stats[name]['avg_confidence'] = person_stats[name]['total_confidence'] / person_stats[name]['count']
# 写入报告
with open(report_path, 'w', encoding='utf-8') as f:
f.write("人脸对比检测报告\n")
f.write("=" * 60 + "\n\n")
f.write(f"对比时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
f.write(f"数据库文件夹: {self.database_folder}\n")
f.write(f"查询文件夹: {self.query_folder}\n\n")
f.write("统计摘要:\n")
f.write("-" * 40 + "\n")
f.write(f"总查询图片数: {total_queries}\n")
f.write(f"成功识别: {len(recognized_queries)}\n")
f.write(f"识别失败: {len(unrecognized_queries)}\n")
f.write(f"识别成功率: {len(recognized_queries)/max(total_queries,1)*100:.1f}%\n\n")
f.write("识别出的人物统计:\n")
f.write("-" * 40 + "\n")
for name, stats in sorted(person_stats.items(), key=lambda x: x[1]['count'], reverse=True):
f.write(f"{name}:\n")
f.write(f" 出现次数: {stats['count']}\n")
f.write(f" 平均置信度: {stats['avg_confidence']:.3f}\n")
f.write(f" 出现的文件: {', '.join(stats['files'][:5])}")
if len(stats['files']) > 5:
f.write(f" ... (共{len(stats['files'])}个文件)")
f.write("\n\n")
f.write("详细识别记录:\n")
f.write("-" * 40 + "\n")
for result in results:
status = "成功" if result['recognized_name'] != "未知" else "失败"
f.write(f"文件: {result['query_file']}\n")
f.write(f" 识别结果: {result['recognized_name']} ({status})\n")
f.write(f" 置信度: {result['recognition_confidence']:.3f}\n")
f.write(f" 处理时间: {result['timestamp']}\n\n")
f.write("数据库信息:\n")
f.write("-" * 40 + "\n")
for name, data in self.face_database.items():
f.write(f"{name}: {data['samples']}个样本, {data['image_count']}张图片\n")
print(f"✅ 对比报告已保存: {report_path}")
# 控制台输出摘要
print(f"\n对比结果摘要:")
print(f"总查询图片: {total_queries}")
print(f"成功识别: {len(recognized_queries)}")
print(f"识别失败: {len(unrecognized_queries)}")
print(f"成功率: {len(recognized_queries)/max(total_queries,1)*100:.1f}%")
if person_stats:
print("\n识别出的人物:")
for name, stats in sorted(person_stats.items(), key=lambda x: x[1]['count'], reverse=True):
print(f" {name}: {stats['count']}次 (平均置信度: {stats['avg_confidence']:.3f})")
def main():
"""主函数"""
# 初始化对比器
comparator = SimpleFaceComparator()
while True:
print("\n简单人脸对比检测系统")
print("1. 开始人脸对比")
print("2. 重新构建数据库")
print("3. 退出")
choice = input("请选择操作 (1-5): ").strip()
if choice == '1':
# 开始对比
print("开始人脸对比...")
comparator.compare_faces()
elif choice == '2':
# 重新构建数据库
print("重新构建数据库...")
comparator.face_database = comparator.build_database_from_folder()
elif choice == '3':
print("再见!")
break
else:
print("无效选择,请重新输入!")
if __name__ == "__main__":
main()

View File

@ -1,857 +0,0 @@
# 使用OpenCV DNN 人脸识别方法
import cv2
import numpy as np
import os
import glob
import json
from datetime import datetime
from collections import defaultdict
import shutil
import dlib
class UnifiedFaceRecognizer:
def __init__(self):
# 数据源路径
self.data_sources = {
'database': "./data/data_faces/person_mabinhao/",
'labeled': "./labeled_faces/",
'query': "./faces_output/all_faces/"
}
# 输出路径
self.output_folder = "./unified_face_results/"
self.features_folder = "./face_features/"
# 模型路径
self.model_paths = {
'face_detector': './models/opencv_face_detector_uint8.pb',
'face_config': './models/opencv_face_detector.pbtxt',
'face_recognizer': './models/openface_nn4.small2.v1.t7',
'shape_predictor': './models/shape_predictor_68_face_landmarks.dat'
}
# 人脸检测模型
self.face_detector = None
self.face_recognizer = None
self.shape_predictor = None
self.dlib_detector = None
# 识别阈值
self.recognition_threshold = 0.65
self.face_confidence_threshold = 0.9
# 人脸特征数据库
self.face_database = {}
self.feature_vectors = {}
# 创建必要的目录
self.create_directories()
# 加载模型
self.load_models()
def create_directories(self):
"""创建必要的目录"""
for folder in [self.output_folder, self.features_folder]:
os.makedirs(folder, exist_ok=True)
print(f"✅ 创建目录: {folder}")
def load_models(self):
"""加载人脸检测、关键点检测和识别模型"""
print("加载人脸识别模型...")
# 加载OpenCV人脸检测器
try:
if os.path.exists(self.model_paths['face_detector']) and \
os.path.exists(self.model_paths['face_config']):
self.face_detector = cv2.dnn.readNetFromTensorflow(
self.model_paths['face_detector'],
self.model_paths['face_config']
)
print("✅ OpenCV人脸检测器加载成功")
else:
print("⚠️ OpenCV人脸检测器模型文件不存在")
except Exception as e:
print(f"❌ OpenCV人脸检测器加载失败: {e}")
# 加载dlib人脸检测器和关键点检测器
try:
# dlib人脸检测器
self.dlib_detector = dlib.get_frontal_face_detector()
print("✅ dlib人脸检测器加载成功")
# dlib关键点检测器
if os.path.exists(self.model_paths['shape_predictor']):
self.shape_predictor = dlib.shape_predictor(self.model_paths['shape_predictor'])
print("✅ dlib关键点检测器加载成功")
else:
print("⚠️ dlib关键点检测器模型文件不存在,请下载 shape_predictor_68_face_landmarks.dat")
except Exception as e:
print(f"❌ dlib模型加载失败: {e}")
# 加载人脸识别器
try:
if os.path.exists(self.model_paths['face_recognizer']):
self.face_recognizer = cv2.dnn.readNetFromTorch(
self.model_paths['face_recognizer']
)
print("✅ 人脸识别器加载成功")
else:
print("⚠️ 人脸识别器模型文件不存在")
except Exception as e:
print(f"❌ 人脸识别器加载失败: {e}")
def detect_faces_dlib(self, image):
"""使用dlib检测人脸和关键点"""
if image is None or image.size == 0:
return []
faces = []
try:
# 转换为RGB格式(dlib需要)
rgb_image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# 使用dlib检测人脸
detections = self.dlib_detector(rgb_image, 1) # 1表示上采样一次
for detection in detections:
# 获取人脸边界框
x1, y1, x2, y2 = detection.left(), detection.top(), detection.right(), detection.bottom()
# 确保坐标在图像范围内
h, w = image.shape[:2]
x1, y1 = max(0, x1), max(0, y1)
x2, y2 = min(w, x2), min(h, y2)
# 检查人脸区域是否有效
if x2 > x1 and y2 > y1:
face_roi = image[y1:y2, x1:x2]
if face_roi.size > 0 and face_roi.shape[0] > 20 and face_roi.shape[1] > 20:
# 检测关键点
landmarks = self.detect_landmarks_dlib(rgb_image, detection)
faces.append({
'roi': face_roi,
'bbox': (x1, y1, x2, y2),
'confidence': 0.9, # dlib没有置信度分数,给一个默认值
'landmarks': landmarks,
'detector': 'dlib'
})
except Exception as e:
print(f"dlib人脸检测错误: {e}")
return faces
def detect_landmarks_dlib(self, rgb_image, face_detection):
"""使用dlib检测68个人脸关键点"""
try:
# 预测关键点
shape = self.shape_predictor(rgb_image, face_detection)
# 提取68个关键点坐标
landmarks = {}
keypoints = []
for i in range(68):
point = shape.part(i)
keypoints.append((point.x, point.y))
# 组织关键点结构
landmarks = {
'jaw': keypoints[0:17], # 0-16: 下巴轮廓
'right_eyebrow': keypoints[17:22], # 17-21: 右眉毛
'left_eyebrow': keypoints[22:27], # 22-26: 左眉毛
'nose': keypoints[27:36], # 27-35: 鼻子
'right_eye': keypoints[36:42], # 36-41: 右眼
'left_eye': keypoints[42:48], # 42-47: 左眼
'mouth': keypoints[48:68], # 48-67: 嘴巴
'all_points': keypoints, # 所有68个点
# 重要特征点
'left_eye_center': self.get_eye_center(keypoints[36:42]),
'right_eye_center': self.get_eye_center(keypoints[42:48]),
'nose_tip': keypoints[30],
'mouth_left': keypoints[48],
'mouth_right': keypoints[54],
'mouth_center': keypoints[66]
}
return landmarks
except Exception as e:
print(f"dlib关键点检测错误: {e}")
return self.get_fallback_landmarks(face_detection)
def get_eye_center(self, eye_points):
"""计算眼睛中心点"""
if not eye_points:
return (0, 0)
x_coords = [p[0] for p in eye_points]
y_coords = [p[1] for p in eye_points]
center_x = sum(x_coords) // len(x_coords)
center_y = sum(y_coords) // len(y_coords)
return (center_x, center_y)
def get_fallback_landmarks(self, face_detection):
"""备用关键点检测(当dlib失败时使用)"""
x1, y1 = face_detection.left(), face_detection.top()
x2, y2 = face_detection.right(), face_detection.bottom()
w, h = x2 - x1, y2 - y1
return {
'left_eye_center': (x1 + w//3, y1 + h//3),
'right_eye_center': (x1 + 2*w//3, y1 + h//3),
'nose_tip': (x1 + w//2, y1 + h//2),
'mouth_left': (x1 + w//3, y1 + 2*h//3),
'mouth_right': (x1 + 2*w//3, y1 + 2*h//3),
'mouth_center': (x1 + w//2, y1 + 2*h//3)
}
def detect_faces_ensemble(self, image):
"""多模型融合的人脸检测"""
if image is None or image.size == 0:
return []
all_faces = []
# 1. 使用dlib检测(主要)
if self.dlib_detector is not None:
try:
dlib_faces = self.detect_faces_dlib(image)
all_faces.extend(dlib_faces)
print(f" dlib检测到 {len(dlib_faces)} 个人脸")
except Exception as e:
print(f"dlib检测失败: {e}")
# 2. 使用OpenCV DNN检测(备用)
if self.face_detector is not None and len(all_faces) == 0:
try:
opencv_faces = self.detect_faces_opencv(image)
all_faces.extend(opencv_faces)
print(f" OpenCV检测到 {len(opencv_faces)} 个人脸")
except Exception as e:
print(f"OpenCV检测失败: {e}")
# 非极大值抑制去除重复检测
if all_faces:
return self.non_max_suppression(all_faces)
else:
return []
def detect_faces_opencv(self, image):
"""OpenCV DNN人脸检测"""
faces = []
h, w = image.shape[:2]
try:
blob = cv2.dnn.blobFromImage(image, 1.0, (300, 300), [104, 117, 123])
self.face_detector.setInput(blob)
detections = self.face_detector.forward()
for i in range(detections.shape[2]):
confidence = detections[0, 0, i, 2]
if confidence > self.face_confidence_threshold:
x1 = int(detections[0, 0, i, 3] * w)
y1 = int(detections[0, 0, i, 4] * h)
x2 = int(detections[0, 0, i, 5] * w)
y2 = int(detections[0, 0, i, 6] * h)
# 确保坐标在图像范围内
x1, y1 = max(0, x1), max(0, y1)
x2, y2 = min(w, x2), min(h, y2)
# 检查人脸区域是否有效
if x2 > x1 and y2 > y1:
face_roi = image[y1:y2, x1:x2]
if face_roi.size > 0 and face_roi.shape[0] > 20 and face_roi.shape[1] > 20:
# 对于OpenCV检测,使用简化关键点
landmarks = self.get_simplified_landmarks(x1, y1, x2, y2)
faces.append({
'roi': face_roi,
'bbox': (x1, y1, x2, y2),
'confidence': confidence,
'landmarks': landmarks,
'detector': 'opencv_dnn'
})
except Exception as e:
print(f"OpenCV DNN检测错误: {e}")
return faces
def get_simplified_landmarks(self, x1, y1, x2, y2):
"""为OpenCV检测的人脸生成简化关键点"""
w, h = x2 - x1, y2 - y1
return {
'left_eye_center': (x1 + w//3, y1 + h//3),
'right_eye_center': (x1 + 2*w//3, y1 + h//3),
'nose_tip': (x1 + w//2, y1 + h//2),
'mouth_left': (x1 + w//3, y1 + 2*h//3),
'mouth_right': (x1 + 2*w//3, y1 + 2*h//3),
'mouth_center': (x1 + w//2, y1 + 2*h//3)
}
def non_max_suppression(self, faces, overlap_threshold=0.5):
"""非极大值抑制去除重复检测"""
if len(faces) == 0:
return []
# 按置信度排序
faces.sort(key=lambda x: x['confidence'], reverse=True)
pick = []
boxes = [face['bbox'] for face in faces]
x1 = np.array([box[0] for box in boxes])
y1 = np.array([box[1] for box in boxes])
x2 = np.array([box[2] for box in boxes])
y2 = np.array([box[3] for box in boxes])
area = (x2 - x1 + 1) * (y2 - y1 + 1)
idxs = np.arange(len(faces))
while len(idxs) > 0:
i = idxs[0]
pick.append(i)
xx1 = np.maximum(x1[i], x1[idxs[1:]])
yy1 = np.maximum(y1[i], y1[idxs[1:]])
xx2 = np.minimum(x2[i], x2[idxs[1:]])
yy2 = np.minimum(y2[i], y2[idxs[1:]])
w = np.maximum(0, xx2 - xx1 + 1)
h = np.maximum(0, yy2 - yy1 + 1)
overlap = (w * h) / area[idxs[1:]]
idxs = np.delete(idxs, np.concatenate(([0], np.where(overlap > overlap_threshold)[0] + 1)))
return [faces[i] for i in pick]
def process_single_image(self, image_path, person_name="unknown", save_aligned=False, save_landmarks=False):
"""处理单张图片:检测→对齐→特征提取"""
results = []
# 读取图片
image = cv2.imread(image_path)
if image is None:
print(f"❌ 无法读取图片: {image_path}")
return results
# 人脸检测(使用融合检测器)
faces = self.detect_faces_ensemble(image)
if not faces:
print(f"❌ 未检测到人脸: {os.path.basename(image_path)}")
return results
print(f"✅ 检测到 {len(faces)} 个人脸: {os.path.basename(image_path)}")
for i, face in enumerate(faces):
try:
# 人脸对齐
aligned_face = self.align_face_procrustes(face['roi'], face['landmarks'])
# 特征提取
features = self.extract_features(aligned_face)
if features is not None:
result = {
'image_path': image_path,
'person_name': person_name,
'features': features,
'bbox': face['bbox'],
'confidence': face['confidence'],
'detector': face.get('detector', 'unknown'),
'landmarks_count': len(face['landmarks'].get('all_points', [])),
'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
}
results.append(result)
print(f" ✅ 人脸 {i+1}: 对齐和特征提取成功 ({result['landmarks_count']} 个关键点)")
else:
print(f" ❌ 人脸 {i+1}: 特征提取失败")
except Exception as e:
print(f" ❌ 人脸 {i+1} 处理失败: {e}")
return results
def align_face_procrustes(self, face_roi, landmarks):
"""使用Procrustes分析进行精确人脸对齐"""
try:
# 获取眼睛中心点
left_eye = landmarks['left_eye_center']
right_eye = landmarks['right_eye_center']
# 计算眼睛连线角度
dx = right_eye[0] - left_eye[0]
dy = right_eye[1] - left_eye[1]
angle = np.degrees(np.arctan2(dy, dx))
# 计算眼睛中心点
eyes_center = (
(left_eye[0] + right_eye[0]) // 2,
(left_eye[1] + right_eye[1]) // 2
)
# 计算缩放比例(基于眼睛距离)
eye_distance = np.sqrt(dx**2 + dy**2)
desired_eye_distance = 50 # 目标眼睛距离
scale = desired_eye_distance / eye_distance
# 旋转矩阵
rotation_matrix = cv2.getRotationMatrix2D(eyes_center, angle, scale)
# 调整平移分量,使眼睛中心位于图像中心
h, w = face_roi.shape[:2]
rotation_matrix[0, 2] += w * 0.5 - eyes_center[0]
rotation_matrix[1, 2] += h * 0.5 - eyes_center[1]
# 应用仿射变换
aligned_face = cv2.warpAffine(face_roi, rotation_matrix, (w, h),
flags=cv2.INTER_CUBIC)
return aligned_face
except Exception as e:
print(f"Procrustes对齐失败: {e}")
return self.align_face_simple(face_roi, landmarks)
def align_face_simple(self, face_roi, landmarks):
"""简化版人脸对齐"""
try:
left_eye = landmarks['left_eye_center']
right_eye = landmarks['right_eye_center']
# 计算眼睛连线角度
dx = right_eye[0] - left_eye[0]
dy = right_eye[1] - left_eye[1]
angle = np.degrees(np.arctan2(dy, dx))
# 计算旋转中心
h, w = face_roi.shape[:2]
center = (w // 2, h // 2)
# 旋转矩阵
rotation_matrix = cv2.getRotationMatrix2D(center, angle, 1.0)
# 应用旋转
aligned_face = cv2.warpAffine(face_roi, rotation_matrix, (w, h),
flags=cv2.INTER_CUBIC)
return aligned_face
except Exception as e:
print(f"简化对齐失败: {e}")
return face_roi
def extract_features(self, aligned_face):
"""提取人脸特征"""
if aligned_face is None or aligned_face.size == 0:
return None
try:
# 调整尺寸为模型输入大小
input_size = (96, 96)
resized_face = cv2.resize(aligned_face, input_size)
# 预处理
blob = cv2.dnn.blobFromImage(
resized_face,
1.0 / 255,
input_size,
(0, 0, 0),
swapRB=True,
crop=False
)
# 前向传播获取特征
self.face_recognizer.setInput(blob)
vec = self.face_recognizer.forward()
# 归一化特征向量
embedding = vec.flatten()
norm = np.linalg.norm(embedding)
if norm > 0:
embedding = embedding / norm
return embedding
else:
return None
except Exception as e:
print(f"特征提取失败: {e}")
return None
def draw_landmarks(self, image, landmarks, color=(0, 255, 0), radius=2):
"""在图像上绘制关键点"""
try:
for point in landmarks.get('all_points', []):
x, y = int(point[0]), int(point[1])
cv2.circle(image, (x, y), radius, color, -1)
# 绘制眼睛中心
left_eye = landmarks.get('left_eye_center')
right_eye = landmarks.get('right_eye_center')
if left_eye:
cv2.circle(image, (int(left_eye[0]), int(left_eye[1])), radius, (255, 0, 0), -1)
if right_eye:
cv2.circle(image, (int(right_eye[0]), int(right_eye[1])), radius, (255, 0, 0), -1)
except Exception as e:
print(f"绘制关键点失败: {e}")
def process_single_image(self, image_path, person_name="unknown", save_aligned=False):
"""处理单张图片:检测→对齐→特征提取"""
results = []
# 读取图片
image = cv2.imread(image_path)
if image is None:
print(f"❌ 无法读取图片: {image_path}")
return results
# 人脸检测
faces = self.detect_faces_dlib(image)
if not faces:
print(f"❌ 未检测到人脸: {os.path.basename(image_path)}")
return results
print(f"✅ 检测到 {len(faces)} 个人脸: {os.path.basename(image_path)}")
for i, face in enumerate(faces):
try:
# 人脸对齐
aligned_face = self.align_face_procrustes(face['roi'], face['landmarks'])
# 特征提取
features = self.extract_features(aligned_face)
if features is not None:
result = {
'image_path': image_path,
'person_name': person_name,
'features': features,
'bbox': face['bbox'],
'confidence': face['confidence'],
'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S')
}
results.append(result)
print(f" ✅ 人脸 {i+1}: 对齐和特征提取成功")
else:
print(f" ❌ 人脸 {i+1}: 特征提取失败")
except Exception as e:
print(f" ❌ 人脸 {i+1} 处理失败: {e}")
return results
def scan_data_source(self, source_name, source_path):
"""扫描数据源并处理所有图片"""
print(f"\n扫描数据源: {source_name} -> {source_path}")
if not os.path.exists(source_path):
print(f"❌ 数据源不存在: {source_path}")
return []
all_results = []
# 根据数据源类型采用不同的处理方式
if source_name == 'labeled':
# labeled_faces: 按人物分目录
person_dirs = [d for d in os.listdir(source_path)
if os.path.isdir(os.path.join(source_path, d))]
for person_dir in person_dirs:
person_path = os.path.join(source_path, person_dir)
image_files = self.find_image_files(person_path)
print(f"处理标注人物: {person_dir} ({len(image_files)} 张图片)")
for image_file in image_files:
results = self.process_single_image(image_file, person_dir, save_aligned=True)
all_results.extend(results)
elif source_name == 'database':
# database: 图片文件名即人物名
image_files = self.find_image_files(source_path)
# 按人物分组
person_images = defaultdict(list)
for image_file in image_files:
person_name = os.path.splitext(os.path.basename(image_file))[0]
person_images[person_name].append(image_file)
for person_name, images in person_images.items():
print(f"处理数据库人物: {person_name} ({len(images)} 张图片)")
for image_file in images:
results = self.process_single_image(image_file, person_name, save_aligned=True)
all_results.extend(results)
elif source_name == 'query':
# query: 未知人物,需要识别
image_files = self.find_image_files(source_path)
print(f"处理查询图片: {len(image_files)}")
for image_file in image_files:
results = self.process_single_image(image_file, "unknown", save_aligned=True)
all_results.extend(results)
print(f"{source_name} 处理完成: {len(all_results)} 个人脸结果")
return all_results
def find_image_files(self, folder_path):
"""查找文件夹中的所有图片文件"""
image_extensions = ['*.jpg', '*.jpeg', '*.png', '*.bmp', '*.tiff']
image_files = []
for extension in image_extensions:
pattern = os.path.join(folder_path, extension)
image_files.extend(glob.glob(pattern))
pattern = os.path.join(folder_path, extension.upper())
image_files.extend(glob.glob(pattern))
return sorted(list(set(image_files)))
def build_face_database(self):
"""构建人脸特征数据库"""
print("\n构建人脸特征数据库...")
# 处理数据库和标注数据来构建数据库
database_results = self.scan_data_source('database', self.data_sources['database'])
labeled_results = self.scan_data_source('labeled', self.data_sources['labeled'])
# 合并结果
known_results = database_results + labeled_results
# 构建特征数据库
self.face_database = {}
self.feature_vectors = {}
for result in known_results:
person_name = result['person_name']
features = result['features']
if person_name not in self.face_database:
self.face_database[person_name] = {
'features_list': [features],
'sample_count': 1,
'image_paths': [result['image_path']]
}
self.feature_vectors[person_name] = features
else:
self.face_database[person_name]['features_list'].append(features)
self.face_database[person_name]['sample_count'] += 1
self.face_database[person_name]['image_paths'].append(result['image_path'])
# 更新特征向量(平均特征)
old_features = self.feature_vectors[person_name]
new_features = (old_features + features) / 2
self.feature_vectors[person_name] = new_features
print(f"✅ 人脸数据库构建完成: {len(self.face_database)} 个已知人物")
return len(self.face_database)
def recognize_face(self, query_features):
"""识别人脸身份"""
if not self.feature_vectors or query_features is None:
return "unknown", 1.0, None
best_name = "unknown"
best_distance = float('inf')
best_similarity = 0.0
for person_name, db_features in self.feature_vectors.items():
# 计算余弦相似度
similarity = np.dot(query_features, db_features)
distance = 1 - similarity
if distance < best_distance:
best_distance = distance
best_similarity = similarity
best_name = person_name
# 判断是否识别成功
if best_similarity > self.recognition_threshold:
return best_name, best_similarity, best_distance
else:
return "unknown", best_similarity, best_distance
def process_all_sources(self):
"""处理所有数据源并进行统一识别"""
print("开始统一处理所有数据源...")
# 1. 构建人脸数据库
if not self.build_face_database():
print("❌ 人脸数据库构建失败")
return None
# 2. 处理查询图片并进行识别
query_results = self.scan_data_source('query', self.data_sources['query'])
# 3. 对查询结果进行识别
recognition_results = []
for result in query_results:
query_image = result['image_path']
query_features = result['features']
recognized_name, similarity, distance = self.recognize_face(query_features)
recognition_result = {
**result,
'recognized_name': recognized_name,
'similarity': similarity,
'distance': distance,
'is_recognized': recognized_name != "unknown"
}
recognition_results.append(recognition_result)
status = "" if recognized_name != "unknown" else ""
print(f"{status} 被识别照片: {query_image} 识别结果: {recognized_name} (相似度: {similarity:.3f})")
# 4. 生成报告
self.generate_comprehensive_report(recognition_results)
return recognition_results
def generate_comprehensive_report(self, results):
"""生成综合报告"""
try:
report_path = os.path.join(self.output_folder, "unified_face_recognition_report.txt")
total_faces = len(results)
recognized_faces = len([r for r in results if r['is_recognized']])
with open(report_path, 'w', encoding='utf-8') as f:
f.write("统一人脸识别系统报告\n")
f.write("=" * 60 + "\n")
f.write(f"处理时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
f.write(f"识别阈值: {self.recognition_threshold}\n")
f.write(f"检测阈值: {self.face_confidence_threshold}\n\n")
# 数据库信息
f.write("人脸数据库信息:\n")
f.write("-" * 30 + "\n")
f.write(f"已知人物数量: {len(self.face_database)}\n")
for person_name, info in self.face_database.items():
f.write(f" - {person_name}: {info['sample_count']} 个样本\n")
f.write("\n")
# 识别统计
f.write("识别结果统计:\n")
f.write("-" * 30 + "\n")
f.write(f"总检测人脸: {total_faces}\n")
f.write(f"成功识别: {recognized_faces}\n")
f.write(f"识别率: {recognized_faces/max(total_faces,1)*100:.1f}%\n\n")
# 详细结果
f.write("详细识别结果:\n")
f.write("-" * 40 + "\n")
for result in results:
status = "成功" if result['is_recognized'] else "失败"
f.write(f"图片: {os.path.basename(result['image_path'])}\n")
f.write(f" 原始标签: {result['person_name']}\n")
f.write(f" 识别结果: {result['recognized_name']} ({status})\n")
f.write(f" 相似度: {result['similarity']:.3f}\n")
f.write(f" 距离: {result['distance']:.3f}\n")
f.write(f" 检测置信度: {result['confidence']:.3f}\n\n")
print(f"✅ 综合报告已保存: {report_path}")
print(f"识别统计: {recognized_faces}/{total_faces} ({recognized_faces/max(total_faces,1)*100:.1f}%)")
except Exception as e:
print(f"生成报告失败: {e}")
def interactive_mode(self):
while True:
print("\n" + "="*50)
print("统一人脸识别系统 - 交互模式")
print("="*50)
print("1. 处理所有数据源并识别")
print("2. 仅构建人脸数据库")
print("3. 仅处理查询图片")
print("4. 查看当前数据库")
print("5. 重新识别查询图片")
print("6. 退出")
print("-"*50)
choice = input("请选择操作 (1-6): ").strip()
if choice == '1':
self.process_all_sources()
elif choice == '2':
self.build_face_database()
self.show_database_info()
elif choice == '3':
query_results = self.scan_data_source('query', self.data_sources['query'])
print(f"处理完成: {len(query_results)} 个人脸")
elif choice == '4':
self.show_database_info()
elif choice == '5':
if not self.face_database:
print("❌ 请先构建人脸数据库")
else:
query_results = self.scan_data_source('query', self.data_sources['query'])
elif choice == '6':
print("再见!")
break
else:
print("无效选择,请重新输入!")
def show_database_info(self):
"""显示数据库信息"""
if not self.face_database:
print("❌ 数据库为空")
return
print(f"\n当前人脸数据库包含 {len(self.face_database)} 个已知人物:")
print("-" * 40)
for i, (person_name, info) in enumerate(self.face_database.items(), 1):
print(f"{i:2d}. {person_name}: {info['sample_count']} 个样本")
print("-" * 40)
def run(self):
"""运行统一人脸识别系统"""
print("=== 统一人脸识别系统启动 ===")
print("将对三个数据源进行统一处理:")
print(" 1. data/data_faces/ - 已知人物数据库")
print(" 2. labeled_faces/ - 手动标注数据")
print(" 3. all_faces/ - 待识别查询数据")
print("="*60)
# 检查模型
if self.face_detector is None or self.face_recognizer is None:
print("❌ 模型加载失败,无法继续")
return False
self.interactive_mode()
def main():
# 创建识别器实例
recognizer = UnifiedFaceRecognizer()
# 运行识别系统
success = recognizer.run()
if success:
print("\n🎉 人脸识别任务完成!")
print(f"结果保存在: {recognizer.output_folder}")
else:
print("\n❌ 人脸识别任务失败!")
if __name__ == '__main__':
main()

383
face_reco_from_camera.py Executable file → Normal file
View File

@ -1,285 +1,156 @@
# Copyright (C) 2018-2021 coneypo
# SPDX-License-Identifier: MIT
# 摄像头实时人脸识别
# Real-time face recognition
import dlib
import numpy as np
import cv2
import pandas as pd
import os
import time
import logging
from PIL import Image, ImageDraw, ImageFont
# Author: coneypo
# Blog: http://www.cnblogs.com/AdaminXie
# GitHub: https://github.com/coneypo/Dlib_face_recognition_from_camera
# Dlib 正向人脸检测器
face_cascade = cv2.CascadeClassifier('models/haar/haarcascade_frontalface_default.xml')
predictor = dlib.shape_predictor('models/dlib/shape_predictor_68_face_landmarks.dat')
face_reco_model = dlib.face_recognition_model_v1("models/dlib/dlib_face_recognition_resnet_model_v1.dat")
# Created at 2018-05-11
# Updated at 2019-03-23
import dlib # 人脸处理的库 Dlib
import numpy as np # 数据处理的库 numpy
import cv2 # 图像处理的库 OpenCv
import pandas as pd # 数据处理的库 Pandas
# 人脸识别模型,提取128D的特征矢量
# face recognition model, the object maps human faces into 128D vectors
# Refer this tutorial: http://dlib.net/python/index.html#dlib.face_recognition_model_v1
facerec = dlib.face_recognition_model_v1("data/data_dlib/dlib_face_recognition_resnet_model_v1.dat")
class Face_Recognizer:
def __init__(self):
# 用于存放录入的人脸特征的数组
self.face_feature_known_list = []
# 用于存放录入的人脸对应的名字
self.face_name_known_list = []
# 计算两个128D向量间的欧式距离
# compute the e-distance between two 128D features
def return_euclidean_distance(feature_1, feature_2):
feature_1 = np.array(feature_1)
feature_2 = np.array(feature_2)
dist = np.sqrt(np.sum(np.square(feature_1 - feature_2)))
print("e_distance: ", dist)
# 当前帧中人脸数的计数器
self.current_frame_face_cnt = 0
# 当前摄像头中捕获到的人脸特征的数组
self.current_frame_face_feature_list = []
# 当前摄像头中捕获到的人脸名字的数组
self.current_frame_face_name_list = []
# 当前摄像头中捕获到的人脸的坐标名字
self.current_frame_face_name_position_list = []
# 特征提取缓存
self.feature_cache = {} # 缓存已计算的特征,避免重复计算
# Update FPS
self.fps = 0
self.fps_show = 0
self.frame_start_time = 0
self.frame_cnt = 0
self.start_time = time.time()
self.font = cv2.FONT_ITALIC
# 加载中文字体
try:
self.font_chinese = ImageFont.truetype("simsun.ttc", 30)
except:
print("警告: 无法加载中文字体,使用默认字体")
self.font_chinese = ImageFont.load_default()
# 添加退出标志
self.exit_flag = False
def get_face_database(self):
if os.path.exists("data/features_all.csv"):
path_features_known_csv = "data/features_all.csv"
csv_rd = pd.read_csv(path_features_known_csv, header=None)
for i in range(csv_rd.shape[0]):
features_someone_arr = []
name = str(csv_rd.iloc[i][0])
self.face_name_known_list.append(name)
for j in range(1, 129):
if csv_rd.iloc[i][j] == '':
features_someone_arr.append('0')
else:
features_someone_arr.append(csv_rd.iloc[i][j])
self.face_feature_known_list.append(features_someone_arr)
logging.info("Faces in Database:%d", len(self.face_feature_known_list))
return 1
else:
logging.warning("'features_all.csv' not found!")
return 0
@staticmethod
def return_euclidean_distance(feature_1, feature_2):
feature_1 = np.array(feature_1)
feature_2 = np.array(feature_2)
dist = np.sqrt(np.sum(np.square(feature_1 - feature_2)))
return dist
if dist > 0.4:
return "diff"
else:
return "same"
def fast_face_detection(self, img_rd):
# 缩小图像进行检测(大幅提高速度)
small_img = cv2.resize(img_rd, (320, 240))
gray = cv2.cvtColor(small_img, cv2.COLOR_BGR2GRAY)
# 使用更快的参数
faces_cv = face_cascade.detectMultiScale(
gray,
scaleFactor=1.1,
minNeighbors=3, # 减少邻居数提高速度
minSize=(50, 50) # 增大最小尺寸减少检测数量
)
# 转换坐标回原图尺寸
scale_x = 640 / 320
scale_y = 480 / 240
faces = []
for (x, y, w, h) in faces_cv:
faces.append(dlib.rectangle(
int(x * scale_x), int(y * scale_y),
int((x + w) * scale_x), int((y + h) * scale_y)
))
return faces
# 处理存放所有人脸特征的 csv
path_features_known_csv = "data/features_all.csv"
csv_rd = pd.read_csv(path_features_known_csv, header=None)
def update_fps(self):
now = time.time()
if str(self.start_time).split(".")[0] != str(now).split(".")[0]:
self.fps_show = self.fps
self.start_time = now
self.frame_time = now - self.frame_start_time
self.fps = 1.0 / self.frame_time
self.frame_start_time = now
# 用来存放所有录入人脸特征的数组
# the array to save the features of faces in the database
features_known_arr = []
def draw_note(self, img_rd):
cv2.putText(img_rd, "Face Recognizer", (20, 40), self.font, 1, (255, 255, 255), 1, cv2.LINE_AA)
cv2.putText(img_rd, "Frame: " + str(self.frame_cnt), (20, 100), self.font, 0.8, (0, 255, 0), 1, cv2.LINE_AA)
cv2.putText(img_rd, "FPS: " + str(self.fps_show.__round__(2)), (20, 130), self.font, 0.8, (0, 255, 0), 1,
cv2.LINE_AA)
cv2.putText(img_rd, "Faces: " + str(self.current_frame_face_cnt), (20, 160), self.font, 0.8, (0, 255, 0), 1,
cv2.LINE_AA)
cv2.putText(img_rd, "Q: Quit", (20, 450), self.font, 0.8, (255, 255, 255), 1, cv2.LINE_AA)
# 读取已知人脸数据
# print known faces
for i in range(csv_rd.shape[0]):
features_someone_arr = []
for j in range(0, len(csv_rd.ix[i, :])):
features_someone_arr.append(csv_rd.ix[i, :][j])
features_known_arr.append(features_someone_arr)
print("Faces in Database:", len(features_known_arr))
def draw_name(self, img_rd):
# 在人脸框下面写人脸名字
img = Image.fromarray(cv2.cvtColor(img_rd, cv2.COLOR_BGR2RGB))
draw = ImageDraw.Draw(img)
for i in range(self.current_frame_face_cnt):
try:
# 安全处理姓名
name = str(self.current_frame_face_name_list[i])
position = tuple(map(int, self.current_frame_face_name_position_list[i]))
draw.text(xy=position, text=name, font=self.font_chinese, fill=(255, 255, 0))
except Exception as e:
print(f"绘制姓名时出错: {e}")
continue
img_rd = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)
return img_rd
# Dlib 检测器和预测器
# The detector and predictor will be used
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor('data/data_dlib/shape_predictor_68_face_landmarks.dat')
def check_window_closed(self, window_name="camera"):
try:
# 尝试获取窗口属性,如果窗口关闭会返回 -1
if cv2.getWindowProperty(window_name, cv2.WND_PROP_VISIBLE) < 1:
return True
return False
except:
# 如果窗口不存在,也会触发异常
return True
# 创建 cv2 摄像头对象
# cv2.VideoCapture(0) to use the default camera of PC,
# and you can use local video name by use cv2.VideoCapture(filename)
cap = cv2.VideoCapture(0)
def process(self, stream):
# 1. 读取存放所有人脸特征的 csv
if not self.get_face_database():
print("ERROR:无法加载人脸数据库")
return
# cap.set(propId, value)
# 设置视频参数,propId 设置的视频参数,value 设置的参数值
cap.set(3, 480)
# 创建窗口并设置为正常模式
cv2.namedWindow("camera", cv2.WINDOW_NORMAL)
cv2.resizeWindow("camera", 800, 600)
# cap.isOpened() 返回 true/false 检查初始化是否成功
# when the camera is open
while cap.isOpened():
while stream.isOpened() and not self.exit_flag:
# 检查窗口是否被关闭
if self.check_window_closed():
print("检测到窗口关闭,退出程序")
break
flag, img_rd = cap.read()
kk = cv2.waitKey(1)
self.frame_cnt += 1
flag, img_rd = stream.read()
# 取灰度
img_gray = cv2.cvtColor(img_rd, cv2.COLOR_RGB2GRAY)
if not flag:
print("ERROR:无法读取视频帧")
break
# 人脸数 faces
faces = detector(img_gray, 0)
# 检测按键和窗口关闭
kk = cv2.waitKey(1) & 0xFF
# 待会要写的字体 font to write later
font = cv2.FONT_HERSHEY_COMPLEX
# 按下 q 键退出
if kk == ord('q') or kk == ord('Q'):
print("接收到退出信号,退出程序")
break
# 存储当前摄像头中捕获到的所有人脸的坐标/名字
# the list to save the positions and names of current faces captured
pos_namelist = []
name_namelist = []
# 检查窗口关闭
if cv2.getWindowProperty("camera", cv2.WND_PROP_VISIBLE) < 1:
print("窗口已关闭,退出程序")
break
# 按下 q 键退出
# press 'q' to exit
if kk == ord('q'):
break
else:
# 检测到人脸 when face detected
if len(faces) != 0:
# 获取当前捕获到的图像的所有人脸的特征,存储到 features_cap_arr
# get the features captured and save into features_cap_arr
features_cap_arr = []
for i in range(len(faces)):
shape = predictor(img_rd, faces[i])
features_cap_arr.append(facerec.compute_face_descriptor(img_rd, shape))
self.draw_note(img_rd)
self.current_frame_face_feature_list = []
self.current_frame_face_cnt = 0
self.current_frame_face_name_position_list = []
self.current_frame_face_name_list = []
# 遍历捕获到的图像中所有的人脸
# traversal all the faces in the database
for k in range(len(faces)):
# 让人名跟随在矩形框的下方
# 确定人名的位置坐标
# 先默认所有人不认识,是 unknown
# set the default names of faces with "unknown"
name_namelist.append("unknown")
# 2. 检测到人脸
faces = self.fast_face_detection(img_rd)
if len(faces) != 0:
# 3. 获取当前捕获到的图像的所有人脸的特征
for i in range(len(faces)):
shape = predictor(img_rd, faces[i])
self.current_frame_face_feature_list.append(face_reco_model.compute_face_descriptor(img_rd, shape))
# 4. 遍历捕获到的图像中所有的人脸
for k in range(len(faces)):
# 先默认所有人不认识
self.current_frame_face_name_list.append("unknown")
# 每个捕获人脸的名字坐标
self.current_frame_face_name_position_list.append(tuple(
[faces[k].left(), int(faces[k].bottom() + (faces[k].bottom() - faces[k].top()) / 4)]))
# 5. 对于某张人脸,遍历所有存储的人脸特征
current_frame_e_distance_list = []
for i in range(len(self.face_feature_known_list)):
if str(self.face_feature_known_list[i][0]) != '0.0':
e_distance_tmp = self.return_euclidean_distance(
self.current_frame_face_feature_list[k],
self.face_feature_known_list[i]
)
current_frame_e_distance_list.append(e_distance_tmp)
else:
current_frame_e_distance_list.append(999999999)
# 6. 寻找出最小的欧式距离匹配
if current_frame_e_distance_list:
similar_person_num = current_frame_e_distance_list.index(min(current_frame_e_distance_list))
min_distance = min(current_frame_e_distance_list)
if min_distance < 0.4:
self.current_frame_face_name_list[k] = self.face_name_known_list[similar_person_num]
# 每个捕获人脸的名字坐标 the positions of faces captured
pos_namelist.append(tuple([faces[k].left(), int(faces[k].bottom() + (faces[k].bottom() - faces[k].top())/4)]))
# 对于某张人脸,遍历所有存储的人脸特征
# for every faces detected, compare the faces in the database
for i in range(len(features_known_arr)):
print("with person_", str(i+1), "the ", end='')
# 将某张人脸与存储的所有人脸数据进行比对
compare = return_euclidean_distance(features_cap_arr[k], features_known_arr[i])
if compare == "same": # 找到了相似脸
# 在这里修改 person_1, person_2 ... 的名字
# 这里只写了前三个
# 可以在这里改称 Jack, Tom and others
# Here you can modify the names shown on the camera
if i == 0:
name_namelist[k] = "Person 1"
elif i == 1:
name_namelist[k] = "Person 2"
elif i == 2:
name_namelist[k] = "Person 3"
# 矩形框
# draw rectangle
for kk, d in enumerate(faces):
# 绘制矩形框
cv2.rectangle(img_rd,
(faces[k].left(), faces[k].top()),
(faces[k].right(), faces[k].bottom()),
(255, 255, 255), 2)
cv2.rectangle(img_rd, tuple([d.left(), d.top()]), tuple([d.right(), d.bottom()]), (0, 255, 255), 2)
self.current_frame_face_cnt = len(faces)
# 在人脸框下面写人脸名字
# write names under rectangle
for i in range(len(faces)):
cv2.putText(img_rd, name_namelist[i], pos_namelist[i], font, 0.8, (0, 255, 255), 1, cv2.LINE_AA)
# 8. 写名字
img_rd = self.draw_name(img_rd)
print("Faces in camera now:", name_namelist, "\n")
# 显示图像
cv2.imshow("camera", img_rd)
cv2.putText(img_rd, "Press 'q': Quit", (20, 450), font, 0.8, (84, 255, 159), 1, cv2.LINE_AA)
cv2.putText(img_rd, "Face Recognition", (20, 40), font, 1, (0, 0, 0), 1, cv2.LINE_AA)
cv2.putText(img_rd, "Faces: " + str(len(faces)), (20, 100), font, 1, (0, 0, 255), 1, cv2.LINE_AA)
# 9. 更新 FPS
self.update_fps()
# 窗口显示 show with opencv
cv2.imshow("camera", img_rd)
# 清理资源
cv2.destroyAllWindows()
print("程序正常退出")
# 释放摄像头 release camera
cap.release()
def run(self):
cap = cv2.VideoCapture(0)
if not cap.isOpened():
print("错误: 无法打开摄像头")
return
# 设置摄像头参数
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
try:
self.process(cap)
except KeyboardInterrupt:
print("\n接收到 Ctrl+C,退出程序")
except Exception as e:
print(f"程序异常: {e}")
finally:
# 确保资源被释放
cap.release()
cv2.destroyAllWindows()
def main():
logging.basicConfig(level=logging.INFO)
print("=== 人脸识别系统启动 ===")
Face_Recognizer_con = Face_Recognizer()
Face_Recognizer_con.run()
if __name__ == '__main__':
main()
# 删除建立的窗口 delete all the windows
cv2.destroyAllWindows()

View File

@ -1,410 +0,0 @@
# Copyright (C) 2018-2021 coneypo
# SPDX-License-Identifier: MIT
import dlib
import numpy as np
import cv2
import pandas as pd
import os
import time
import logging
from PIL import Image, ImageDraw, ImageFont
# Dlib 正向人脸检测器
face_cascade = cv2.CascadeClassifier('models/haar/haarcascade_frontalface_default.xml')
predictor = dlib.shape_predictor('models/dlib/shape_predictor_68_face_landmarks.dat')
face_reco_model = dlib.face_recognition_model_v1("models/dlib/dlib_face_recognition_resnet_model_v1.dat")
class Face_Recognizer:
def __init__(self):
# 用于存放录入的人脸特征的数组
self.face_feature_known_list = []
# 用于存放录入的人脸对应的名字
self.face_name_known_list = []
# 人脸追踪相关变量
self.last_frame_face_centroid_list = [] # 上一帧人脸质心列表
self.current_frame_face_centroid_list = [] # 当前帧人脸质心列表
self.last_frame_face_name_list = [] # 上一帧人脸名字列表
self.current_frame_face_name_list = [] # 当前帧人脸名字列表
self.last_frame_face_cnt = 0 # 上一帧人脸数量
self.current_frame_face_cnt = 0 # 当前帧人脸数量
# 再识别控制
self.reclassify_interval_cnt = 0 # 再识别计数
self.reclassify_interval = 10 # 再识别间隔帧数
# 当前摄像头中捕获到的人脸特征的数组
self.current_frame_face_feature_list = []
# 当前摄像头中捕获到的人脸的坐标名字
self.current_frame_face_name_position_list = []
# Update FPS
self.fps = 0
self.fps_show = 0
self.frame_start_time = 0
self.frame_cnt = 0
self.start_time = time.time()
self.font = cv2.FONT_HERSHEY_SIMPLEX
# 加载中文字体
try:
self.font_chinese = ImageFont.truetype("simsun.ttc", 30)
except:
try:
self.font_chinese = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 20)
except:
print("警告: 无法加载中文字体,使用默认字体")
self.font_chinese = ImageFont.load_default()
# 添加退出标志
self.exit_flag = False
def get_face_database(self):
if os.path.exists("data/features_all.csv"):
path_features_known_csv = "data/features_all.csv"
csv_rd = pd.read_csv(path_features_known_csv, header=None)
for i in range(csv_rd.shape[0]):
features_someone_arr = []
name = str(csv_rd.iloc[i][0])
self.face_name_known_list.append(name)
for j in range(1, 129):
if csv_rd.iloc[i][j] == '':
features_someone_arr.append(0)
else:
features_someone_arr.append(float(csv_rd.iloc[i][j]))
self.face_feature_known_list.append(features_someone_arr)
logging.info("Faces in Database:%d", len(self.face_feature_known_list))
return True
else:
logging.warning("'features_all.csv' not found!")
return False
@staticmethod
def return_euclidean_distance(feature_1, feature_2):
feature_1 = np.array(feature_1)
feature_2 = np.array(feature_2)
dist = np.sqrt(np.sum(np.square(feature_1 - feature_2)))
return dist
def centroid_tracker(self):
"""
质心追踪算法:通过计算质心间的欧氏距离来关联前后帧的人脸
"""
# 如果上一帧没有人脸,直接返回
if not self.last_frame_face_centroid_list:
return
for i in range(len(self.current_frame_face_centroid_list)):
e_distance_current_frame_person_x_list = []
# 对于当前帧中的每个人脸,计算与上一帧所有人脸的欧氏距离
for j in range(len(self.last_frame_face_centroid_list)):
distance = self.return_euclidean_distance(
self.current_frame_face_centroid_list[i],
self.last_frame_face_centroid_list[j]
)
e_distance_current_frame_person_x_list.append(distance)
# 找到最小距离的匹配
if e_distance_current_frame_person_x_list:
min_distance_index = e_distance_current_frame_person_x_list.index(
min(e_distance_current_frame_person_x_list)
)
# 如果距离小于阈值且索引有效,认为是同一个人
if (min(e_distance_current_frame_person_x_list) < 100 and
min_distance_index < len(self.last_frame_face_name_list)):
self.current_frame_face_name_list[i] = self.last_frame_face_name_list[min_distance_index]
logging.debug(f"追踪匹配: 人脸{i} -> {self.last_frame_face_name_list[min_distance_index]}, 距离: {min(e_distance_current_frame_person_x_list):.2f}")
def calculate_face_centroid(self, face_rect):
""" 计算人脸边界框的质心 """
center_x = (face_rect.left() + face_rect.right()) / 2
center_y = (face_rect.top() + face_rect.bottom()) / 2
return [center_x, center_y]
def fast_face_detection(self, img_rd):
try:
# 缩小图像进行检测(大幅提高速度)
small_img = cv2.resize(img_rd, (320, 240))
gray = cv2.cvtColor(small_img, cv2.COLOR_BGR2GRAY)
# 使用更快的参数
faces_cv = face_cascade.detectMultiScale(
gray,
scaleFactor=1.1,
minNeighbors=3, # 减少邻居数提高速度
minSize=(50, 50) # 增大最小尺寸减少检测数量
)
# 转换坐标回原图尺寸
scale_x = img_rd.shape[1] / 320
scale_y = img_rd.shape[0] / 240
faces = []
for (x, y, w, h) in faces_cv:
faces.append(dlib.rectangle(
int(x * scale_x), int(y * scale_y),
int((x + w) * scale_x), int((y + h) * scale_y)
))
return faces
except Exception as e:
print(f"人脸检测错误: {e}")
return []
def update_fps(self):
now = time.time()
if str(self.start_time).split(".")[0] != str(now).split(".")[0]:
self.fps_show = self.fps
self.start_time = now
self.frame_time = now - self.frame_start_time
self.fps = 1.0 / self.frame_time if self.frame_time > 0 else 0
self.frame_start_time = now
def draw_note(self, img_rd):
cv2.putText(img_rd, "Face Recognizer with OT", (20, 40), self.font, 1, (255, 255, 255), 2, cv2.LINE_AA)
cv2.putText(img_rd, "Frame: " + str(self.frame_cnt), (20, 80), self.font, 0.8, (0, 255, 0), 2, cv2.LINE_AA)
cv2.putText(img_rd, "FPS: " + str(self.fps_show.__round__(2)), (20, 120), self.font, 0.8, (0, 255, 0), 2,
cv2.LINE_AA)
cv2.putText(img_rd, "Faces: " + str(self.current_frame_face_cnt), (20, 160), self.font, 0.8, (0, 255, 0), 2,
cv2.LINE_AA)
cv2.putText(img_rd, "Q: Quit", (20, 450), self.font, 0.8, (255, 255, 255), 2, cv2.LINE_AA)
# 显示追踪信息
cv2.putText(img_rd, f"ReID Count: {self.reclassify_interval_cnt}/{self.reclassify_interval}",
(20, 200), self.font, 0.8, (0, 255, 255), 2, cv2.LINE_AA)
def draw_name(self, img_rd):
# 在人脸框下面写人脸名字
img = Image.fromarray(cv2.cvtColor(img_rd, cv2.COLOR_BGR2RGB))
draw = ImageDraw.Draw(img)
for i in range(min(len(self.current_frame_face_name_list), len(self.current_frame_face_name_position_list))):
try:
# 安全处理姓名
name = str(self.current_frame_face_name_list[i])
position = tuple(map(int, self.current_frame_face_name_position_list[i]))
# 绘制名字
draw.text(xy=position, text=name, font=self.font_chinese, fill=(255, 255, 0))
# 绘制质心点(如果存在)
if i < len(self.current_frame_face_centroid_list):
centroid = self.current_frame_face_centroid_list[i]
centroid = [int(centroid[0]), int(centroid[1])]
draw.ellipse([centroid[0]-3, centroid[1]-3, centroid[0]+3, centroid[1]+3],
fill=(255, 0, 0), outline=(255, 255, 255))
except Exception as e:
print(f"绘制姓名时出错: {e}")
continue
img_rd = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)
return img_rd
def check_window_closed(self, window_name="camera"):
try:
# 尝试获取窗口属性,如果窗口关闭会返回 -1
if cv2.getWindowProperty(window_name, cv2.WND_PROP_VISIBLE) < 1:
return True
return False
except:
# 如果窗口不存在,也会触发异常
return True
def recognize_face(self, face_feature):
"""
识别人脸并返回名字和最小距离
"""
if not self.face_feature_known_list:
return "unknown", 999999999
current_frame_e_distance_list = []
for i in range(len(self.face_feature_known_list)):
if len(self.face_feature_known_list[i]) > 0 and self.face_feature_known_list[i][0] != 0.0:
e_distance_tmp = self.return_euclidean_distance(
face_feature,
self.face_feature_known_list[i]
)
current_frame_e_distance_list.append(e_distance_tmp)
else:
current_frame_e_distance_list.append(999999999)
# 寻找出最小的欧式距离匹配
if current_frame_e_distance_list:
similar_person_num = current_frame_e_distance_list.index(min(current_frame_e_distance_list))
min_distance = min(current_frame_e_distance_list)
if min_distance < 0.4:
return self.face_name_known_list[similar_person_num], min_distance
return "unknown", 999999999
def process(self, stream):
# 1. 读取存放所有人脸特征的 csv
if not self.get_face_database():
print("ERROR:无法加载人脸数据库")
return
# 创建窗口并设置为正常模式
cv2.namedWindow("camera", cv2.WINDOW_NORMAL)
cv2.resizeWindow("camera", 800, 600)
print("系统启动成功,按Q键退出...")
while stream.isOpened() and not self.exit_flag:
# 检查窗口是否被关闭
if self.check_window_closed():
print("检测到窗口关闭,退出程序")
break
self.frame_cnt += 1
flag, img_rd = stream.read()
if not flag:
print("ERROR:无法读取视频帧")
break
# 调整图像大小
img_rd = cv2.resize(img_rd, (640, 480))
# 检测按键和窗口关闭
kk = cv2.waitKey(1) & 0xFF
# 按下 q 键退出
if kk == ord('q') or kk == ord('Q'):
print("接收到退出信号,退出程序")
break
# 初始化当前帧变量
self.current_frame_face_feature_list = []
self.current_frame_face_centroid_list = []
self.current_frame_face_name_position_list = []
self.current_frame_face_name_list = []
# 2. 检测人脸
faces = self.fast_face_detection(img_rd)
self.current_frame_face_cnt = len(faces)
# 绘制基本信息
self.draw_note(img_rd)
if self.current_frame_face_cnt > 0:
# 计算当前帧的质心
for face in faces:
self.current_frame_face_centroid_list.append(self.calculate_face_centroid(face))
self.current_frame_face_name_position_list.append(
[face.left(), int(face.bottom() + 20)]
)
# 场景1: 人脸数量没有变化,使用追踪
if (self.current_frame_face_cnt == self.last_frame_face_cnt and
self.last_frame_face_cnt > 0 and
self.reclassify_interval_cnt < self.reclassify_interval):
# 初始化名字列表为unknown
self.current_frame_face_name_list = ["unknown"] * self.current_frame_face_cnt
# 执行质心追踪
self.centroid_tracker()
# 如果有未知人脸,增加再识别计数
if "unknown" in self.current_frame_face_name_list:
self.reclassify_interval_cnt += 1
# 场景2: 人脸数量变化或需要重新识别
else:
# print("重新识别人脸")
self.reclassify_interval_cnt = 0
# 获取所有人脸特征
for i in range(len(faces)):
try:
shape = predictor(img_rd, faces[i])
face_descriptor = face_reco_model.compute_face_descriptor(img_rd, shape)
self.current_frame_face_feature_list.append(face_descriptor)
except Exception as e:
print(f"特征提取错误: {e}")
self.current_frame_face_feature_list.append([])
# 识别人脸
for k in range(len(faces)):
if k < len(self.current_frame_face_feature_list) and self.current_frame_face_feature_list[k]:
name, distance = self.recognize_face(self.current_frame_face_feature_list[k])
self.current_frame_face_name_list.append(name)
else:
self.current_frame_face_name_list.append("unknown")
else:
# 没有人脸时重置计数
self.reclassify_interval_cnt = 0
self.current_frame_face_name_list = []
# 绘制人脸框和信息
for k in range(len(faces)):
# 绘制矩形框
cv2.rectangle(img_rd,
(faces[k].left(), faces[k].top()),
(faces[k].right(), faces[k].bottom()),
(255, 255, 255), 2)
# 在框上方显示ID
cv2.putText(img_rd, f"ID:{k}",
(faces[k].left(), faces[k].top()-10),
self.font, 0.5, (255, 255, 0), 1, cv2.LINE_AA)
# 绘制名字和质心
if self.current_frame_face_cnt > 0:
img_rd = self.draw_name(img_rd)
# 更新上一帧信息(使用拷贝避免引用问题)
self.last_frame_face_cnt = self.current_frame_face_cnt
self.last_frame_face_centroid_list = self.current_frame_face_centroid_list[:] # 使用切片拷贝
self.last_frame_face_name_list = self.current_frame_face_name_list[:] # 使用切片拷贝
# 显示图像
cv2.imshow("camera", img_rd)
# 更新 FPS
self.update_fps()
# 清理资源
cv2.destroyAllWindows()
print("程序正常退出")
def run(self):
cap = cv2.VideoCapture(0)
if not cap.isOpened():
print("错误: 无法打开摄像头")
return
# 设置摄像头参数
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
cap.set(cv2.CAP_PROP_FPS, 30)
try:
self.process(cap)
except KeyboardInterrupt:
print("\n接收到 Ctrl+C,退出程序")
except Exception as e:
print(f"程序异常: {e}")
import traceback
traceback.print_exc()
finally:
# 确保资源被释放
cap.release()
cv2.destroyAllWindows()
def main():
logging.basicConfig(level=logging.INFO)
print("=== 带OT追踪的人脸识别系统启动 ===")
Face_Recognizer_con = Face_Recognizer()
Face_Recognizer_con.run()
if __name__ == '__main__':
main()

View File

@ -1,806 +0,0 @@
import cv2
import numpy as np
import time
import os
import json
from collections import deque
from datetime import datetime
import logging
import urllib.request
import math
# 日志格式
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
# 用于检查人脸质量
class FaceQualityAssessor:
def __init__(self):
# 设置人脸图像最小尺寸及人脸质量最低分数
self.min_face_size = 80
self.min_quality_score = 0.6
# 计算清晰度
def calculate_sharpness(self, image):
if len(image.shape) == 3:
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
else:
gray = image
return cv2.Laplacian(gray, cv2.CV_64F).var()
# 检查亮度
def assess_brightness(self, image):
if len(image.shape) == 3:
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
brightness = np.mean(hsv[:,:,2]) / 255.0
else:
brightness = np.mean(image) / 255.0
# 理想亮度在0.3-0.8之间
if 0.3 <= brightness <= 0.8:
return 1.0
else:
return max(0, 1 - abs(brightness - 0.55))
# 检查人脸姿势
def assess_face_pose(self, face_image):
height, width = face_image.shape[:2]
aspect_ratio = width / height if height > 0 else 1
# 正面人脸的宽高比通常在0.8-1.2之间
if 0.8 <= aspect_ratio <= 1.2:
return 0.9
elif 0.6 <= aspect_ratio <= 1.4:
return 0.7
else:
return 0.4
# 检查遮挡情况
def assess_occlusion(self, face_image):
gray = cv2.cvtColor(face_image, cv2.COLOR_BGR2GRAY) if len(face_image.shape) == 3 else face_image
edges = cv2.Canny(gray, 50, 150)
edge_density = np.sum(edges) / (gray.shape[0] * gray.shape[1] * 255)
if edge_density > 0.1:
return 0.8
else:
return 0.5
# 检查人脸质量
def assess_face_quality(self, face_image, bbox=None):
if face_image is None or face_image.size == 0:
return 0.0, "invalid_image"
height, width = face_image.shape[:2]
# 1. 尺寸检查
if height < self.min_face_size or width < self.min_face_size:
return 0.0, "face_too_small"
# 2. 清晰度评估
sharpness = self.calculate_sharpness(face_image)
if sharpness < 20:
return 0.0, "blurry"
sharpness_score = min(1.0, sharpness / 100.0)
# 3. 亮度评估
brightness_score = self.assess_brightness(face_image)
if brightness_score < 0.3:
return 0.0, "poor_lighting"
# 4. 姿态评估
pose_score = self.assess_face_pose(face_image)
# 5. 遮挡评估
occlusion_score = self.assess_occlusion(face_image)
# 综合质量分数
quality_score = (sharpness_score * 0.3 +
brightness_score * 0.25 +
pose_score * 0.25 +
occlusion_score * 0.2)
quality_status = "good" if quality_score > self.min_quality_score else "poor_quality"
return quality_score, quality_status
# 人体追踪器
class PersonTracker:
def __init__(self, max_frames_lost=30):
self.tracks = {}
self.next_id = 0
self.max_frames_lost = max_frames_lost
def calculate_iou(self, box1, box2):
"""计算两个框的IOU"""
x1_1, y1_1, x2_1, y2_1 = box1
x1_2, y1_2, x2_2, y2_2 = box2
# 计算交集区域
xi1 = max(x1_1, x1_2)
yi1 = max(y1_1, y1_2)
xi2 = min(x2_1, x2_2)
yi2 = min(y2_1, y2_2)
inter_area = max(0, xi2 - xi1) * max(0, yi2 - yi1)
# 计算并集区域
box1_area = (x2_1 - x1_1) * (y2_1 - y1_1)
box2_area = (x2_2 - x1_2) * (y2_2 - y1_2)
union_area = box1_area + box2_area - inter_area
return inter_area / union_area if union_area > 0 else 0
def match_detection(self, detection_bbox):
"""匹配检测到现有跟踪"""
best_iou = 0.3
best_id = None
for track_id, track in self.tracks.items():
iou = self.calculate_iou(track['bbox'], detection_bbox)
if iou > best_iou:
best_iou = iou
best_id = track_id
return best_id
def update(self, detections, frame_count):
"""更新跟踪器"""
active_ids = []
for det in detections:
bbox = det['bbox']
matched_id = self.match_detection(bbox)
if matched_id is None:
matched_id = self.next_id
self.next_id += 1
self.tracks[matched_id] = {
'bbox': bbox,
'last_seen': frame_count,
'first_seen': frame_count,
'best_face': None,
'best_quality': 0,
'face_history': deque(maxlen=15),
'last_face_capture': 0
}
else:
# 更新现有跟踪
self.tracks[matched_id]['bbox'] = bbox
self.tracks[matched_id]['last_seen'] = frame_count
active_ids.append(matched_id)
# 清理丢失的跟踪
self.cleanup_tracks(frame_count)
return active_ids
def cleanup_tracks(self, frame_count):
"""清理丢失的跟踪"""
tracks_to_remove = []
for track_id, track in self.tracks.items():
if frame_count - track['last_seen'] > self.max_frames_lost:
tracks_to_remove.append(track_id)
for track_id in tracks_to_remove:
del self.tracks[track_id]
def update_face_for_track(self, track_id, face_image, quality_score, frame_count):
"""为跟踪目标更新最佳人脸"""
if track_id in self.tracks:
track = self.tracks[track_id]
# 只保存质量更好的人脸
if quality_score > track['best_quality']:
track['best_face'] = {
'image': face_image.copy(),
'quality': quality_score,
'frame_count': frame_count,
'timestamp': datetime.now().strftime("%Y%m%d_%H%M%S_%f")
}
track['best_quality'] = quality_score
track['last_face_capture'] = frame_count
# 保存到历史记录
track['face_history'].append({
'image': face_image.copy(),
'quality': quality_score,
'frame_count': frame_count
})
# 使用OpenCV DNN检测器识别人脸
class OpenCVDNNDetector:
def __init__(self, config_path, model_path, input_size, scale_factor,
mean_values, swap_rb=True, confidence_threshold=0.5):
self.net = None
self.config_path = config_path
self.model_path = model_path
self.input_size = input_size
self.scale_factor = scale_factor
self.mean_values = mean_values
self.swap_rb = swap_rb
self.confidence_threshold = confidence_threshold
self.load_model()
def load_model(self):
"""加载模型"""
if not os.path.exists(self.model_path) or not os.path.exists(self.config_path):
logger.warning(f"模型文件不存在: {self.config_path}{self.model_path}")
return False
try:
if self.config_path.endswith('.cfg'):
self.net = cv2.dnn.readNetFromDarknet(self.config_path, self.model_path)
elif self.config_path.endswith('.prototxt'):
self.net = cv2.dnn.readNetFromCaffe(self.config_path, self.model_path)
else:
self.net = cv2.dnn.readNetFromONNX(self.model_path)
# 使用CPU确保兼容性
self.net.setPreferableBackend(cv2.dnn.DNN_BACKEND_OPENCV)
self.net.setPreferableTarget(cv2.dnn.DNN_TARGET_CPU)
logger.info("使用CPU进行推理")
return True
except Exception as e:
logger.error(f"模型加载失败: {e}")
return False
# 使用YOLO v4模型识别人体
class YOLODetector(OpenCVDNNDetector):
def __init__(self, config_path, model_path, confidence_threshold=0.5):
super().__init__(config_path, model_path, (416, 416), 1/255.0,
(0, 0, 0), True, confidence_threshold)
self.class_names = self.load_class_names()
def load_class_names(self):
"""加载COCO类别名称"""
coco_classes = [
'person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train',
'truck', 'boat', 'traffic light', 'fire hydrant', 'stop sign',
'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep', 'cow'
]
return coco_classes
def detect(self, frame):
"""使用YOLO检测人体"""
if self.net is None:
return []
(h, w) = frame.shape[:2]
# 准备输入blob
blob = cv2.dnn.blobFromImage(frame, self.scale_factor, self.input_size,
self.mean_values, swapRB=self.swap_rb, crop=False)
self.net.setInput(blob)
# 获取输出层名称
layer_names = self.net.getLayerNames()
try:
# OpenCV 4.x
output_layers = [layer_names[i - 1] for i in self.net.getUnconnectedOutLayers()]
except:
# OpenCV 3.x
output_layers = [layer_names[i[0] - 1] for i in self.net.getUnconnectedOutLayers()]
# 前向传播
outputs = self.net.forward(output_layers)
detections = []
boxes = []
confidences = []
class_ids = []
for output in outputs:
for detection in output:
scores = detection[5:]
class_id = np.argmax(scores)
confidence = scores[class_id]
# 只检测person类 (COCO数据集中的0类)
if confidence > self.confidence_threshold and class_id == 0:
center_x = int(detection[0] * w)
center_y = int(detection[1] * h)
box_width = int(detection[2] * w)
box_height = int(detection[3] * h)
x = int(center_x - box_width / 2)
y = int(center_y - box_height / 2)
boxes.append([x, y, box_width, box_height])
confidences.append(float(confidence))
class_ids.append(class_id)
# 应用非极大值抑制
indices = cv2.dnn.NMSBoxes(boxes, confidences, self.confidence_threshold, 0.4)
if len(indices) > 0:
for i in indices.flatten():
x, y, w, h = boxes[i]
detections.append({
'bbox': (x, y, x + w, y + h),
'confidence': confidences[i],
'class_id': class_ids[i],
'class_name': self.class_names[class_ids[i]] if class_ids[i] < len(self.class_names) else 'unknown'
})
return detections
# SSD人脸检测器
class SSDFaceDetector(OpenCVDNNDetector):
def __init__(self, config_path, model_path, confidence_threshold=0.5):
super().__init__(config_path, model_path, (300, 300), 1.0,
(104.0, 177.0, 123.0), False, confidence_threshold)
def detect(self, frame, roi_bbox=None):
"""检测人脸"""
if self.net is None:
return []
if roi_bbox is not None:
x1, y1, x2, y2 = roi_bbox
roi = frame[y1:y2, x1:x2]
if roi.size == 0:
return []
input_frame = roi
offset_x, offset_y = x1, y1
else:
input_frame = frame
offset_x, offset_y = 0, 0
(h, w) = input_frame.shape[:2]
# 准备输入blob
blob = cv2.dnn.blobFromImage(cv2.resize(input_frame, self.input_size),
self.scale_factor, self.input_size,
self.mean_values, swapRB=self.swap_rb)
self.net.setInput(blob)
detections = self.net.forward()
faces = []
for i in range(detections.shape[2]):
confidence = detections[0, 0, i, 2]
if confidence > self.confidence_threshold:
box = detections[0, 0, i, 3:7] * np.array([w, h, w, h])
(startX, startY, endX, endY) = box.astype("int")
# 转换回原图坐标
startX += offset_x
startY += offset_y
endX += offset_x
endY += offset_y
# 确保坐标在图像范围内
startX = max(0, startX)
startY = max(0, startY)
endX = min(frame.shape[1], endX)
endY = min(frame.shape[0], endY)
if endX > startX and endY > startY:
face_image = frame[startY:endY, startX:endX]
faces.append({
'bbox': (startX, startY, endX, endY),
'confidence': confidence,
'image': face_image
})
return faces
# 实现视频监控个人脸识别
class VideoSurveillanceFaceRecognition:
# 初始化基础数据
def __init__(self, config=None):
self.config = config or {}
# 播放监控视频选项
self.display_enabled = self.config.get('display_enabled', True)
# 保存所有人脸选项
self.save_all_faces = self.config.get('save_all_faces', False)
# 初始化检测器
self.person_detector = self.init_person_detector()
self.face_detector = self.init_face_detector()
self.quality_assessor = FaceQualityAssessor()
self.tracker = PersonTracker()
# 统计信息
self.stats = {
'total_frames': 0,
'persons_detected': 0,
'faces_captured': 0,
'high_quality_faces': 0,
'save_all_faces': 0,
'start_time': time.time()
}
# 创建输出目录
self.output_dir = self.config.get('output_dir', './faces_output')
os.makedirs(self.output_dir, exist_ok=True)
# 捕获人脸保存目录
self.all_faces_dir = os.path.join(self.output_dir, 'all_faces')
os.makedirs(self.all_faces_dir, exist_ok=True)
# 高质量人脸保存目录
self.faces_dir = os.path.join(self.output_dir, 'captured_faces')
os.makedirs(self.faces_dir, exist_ok=True)
logger.info("视频监控人脸识别系统初始化完成")
# 初始化人体检测器
def init_person_detector(self):
try:
# 使用YOLOv4-tiny进行人体检测
config_path = './models/yolov4-tiny.cfg'
model_path = './models/yolov4-tiny.weights'
detector = YOLODetector(config_path, model_path, confidence_threshold=0.5)
if detector.net is not None:
logger.info("YOLOv4-tiny人体检测器加载成功")
return detector
except Exception as e:
logger.error(f"人体检测器加载失败: {e}")
return None
# 初始化人脸检测器
def init_face_detector(self):
try:
# 使用OpenCV DNN人脸检测器
config_path = './models/deploy.prototxt'
model_path = './models/res10_300x300_ssd_iter_140000.caffemodel'
detector = SSDFaceDetector(config_path, model_path, confidence_threshold=0.5)
if detector.net is not None:
logger.info("SSD人脸检测器加载成功")
return detector
except Exception as e:
logger.error(f"人脸检测器加载失败: {e}")
return None
# 检测人体
def detect_persons(self, frame):
"""检测人体"""
if self.person_detector is None:
return []
return self.person_detector.detect(frame)
# 检测人脸
def detect_faces(self, frame, roi_bbox=None):
"""检测人脸"""
if self.face_detector is None:
return []
return self.face_detector.detect(frame, roi_bbox)
# 提取上半身ROI
def extract_upper_body(self, frame, person_bbox):
x1, y1, x2, y2 = person_bbox
height = y2 - y1
# 上半身通常占人体的上半部分 (60%)
upper_height = int(height * 0.6)
upper_y1 = y1
upper_y2 = y1 + upper_height
# 确保坐标有效
upper_y1 = max(0, upper_y1)
upper_y2 = min(frame.shape[0], upper_y2)
if upper_y2 > upper_y1:
return frame[upper_y1:upper_y2, x1:x2], (x1, upper_y1, x2, upper_y2)
else:
return None, None
# 是否应该捕获人脸
def should_capture_face(self, frame_count, track_id):
if track_id not in self.tracker.tracks:
return False
track = self.tracker.tracks[track_id]
frames_since_last = frame_count - track.get('last_face_capture', 0)
# 基础捕获间隔
base_interval = 15
adjusted_interval = max(3, base_interval // 3)
# 如果还没有高质量人脸,增加捕获频率
if track['best_quality'] < 0.7:
return frames_since_last > (8 // 3)
else:
return frames_since_last > base_interval
# 保存所有捕获的人脸信息 - 使用无损格式保存
def save_all_detected_faces(self, track_id, face, quality_score, quality_status, frame_count):
try:
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f")
# 创建文件名,包含质量信息
filename = f"person_{track_id}_{timestamp}_q{quality_score:.2f}_{quality_status}.png" # 使用PNG无损格式
filepath = os.path.join(self.all_faces_dir, filename)
# 使用无损格式保存人脸图片
cv2.imwrite(filepath, face['image'], [cv2.IMWRITE_PNG_COMPRESSION, 0])
# 更新统计
self.stats['save_all_faces'] += 1
except Exception as e:
logger.error(f"保存所有人脸失败: {e}")
# 保存捕获高质量人脸信息 - 使用无损格式保存
def save_high_quality_face(self, track_id, face_info):
if track_id not in self.tracker.tracks:
return
track = self.tracker.tracks[track_id]
timestamp = face_info['timestamp']
# 保存人脸图像 - 使用PNG无损格式
filename = f"person_{track_id}_{timestamp}_q{face_info['quality']:.2f}.png"
filepath = os.path.join(self.faces_dir, filename)
try:
cv2.imwrite(filepath, face_info['image'], [cv2.IMWRITE_PNG_COMPRESSION, 0])
logger.info(f"保存高质量人脸: {filename}")
self.stats['high_quality_faces'] += 1
except Exception as e:
logger.error(f"保存人脸失败: {e}")
# 输出结果 - 直接在原图画框,不创建新帧
def visualize_detections(self, frame, active_tracks, face_detections=None):
"""在原图上直接绘制检测结果,避免不必要的复制和编码"""
result_frame = frame # 直接使用原图,避免复制
# 绘制人体检测框
for track_id in active_tracks:
track = self.tracker.tracks[track_id]
x1, y1, x2, y2 = track['bbox']
# 绘制人体框(绿色)
cv2.rectangle(result_frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
# 绘制跟踪ID和质量信息
quality_info = f"ID:{track_id} Q:{track['best_quality']:.2f}"
cv2.putText(result_frame, quality_info, (x1, y1 - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
# 绘制人脸检测框(红色)
if face_detections:
for face in face_detections:
x1, y1, x2, y2 = face['bbox']
cv2.rectangle(result_frame, (x1, y1), (x2, y2), (0, 0, 255), 2)
cv2.putText(result_frame, f"Face:{face['confidence']:.2f}",
(x1, y1 - 25), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2)
# 添加统计信息
stats_text = [
f"Frames: {self.stats['total_frames']}",
f"Persons: {self.stats['persons_detected']}",
f"Faces: {self.stats['faces_captured']}",
f"HQ Faces: {self.stats['high_quality_faces']}"
]
for i, text in enumerate(stats_text):
cv2.putText(result_frame, text, (10, 30 + i * 25),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
return result_frame
def process_video_stream(self, video_path):
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
logger.error(f"无法打开视频文件: {video_path}")
return
# 获取视频信息
fps = cap.get(cv2.CAP_PROP_FPS)
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
logger.info(f"视频信息: {width}x{height}, {fps:.2f} FPS, 总帧数: {total_frames}")
frame_count = 0
last_stat_time = time.time()
# 处理间隔设置
process_interval = 2 # 每3帧处理一次
# 窗口创建
window_name = 'Video Surveillance Face Recognition'
if self.display_enabled:
cv2.namedWindow(window_name, cv2.WINDOW_NORMAL)
# 如果图像太大,适当缩放以适应屏幕,但保持宽高比
screen_width = 1920 # 假设屏幕宽度
screen_height = 1080 # 假设屏幕高度
if width > screen_width or height > screen_height:
scale = min(screen_width / width, screen_height / height)
display_width = int(width * scale)
display_height = int(height * scale)
else:
# 直接使用原始分辨率
display_width = width
display_height = height
cv2.resizeWindow(window_name, 1920, 1080)
logger.info("开始处理视频...")
logger.info("'q' 键退出,按 'p' 键暂停/继续")
paused = False
while True:
if not paused:
ret, frame = cap.read()
if not ret:
break
frame_count += 1
self.stats['total_frames'] = frame_count
should_process = (frame_count % process_interval == 1)
if should_process:
# 1. 人体检测(每帧都做)
person_detections = self.detect_persons(frame)
self.stats['persons_detected'] += len(person_detections)
# 2. 更新跟踪器
active_tracks = self.tracker.update(person_detections, frame_count)
current_face_detections = []
# 3. 人脸捕获
for track_id in active_tracks:
if self.should_capture_face(frame_count, track_id):
person_bbox = self.tracker.tracks[track_id]['bbox']
# 提取上半身ROI
upper_body_roi, roi_bbox = self.extract_upper_body(frame, person_bbox)
if upper_body_roi is not None:
# 在ROI内检测人脸
face_detections = self.detect_faces(frame, roi_bbox)
current_face_detections.extend(face_detections)
for face in face_detections:
# 评估人脸质量
quality_score, quality_status = self.quality_assessor.assess_face_quality(
face['image'], face['bbox']
)
self.stats['faces_captured'] += 1
if self.save_all_faces:
self.save_all_detected_faces(track_id, face, quality_score, quality_status, frame_count)
if quality_status == "good":
# 更新跟踪器
self.tracker.update_face_for_track(
track_id, face['image'], quality_score, frame_count
)
# 保存高质量人脸
if track_id in self.tracker.tracks:
best_face = self.tracker.tracks[track_id]['best_face']
if best_face and best_face['quality'] == quality_score:
self.save_high_quality_face(track_id, best_face)
# 4. 绘制结果 - 直接在原图上绘制
visualized_frame = self.visualize_detections(frame, active_tracks, current_face_detections)
# 5. 显示视频窗口 - 保持原始分辨率显示
if self.display_enabled:
# 根据预设的显示尺寸调整图像
if 'display_width' in locals() and display_width != width:
display_frame = cv2.resize(visualized_frame, (display_width, display_height))
cv2.imshow(window_name, display_frame)
else:
# 直接显示原始分辨率
cv2.imshow(window_name, visualized_frame)
# 键盘控制
key = cv2.waitKey(1) & 0xFF
if key == ord('q'): # 退出
break
elif key == ord('p'): # 暂停/继续
paused = not paused
logger.info("暂停" if paused else "继续")
elif key == ord('s'): # 保存当前帧 - 使用无损格式
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f")
save_path = os.path.join(self.output_dir, f"snapshot_{timestamp}.png")
cv2.imwrite(save_path, frame, [cv2.IMWRITE_PNG_COMPRESSION, 0]) # 保存原始帧,无损格式
logger.info(f"保存快照(无损): {save_path}")
# 6. 定期输出统计信息
current_time = time.time()
if current_time - last_stat_time > 5 and not paused: # 每5秒输出一次
self.report_statistics(frame_count, total_frames)
last_stat_time = current_time
# 7. 显示进度(可选)
if frame_count % 100 == 0 and not paused:
progress = (frame_count / total_frames) * 100 if total_frames > 0 else 0
logger.info(f"处理进度: {progress:.1f}% ({frame_count}/{total_frames})")
# 清理资源
cap.release()
if self.display_enabled:
cv2.destroyAllWindows()
# 最终统计报告
self.final_report()
logger.info("视频处理完成")
def report_statistics(self, frame_count, total_frames):
"""输出统计信息"""
elapsed_time = time.time() - self.stats['start_time']
fps = frame_count / elapsed_time if elapsed_time > 0 else 0
progress = (frame_count / total_frames) * 100 if total_frames > 0 else 0
logger.info(
f"进度: {progress:.1f}% | "
f"帧率: {fps:.1f} FPS | "
f"人体: {self.stats['persons_detected']} | "
f"人脸: {self.stats['faces_captured']} | "
f"高质量: {self.stats['high_quality_faces']}"
)
def final_report(self):
"""最终统计报告"""
total_time = time.time() - self.stats['start_time']
logger.info("\n" + "="*50)
logger.info("处理完成统计报告:")
logger.info(f"总处理时间: {total_time:.2f}")
logger.info(f"总帧数: {self.stats['total_frames']}")
logger.info(f"检测到人体数: {self.stats['persons_detected']}")
logger.info(f"捕获人脸数: {self.stats['faces_captured']}")
logger.info(f"高质量人脸数: {self.stats['high_quality_faces']}")
logger.info(f"平均帧率: {self.stats['total_frames']/total_time:.2f} FPS")
# 跟踪统计
total_tracks = self.tracker.next_id
active_tracks = len(self.tracker.tracks)
logger.info(f"总跟踪目标: {total_tracks}, 活跃目标: {active_tracks}")
# 高质量人脸统计
hq_count = 0
for track_id, track in self.tracker.tracks.items():
if track['best_quality'] > 0.7:
hq_count += 1
logger.info(f"获得高质量人脸的目标数: {hq_count}/{total_tracks}")
logger.info("="*50)
def main():
config = {
'output_dir': './faces_output',
'display_enabled': True, # 启用视频显示
'save_all_faces': True,
}
# 创建系统实例
system = VideoSurveillanceFaceRecognition(config)
# 处理视频 - 不输出视频文件,专注于高质量人脸捕获
video_path = "/home/mbh/视频/2025-10-30/2_28_zhongwu.mp4"
if not os.path.exists(video_path):
logger.error(f"视频文件不存在: {video_path}")
return
system.process_video_stream(video_path)
if __name__ == "__main__":
main()

View File

@ -1,781 +0,0 @@
import cv2
import numpy as np
import time
import os
import json
from collections import deque
from datetime import datetime
import logging
import urllib.request
import math
# 日志格式
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
# 用于检查人脸质量
class FaceQualityAssessor:
def __init__(self):
# 设置人脸图像最小尺寸及人脸质量最低分数
self.min_face_size = 80
self.min_quality_score = 0.6
# 计算清晰度
def calculate_sharpness(self, image):
if len(image.shape) == 3:
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
else:
gray = image
return cv2.Laplacian(gray, cv2.CV_64F).var()
# 检查亮度
def assess_brightness(self, image):
if len(image.shape) == 3:
hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
brightness = np.mean(hsv[:,:,2]) / 255.0
else:
brightness = np.mean(image) / 255.0
# 理想亮度在0.3-0.8之间
if 0.3 <= brightness <= 0.8:
return 1.0
else:
return max(0, 1 - abs(brightness - 0.55))
# 检查人脸姿势
def assess_face_pose(self, face_image):
height, width = face_image.shape[:2]
aspect_ratio = width / height if height > 0 else 1
# 正面人脸的宽高比通常在0.8-1.2之间
if 0.8 <= aspect_ratio <= 1.2:
return 0.9
elif 0.6 <= aspect_ratio <= 1.4:
return 0.7
else:
return 0.4
# 检查遮挡情况
def assess_occlusion(self, face_image):
gray = cv2.cvtColor(face_image, cv2.COLOR_BGR2GRAY) if len(face_image.shape) == 3 else face_image
edges = cv2.Canny(gray, 50, 150)
edge_density = np.sum(edges) / (gray.shape[0] * gray.shape[1] * 255)
if edge_density > 0.1:
return 0.8
else:
return 0.5
# 检查人脸质量
def assess_face_quality(self, face_image, bbox=None):
if face_image is None or face_image.size == 0:
return 0.0, "invalid_image"
height, width = face_image.shape[:2]
# 1. 尺寸检查
if height < self.min_face_size or width < self.min_face_size:
return 0.0, "face_too_small"
# 2. 清晰度评估
sharpness = self.calculate_sharpness(face_image)
if sharpness < 20:
return 0.0, "blurry"
sharpness_score = min(1.0, sharpness / 100.0)
# 3. 亮度评估
brightness_score = self.assess_brightness(face_image)
if brightness_score < 0.3:
return 0.0, "poor_lighting"
# 4. 姿态评估
pose_score = self.assess_face_pose(face_image)
# 5. 遮挡评估
occlusion_score = self.assess_occlusion(face_image)
# 综合质量分数
quality_score = (sharpness_score * 0.3 +
brightness_score * 0.25 +
pose_score * 0.25 +
occlusion_score * 0.2)
quality_status = "good" if quality_score > self.min_quality_score else "poor_quality"
return quality_score, quality_status
# 人体追踪器
class PersonTracker:
def __init__(self, max_frames_lost=30):
self.tracks = {}
self.next_id = 0
self.max_frames_lost = max_frames_lost
def calculate_iou(self, box1, box2):
"""计算两个框的IOU"""
x1_1, y1_1, x2_1, y2_1 = box1
x1_2, y1_2, x2_2, y2_2 = box2
# 计算交集区域
xi1 = max(x1_1, x1_2)
yi1 = max(y1_1, y1_2)
xi2 = min(x2_1, x2_2)
yi2 = min(y2_1, y2_2)
inter_area = max(0, xi2 - xi1) * max(0, yi2 - yi1)
# 计算并集区域
box1_area = (x2_1 - x1_1) * (y2_1 - y1_1)
box2_area = (x2_2 - x1_2) * (y2_2 - y1_2)
union_area = box1_area + box2_area - inter_area
return inter_area / union_area if union_area > 0 else 0
def match_detection(self, detection_bbox):
"""匹配检测到现有跟踪"""
best_iou = 0.3
best_id = None
for track_id, track in self.tracks.items():
iou = self.calculate_iou(track['bbox'], detection_bbox)
if iou > best_iou:
best_iou = iou
best_id = track_id
return best_id
def update(self, detections, frame_count):
"""更新跟踪器"""
active_ids = []
for det in detections:
bbox = det['bbox']
matched_id = self.match_detection(bbox)
if matched_id is None:
matched_id = self.next_id
self.next_id += 1
self.tracks[matched_id] = {
'bbox': bbox,
'last_seen': frame_count,
'first_seen': frame_count,
'best_face': None,
'best_quality': 0,
'face_history': deque(maxlen=15),
'last_face_capture': 0
}
else:
# 更新现有跟踪
self.tracks[matched_id]['bbox'] = bbox
self.tracks[matched_id]['last_seen'] = frame_count
active_ids.append(matched_id)
# 清理丢失的跟踪
self.cleanup_tracks(frame_count)
return active_ids
def cleanup_tracks(self, frame_count):
"""清理丢失的跟踪"""
tracks_to_remove = []
for track_id, track in self.tracks.items():
if frame_count - track['last_seen'] > self.max_frames_lost:
tracks_to_remove.append(track_id)
for track_id in tracks_to_remove:
del self.tracks[track_id]
def update_face_for_track(self, track_id, face_image, quality_score, frame_count):
"""为跟踪目标更新最佳人脸"""
if track_id in self.tracks:
track = self.tracks[track_id]
# 只保存质量更好的人脸
if quality_score > track['best_quality']:
track['best_face'] = {
'image': face_image.copy(),
'quality': quality_score,
'frame_count': frame_count,
'timestamp': datetime.now().strftime("%Y%m%d_%H%M%S_%f")
}
track['best_quality'] = quality_score
track['last_face_capture'] = frame_count
# 保存到历史记录
track['face_history'].append({
'image': face_image.copy(),
'quality': quality_score,
'frame_count': frame_count
})
# 使用OpenCV DNN检测器识别人脸
class OpenCVDNNDetector:
def __init__(self, config_path, model_path, input_size, scale_factor,
mean_values, swap_rb=True, confidence_threshold=0.5):
self.net = None
self.config_path = config_path
self.model_path = model_path
self.input_size = input_size
self.scale_factor = scale_factor
self.mean_values = mean_values
self.swap_rb = swap_rb
self.confidence_threshold = confidence_threshold
self.load_model()
def load_model(self):
"""加载模型"""
if not os.path.exists(self.model_path) or not os.path.exists(self.config_path):
logger.warning(f"模型文件不存在: {self.config_path}{self.model_path}")
return False
try:
if self.config_path.endswith('.cfg'):
self.net = cv2.dnn.readNetFromDarknet(self.config_path, self.model_path)
elif self.config_path.endswith('.prototxt'):
self.net = cv2.dnn.readNetFromCaffe(self.config_path, self.model_path)
else:
self.net = cv2.dnn.readNetFromONNX(self.model_path)
# 使用CPU确保兼容性
self.net.setPreferableBackend(cv2.dnn.DNN_BACKEND_OPENCV)
self.net.setPreferableTarget(cv2.dnn.DNN_TARGET_CPU)
logger.info("使用CPU进行推理")
return True
except Exception as e:
logger.error(f"模型加载失败: {e}")
return False
# 使用YOLO v4模型识别人体
class YOLODetector(OpenCVDNNDetector):
def __init__(self, config_path, model_path, confidence_threshold=0.5):
super().__init__(config_path, model_path, (416, 416), 1/255.0,
(0, 0, 0), True, confidence_threshold)
self.class_names = self.load_class_names()
def load_class_names(self):
"""加载COCO类别名称"""
coco_classes = [
'person', 'bicycle', 'car', 'motorcycle', 'airplane', 'bus', 'train',
'truck', 'boat', 'traffic light', 'fire hydrant', 'stop sign',
'parking meter', 'bench', 'bird', 'cat', 'dog', 'horse', 'sheep', 'cow'
]
return coco_classes
def detect(self, frame):
"""使用YOLO检测人体"""
if self.net is None:
return []
(h, w) = frame.shape[:2]
# 准备输入blob
blob = cv2.dnn.blobFromImage(frame, self.scale_factor, self.input_size,
self.mean_values, swapRB=self.swap_rb, crop=False)
self.net.setInput(blob)
# 获取输出层名称
layer_names = self.net.getLayerNames()
try:
# OpenCV 4.x
output_layers = [layer_names[i - 1] for i in self.net.getUnconnectedOutLayers()]
except:
# OpenCV 3.x
output_layers = [layer_names[i[0] - 1] for i in self.net.getUnconnectedOutLayers()]
# 前向传播
outputs = self.net.forward(output_layers)
detections = []
boxes = []
confidences = []
class_ids = []
for output in outputs:
for detection in output:
scores = detection[5:]
class_id = np.argmax(scores)
confidence = scores[class_id]
# 只检测person类 (COCO数据集中的0类)
if confidence > self.confidence_threshold and class_id == 0:
center_x = int(detection[0] * w)
center_y = int(detection[1] * h)
box_width = int(detection[2] * w)
box_height = int(detection[3] * h)
x = int(center_x - box_width / 2)
y = int(center_y - box_height / 2)
boxes.append([x, y, box_width, box_height])
confidences.append(float(confidence))
class_ids.append(class_id)
# 应用非极大值抑制
indices = cv2.dnn.NMSBoxes(boxes, confidences, self.confidence_threshold, 0.4)
if len(indices) > 0:
for i in indices.flatten():
x, y, w, h = boxes[i]
detections.append({
'bbox': (x, y, x + w, y + h),
'confidence': confidences[i],
'class_id': class_ids[i],
'class_name': self.class_names[class_ids[i]] if class_ids[i] < len(self.class_names) else 'unknown'
})
return detections
# SSD人脸检测器
class SSDFaceDetector(OpenCVDNNDetector):
def __init__(self, config_path, model_path, confidence_threshold=0.5):
super().__init__(config_path, model_path, (300, 300), 1.0,
(104.0, 177.0, 123.0), False, confidence_threshold)
def detect(self, frame, roi_bbox=None):
"""检测人脸"""
if self.net is None:
return []
if roi_bbox is not None:
x1, y1, x2, y2 = roi_bbox
roi = frame[y1:y2, x1:x2]
if roi.size == 0:
return []
input_frame = roi
offset_x, offset_y = x1, y1
else:
input_frame = frame
offset_x, offset_y = 0, 0
(h, w) = input_frame.shape[:2]
# 准备输入blob
blob = cv2.dnn.blobFromImage(cv2.resize(input_frame, self.input_size),
self.scale_factor, self.input_size,
self.mean_values, swapRB=self.swap_rb)
self.net.setInput(blob)
detections = self.net.forward()
faces = []
for i in range(detections.shape[2]):
confidence = detections[0, 0, i, 2]
if confidence > self.confidence_threshold:
box = detections[0, 0, i, 3:7] * np.array([w, h, w, h])
(startX, startY, endX, endY) = box.astype("int")
# 转换回原图坐标
startX += offset_x
startY += offset_y
endX += offset_x
endY += offset_y
# 确保坐标在图像范围内
startX = max(0, startX)
startY = max(0, startY)
endX = min(frame.shape[1], endX)
endY = min(frame.shape[0], endY)
if endX > startX and endY > startY:
face_image = frame[startY:endY, startX:endX]
faces.append({
'bbox': (startX, startY, endX, endY),
'confidence': confidence,
'image': face_image
})
return faces
# 实现视频监控个人脸识别
class VideoSurveillanceFaceRecognition:
# 初始化基础数据
def __init__(self, config=None):
self.config = config or {}
# 播放监控视频选项
self.display_enabled = self.config.get('display_enabled', True)
# 保存所有人脸选项
self.save_all_faces = self.config.get('save_all_faces', False)
# 初始化检测器
self.person_detector = self.init_person_detector()
self.face_detector = self.init_face_detector()
self.quality_assessor = FaceQualityAssessor()
self.tracker = PersonTracker()
# 统计信息
self.stats = {
'total_frames': 0,
'persons_detected': 0,
'faces_captured': 0,
'high_quality_faces': 0,
'save_all_faces': 0,
'start_time': time.time()
}
# 创建输出目录
self.output_dir = self.config.get('output_dir', './faces_output')
os.makedirs(self.output_dir, exist_ok=True)
# 捕获人脸保存目录
self.all_faces_dir = os.path.join(self.output_dir, 'all_faces')
os.makedirs(self.all_faces_dir, exist_ok=True)
# 高质量人脸保存目录
self.faces_dir = os.path.join(self.output_dir, 'captured_faces')
os.makedirs(self.faces_dir, exist_ok=True)
logger.info("视频监控人脸识别系统初始化完成")
# 初始化人体检测器
def init_person_detector(self):
try:
# 使用YOLOv4-tiny进行人体检测
config_path = './models/yolov4-tiny.cfg'
model_path = './models/yolov4-tiny.weights'
detector = YOLODetector(config_path, model_path, confidence_threshold=0.5)
if detector.net is not None:
logger.info("YOLOv4-tiny人体检测器加载成功")
return detector
except Exception as e:
logger.error(f"人体检测器加载失败: {e}")
return None
# 初始化人脸检测器
def init_face_detector(self):
try:
# 使用OpenCV DNN人脸检测器
config_path = './models/deploy.prototxt'
model_path = './models/res10_300x300_ssd_iter_140000.caffemodel'
detector = SSDFaceDetector(config_path, model_path, confidence_threshold=0.5)
if detector.net is not None:
logger.info("SSD人脸检测器加载成功")
return detector
except Exception as e:
logger.error(f"人脸检测器加载失败: {e}")
return None
# 检测人体
def detect_persons(self, frame):
"""检测人体"""
if self.person_detector is None:
return []
return self.person_detector.detect(frame)
# 检测人脸
def detect_faces(self, frame, roi_bbox=None):
"""检测人脸"""
if self.face_detector is None:
return []
return self.face_detector.detect(frame, roi_bbox)
# 是否应该捕获人脸
def should_capture_face(self, frame_count, track_id):
if track_id not in self.tracker.tracks:
return False
track = self.tracker.tracks[track_id]
frames_since_last = frame_count - track.get('last_face_capture', 0)
# 基础捕获间隔
base_interval = 15
adjusted_interval = max(3, base_interval // 3)
# 如果还没有高质量人脸,增加捕获频率
if track['best_quality'] < 0.7:
return frames_since_last > (8 // 3)
else:
return frames_since_last > base_interval
# 保存所有捕获的人脸信息 - 使用无损格式保存
def save_all_detected_faces(self, track_id, face, quality_score, quality_status, frame_count):
try:
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f")
# 创建文件名,包含质量信息
filename = f"person_{track_id}_{timestamp}_q{quality_score:.2f}_{quality_status}.png" # 使用PNG无损格式
filepath = os.path.join(self.all_faces_dir, filename)
# 使用无损格式保存人脸图片
cv2.imwrite(filepath, face['image'], [cv2.IMWRITE_PNG_COMPRESSION, 0])
# 更新统计
self.stats['save_all_faces'] += 1
except Exception as e:
logger.error(f"保存所有人脸失败: {e}")
# 保存捕获高质量人脸信息 - 使用无损格式保存
def save_high_quality_face(self, track_id, face_info):
if track_id not in self.tracker.tracks:
return
track = self.tracker.tracks[track_id]
timestamp = face_info['timestamp']
# 保存人脸图像 - 使用PNG无损格式
filename = f"person_{track_id}_{timestamp}_q{face_info['quality']:.2f}.png"
filepath = os.path.join(self.faces_dir, filename)
try:
cv2.imwrite(filepath, face_info['image'], [cv2.IMWRITE_PNG_COMPRESSION, 0])
logger.info(f"保存高质量人脸: {filename}")
self.stats['high_quality_faces'] += 1
except Exception as e:
logger.error(f"保存人脸失败: {e}")
# 输出结果 - 直接在原图画框,不创建新帧
def visualize_detections(self, frame, active_tracks, face_detections=None):
"""在原图上直接绘制检测结果,避免不必要的复制和编码"""
result_frame = frame # 直接使用原图,避免复制
# 绘制人体检测框
for track_id in active_tracks:
track = self.tracker.tracks[track_id]
x1, y1, x2, y2 = track['bbox']
# 绘制人体框(绿色)
cv2.rectangle(result_frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
# 绘制跟踪ID和质量信息
quality_info = f"ID:{track_id} Q:{track['best_quality']:.2f}"
cv2.putText(result_frame, quality_info, (x1, y1 - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
# 绘制人脸检测框(红色)
if face_detections:
for face in face_detections:
x1, y1, x2, y2 = face['bbox']
cv2.rectangle(result_frame, (x1, y1), (x2, y2), (0, 0, 255), 2)
cv2.putText(result_frame, f"Face:{face['confidence']:.2f}",
(x1, y1 - 25), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2)
# 添加统计信息
stats_text = [
f"Frames: {self.stats['total_frames']}",
f"Persons: {self.stats['persons_detected']}",
f"Faces: {self.stats['faces_captured']}",
f"HQ Faces: {self.stats['high_quality_faces']}"
]
for i, text in enumerate(stats_text):
cv2.putText(result_frame, text, (10, 30 + i * 25),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
return result_frame
def process_video_stream(self, video_path):
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
logger.error(f"无法打开视频文件: {video_path}")
return
# 获取视频信息
fps = cap.get(cv2.CAP_PROP_FPS)
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
logger.info(f"视频信息: {width}x{height}, {fps:.2f} FPS, 总帧数: {total_frames}")
frame_count = 0
last_stat_time = time.time()
# 处理间隔设置
process_interval = 2 # 每3帧处理一次
# 窗口创建
window_name = 'Video Surveillance Face Recognition'
if self.display_enabled:
cv2.namedWindow(window_name, cv2.WINDOW_NORMAL)
# 如果图像太大,适当缩放以适应屏幕,但保持宽高比
screen_width = 1920 # 假设屏幕宽度
screen_height = 1080 # 假设屏幕高度
if width > screen_width or height > screen_height:
scale = min(screen_width / width, screen_height / height)
display_width = int(width * scale)
display_height = int(height * scale)
else:
# 直接使用原始分辨率
display_width = width
display_height = height
cv2.resizeWindow(window_name, 1920, 1080)
logger.info("开始处理视频...")
logger.info("'q' 键退出,按 'p' 键暂停/继续")
paused = False
while True:
if not paused:
ret, frame = cap.read()
if not ret:
break
frame_count += 1
self.stats['total_frames'] = frame_count
should_process = (frame_count % process_interval == 1)
if should_process:
# 1. 人体检测(每帧都做)
person_detections = self.detect_persons(frame)
self.stats['persons_detected'] += len(person_detections)
# 2. 更新跟踪器
active_tracks = self.tracker.update(person_detections, frame_count)
current_face_detections = []
# 3. 人脸捕获
for track_id in active_tracks:
if self.should_capture_face(frame_count, track_id):
person_bbox = self.tracker.tracks[track_id]['bbox']
# 直接在整个人体区域内检测人脸
face_detections = self.detect_faces(frame, person_bbox)
current_face_detections.extend(face_detections)
for face in face_detections:
# 评估人脸质量
quality_score, quality_status = self.quality_assessor.assess_face_quality(
face['image'], face['bbox']
)
self.stats['faces_captured'] += 1
if self.save_all_faces:
self.save_all_detected_faces(track_id, face, quality_score, quality_status, frame_count)
if quality_status == "good":
# 更新跟踪器
self.tracker.update_face_for_track(
track_id, face['image'], quality_score, frame_count
)
# 保存高质量人脸
if track_id in self.tracker.tracks:
best_face = self.tracker.tracks[track_id]['best_face']
if best_face and best_face['quality'] == quality_score:
self.save_high_quality_face(track_id, best_face)
# 4. 绘制结果 - 直接在原图上绘制
visualized_frame = self.visualize_detections(frame, active_tracks, current_face_detections)
else:
# 非处理帧,直接显示原图
visualized_frame = frame
# 5. 显示视频窗口 - 保持原始分辨率显示
if self.display_enabled:
# 根据预设的显示尺寸调整图像
if 'display_width' in locals() and display_width != width:
display_frame = cv2.resize(visualized_frame, (display_width, display_height))
cv2.imshow(window_name, display_frame)
else:
# 直接显示原始分辨率
cv2.imshow(window_name, visualized_frame)
# 键盘控制
key = cv2.waitKey(1) & 0xFF
if key == ord('q'): # 退出
break
elif key == ord('p'): # 暂停/继续
paused = not paused
logger.info("暂停" if paused else "继续")
elif key == ord('s'): # 保存当前帧 - 使用无损格式
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f")
save_path = os.path.join(self.output_dir, f"snapshot_{timestamp}.png")
cv2.imwrite(save_path, frame, [cv2.IMWRITE_PNG_COMPRESSION, 0]) # 保存原始帧,无损格式
logger.info(f"保存快照(无损): {save_path}")
# 6. 定期输出统计信息
current_time = time.time()
if current_time - last_stat_time > 7 and not paused: # 每7秒输出一次
self.report_statistics(frame_count, total_frames)
last_stat_time = current_time
# 7. 显示进度(可选)
if frame_count % 100 == 0 and not paused:
progress = (frame_count / total_frames) * 100 if total_frames > 0 else 0
logger.info(f"处理进度: {progress:.1f}% ({frame_count}/{total_frames})")
# 清理资源
cap.release()
if self.display_enabled:
cv2.destroyAllWindows()
# 最终统计报告
self.final_report()
logger.info("视频处理完成")
def report_statistics(self, frame_count, total_frames):
"""输出统计信息"""
elapsed_time = time.time() - self.stats['start_time']
fps = frame_count / elapsed_time if elapsed_time > 0 else 0
progress = (frame_count / total_frames) * 100 if total_frames > 0 else 0
logger.info(
f"进度: {progress:.1f}% | "
f"帧率: {fps:.1f} FPS | "
f"人体: {self.stats['persons_detected']} | "
f"人脸: {self.stats['faces_captured']} | "
f"高质量: {self.stats['high_quality_faces']}"
)
def final_report(self):
total_time = time.time() - self.stats['start_time']
logger.info("\n" + "="*50)
logger.info("处理完成统计报告:")
logger.info(f"总处理时间: {total_time:.2f}")
logger.info(f"总帧数: {self.stats['total_frames']}")
logger.info(f"检测到人体数: {self.stats['persons_detected']}")
logger.info(f"捕获人脸数: {self.stats['faces_captured']}")
logger.info(f"高质量人脸数: {self.stats['high_quality_faces']}")
logger.info(f"平均帧率: {self.stats['total_frames']/total_time:.2f} FPS")
# 跟踪统计
total_tracks = self.tracker.next_id
active_tracks = len(self.tracker.tracks)
logger.info(f"总跟踪目标: {total_tracks}, 活跃目标: {active_tracks}")
# 高质量人脸统计
hq_count = 0
for track_id, track in self.tracker.tracks.items():
if track['best_quality'] > 0.7:
hq_count += 1
logger.info(f"获得高质量人脸的目标数: {hq_count}/{total_tracks}")
logger.info("="*50)
def main():
config = {
'output_dir': './faces_output',
'display_enabled': True,
'save_all_faces': True,
}
# 创建系统实例
system = VideoSurveillanceFaceRecognition(config)
video_path = "/home/mbh/视频/2025-10-30/3_28_xiawu.mp4"
if not os.path.exists(video_path):
logger.error(f"视频文件不存在: {video_path}")
return
system.process_video_stream(video_path)
if __name__ == "__main__":
main()

View File

@ -1,136 +0,0 @@
# Copyright (C) 2018-2021 coneypo
# SPDX-License-Identifier: MIT
import os
import dlib
import csv
import numpy as np
import logging
import cv2
from PIL import Image
# 要读取人脸图像文件的路径
path_images_from_camera = "data/data_faces"
# Dlib 检测器
detector = dlib.get_frontal_face_detector()
predictor = dlib.shape_predictor('models/dlib/shape_predictor_68_face_landmarks.dat')
face_reco_model = dlib.face_recognition_model_v1("models/dlib/dlib_face_recognition_resnet_model_v1.dat")
def return_128d_features(path_img):
"""返回单张图像的 128D 特征"""
try:
img_pil = Image.open(path_img)
img_np = np.array(img_pil)
img_rd = cv2.cvtColor(img_np, cv2.COLOR_RGB2BGR)
faces = detector(img_rd, 1)
logging.info("%-40s %-20s", "检测到人脸的图像:", path_img)
if len(faces) != 0:
shape = predictor(img_rd, faces[0])
face_descriptor = face_reco_model.compute_face_descriptor(img_rd, shape)
return face_descriptor
else:
logging.warning("未检测到人脸: %s", path_img)
return None
except Exception as e:
logging.error("处理图像时出错 %s: %s", path_img, e)
return None
def return_features_mean_personX(path_face_personX):
"""返回 personX 的 128D 特征均值"""
features_list_personX = []
photos_list = os.listdir(path_face_personX)
if photos_list:
for photo in photos_list:
photo_path = os.path.join(path_face_personX, photo)
logging.info("正在读取图像: %s", photo_path)
features_128d = return_128d_features(photo_path)
if features_128d is not None:
features_list_personX.append(features_128d)
else:
logging.warning("文件夹为空: %s", path_face_personX)
# 计算 128D 特征的均值
if features_list_personX:
features_mean_personX = np.array(features_list_personX).mean(axis=0)
else:
features_mean_personX = np.zeros(128, dtype=np.float64)
return features_mean_personX
def get_person_name_from_folder(folder_name):
"""从文件夹名称获取有意义的姓名"""
# 常见的文件夹前缀
prefixes = ['person_', 'face_', 'user_']
for prefix in prefixes:
if folder_name.startswith(prefix):
name_part = folder_name[len(prefix):]
# 如果剩下的部分是纯数字,使用完整文件夹名
if name_part.isdigit():
return folder_name
else:
return name_part
return folder_name
def main():
logging.basicConfig(level=logging.INFO)
# 检查源文件夹是否存在
if not os.path.exists(path_images_from_camera):
logging.error("人脸图像文件夹不存在: %s", path_images_from_camera)
return
# 获取人脸文件夹列表
person_list = os.listdir(path_images_from_camera)
person_list.sort()
if not person_list:
logging.error("没有人脸文件夹可处理")
return
logging.info("找到 %d 个人脸文件夹: %s", len(person_list), person_list)
# 创建CSV文件
with open("data/features_all.csv", "w", newline="", encoding="utf-8") as csvfile:
writer = csv.writer(csvfile)
successful_count = 0
for person_folder in person_list:
folder_path = os.path.join(path_images_from_camera, person_folder)
if not os.path.isdir(folder_path):
continue
logging.info("处理文件夹: %s", person_folder)
# 提取特征
features_mean = return_features_mean_personX(folder_path)
# 获取有意义的姓名
person_name = get_person_name_from_folder(person_folder)
logging.info("使用姓名: %s", person_name)
# 构建行数据:姓名 + 128维特征
row_data = [person_name] + features_mean.tolist()
writer.writerow(row_data)
successful_count += 1
logging.info("完成: %s", person_name)
logging.info("-" * 50)
logging.info("成功处理 %d/%d 个人脸文件夹", successful_count, len(person_list))
logging.info("特征数据已保存到: data/features_all.csv")
if __name__ == '__main__':
main()

View File

@ -1,483 +0,0 @@
# Copyright (C) 2018-2021 coneypo
# SPDX-License-Identifier: MIT
import os
import dlib
import csv
import numpy as np
import logging
import cv2
from PIL import Image
# 要读取人脸图像文件的路径
path_images_from_camera = "data/data_faces"
# Dlib 检测器
predictor = dlib.shape_predictor('models/dlib/shape_predictor_68_face_landmarks.dat')
face_reco_model = dlib.face_recognition_model_v1("models/dlib/dlib_face_recognition_resnet_model_v1.dat")
class DNNFaceDetector:
def __init__(self, confidence_threshold=0.7):
"""初始化OpenCV DNN人脸检测器"""
self.confidence_threshold = confidence_threshold
self.net = self.load_face_detection_model()
def load_face_detection_model(self):
"""加载OpenCV DNN人脸检测模型"""
try:
# 方法1: 尝试加载TensorFlow模型
model_path = "models/opencv_face_detector_uint8.pb"
config_path = "models/opencv_face_detector.pbtxt"
if os.path.exists(model_path) and os.path.exists(config_path):
net = cv2.dnn.readNetFromTensorflow(model_path, config_path)
print("使用 TensorFlow 人脸检测模型")
self.use_tensorflow = True
return net
except Exception as e:
print(f"TensorFlow模型加载失败: {e}")
try:
# 方法2: 尝试加载Caffe模型
proto_path = "models/deploy.prototxt"
model_path = "models/res10_300x300_ssd_iter_140000_fp16.caffemodel"
if os.path.exists(proto_path) and os.path.exists(model_path):
net = cv2.dnn.readNetFromCaffe(proto_path, model_path)
print("使用 Caffe 人脸检测模型")
self.use_tensorflow = False
return net
except Exception as e:
print(f"Caffe模型加载失败: {e}")
# 方法3: 使用OpenCV内置的Haar级联作为备选
print("使用 Haar Cascade 作为备选检测器")
self.use_haar = True
cascade_path ='models/haarcascade_frontalface_default.xml'
if os.path.exists(cascade_path):
return cv2.CascadeClassifier(cascade_path)
else:
# 如果内置路径不存在,尝试其他路径
possible_paths = [
'/usr/share/opencv4/haarcascades/haarcascade_frontalface_default.xml',
'/usr/share/opencv/haarcascades/haarcascade_frontalface_default.xml',
'haarcascade_frontalface_default.xml'
]
for path in possible_paths:
if os.path.exists(path):
return cv2.CascadeClassifier(path)
print("错误: 未找到任何人脸检测模型")
return None
def detect_faces(self, image):
"""使用DNN检测人脸"""
if hasattr(self, 'use_haar') and self.use_haar:
return self.detect_faces_haar(image)
if self.net is None:
return []
h, w = image.shape[:2]
# 创建blob输入
if hasattr(self, 'use_tensorflow') and self.use_tensorflow:
blob = cv2.dnn.blobFromImage(image, 1.0, (300, 300), [104, 117, 123], False, False)
else:
# Caffe模型
blob = cv2.dnn.blobFromImage(cv2.resize(image, (300, 300)), 1.0,
(300, 300), (104.0, 177.0, 123.0))
self.net.setInput(blob)
detections = self.net.forward()
faces = []
for i in range(detections.shape[2]):
confidence = detections[0, 0, i, 2]
if confidence > self.confidence_threshold:
# 提取边界框坐标
if hasattr(self, 'use_tensorflow') and self.use_tensorflow:
# TensorFlow模型输出格式
x1 = int(detections[0, 0, i, 3] * w)
y1 = int(detections[0, 0, i, 4] * h)
x2 = int(detections[0, 0, i, 5] * w)
y2 = int(detections[0, 0, i, 6] * h)
else:
# Caffe模型输出格式
x1 = int(detections[0, 0, i, 3] * w)
y1 = int(detections[0, 0, i, 4] * h)
x2 = int(detections[0, 0, i, 5] * w)
y2 = int(detections[0, 0, i, 6] * h)
# 确保坐标在图像范围内
x1, y1 = max(0, x1), max(0, y1)
x2, y2 = min(w, x2), min(h, y2)
# 转换为dlib矩形格式(保持兼容性)
if x2 > x1 and y2 > y1: # 确保是有效的矩形
face_rect = dlib.rectangle(x1, y1, x2, y2)
faces.append((face_rect, confidence))
return faces
def detect_faces_haar(self, image):
"""备选:使用Haar级联检测"""
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
faces_cv = self.net.detectMultiScale(
gray,
scaleFactor=1.1,
minNeighbors=5,
minSize=(30, 30)
)
faces = []
for (x, y, w, h) in faces_cv:
face_rect = dlib.rectangle(x, y, x + w, y + h)
faces.append((face_rect, 0.9)) # Haar没有置信度
return faces
# 全局DNN检测器实例
dnn_detector = DNNFaceDetector(confidence_threshold=0.7)
class DNNFaceDetector:
def __init__(self, confidence_threshold=0.7):
"""初始化OpenCV DNN人脸检测器"""
self.confidence_threshold = confidence_threshold
self.net = self.load_face_detection_model()
def load_face_detection_model(self):
"""加载OpenCV DNN人脸检测模型"""
try:
# 方法1: 尝试加载TensorFlow模型
model_path = "models/opencv_face_detector_uint8.pb"
config_path = "models/opencv_face_detector.pbtxt"
if os.path.exists(model_path) and os.path.exists(config_path):
net = cv2.dnn.readNetFromTensorflow(model_path, config_path)
print("使用 TensorFlow 人脸检测模型")
self.use_tensorflow = True
return net
except Exception as e:
print(f"TensorFlow模型加载失败: {e}")
try:
# 方法2: 尝试加载Caffe模型
proto_path = "models/deploy.prototxt"
model_path = "models/res10_300x300_ssd_iter_140000_fp16.caffemodel"
if os.path.exists(proto_path) and os.path.exists(model_path):
net = cv2.dnn.readNetFromCaffe(proto_path, model_path)
print("使用 Caffe 人脸检测模型")
self.use_tensorflow = False
return net
except Exception as e:
print(f"Caffe模型加载失败: {e}")
# 方法3: 使用OpenCV内置的Haar级联作为备选
print("使用 Haar Cascade 作为备选检测器")
self.use_haar = True
cascade_path ='models/haarcascade_frontalface_default.xml'
if os.path.exists(cascade_path):
return cv2.CascadeClassifier(cascade_path)
else:
# 如果内置路径不存在,尝试其他路径
possible_paths = [
'/usr/share/opencv4/haarcascades/haarcascade_frontalface_default.xml',
'/usr/share/opencv/haarcascades/haarcascade_frontalface_default.xml',
'models/haarcascade_frontalface_default.xml'
]
for path in possible_paths:
if os.path.exists(path):
return cv2.CascadeClassifier(path)
print("错误: 未找到任何人脸检测模型")
return None
def detect_faces(self, image):
"""使用DNN检测人脸"""
if hasattr(self, 'use_haar') and self.use_haar:
return self.detect_faces_haar(image)
if self.net is None:
return []
h, w = image.shape[:2]
# 创建blob输入
if hasattr(self, 'use_tensorflow') and self.use_tensorflow:
blob = cv2.dnn.blobFromImage(image, 1.0, (300, 300), [104, 117, 123], False, False)
else:
# Caffe模型
blob = cv2.dnn.blobFromImage(cv2.resize(image, (300, 300)), 1.0,
(300, 300), (104.0, 177.0, 123.0))
self.net.setInput(blob)
detections = self.net.forward()
faces = []
for i in range(detections.shape[2]):
confidence = detections[0, 0, i, 2]
if confidence > self.confidence_threshold:
# 提取边界框坐标
if hasattr(self, 'use_tensorflow') and self.use_tensorflow:
# TensorFlow模型输出格式
x1 = int(detections[0, 0, i, 3] * w)
y1 = int(detections[0, 0, i, 4] * h)
x2 = int(detections[0, 0, i, 5] * w)
y2 = int(detections[0, 0, i, 6] * h)
else:
# Caffe模型输出格式
x1 = int(detections[0, 0, i, 3] * w)
y1 = int(detections[0, 0, i, 4] * h)
x2 = int(detections[0, 0, i, 5] * w)
y2 = int(detections[0, 0, i, 6] * h)
# 确保坐标在图像范围内
x1, y1 = max(0, x1), max(0, y1)
x2, y2 = min(w, x2), min(h, y2)
# 转换为dlib矩形格式(保持兼容性)
if x2 > x1 and y2 > y1: # 确保是有效的矩形
face_rect = dlib.rectangle(x1, y1, x2, y2)
faces.append((face_rect, confidence))
return faces
def detect_faces_haar(self, image):
"""备选:使用Haar级联检测"""
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
faces_cv = self.net.detectMultiScale(
gray,
scaleFactor=1.1,
minNeighbors=5,
minSize=(30, 30)
)
faces = []
for (x, y, w, h) in faces_cv:
face_rect = dlib.rectangle(x, y, x + w, y + h)
faces.append((face_rect, 0.9)) # Haar没有置信度
return faces
# 全局DNN检测器实例
dnn_detector = DNNFaceDetector(confidence_threshold=0.7)
def return_128d_features(path_img):
"""返回单张图像的 128D 特征(使用OpenCV DNN检测)"""
"""返回单张图像的 128D 特征(使用OpenCV DNN检测)"""
try:
# 直接使用OpenCV读取图像,避免PIL转换
img_rd = cv2.imread(path_img)
if img_rd is None:
logging.error("无法读取图像: %s", path_img)
return None
# 对于大图像,先缩小以提高检测速度
h, w = img_rd.shape[:2]
if w > 1000 or h > 1000:
scale = 1000 / max(w, h)
small_img = cv2.resize(img_rd, (int(w * scale), int(h * scale)))
face_results = dnn_detector.detect_faces(small_img)
# 缩放坐标回原图
scaled_faces = []
for face_rect, confidence in face_results:
scaled_face = dlib.rectangle(
int(face_rect.left() / scale),
int(face_rect.top() / scale),
int(face_rect.right() / scale),
int(face_rect.bottom() / scale)
)
scaled_faces.append((scaled_face, confidence))
face_results = scaled_faces
else:
face_results = dnn_detector.detect_faces(img_rd)
if len(face_results) != 0:
# 取置信度最高的人脸
best_face = max(face_results, key=lambda x: x[1])
face_rect = best_face[0]
logging.info("%-40s %-20s", "检测到人脸的图像:", path_img)
logging.info("检测置信度: %.3f", best_face[1])
# 使用dlib进行特征点检测和特征提取
shape = predictor(img_rd, face_rect)
# 直接使用OpenCV读取图像,避免PIL转换
img_rd = cv2.imread(path_img)
if img_rd is None:
logging.error("无法读取图像: %s", path_img)
return None
# 对于大图像,先缩小以提高检测速度
h, w = img_rd.shape[:2]
if w > 1000 or h > 1000:
scale = 1000 / max(w, h)
small_img = cv2.resize(img_rd, (int(w * scale), int(h * scale)))
face_results = dnn_detector.detect_faces(small_img)
# 缩放坐标回原图
scaled_faces = []
for face_rect, confidence in face_results:
scaled_face = dlib.rectangle(
int(face_rect.left() / scale),
int(face_rect.top() / scale),
int(face_rect.right() / scale),
int(face_rect.bottom() / scale)
)
scaled_faces.append((scaled_face, confidence))
face_results = scaled_faces
else:
face_results = dnn_detector.detect_faces(img_rd)
if len(face_results) != 0:
# 取置信度最高的人脸
best_face = max(face_results, key=lambda x: x[1])
face_rect = best_face[0]
logging.info("%-40s %-20s", "检测到人脸的图像:", path_img)
logging.info("检测置信度: %.3f", best_face[1])
# 使用dlib进行特征点检测和特征提取
shape = predictor(img_rd, face_rect)
face_descriptor = face_reco_model.compute_face_descriptor(img_rd, shape)
return face_descriptor
else:
logging.warning("未检测到人脸: %s", path_img)
return None
except Exception as e:
logging.error("处理图像时出错 %s: %s", path_img, e)
return None
def return_features_mean_personX(path_face_personX):
"""返回 personX 的 128D 特征均值"""
features_list_personX = []
photos_list = os.listdir(path_face_personX)
if photos_list:
for photo in photos_list:
# 只处理图像文件
if not photo.lower().endswith(('.jpg', '.jpeg', '.png', '.bmp')):
continue
# 只处理图像文件
if not photo.lower().endswith(('.jpg', '.jpeg', '.png', '.bmp')):
continue
photo_path = os.path.join(path_face_personX, photo)
logging.info("正在读取图像: %s", photo_path)
features_128d = return_128d_features(photo_path)
if features_128d is not None:
features_list_personX.append(features_128d)
else:
logging.warning("无法从图像中提取特征: %s", photo_path)
else:
logging.warning("文件夹为空: %s", path_face_personX)
# 计算 128D 特征的均值
if features_list_personX:
features_mean_personX = np.array(features_list_personX).mean(axis=0)
logging.info("成功提取 %d 张人脸特征", len(features_list_personX))
else:
features_mean_personX = np.zeros(128, dtype=np.float64)
logging.warning("未提取到任何特征: %s", path_face_personX)
return features_mean_personX
def get_person_name_from_folder(folder_name):
"""从文件夹名称获取有意义的姓名"""
# 常见的文件夹前缀
prefixes = ['person_', 'face_', 'user_']
for prefix in prefixes:
if folder_name.startswith(prefix):
name_part = folder_name[len(prefix):]
# 如果剩下的部分是纯数字,使用完整文件夹名
if name_part.isdigit():
return folder_name
else:
return name_part
return folder_name
def main():
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
# 检查源文件夹是否存在
if not os.path.exists(path_images_from_camera):
logging.error("人脸图像文件夹不存在: %s", path_images_from_camera)
return
# 获取人脸文件夹列表
person_list = os.listdir(path_images_from_camera)
person_list.sort()
if not person_list:
logging.error("没有人脸文件夹可处理")
return
logging.info("找到 %d 个人脸文件夹: %s", len(person_list), person_list)
# 创建CSV文件
with open("data/features_all.csv", "w", newline="", encoding="utf-8") as csvfile:
writer = csv.writer(csvfile)
successful_count = 0
for person_folder in person_list:
folder_path = os.path.join(path_images_from_camera, person_folder)
if not os.path.isdir(folder_path):
logging.warning("跳过非文件夹: %s", person_folder)
logging.warning("跳过非文件夹: %s", person_folder)
continue
logging.info("=" * 60)
logging.info("=" * 60)
logging.info("处理文件夹: %s", person_folder)
# 提取特征
features_mean = return_features_mean_personX(folder_path)
# 获取有意义的姓名
person_name = get_person_name_from_folder(person_folder)
logging.info("使用姓名: %s", person_name)
# 检查特征是否有效(非全零)
if np.all(features_mean == 0):
logging.warning("特征提取失败,跳过: %s", person_folder)
continue
# 检查特征是否有效(非全零)
if np.all(features_mean == 0):
logging.warning("特征提取失败,跳过: %s", person_folder)
continue
# 构建行数据:姓名 + 128维特征
row_data = [person_name] + features_mean.tolist()
writer.writerow(row_data)
successful_count += 1
logging.info("完成: %s", person_name)
logging.info("-" * 50)
logging.info("=" * 60)
logging.info("处理完成: 成功 %d/%d 个人脸文件夹", successful_count, len(person_list))
logging.info("=" * 60)
logging.info("处理完成: 成功 %d/%d 个人脸文件夹", successful_count, len(person_list))
logging.info("特征数据已保存到: data/features_all.csv")
if __name__ == '__main__':
main()

View File

@ -1,233 +0,0 @@
# Copyright (C) 2018-2021 coneypo
# SPDX-License-Identifier: MIT
import dlib
import numpy as np
import cv2
import os
import shutil
import time
import logging
# Dlib 正向人脸检测器
detector = dlib.get_frontal_face_detector()
class Face_Register:
def __init__(self):
self.path_photos_from_camera = "data/data_faces/"
self.font = cv2.FONT_ITALIC
self.existing_faces_cnt = 0 # 已注册的人数统计
self.ss_cnt = 0 # 当前人员已保存图片数量
self.current_frame_faces_cnt = 0 # 用于存储摄像头中出现的人数
self.save_flag = 1
self.press_n_flag = 0
self.current_person_name = "" # 新增:当前录入人姓名
# FPS
self.frame_time = 0
self.frame_start_time = 0
self.fps = 0
self.fps_show = 0
self.start_time = time.time()
# 创建数据目录
def pre_work_mkdir(self):
if os.path.isdir(self.path_photos_from_camera):
pass
else:
os.makedirs(self.path_photos_from_camera, exist_ok=True)
# 删除所有数据
def pre_work_del_old_face_folders(self):
folders_rd = os.listdir(self.path_photos_from_camera)
for folder in folders_rd:
shutil.rmtree(os.path.join(self.path_photos_from_camera, folder))
if os.path.isfile("data/features_all.csv"):
os.remove("data/features_all.csv")
# 统计已经录入的人脸数据
def check_existing_faces_cnt(self):
if os.listdir(self.path_photos_from_camera):
person_list = os.listdir(self.path_photos_from_camera)
# 统计数量
self.existing_faces_cnt = len(person_list)
else:
self.existing_faces_cnt = 0
# 计算帧数
def update_fps(self):
now = time.time()
if str(self.start_time).split(".")[0] != str(now).split(".")[0]:
self.fps_show = self.fps
self.start_time = now
self.frame_time = now - self.frame_start_time
self.fps = 1.0 / self.frame_time
self.frame_start_time = now
# 添加图像说明
def draw_note(self, img_rd):
# 添加说明
cv2.putText(img_rd, "Face Register", (20, 40), self.font, 1, (255, 255, 255), 1, cv2.LINE_AA)
cv2.putText(img_rd, "FPS: " + str(self.fps_show.__round__(2)), (20, 100), self.font, 0.8, (0, 255, 0), 1,
cv2.LINE_AA)
cv2.putText(img_rd, "Faces: " + str(self.current_frame_faces_cnt), (20, 140), self.font, 0.8, (0, 255, 0), 1,
cv2.LINE_AA)
# 修改:显示当前录入人姓名
if self.current_person_name:
cv2.putText(img_rd, f"Name: {self.current_person_name}", (20, 180), self.font, 0.8, (255, 255, 0), 1,
cv2.LINE_AA)
cv2.putText(img_rd, "N: Input Name & Create folder", (20, 320), self.font, 0.8, (255, 255, 255), 1, cv2.LINE_AA)
cv2.putText(img_rd, "S: Save current face", (20, 350), self.font, 0.8, (255, 255, 255), 1, cv2.LINE_AA)
cv2.putText(img_rd, "Q: Quit", (20, 380), self.font, 0.8, (255, 255, 255), 1, cv2.LINE_AA)
cv2.putText(img_rd, "D: Delete all data", (20, 410), self.font, 0.8, (255, 255, 255), 1, cv2.LINE_AA)
# 用户输入姓名
def get_person_name_from_input(self):
print("\n请输入姓名(中文或英文):")
name = input().strip()
return name if name else f"person_{self.existing_faces_cnt + 1}"
# 创建对应的文件夹保存数据
def create_person_folder(self, person_name):
# 清理文件名中的非法字符
safe_name = "".join(c for c in person_name if c.isalnum() or c in (' ', '-', '_')).rstrip()
if not safe_name:
safe_name = f"person_{self.existing_faces_cnt + 1}"
# 创建文件夹路径
folder_name = f"person_{safe_name}"
current_face_dir = os.path.join(self.path_photos_from_camera, folder_name)
os.makedirs(current_face_dir, exist_ok=True)
logging.info("新建人脸文件夹: %s", current_face_dir)
return current_face_dir, safe_name
# 人脸录入过程
def get_face_process(self, stream):
# 1. 新建储存人脸图像文件目录
self.pre_work_mkdir()
# 2. 检查已有人脸文件
self.check_existing_faces_cnt()
current_face_dir = ""
while stream.isOpened():
flag, img_rd = stream.read()
if not flag:
break
kk = cv2.waitKey(1)
faces = detector(img_rd, 0)
# 4. 按下 'n' 输入姓名并新建文件夹
if kk == ord('n'):
person_name = self.get_person_name_from_input()
current_face_dir, self.current_person_name = self.create_person_folder(person_name)
self.ss_cnt = 0
self.press_n_flag = 1
print(f"已创建文件夹: {current_face_dir}")
print("请调整位置并按 'S' 保存人脸")
# 5. 按下 'd' 删除所有数据
elif kk == ord('d'):
confirm = input("确定要删除所有数据吗?(y/n): ")
if confirm.lower() == 'y':
self.pre_work_del_old_face_folders()
self.existing_faces_cnt = 0
self.current_person_name = ""
self.press_n_flag = 0
print("所有数据已删除")
continue
# 6. 检测到人脸
if len(faces) != 0:
for k, d in enumerate(faces):
# 计算矩形框大小
height = d.bottom() - d.top()
width = d.right() - d.left()
hh = int(height / 2)
ww = int(width / 2)
# 判断人脸是否在范围内
if (d.right() + ww > 640 or d.bottom() + hh > 480 or
d.left() - ww < 0 or d.top() - hh < 0):
cv2.putText(img_rd, "OUT OF RANGE", (20, 300), self.font, 0.8, (0, 0, 255), 1, cv2.LINE_AA)
color_rectangle = (0, 0, 255)
save_flag = 0
else:
color_rectangle = (255, 255, 255)
save_flag = 1
# 绘制人脸框
cv2.rectangle(img_rd,
(d.left() - ww, d.top() - hh),
(d.right() + ww, d.bottom() + hh),
color_rectangle, 2)
# 创建空白图像用于保存人脸
img_blank = np.zeros((height * 2, width * 2, 3), np.uint8)
if save_flag and kk == ord('s'):
# 检查是否已创建文件夹
if self.press_n_flag:
self.ss_cnt += 1
# 提取人脸区域
for ii in range(height * 2):
for jj in range(width * 2):
img_blank[ii][jj] = img_rd[d.top() - hh + ii][d.left() - ww + jj]
# 保存人脸图像
filename = f"img_face_{self.ss_cnt}.jpg"
filepath = os.path.join(current_face_dir, filename)
cv2.imwrite(filepath, img_blank)
logging.info("保存人脸: %s", filepath)
print(f"已保存第 {self.ss_cnt} 张人脸图片")
else:
logging.warning("请先按 'N' 输入姓名创建文件夹")
self.current_frame_faces_cnt = len(faces)
# 绘制说明文字
self.draw_note(img_rd)
# 按下 'q' 退出
if kk == ord('q'):
break
# 更新 FPS
self.update_fps()
cv2.imshow("Face Register", img_rd)
def run(self):
cap = cv2.VideoCapture(0)
if not cap.isOpened():
print("错误: 无法打开摄像头")
return
# 设置摄像头参数
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
self.get_face_process(cap)
cap.release()
cv2.destroyAllWindows()
print("程序结束")
def main():
logging.basicConfig(level=logging.INFO)
Face_Register_con = Face_Register()
Face_Register_con.run()
if __name__ == '__main__':
main()

View File

@ -1,378 +0,0 @@
# Copyright (C) 2018-2021 coneypo
# SPDX-License-Identifier: MIT
# Author: coneypo
# Blog: http://www.cnblogs.com/AdaminXie
# GitHub: https://github.com/coneypo/Dlib_face_recognition_from_camera
# Mail: coneypo@foxmail.com
# 人脸录入 Tkinter GUI / Face register GUI with tkinter
import dlib
import numpy as np
import cv2
import os
import shutil
import time
import logging
import tkinter as tk
from tkinter import font as tkFont
from tkinter import messagebox
from PIL import Image, ImageTk
# Dlib 正向人脸检测器 / Use frontal face detector of Dlib
detector = dlib.get_frontal_face_detector()
class Face_Register:
def __init__(self):
self.current_frame_faces_cnt = 0 # 当前帧中人脸计数器 / cnt for counting faces in current frame
self.existing_faces = 0 # 已录入的人脸数 / cnt for counting saved faces
self.ss_cnt = 0 # 录入 person_n 人脸时图片计数器 / cnt for screen shots
self.registered_names = [] # 已录入的人脸名字 / names of registered faces
self.path_photos_from_camera = "data/data_faces/"
self.current_face_dir = ""
self.font = cv2.FONT_ITALIC
if os.listdir(self.path_photos_from_camera):
self.existing_faces = len(os.listdir(self.path_photos_from_camera))
# Tkinter GUI
self.win = tk.Tk()
self.win.title("人脸录入")
# PLease modify window size here if needed
self.win.geometry("1300x550")
# GUI left part
self.frame_left_camera = tk.Frame(self.win)
self.label = tk.Label(self.win)
self.label.pack(side=tk.LEFT)
self.frame_left_camera.pack()
# GUI right part
self.frame_right_info = tk.Frame(self.win)
self.label_cnt_face_in_database = tk.Label(self.frame_right_info, text=str(self.existing_faces))
self.label_fps_info = tk.Label(self.frame_right_info, text="")
self.input_name = tk.Entry(self.frame_right_info, width=25)
self.input_name_char = ""
self.label_warning = tk.Label(self.frame_right_info)
self.label_face_cnt = tk.Label(self.frame_right_info, text="Faces in current frame: ")
self.log_all = tk.Label(self.frame_right_info)
self.font_title = tkFont.Font(family='Helvetica', size=20, weight='bold')
self.font_step_title = tkFont.Font(family='Helvetica', size=15, weight='bold')
self.font_warning = tkFont.Font(family='Helvetica', size=15, weight='bold')
# Current frame and face ROI position
self.current_frame = np.ndarray
self.face_ROI_image = np.ndarray
self.face_ROI_width_start = 0
self.face_ROI_height_start = 0
self.face_ROI_width = 0
self.face_ROI_height = 0
self.ww = 0
self.hh = 0
self.out_of_range_flag = False
self.face_folder_created_flag = False
# FPS
self.frame_time = 0
self.frame_start_time = 0
self.fps = 0
self.fps_show = 0
self.start_time = time.time()
self.cap = cv2.VideoCapture(0) # Get video stream from camera
# self.cap = cv2.VideoCapture("test.mp4") # Input local video
# 删除之前存的人脸数据文件夹 / Delete old face folders
def GUI_clear_data(self):
# 删除之前存的人脸数据文件夹, 删除 "/data_faces/person_x/"...
folders_rd = os.listdir(self.path_photos_from_camera)
for i in range(len(folders_rd)):
shutil.rmtree(self.path_photos_from_camera + folders_rd[i])
if os.path.isfile("./data/features_all.csv"):
os.remove("./data/features_all.csv")
self.label_cnt_face_in_database['text'] = "0"
self.registered_names.clear()
self.log_all["text"] = "全部图片和`features_all.csv`已全部移除!"
self.log_all["fg"] = "green"
def GUI_get_input_name(self):
self.input_name_char = self.input_name.get()
if self.input_name_char:
if self.input_name_char not in self.registered_names:
self.create_face_folder()
self.registered_names.append(self.input_name_char)
self.label_cnt_face_in_database['text'] = str(self.registered_names.__len__())
else:
self.log_all["text"] = "此名字已被录入,请输入新的名字!"
self.log_all["fg"] = "red"
else:
self.log_all["text"] = "请输入姓名"
self.log_all["fg"] = "red"
def delete_name(self):
self.input_name_char = self.input_name.get()
if self.input_name_char:
if self.input_name_char in self.registered_names:
self.remove_face_dir(self.path_photos_from_camera + "person_" + self.input_name_char)
self.log_all["text"] = "'" + self.input_name_char + "'" + "已移除!"
self.log_all["fg"] = "green"
self.registered_names.remove(self.input_name_char)
self.label_cnt_face_in_database['text'] = str(self.registered_names.__len__())
else:
self.log_all["text"] = "此名字不存在,请输入正确的名字!"
self.log_all["fg"] = "red"
else:
self.log_all["text"] = "请先输入要删除的姓名"
self.log_all["fg"] = "red"
def change_name(self):
self.input_name_char = self.input_name.get()
if self.input_name_char:
if self.input_name_char in self.registered_names:
self.current_face_dir = self.path_photos_from_camera + \
"person_" + \
self.input_name_char
pecturt_list = os.listdir(self.current_face_dir)
self.ss_cnt = len(pecturt_list) # 将人脸计数器置为原来的 / Clear the cnt of screen shots
self.face_folder_created_flag = True # Face folder already created
self.label_cnt_face_in_database['text'] = str(self.registered_names.__len__())
self.log_all["text"] = "可以添加新照片了!"
self.log_all["fg"] = "green"
else:
self.log_all["text"] = "此名字不存在,请输入正确的名字!"
self.log_all["fg"] = "red"
else:
self.log_all["text"] = "请先输入要更改的姓名"
self.log_all["fg"] = "red"
def GUI_info(self):
tk.Label(self.frame_right_info,
text="Face register",
font=self.font_title).grid(row=0, column=0, columnspan=3, sticky=tk.W, padx=2, pady=20)
tk.Label(self.frame_right_info,
text="FPS: ").grid(row=1, column=0, columnspan=2, sticky=tk.W, padx=5, pady=2)
self.label_fps_info.grid(row=1, column=2, sticky=tk.W, padx=5, pady=2)
tk.Label(self.frame_right_info,
text="数据库中已有的人脸: ").grid(row=2, column=0, columnspan=2, sticky=tk.W, padx=5, pady=2)
self.label_cnt_face_in_database.grid(row=2, column=2, columnspan=3, sticky=tk.W, padx=5, pady=2)
tk.Label(self.frame_right_info,
text="当前帧中的人脸: ").grid(row=3, column=0, columnspan=2, sticky=tk.W, padx=5, pady=2)
self.label_face_cnt.grid(row=3, column=2, columnspan=3, sticky=tk.W, padx=5, pady=2)
self.label_warning.grid(row=4, column=0, columnspan=3, sticky=tk.W, padx=5, pady=2)
# Step 1: Clear old data
tk.Label(self.frame_right_info,
font=self.font_step_title,
text="删除之前存的人脸数据文件夹").grid(row=5, column=0, columnspan=2, sticky=tk.W, padx=5, pady=20)
tk.Button(self.frame_right_info,
text='删除全部',
command=self.GUI_clear_data).grid(row=6, column=0, columnspan=3, sticky=tk.W, padx=5, pady=2)
# Step 2: Input name and create folders for face
tk.Label(self.frame_right_info,
font=self.font_step_title,
text="Step 1: 输入姓名").grid(row=7, column=0, columnspan=2, sticky=tk.W, padx=5, pady=20)
tk.Label(self.frame_right_info, text="姓名: ").grid(row=8, column=0, sticky=tk.W, padx=5, pady=0)
self.input_name.grid(row=8, column=1, sticky=tk.W, padx=0, pady=2)
tk.Button(self.frame_right_info,
text='录入',
command=self.GUI_get_input_name).grid(row=8, column=2, padx=5)
tk.Button(self.frame_right_info,
text='更改',
command=self.change_name).grid(row=8, column=3, padx=5)
tk.Button(self.frame_right_info,
text='删除',
command=self.delete_name).grid(row=8, column=4, padx=5)
# Step 3: Save current face in frame
tk.Label(self.frame_right_info,
font=self.font_step_title,
text="Step 2: 保存当前人脸图片").grid(row=9, column=0, columnspan=2, sticky=tk.W, padx=5, pady=20)
tk.Button(self.frame_right_info,
text='保存',
command=self.save_current_face).grid(row=10, column=0, columnspan=3, sticky=tk.W)
# Show log in GUI
self.log_all.grid(row=11, column=0, columnspan=20, sticky=tk.W, padx=5, pady=20)
self.frame_right_info.pack()
# 新建保存人脸图像文件和数据 CSV 文件夹 / Mkdir for saving photos and csv
def pre_work_mkdir(self):
# 新建文件夹 / Create folders to save face images and csv
if os.path.isdir(self.path_photos_from_camera):
pass
else:
os.makedirs(self.path_photos_from_camera)
# 如果有之前录入的人脸, 在之前 person_x 的序号按照 person_x+1 开始录入 / Start from person_x+1
def check_existing_faces(self):
if os.listdir(self.path_photos_from_camera):
# 获取已录入的最后一个人脸序号 / Get the order of latest person
person_list = os.listdir(self.path_photos_from_camera)
for person in person_list:
name = person.split('_')[1]
self.registered_names.append(name)
self.existing_faces = len(person_list)
# 如果第一次存储或者没有之前录入的人脸, 按照 person_1 开始录入 / Start from person_1
else:
self.registered_names.clear()
print("No previous data.")
# 更新 FPS / Update FPS of Video stream
def update_fps(self):
now = time.time()
# 每秒刷新 fps / Refresh fps per second
if str(self.start_time).split(".")[0] != str(now).split(".")[0]:
self.fps_show = self.fps
self.start_time = now
self.frame_time = now - self.frame_start_time
self.fps = 1.0 / self.frame_time
self.frame_start_time = now
formatted_fps = "{:.2f}".format(self.fps)
self.label_fps_info["text"] = str(formatted_fps)
def create_face_folder(self):
# 新建存储人脸的文件夹 / Create the folders for saving faces
self.current_face_dir = self.path_photos_from_camera + \
"person_" + \
self.input_name_char
os.makedirs(self.current_face_dir)
self.log_all["text"] = "\"" + self.current_face_dir + "/\" created!"
self.log_all["fg"] = "green"
logging.info("\n%-40s %s", "新建的人脸文件夹 / Create folders:", self.current_face_dir)
self.ss_cnt = 0 # 将人脸计数器清零 / Clear the cnt of screen shots
self.face_folder_created_flag = True # Face folder already created
def remove_face_dir(self, folder_path):
try:
shutil.rmtree(folder_path)
print(f"Folder '{folder_path}' has been deleted successfully.")
except Exception as e:
print(f"Failed to delete folder '{folder_path}'. Error: {e}")
def save_current_face(self):
if self.face_folder_created_flag:
if self.current_frame_faces_cnt == 1:
if not self.out_of_range_flag:
self.ss_cnt += 1
# 根据人脸大小生成空的图像 / Create blank image according to the size of face detected
self.face_ROI_image = np.zeros((int(self.face_ROI_height * 2), self.face_ROI_width * 2, 3),
np.uint8)
for ii in range(self.face_ROI_height * 2):
for jj in range(self.face_ROI_width * 2):
self.face_ROI_image[ii][jj] = self.current_frame[self.face_ROI_height_start - self.hh + ii][
self.face_ROI_width_start - self.ww + jj]
self.log_all["text"] = "\"" + self.current_face_dir + "/img_face_" + str(
self.ss_cnt) + ".jpg\"" + " 保存成功!"
self.log_all["fg"] = "green"
# 使用Pillow保存图像
img_pil = Image.fromarray(self.face_ROI_image)
img_pil.save(self.current_face_dir + "/img_face_" + str(self.ss_cnt) + ".jpg")
logging.info("%-40s %s/img_face_%s.jpg", "写入本地 / Save into:",
str(self.current_face_dir), str(self.ss_cnt) + ".jpg")
else:
self.log_all["text"] = "人脸不在范围内(人脸框白色才能保存)!"
self.log_all["fg"] = "red"
else:
self.log_all["text"] = "没找到人脸或者找到多个人脸"
self.log_all["fg"] = "red"
else:
self.log_all["text"] = "请先执行step 1"
self.log_all["fg"] = "red"
def get_frame(self):
try:
if self.cap.isOpened():
ret, frame = self.cap.read()
if ret:
return ret, cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
else:
raise Exception("Unable to open the camera")
except Exception as e:
messagebox.showerror("Error", f"没有找到摄像头!!!{e}\n")
print("Error: No video input!!!{e}")
# 获取人脸 / Main process of face detection and saving
def process(self):
ret, self.current_frame = self.get_frame()
faces = detector(self.current_frame, 0)
# Get frame
if ret:
self.update_fps()
self.label_face_cnt["text"] = str(len(faces))
# 检测到人脸 / Face detected
if len(faces) != 0:
# 矩形框 / Show the ROI of faces
for k, d in enumerate(faces):
self.face_ROI_width_start = d.left()
self.face_ROI_height_start = d.top()
# 计算矩形框大小 / Compute the size of rectangle box
self.face_ROI_height = (d.bottom() - d.top())
self.face_ROI_width = (d.right() - d.left())
self.hh = int(self.face_ROI_height / 2)
self.ww = int(self.face_ROI_width / 2)
# 判断人脸矩形框是否超出 480x640 / If the size of ROI > 480x640
if (d.right() + self.ww) > 640 or (d.bottom() + self.hh > 480) or (d.left() - self.ww < 0) or (
d.top() - self.hh < 0):
self.label_warning["text"] = "OUT OF RANGE"
self.label_warning['fg'] = 'red'
self.out_of_range_flag = True
color_rectangle = (255, 0, 0)
else:
self.out_of_range_flag = False
self.label_warning["text"] = ""
color_rectangle = (255, 255, 255)
self.current_frame = cv2.rectangle(self.current_frame,
tuple([d.left() - self.ww, d.top() - self.hh]),
tuple([d.right() + self.ww, d.bottom() + self.hh]),
color_rectangle, 2)
self.current_frame_faces_cnt = len(faces)
# Convert PIL.Image.Image to PIL.Image.PhotoImage
img_Image = Image.fromarray(self.current_frame)
img_PhotoImage = ImageTk.PhotoImage(image=img_Image)
self.label.img_tk = img_PhotoImage
self.label.configure(image=img_PhotoImage)
# Refresh frame
self.win.after(20, self.process)
def run(self):
self.pre_work_mkdir()
self.check_existing_faces()
self.GUI_info()
self.process()
self.win.mainloop()
def main():
logging.basicConfig(level=logging.INFO)
Face_Register_con = Face_Register()
Face_Register_con.run()
if __name__ == '__main__':
main()

View File

@ -1,333 +0,0 @@
import dlib
import numpy as np
import cv2
import os
import shutil
import time
import logging
# 使用 OpenCV DNN 加载深度学习人脸检测模型
class DeepLearningFaceDetector:
def __init__(self):
# 模型文件路径 - 需要下载对应的模型文件
self.proto_path = "models/deploy.prototxt"
self.model_path = "models/res10_300x300_ssd_iter_140000_fp16.caffemodel"
# 如果模型文件不存在,尝试从OpenCV示例中加载
if not os.path.exists(self.proto_path) or not os.path.exists(self.model_path):
# 使用OpenCV自带的模型(如果可用)
try:
self.net = cv2.dnn.readNetFromTensorflow('models/opencv_face_detector_uint8.pb',
'models/opencv_face_detector.pbtxt')
self.use_tensorflow = True
logging.info("使用 TensorFlow 模型进行人脸检测")
except:
# 如果都没有,使用dlib检测器
logging.warning("未找到深度学习模型文件,将使用Dlib检测器作为备选")
self.dlib_detector = dlib.get_frontal_face_detector()
self.use_dlib = True
else:
self.net = cv2.dnn.readNetFromCaffe(self.proto_path, self.model_path)
self.use_tensorflow = False
self.use_dlib = False
logging.info("使用 Caffe 模型进行人脸检测")
# Dlib 人脸特征点检测器和识别器
try:
self.predictor = dlib.shape_predictor("models/dlib/shape_predictor_68_face_landmarks.dat")
self.face_recognizer = dlib.face_recognition_model_v1("models/dlib/dlib_face_recognition_resnet_model_v1.dat")
logging.info("Dlib 特征提取模型加载成功")
except:
logging.warning("Dlib 特征模型未找到,仅进行人脸检测")
self.predictor = None
self.face_recognizer = None
def detect_faces(self, image, confidence_threshold=0.7):
"""使用深度学习模型检测人脸"""
if hasattr(self, 'use_dlib') and self.use_dlib:
# 备选方案:使用Dlib检测器
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
faces = self.dlib_detector(gray, 1)
results = []
for face in faces:
x1, y1, x2, y2 = face.left(), face.top(), face.right(), face.bottom()
results.append((x1, y1, x2, y2, 0.9)) # Dlib没有置信度,设为0.9
return results
h, w = image.shape[:2]
if hasattr(self, 'use_tensorflow') and self.use_tensorflow:
# TensorFlow 模型
blob = cv2.dnn.blobFromImage(image, 1.0, (300, 300), [104, 117, 123], False, False)
else:
# Caffe 模型
blob = cv2.dnn.blobFromImage(cv2.resize(image, (300, 300)), 1.0,
(300, 300), (104.0, 177.0, 123.0))
self.net.setInput(blob)
detections = self.net.forward()
faces = []
for i in range(detections.shape[2]):
confidence = detections[0, 0, i, 2]
if confidence > confidence_threshold:
if hasattr(self, 'use_tensorflow') and self.use_tensorflow:
# TensorFlow 模型输出格式
x1 = int(detections[0, 0, i, 3] * w)
y1 = int(detections[0, 0, i, 4] * h)
x2 = int(detections[0, 0, i, 5] * w)
y2 = int(detections[0, 0, i, 6] * h)
else:
# Caffe 模型输出格式
x1 = int(detections[0, 0, i, 3] * w)
y1 = int(detections[0, 0, i, 4] * h)
x2 = int(detections[0, 0, i, 5] * w)
y2 = int(detections[0, 0, i, 6] * h)
# 确保坐标在图像范围内
x1, y1 = max(0, x1), max(0, y1)
x2, y2 = min(w, x2), min(h, y2)
faces.append((x1, y1, x2, y2, confidence))
return faces
def get_face_embedding(self, image, face_rect):
"""使用Dlib提取人脸特征向量"""
if self.predictor is None or self.face_recognizer is None:
return None
x1, y1, x2, y2 = face_rect[:4]
# 转换为dlib的矩形格式
dlib_rect = dlib.rectangle(x1, y1, x2, y2)
# 检测人脸特征点
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
shape = self.predictor(gray, dlib_rect)
# 计算人脸特征向量(128维)
face_descriptor = self.face_recognizer.compute_face_descriptor(image, shape)
return np.array(face_descriptor)
class Face_Register:
def __init__(self):
self.path_photos_from_camera = "data/data_faces/"
self.font = cv2.FONT_ITALIC
# 使用深度学习人脸检测器
self.detector = DeepLearningFaceDetector()
self.existing_faces_cnt = 0
self.ss_cnt = 0
self.current_frame_faces_cnt = 0
self.save_flag = 1
self.press_n_flag = 0
self.current_person_name = ""
# FPS
self.frame_time = 0
self.frame_start_time = 0
self.fps = 0
self.fps_show = 0
self.start_time = time.time()
def pre_work_mkdir(self):
if os.path.isdir(self.path_photos_from_camera):
pass
else:
os.makedirs(self.path_photos_from_camera, exist_ok=True)
def pre_work_del_old_face_folders(self):
folders_rd = os.listdir(self.path_photos_from_camera)
for folder in folders_rd:
shutil.rmtree(os.path.join(self.path_photos_from_camera, folder))
if os.path.isfile("data/features_all.csv"):
os.remove("data/features_all.csv")
def check_existing_faces_cnt(self):
if os.listdir(self.path_photos_from_camera):
person_list = os.listdir(self.path_photos_from_camera)
self.existing_faces_cnt = len(person_list)
else:
self.existing_faces_cnt = 0
def update_fps(self):
now = time.time()
if str(self.start_time).split(".")[0] != str(now).split(".")[0]:
self.fps_show = self.fps
self.start_time = now
self.frame_time = now - self.frame_start_time
self.fps = 1.0 / self.frame_time
self.frame_start_time = now
def draw_note(self, img_rd):
# 添加说明
cv2.putText(img_rd, "Face Register (Deep Learning)", (20, 40), self.font, 1, (255, 255, 255), 1, cv2.LINE_AA)
cv2.putText(img_rd, "FPS: " + str(self.fps_show.__round__(2)), (20, 100), self.font, 0.8, (0, 255, 0), 1,
cv2.LINE_AA)
cv2.putText(img_rd, "Faces: " + str(self.current_frame_faces_cnt), (20, 140), self.font, 0.8, (0, 255, 0), 1,
cv2.LINE_AA)
if self.current_person_name:
cv2.putText(img_rd, f"Name: {self.current_person_name}", (20, 180), self.font, 0.8, (255, 255, 0), 1,
cv2.LINE_AA)
cv2.putText(img_rd, "N: Input Name & Create folder", (20, 320), self.font, 0.8, (255, 255, 255), 1, cv2.LINE_AA)
cv2.putText(img_rd, "S: Save current face", (20, 350), self.font, 0.8, (255, 255, 255), 1, cv2.LINE_AA)
cv2.putText(img_rd, "Q: Quit", (20, 380), self.font, 0.8, (255, 255, 255), 1, cv2.LINE_AA)
cv2.putText(img_rd, "D: Delete all data", (20, 410), self.font, 0.8, (255, 255, 255), 1, cv2.LINE_AA)
def get_person_name_from_input(self):
"""从用户输入获取姓名"""
print("\n请输入姓名(中文或英文):")
name = input().strip()
return name if name else f"person_{self.existing_faces_cnt + 1}"
def create_person_folder(self, person_name):
"""创建人员文件夹"""
safe_name = "".join(c for c in person_name if c.isalnum() or c in (' ', '-', '_')).rstrip()
if not safe_name:
safe_name = f"person_{self.existing_faces_cnt + 1}"
folder_name = f"person_{safe_name}"
current_face_dir = os.path.join(self.path_photos_from_camera, folder_name)
os.makedirs(current_face_dir, exist_ok=True)
logging.info("新建人脸文件夹: %s", current_face_dir)
return current_face_dir, safe_name
def process(self, stream):
self.pre_work_mkdir()
self.check_existing_faces_cnt()
current_face_dir = ""
print("人脸录入说明:")
print("- 按 'N': 输入姓名并创建新人员文件夹")
print("- 按 'S': 保存当前检测到的人脸")
print("- 按 'D': 删除所有已录入数据")
print("- 按 'Q': 退出程序")
while stream.isOpened():
flag, img_rd = stream.read()
if not flag:
break
kk = cv2.waitKey(1)
# 使用深度学习模型检测人脸
faces = self.detector.detect_faces(img_rd)
if len(faces) != 0:
for i, (x1, y1, x2, y2, confidence) in enumerate(faces):
# 计算矩形框大小
height = y2 - y1
width = x2 - x1
hh = int(height / 2)
ww = int(width / 2)
# 判断人脸是否在范围内
if (x2 + ww > 640 or y2 + hh > 480 or
x1 - ww < 0 or y1 - hh < 0):
cv2.putText(img_rd, "OUT OF RANGE", (20, 300), self.font, 0.8, (0, 0, 255), 1, cv2.LINE_AA)
color_rectangle = (0, 0, 255)
save_flag = 0
else:
color_rectangle = (255, 255, 255)
save_flag = 1
# 绘制人脸框和置信度
cv2.rectangle(img_rd, (x1 - ww, y1 - hh), (x2 + ww, y2 + hh), color_rectangle, 2)
# 显示置信度
conf_text = f"{confidence:.2f}"
cv2.putText(img_rd, conf_text, (x1, y1 - 10), self.font, 0.5, color_rectangle, 1, cv2.LINE_AA)
# 创建空白图像用于保存人脸
img_blank = np.zeros((height * 2, width * 2, 3), np.uint8)
if save_flag and kk == ord('s'):
if self.press_n_flag:
self.ss_cnt += 1
# 提取人脸区域
for ii in range(height * 2):
for jj in range(width * 2):
img_blank[ii][jj] = img_rd[y1 - hh + ii][x1 - ww + jj]
# 保存人脸图像
filename = f"img_face_{self.ss_cnt}.jpg"
filepath = os.path.join(current_face_dir, filename)
cv2.imwrite(filepath, img_blank)
# 提取并保存人脸特征(如果可用)
face_embedding = self.detector.get_face_embedding(img_rd, (x1, y1, x2, y2))
if face_embedding is not None:
embedding_path = os.path.join(current_face_dir, f"embedding_{self.ss_cnt}.npy")
np.save(embedding_path, face_embedding)
logging.info("保存人脸: %s", filepath)
print(f"已保存第 {self.ss_cnt} 张人脸图片")
else:
logging.warning("请先按 'N' 输入姓名创建文件夹")
self.current_frame_faces_cnt = len(faces)
# 按下 'n' 输入姓名并新建文件夹
if kk == ord('n'):
person_name = self.get_person_name_from_input()
current_face_dir, self.current_person_name = self.create_person_folder(person_name)
self.ss_cnt = 0
self.press_n_flag = 1
print(f"已创建文件夹: {current_face_dir}")
print("请调整位置并按 'S' 保存人脸")
# 按下 'd' 删除所有数据
elif kk == ord('d'):
confirm = input("确定要删除所有数据吗?(y/n): ")
if confirm.lower() == 'y':
self.pre_work_del_old_face_folders()
self.existing_faces_cnt = 0
self.current_person_name = ""
self.press_n_flag = 0
print("所有数据已删除")
continue
# 绘制说明文字
self.draw_note(img_rd)
# 按下 'q' 退出
if kk == ord('q'):
break
# 更新 FPS
self.update_fps()
cv2.imshow("Face Register (Deep Learning)", img_rd)
def run(self):
cap = cv2.VideoCapture(0)
if not cap.isOpened():
print("错误: 无法打开摄像头")
return
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
self.process(cap)
cap.release()
cv2.destroyAllWindows()
print("程序结束")
def main():
logging.basicConfig(level=logging.INFO)
Face_Register_con = Face_Register()
Face_Register_con.run()
if __name__ == '__main__':
main()

197
get_faces_from_camera.py Normal file
View File

@ -0,0 +1,197 @@
# 进行人脸录入 / face register
# 录入多张人脸 / support multi-faces
# Author: coneypo
# Blog: http://www.cnblogs.com/AdaminXie
# GitHub: https://github.com/coneypo/Dlib_face_recognition_from_camera
# Mail: coneypo@foxmail.com
# Created at 2018-05-11
# Updated at 2019-03-23
import dlib # 人脸处理的库 Dlib
import numpy as np # 数据处理的库 Numpy
import cv2 # 图像处理的库 OpenCv
import os # 读写文件
import shutil # 读写文件
# Dlib 正向人脸检测器 / frontal face detector
detector = dlib.get_frontal_face_detector()
# Dlib 68 点特征预测器 / 68 points features predictor
predictor = dlib.shape_predictor('data/data_dlib/shape_predictor_68_face_landmarks.dat')
# OpenCv 调用摄像头 use camera
cap = cv2.VideoCapture(0)
# 设置视频参数 set camera
cap.set(3, 480)
# 人脸截图的计数器 the counter for screen shoot
cnt_ss = 0
# 存储人脸的文件夹 the folder to save faces
current_face_dir = ""
# 保存 photos/csv 的路径 the directory to save photos/csv
path_photos_from_camera = "data/data_faces_from_camera/"
path_csv_from_photos = "data/data_csvs_from_camera/"
# 新建保存人脸图像文件和数据CSV文件夹
# mkdir for saving photos and csv
def pre_work_mkdir():
# 新建文件夹 / make folders to save faces images and csv
if os.path.isdir(path_photos_from_camera):
pass
else:
os.mkdir(path_photos_from_camera)
if os.path.isdir(path_csv_from_photos):
pass
else:
os.mkdir(path_csv_from_photos)
pre_work_mkdir()
##### optional/可选, 默认关闭 #####
# 删除之前存的人脸数据文件夹
# delete the old data of faces
def pre_work_del_old_face_folders():
# 删除之前存的人脸数据文件夹
# 删除 "/data_faces_from_camera/person_x/"...
folders_rd = os.listdir(path_photos_from_camera)
for i in range(len(folders_rd)):
shutil.rmtree(path_photos_from_camera+folders_rd[i])
csv_rd = os.listdir(path_csv_from_photos)
for i in range(len(csv_rd)):
os.remove(path_csv_from_photos+csv_rd[i])
# 这里在每次程序录入之前, 删掉之前存的人脸数据
# 如果这里打开,每次进行人脸录入的时候都会删掉之前的人脸图像文件夹
# pre_work_del_old_face_folders()
##################################
# 如果有之前录入的人脸
# 在之前 person_x 的序号按照 person_x+1 开始录入
# if old face exists, start from person_x+1
if os.listdir("data/data_faces_from_camera/"):
# 获取已录入的最后一个人脸序号
person_list = os.listdir("data/data_faces_from_camera/")
person_list.sort()
person_num_latest = int(str(person_list[-1]).split("_")[-1])
person_cnt = person_num_latest
# 如果第一次存储或者没有之前录入的人脸, 按照 person_1 开始录入
# start from person_1
else:
person_cnt = 0
# 之后用来控制是否保存图像的 flag / the flag to control if save
save_flag = 1
# 之后用来检查是否先按 'n' 再按 's' / the flag to check if press 'n' before 's'
press_n_flag = 0
while cap.isOpened():
# 480 height * 640 width
flag, img_rd = cap.read()
kk = cv2.waitKey(1)
img_gray = cv2.cvtColor(img_rd, cv2.COLOR_RGB2GRAY)
# 人脸数 faces
faces = detector(img_gray, 0)
# 待会要写的字体 / font to write
font = cv2.FONT_HERSHEY_COMPLEX
# 按下 'n' 新建存储人脸的文件夹 / press 'n' to create the folders for saving faces
if kk == ord('n'):
person_cnt += 1
current_face_dir = path_photos_from_camera + "person_" + str(person_cnt)
os.makedirs(current_face_dir)
print('\n')
print("新建的人脸文件夹 / Create folders: ", current_face_dir)
cnt_ss = 0 # 将人脸计数器清零 / clear the cnt of faces
press_n_flag = 1 # 已经按下 'n' / have pressed 'n'
# 检测到人脸 / if face detected
if len(faces) != 0:
# 矩形框
# show the rectangle box
for k, d in enumerate(faces):
# 计算矩形大小
# we need to compute the width and height of the box
# (x,y), (宽度width, 高度height)
pos_start = tuple([d.left(), d.top()])
pos_end = tuple([d.right(), d.bottom()])
# 计算矩形框大小 / compute the size of rectangle box
height = (d.bottom() - d.top())
width = (d.right() - d.left())
hh = int(height/2)
ww = int(width/2)
# 设置颜色 / the color of rectangle of faces detected
color_rectangle = (255, 255, 255)
if (d.right()+ww) > 640 or (d.bottom()+hh > 480) or (d.left()-ww < 0) or (d.top()-hh < 0):
cv2.putText(img_rd, "OUT OF RANGE", (20, 300), font, 0.8, (0, 0, 255), 1, cv2.LINE_AA)
color_rectangle = (0, 0, 255)
save_flag = 0
else:
color_rectangle = (255, 255, 255)
save_flag = 1
cv2.rectangle(img_rd,
tuple([d.left() - ww, d.top() - hh]),
tuple([d.right() + ww, d.bottom() + hh]),
color_rectangle, 2)
# 根据人脸大小生成空的图像 / create blank image according to the size of face detected
im_blank = np.zeros((int(height*2), width*2, 3), np.uint8)
save_flag = 1;
if save_flag:
# 按下 's' 保存摄像头中的人脸到本地 / press 's' to save faces into local images
if kk == ord('s'):
# 检查有没有先按'n'新建文件夹 / check if you have pressed 'n'
if press_n_flag:
cnt_ss += 1
for ii in range(height*2):
for jj in range(width*2):
im_blank[ii][jj] = img_rd[d.top()-hh + ii][d.left()-ww + jj]
cv2.imwrite(current_face_dir + "/img_face_" + str(cnt_ss) + ".jpg", im_blank)
print("写入本地 / Save into:", str(current_face_dir) + "/img_face_" + str(cnt_ss) + ".jpg")
else:
print("请在按 'S' 之前先按 'N' 来建文件夹 / Please press 'N' before 'S'")
# 显示人脸数 / show the numbers of faces detected
cv2.putText(img_rd, "Faces: " + str(len(faces)), (20, 100), font, 0.8, (0, 255, 0), 1, cv2.LINE_AA)
# 添加说明 / add some statements
cv2.putText(img_rd, "Face Register", (20, 40), font, 1, (0, 0, 0), 1, cv2.LINE_AA)
cv2.putText(img_rd, "N: New face folder", (20, 350), font, 0.8, (0, 0, 0), 1, cv2.LINE_AA)
cv2.putText(img_rd, "S: Save current face", (20, 400), font, 0.8, (0, 0, 0), 1, cv2.LINE_AA)
cv2.putText(img_rd, "Q: Quit", (20, 450), font, 0.8, (0, 0, 0), 1, cv2.LINE_AA)
# 按下 'q' 键退出 / press 'q' to exit
if kk == ord('q'):
break
# 如果需要摄像头窗口大小可调 / uncomment this line if you want the camera window is resizeable
# cv2.namedWindow("camera", 0)
cv2.imshow("camera", img_rd)
# 释放摄像头 / release camera
cap.release()
cv2.destroyAllWindows()

137
get_features_into_CSV.py Normal file
View File

@ -0,0 +1,137 @@
# 从人脸图像文件中提取人脸特征存入 CSV
# Get features from images and save into features_all.csv
# Author: coneypo
# Blog: http://www.cnblogs.com/AdaminXie
# GitHub: https://github.com/coneypo/Dlib_face_recognition_from_camera
# Mail: coneypo@foxmail.com
# Created at 2018-05-11
# Updated at 2019-02-25
# 增加录入多张人脸到 CSV 的功能
# return_128d_features() 获取某张图像的 128D 特征
# write_into_csv() 获取某个路径下所有图像的特征,并写入 CSV
# compute_the_mean() 从 CSV 中读取 128D 特征,并计算特征均值
import cv2
import os
import dlib
from skimage import io
import csv
import numpy as np
import pandas as pd
# 要读取人脸图像文件的路径
path_photos_from_camera = "data/data_faces_from_camera/"
# 储存人脸特征 csv 的路径
path_csv_from_photos = "data/data_csvs_from_camera/"
# Dlib 正向人脸检测器
detector = dlib.get_frontal_face_detector()
# Dlib 人脸预测器
predictor = dlib.shape_predictor("data/data_dlib/shape_predictor_5_face_landmarks.dat")
# Dlib 人脸识别模型
# Face recognition model, the object maps human faces into 128D vectors
facerec = dlib.face_recognition_model_v1("data/data_dlib/dlib_face_recognition_resnet_model_v1.dat")
# 返回单张图像的 128D 特征
def return_128d_features(path_img):
img = io.imread(path_img)
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
faces = detector(img_gray, 1)
print("%-40s %-20s" % ("检测到人脸的图像 / image with faces detected:", path_img), '\n')
# 因为有可能截下来的人脸再去检测,检测不出来人脸了
# 所以要确保是 检测到人脸的人脸图像 拿去算特征
if len(faces) != 0:
shape = predictor(img_gray, faces[0])
face_descriptor = facerec.compute_face_descriptor(img_gray, shape)
else:
face_descriptor = 0
print("no face")
# print(face_descriptor)
return face_descriptor
# 将文件夹中照片特征提取出来, 写入 CSV
# path_faces_personX: 图像文件夹的路径
# path_csv_from_photos: 要生成的 CSV 路径
def write_into_csv(path_faces_personX, path_csv_from_photos):
photos_list = os.listdir(path_faces_personX)
with open(path_csv_from_photos, "w", newline="") as csvfile:
writer = csv.writer(csvfile)
if photos_list:
for i in range(len(photos_list)):
# 调用return_128d_features()得到128d特征
print("%-40s %-20s" % ("正在读的人脸图像 / image to read:", path_faces_personX + "/" + photos_list[i]))
features_128d = return_128d_features(path_faces_personX + "/" + photos_list[i])
# print(features_128d)
# 遇到没有检测出人脸的图片跳过
if features_128d == 0:
i += 1
else:
writer.writerow(features_128d)
else:
print("文件夹内图像文件为空 / Warning: Empty photos in " + path_faces_personX + '/', '\n')
writer.writerow("")
# 读取某人所有的人脸图像的数据,写入 person_X.csv
faces = os.listdir(path_photos_from_camera)
faces.sort()
for person in faces:
print("##### " + person + " #####")
print(path_csv_from_photos + person + ".csv")
write_into_csv(path_photos_from_camera + person, path_csv_from_photos + person + ".csv")
print('\n')
# 从 CSV 中读取数据,计算 128D 特征的均值
def compute_the_mean(path_csv_from_photos):
column_names = []
# 128D 特征
for feature_num in range(128):
column_names.append("features_" + str(feature_num + 1))
# 利用 pandas 读取 csv
rd = pd.read_csv(path_csv_from_photos, names=column_names)
if rd.size != 0:
# 存放 128D 特征的均值
feature_mean_list = []
for feature_num in range(128):
tmp_arr = rd["features_" + str(feature_num + 1)]
tmp_arr = np.array(tmp_arr)
# 计算某一个特征的均值
tmp_mean = np.mean(tmp_arr)
feature_mean_list.append(tmp_mean)
else:
feature_mean_list = []
return feature_mean_list
# 存放所有特征均值的 CSV 的路径
path_csv_from_photos_feature_all = "data/features_all.csv"
# 存放人脸特征的 CSV 的路径
path_csv_from_photos = "data/data_csvs_from_camera/"
with open(path_csv_from_photos_feature_all, "w", newline="") as csvfile:
writer = csv.writer(csvfile)
csv_rd = os.listdir(path_csv_from_photos)
csv_rd.sort()
print("##### 得到的特征均值 / The generated average values of features stored in: #####")
for i in range(len(csv_rd)):
feature_mean_list = compute_the_mean(path_csv_from_photos + csv_rd[i])
print(path_csv_from_photos + csv_rd[i])
writer.writerow(feature_mean_list)

58
how_to_use_camera.py Normal file
View File

@ -0,0 +1,58 @@
# OpenCv 调用摄像头
# 默认调用笔记本摄像头
# Author: coneypo
# Blog: http://www.cnblogs.com/AdaminXie
# GitHub: https://github.com/coneypo/Dlib_face_recognition_from_camera
# Mail: coneypo@foxmail.com
import cv2
cap = cv2.VideoCapture(0)
# cap.set(propId, value)
# 设置视频参数: propId - 设置的视频参数, value - 设置的参数值
cap.set(3, 480)
# cap.isOpened() 返回 true/false, 检查摄像头初始化是否成功
print(cap.isOpened())
# cap.read()
"""
返回两个值
先返回一个布尔值, 如果视频读取正确, 则为 True, 如果错误, 则为 False;
也可用来判断是否到视频末尾;
再返回一个值, 为每一帧的图像, 该值是一个三维矩阵;
通用接收方法为:
ret,frame = cap.read();
ret: 布尔值;
frame: 图像的三维矩阵;
这样 ret 存储布尔值, frame 存储图像;
若使用一个变量来接收两个值, 如:
frame = cap.read()
则 frame 为一个元组, 原来使用 frame 处需更改为 frame[1]
"""
while cap.isOpened():
ret_flag, img_camera = cap.read()
cv2.imshow("camera", img_camera)
# 每帧数据延时 1ms, 延时为0, 读取的是静态帧
k = cv2.waitKey(1)
# 按下 's' 保存截图
if k == ord('s'):
cv2.imwrite("test.jpg", img_camera)
# 按下 'q' 退出
if k == ord('q'):
break
# 释放所有摄像头
cap.release()
# 删除建立的所有窗口
cv2.destroyAllWindows()

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 457 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 499 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 425 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 416 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 433 KiB

BIN
introduction/overview.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 445 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More