前言?
此處介紹強大的 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++與命令行、系統時間的交互。——實際上,不少我們熟知的、主觀上認為獨立的計算機概念,可能都以類似的方式彼此連接著。