MTCNN 人脸识别
前言
此处介绍强大的 MTCNN 模块,给出demo,展示MTCNN 的 OOP, 以及ROS利用 C++ 节点,命令行调用脚本执行实际工作的思路。
MTCNN Script
import argparse
import cv2
from mtcnn import MTCNN
import os
class MTCNNProcessor:
def __init__(self):
"""初始化MTCNN检测器和绘图配置"""
self.detector = MTCNN() # 模型只加载一次
self.keypoint_colors = { # 关键点颜色配置
'left_eye': (0, 255, 0),
'right_eye': (0, 255, 0),
'nose': (255, 0, 255),
'mouth_left': (255, 255, 0),
'mouth_right': (255, 255, 0)
}
def process_image(self, input_path="/home/ncut/Pictures/MTCNN_test.jpg", output_path=None):
"""
完整处理流程入口
:param input_path: 输入图像路径
:param output_path: 输出图像路径 (None则不保存)
:return: 带标注的BGR图像数组
"""
image = self._load_image(input_path)
if image is None:
raise FileNotFoundError(f"图像文件 {input_path} 不存在或无法读取")
results = self.detect_faces(image)
annotated_image = self.draw_results(image.copy(), results)
if output_path:
self._save_image(annotated_image, output_path)
return annotated_image
def detect_faces(self, image):
"""执行人脸检测"""
return self.detector.detect_faces(image)
def draw_results(self, image, results):
"""在图像上绘制检测结果"""
for result in results:
x, y, w, h = result['box']
confidence = result['confidence']
# 绘制边界框和置信度
cv2.rectangle(image, (x, y), (x + w, y + h), (255, 0, 0), 2)
cv2.putText(image, f"{confidence:.2%}", (x, y - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2)
# 绘制关键点
for name, pos in result['keypoints'].items():
cv2.circle(image, pos, 3, self.keypoint_colors.get(name, (255, 255, 255)), 2)
return image
@staticmethod
def _load_image(path):
"""加载图像并转换为RGB格式"""
if not os.path.exists(path):
return None
image = cv2.imread(path)
return cv2.cvtColor(image, cv2.COLOR_BGR2RGB) if image is not None else None
@staticmethod
def _save_image(image, path):
"""保存图像到指定路径"""
cv2.imwrite(path, cv2.cvtColor(image, cv2.COLOR_RGB2BGR))
def main():
# 命令行参数解析
print("in python begin")
parser = argparse.ArgumentParser(description='MTCNN人脸检测处理器')
parser.add_argument('--input', required=True, help='输入图像路径')
parser.add_argument('--output', help='输出图像路径 (可选)')
args = parser.parse_args()
print("parse succeed")
# 创建处理器实例
processor = MTCNNProcessor()
try:
# 执行处理流程
result_image = processor.process_image(args.input, args.output)
# 可选:显示结果(调试时使用)
if os.environ.get('DEBUG_SHOW'):
import matplotlib.pyplot as plt
plt.imshow(result_image)
plt.axis('off')
plt.show()
except Exception as e:
print(f"处理失败: {str(e)}")
exit(1)
print("python process successfully done")
if __name__ == "__main__":
main()
上文中,我们在 init部分实现了初始化,包括模型的加载,关键点颜色的硬编码——这部分将直接作用于后续的图像绘制。因为MTCNN模型的帮助,这里我们不需要预处理图像,直接调用方法进行。注意到类中有两个 static method,用修饰符@标识,这是python特有的语法,类似C++中的 static method,独立于变量,由类的方式调用,也许跟单例有关。
实际上,对于MTCNN模块,在环境正确配置后,通过下述语句,能够进行推理
from mtcnn import MTCNN
detector = MTCNN()
image = cv2.cvtColor(cv2.imread('/home/ncut/Pictures/MTCNN_test.jpg'), cv2.COLOR_BGR2RGB)
results = detector.detect_faces(image)
x, y, width, height = result['box']
confidence = result['confidence']
ROS Topic subscriber
C++的sub节点,订阅topic,保存图片,通过命令行方式指定python版本,执行模型推理。
#include <ros/ros.h>
#include <sensor_msgs/Image.h>
#include <cv_bridge/cv_bridge.h>
#include <opencv2/opencv.hpp>
#include <cstdlib>
#include <ctime>
#include <string>
#include <sstream>
// 生成唯一文件名(替代 generate_uuid())
std::string generate_unique_id() {
static int counter = 0;
std::stringstream ss;
ss << time(nullptr) << "_" << counter++; // 时间戳 + 计数器
return ss.str();
}
void imageCallback(const sensor_msgs::ImageConstPtr& msg) {
ROS_INFO("process callback");
try {
// 转换 ROS 图像消息
cv_bridge::CvImagePtr cv_ptr = cv_bridge::toCvCopy(msg, "bgr8");
cv::Mat image = cv_ptr->image;
// 生成唯一文件名(避免多帧覆盖)
std::string uuid = generate_unique_id();
std::string temp_path = "/dev/shm/ros_input_" + uuid + ".jpg";
std::string output_path = "/dev/shm/ros_output_" + uuid + ".jpg";
// 保存输入图像
cv::imwrite(temp_path, image);
// 构建 Python 调用命令
std::string command =
"DEBUG_SHOW=1 "
"/home/ncut/miniconda3/envs/tf/bin/python /home/ncut/my_ws/src/graduation_design/scripts/MTCNN_photo.py "
"--input " + temp_path + " "
"--output " + output_path + " "
"&"; // attention here
// 调用 Python 脚本
int ret = std::system(command.c_str());
if (ret != 0) {
ROS_ERROR("Python脚本调用失败,返回码: %d", ret);
return;
}
ROS_INFO("invoke python script sucessfully");
} catch (cv_bridge::Exception& e) {
ROS_ERROR("cv_bridge异常: %s", e.what());
}
}
void MessageCallback()
{
ROS_INFO("NOW I am in the callback funciton");
return ;
}
int main(int argc, char** argv) {
ros::init(argc, argv, "MTCNN_sub_photo");
ros::NodeHandle nh;
// PC test, topic name is camera/image_raw, which matches the video_pub.py
ros::Subscriber sub = nh.subscribe("/camera/rgb/image_raw", 2, imageCallback);
ROS_INFO("now i will go into the ros::ok() loop");
ros::Rate loop_rate(0.04);
while(ros::ok()) {
ros::spinOnce(); // asynchronous way
loop_rate.sleep();
}
//ros::spin();
system("rm -f /dev/shm/ros_input_*.jpg /dev/shm/ros_output_*.jpg");
return 0;
}
这份代码展示了 ROS 编程的范例,比如 while loop 以 ros::ok() 为循环判断条件。用于生成唯一std::stringstream变量名的time()使用,附加计数器标识图片文件先后存储顺序。在 /dev/shm/共享内存目录保存文件,减少文件读写 I/O, 以及通过 std::stream()方式,运行命令行指令。
In conclusion
这些技巧展示了解决方案的多样性,也展示了C++与命令行、系统时间的交互。——实际上,不少我们熟知的、主观上认为独立的计算机概念,可能都以类似的方式彼此连接着。