Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cf88a17a47 | |||
| cbb8b935ce |
5
.idea/.gitignore
generated
vendored
@ -1,5 +0,0 @@
|
||||
# 默认忽略的文件
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# 基于编辑器的 HTTP 客户端请求
|
||||
/httpRequests/
|
||||
8
.idea/Dlib_face_recognition_from_camera.iml
generated
@ -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
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Encoding" addBOMForNewFiles="with NO BOM" />
|
||||
</project>
|
||||
12
.idea/inspectionProfiles/Project_Default.xml
generated
Normal 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>
|
||||
6
.idea/inspectionProfiles/profiles_settings.xml
generated
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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.
|
||||
0
models/dlib/dlib_face_recognition_resnet_model_v1.dat → data/data_dlib/dlib_face_recognition_resnet_model_v1.dat
Executable file → Normal file
0
models/dlib/shape_predictor_68_face_landmarks.dat → data/data_dlib/shape_predictor_68_face_landmarks.dat
Executable file → Normal file
BIN
data/data_faces_examples/John_Salley/000179_02159509.jpg
Normal file
|
After Width: | Height: | Size: 9.0 KiB |
BIN
data/data_faces_examples/John_Salley/000183_02159543.jpg
Normal file
|
After Width: | Height: | Size: 9.6 KiB |
BIN
data/data_faces_examples/John_Salley/000186_02159346.jpg
Normal file
|
After Width: | Height: | Size: 8.0 KiB |
BIN
data/data_faces_examples/John_Salley/000189_02159361.jpg
Normal file
|
After Width: | Height: | Size: 8.8 KiB |
BIN
data/data_faces_examples/John_Salley/000190_02159501.jpg
Normal file
|
After Width: | Height: | Size: 7.9 KiB |
BIN
data/data_faces_examples/John_Salley/000192_02159531.jpg
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
BIN
data/data_faces_examples/John_Salley/000194_02159572.jpg
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
BIN
data/data_faces_examples/John_Salley/000197_02159322.jpg
Normal file
|
After Width: | Height: | Size: 9.0 KiB |
BIN
data/data_faces_examples/John_Salley/000197_02159525.jpg
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
data/data_faces_examples/John_Salley/000198_02159470.jpg
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
data/data_faces_examples/John_Salley/000200_02159354.jpg
Normal file
|
After Width: | Height: | Size: 8.3 KiB |
BIN
data/data_faces_examples/John_Savage/000264_01099001.jpg
Normal file
|
After Width: | Height: | Size: 6.3 KiB |
BIN
data/data_faces_examples/John_Savage/000274_01099061.jpg
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
data/data_faces_examples/John_Savage/000277_01099000.jpg
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
BIN
data/data_faces_examples/John_Savage/000289_01099139.jpg
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
data/data_faces_examples/John_Savage/000290_01099067.jpg
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
BIN
data/data_faces_examples/John_Savage/000290_01099090.jpg
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
data/data_faces_examples/John_Savage/000291_01099023.jpg
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
data/data_faces_examples/John_Savage/000291_01099214.jpg
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
data/data_faces_examples/John_Savage/000293_01099081.jpg
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
BIN
data/data_faces_examples/John_Savage/000296_01099007.jpg
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
data/data_faces_examples/John_Savage/000299_01099008.jpg
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
data/data_faces_examples/John_Schneider/000288_00925786.jpg
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
BIN
data/data_faces_examples/John_Schneider/000302_00925785.jpg
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
data/data_faces_examples/John_Schneider/000307_00925823.jpg
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
data/data_faces_examples/John_Schneider/000325_00925954.jpg
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
BIN
data/data_faces_examples/John_Schneider/000326_00925765.jpg
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
BIN
data/data_faces_examples/John_Schneider/000326_00926089.jpg
Normal file
|
After Width: | Height: | Size: 7.0 KiB |
BIN
data/data_faces_examples/John_Schneider/000326_00926128.jpg
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
data/data_faces_examples/John_Schneider/000326_00926139.jpg
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
BIN
data/data_faces_examples/John_Schneider/000329_00925859.jpg
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
BIN
data/data_faces_examples/John_Schneider/000329_00925963.jpg
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
BIN
data/data_faces_examples/John_Schneider/000331_00926012.jpg
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
BIN
data/data_faces_examples/John_Shimkus/000373_03228153.jpg
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
BIN
data/data_faces_examples/John_Shimkus/000375_03227651.jpg
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
data/data_faces_examples/John_Shimkus/000376_02340068.jpg
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
BIN
data/data_faces_examples/John_Shimkus/000378_02340151.jpg
Normal file
|
After Width: | Height: | Size: 7.0 KiB |
BIN
data/data_faces_examples/John_Shimkus/000378_03227610.jpg
Normal file
|
After Width: | Height: | Size: 7.0 KiB |
BIN
data/data_faces_examples/John_Shimkus/000383_03227939.jpg
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
data/data_faces_examples/John_Shimkus/000385_03227766.jpg
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
data/data_faces_examples/John_Shimkus/000388_03227773.jpg
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
data/data_faces_examples/John_Shimkus/000390_03227666.jpg
Normal file
|
After Width: | Height: | Size: 7.7 KiB |
BIN
data/data_faces_examples/John_Shimkus/000394_02340150.jpg
Normal file
|
After Width: | Height: | Size: 9.9 KiB |
BIN
data/data_faces_examples/John_Shimkus/000396_03227722.jpg
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
data/data_faces_examples/John_Simm/000288_00470387.jpg
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
data/data_faces_examples/John_Simm/000297_00470170.jpg
Normal file
|
After Width: | Height: | Size: 7.0 KiB |
BIN
data/data_faces_examples/John_Simm/000300_00470148.jpg
Normal file
|
After Width: | Height: | Size: 7.1 KiB |
BIN
data/data_faces_examples/John_Simm/000304_00470122.jpg
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
data/data_faces_examples/John_Simm/000305_00470162.jpg
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
BIN
data/data_faces_examples/John_Simm/000305_00470717.jpg
Normal file
|
After Width: | Height: | Size: 8.5 KiB |
BIN
data/data_faces_examples/John_Simm/000306_00470222.jpg
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
data/data_faces_examples/John_Simm/000306_00470223.jpg
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
BIN
data/data_faces_examples/John_Simm/000309_00470287.jpg
Normal file
|
After Width: | Height: | Size: 6.0 KiB |
BIN
data/data_faces_examples/John_Simm/000310_00470421.jpg
Normal file
|
After Width: | Height: | Size: 5.4 KiB |
BIN
data/data_faces_examples/John_Simm/000310_00470511.jpg
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
BIN
data/data_faces_examples/John_test/000183_02159543.jpg
Normal file
|
After Width: | Height: | Size: 9.6 KiB |
BIN
data/data_faces_examples/John_test/000297_00470170.jpg
Normal file
|
After Width: | Height: | Size: 7.0 KiB |
BIN
data/data_faces_examples/John_test/000376_02340068.jpg
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
@ -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()
|
||||
@ -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()
|
||||
@ -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
@ -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()
|
||||
@ -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()
|
||||
@ -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()
|
||||
@ -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()
|
||||
136
features.py
@ -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()
|
||||
@ -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()
|
||||
233
get_faces.py
@ -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()
|
||||
378
get_faces_UI.py
@ -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()
|
||||
333
get_faces_dnn.py
@ -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
@ -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
@ -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
@ -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()
|
||||
BIN
introduction/Dlib_Face_recognition_by_coneypo.pptx
Normal file
BIN
introduction/face_reco_single_person.png
Normal file
|
After Width: | Height: | Size: 428 KiB |
BIN
introduction/face_reco_single_person_customize_name.png
Normal file
|
After Width: | Height: | Size: 457 KiB |
BIN
introduction/face_reco_two_people.png
Normal file
|
After Width: | Height: | Size: 499 KiB |
BIN
introduction/face_reco_two_people_in_database.png
Normal file
|
After Width: | Height: | Size: 425 KiB |
BIN
introduction/get_face_from_camera.png
Normal file
|
After Width: | Height: | Size: 416 KiB |
BIN
introduction/get_face_from_camera_out_of_range.png
Normal file
|
After Width: | Height: | Size: 433 KiB |
BIN
introduction/overview.png
Normal file
|
After Width: | Height: | Size: 445 KiB |