前言
此处介绍强大的 MTCNN 模块,给出demo,展示MTCNN 的 OOP, 以及ROS利用 C++ 节点,命令行调用脚本执行实际工作的思路。
MTCNN Script
import argparse
import cv2
from mtcnn import MTCNN
import osclass 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_imagedef 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@staticmethoddef _load_image(path):"""加载图像并转换为RGB格式"""if not os.path.exists(path):return Noneimage = cv2.imread(path)return cv2.cvtColor(image, cv2.COLOR_BGR2RGB) if image is not None else None@staticmethoddef _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 pltplt.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.pyros::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 wayloop_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++与命令行、系统时间的交互。——实际上,不少我们熟知的、主观上认为独立的计算机概念,可能都以类似的方式彼此连接着。