// vision-anti-pinch - V4L2 MMAP 取图 + USBCANFD CAN 收发 (OpenCV 版本) // // 使用 OpenCV 进行 UYVY/YUYV -> BGR 转换,避免手动转换公式问题 // CAN 收发默认开启, --no-can 关闭 // // 用法: // ./vision-anti-pinch -d /dev/video5 -n 10 -s 2 -t jpg -o ./out #include "v4l2_capture.h" #include "can_comm.h" #include "send.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include std::atomic g_Feedback_Of_The_Actual_Position_Of_The_Left_Front_Door_391_S; std::atomic g_Feedback_Of_The_Actual_Position_Of_The_Right_Front_Door_391_S; std::atomic g_Feedback_Of_The_Actual_Position_Of_The_Left_Rear_Door_391_S; std::atomic g_Feedback_Of_The_Actual_Position_Of_The_Right_Rear_Door_391_S; // Sensing GMSL2 驱动自定义控制 ID #define CID_SENSOR_MODE 0x009a2008 #define CID_TRIG_PIN 0x009a2084 #define CID_TRIG_MODE 0x009a2085 #define CID_GAIN 0x009a2009 #define CID_EXPOSURE 0x009a200a static volatile sig_atomic_t g_stop = 0; static void onSignal(int) { g_stop = 1; } static void printUsage(const char *prog) { printf( "Usage: %s [options]\n" "V4L2 MMAP capture demo for Sensing GMSL2 camera on Jetson Orin.\n" "\n" "Capture options:\n" " -d V4L2 device node (default: /dev/video5)\n" " -W frame width (default: 1920)\n" " -H frame height (default: 1536)\n" " -f target frame rate (default: 30)\n" " -F pixel format: UYVY | YUYV (default: UYVY)\n" " -m sensor_mode control value\n" " -p trig_pin control value (hex/dec)\n" " --gain sensor gain\n" " --exposure sensor exposure\n" "\n" "Output options:\n" " -n frames to capture, 0 = run forever\n" " -s frames to save\n" " -o output directory (default: ./frames)\n" " -t file type: jpg | png | bmp | raw (default: jpg)\n" " --quality <1..100> JPEG quality (default: 95)\n" " -v verbose output\n" " -h print this help\n" "\n" "CAN options:\n" " --no-can disable USBCANFD CAN/CANFD receive (enabled by default)\n" "\n" "Example:\n" " %s -d /dev/video5 -n 100 -s 5 -t jpg -o ./out\n" " %s -d /dev/video5 -n 0 -v\n", prog, prog, prog); } struct Args { std::string device = "/dev/video5"; uint32_t width = 1920; uint32_t height = 1536; // SG3S-ISX031C-GMSL2F 默认分辨率 uint32_t fps = 30; std::string format = "UYVY"; long sensor_mode = -1; long trig_pin = -1; long trig_mode = -1; long gain = -1; long exposure = -1; long count = 0; long save = 0; std::string out_dir = "./frames"; std::string save_type = "jpg"; int quality = 95; bool verbose = false; bool can_enabled = true; // 默认启用 CAN 收发, --no-can 关闭 }; // 手动解析参数 static bool parseArgs(int argc, char **argv, Args &a) { auto need_val = [&](int &i) -> std::string { if (i + 1 >= argc) { printf("missing value for %s\n", argv[i]); std::exit(1); } return argv[++i]; }; for (int i = 1; i < argc; ++i) { std::string arg = argv[i]; if (arg == "-d") a.device = need_val(i); else if (arg == "-W") a.width = (uint32_t)std::atoi(need_val(i).c_str()); else if (arg == "-H") a.height = (uint32_t)std::atoi(need_val(i).c_str()); else if (arg == "-f") a.fps = (uint32_t)std::atoi(need_val(i).c_str()); else if (arg == "-F") a.format = need_val(i); else if (arg == "-m") a.sensor_mode = std::strtol(need_val(i).c_str(), nullptr, 0); else if (arg == "-p") a.trig_pin = std::strtol(need_val(i).c_str(), nullptr, 0); else if (arg == "-M") a.trig_mode = std::strtol(need_val(i).c_str(), nullptr, 0); else if (arg == "-n") a.count = std::strtol(need_val(i).c_str(), nullptr, 0); else if (arg == "-s") a.save = std::strtol(need_val(i).c_str(), nullptr, 0); else if (arg == "-o") a.out_dir = need_val(i); else if (arg == "-t") a.save_type = need_val(i); else if (arg == "-v") a.verbose = true; else if (arg == "-h") { printUsage(argv[0]); std::exit(0); } else if (arg == "--gain") a.gain = std::strtol(need_val(i).c_str(), nullptr, 0); else if (arg == "--exposure") a.exposure = std::strtol(need_val(i).c_str(), nullptr, 0); else if (arg == "--quality") a.quality = std::atoi(need_val(i).c_str()); else if (arg == "--no-can") a.can_enabled = false; else { printf("unknown option: %s\n", arg.c_str()); printUsage(argv[0]); return false; } } return true; } // 使用 OpenCV 将 V4L2 帧 (UYVY/YUYV) 转为 BGR Mat static cv::Mat frameToBgr(const V4l2Capture::Frame &frame) { // OpenCV 原生支持 UYVY/YUYV -> BGR 转换 // UYVY = COLOR_YUV2BGR_UYVY, YUYV = COLOR_YUV2BGR_YUYV int code = (frame.pixelformat == V4L2_PIX_FMT_UYVY) ? cv::COLOR_YUV2BGR_UYVY : cv::COLOR_YUV2BGR_YUYV; cv::Mat yuv((int)frame.height, (int)frame.width, CV_8UC2, (void *)frame.data); cv::Mat bgr; cv::cvtColor(yuv, bgr, code); return bgr; } /* ===================================================================== * CAN 报文接收解析 (由 can_comm 接收线程回调) * ===================================================================== */ // 512 位报文位域, deal_msg 中按 msg_id 拷贝后可按位解析信号 static std::bitset<512> g_bitMsg; static uint64_t get_bit_value(int star_bit, int bit_len) { uint64_t signal_value = 0; for (int i = 0; i < bit_len; ++i) { signal_value |= g_bitMsg[star_bit + i] << i; } return signal_value; } // 收到报文的回调点: 目前仅解析 0x391, 后续信号解析在此扩展 static void deal_msg(int64_t time, uint32_t msg_id, const uint8_t *data, uint32_t len) { if (msg_id == 0x391 && len >= 4 && len <= 64) { memcpy(&g_bitMsg, data, len); g_Feedback_Of_The_Actual_Position_Of_The_Left_Front_Door_391_S = data[0]; g_Feedback_Of_The_Actual_Position_Of_The_Right_Front_Door_391_S = data[1]; g_Feedback_Of_The_Actual_Position_Of_The_Left_Rear_Door_391_S = data[2]; g_Feedback_Of_The_Actual_Position_Of_The_Right_Rear_Door_391_S = data[3]; // Rear_BCM_0x391,0x391,64,1.0-1.7,8,Feedback_Of_The_Actual_Position_Of_The_Left_Front_Door_391_S // Rear_BCM_0x391,0x391,64,2.0-2.7,8,Feedback_Of_The_Actual_Position_Of_The_Right_Front_Door_391_S // Rear_BCM_0x391,0x391,64,3.0-3.7,8,Feedback_Of_The_Actual_Position_Of_The_Left_Rear_Door_391_S // Rear_BCM_0x391,0x391,64,4.0-4.7,8,Feedback_Of_The_Actual_Position_Of_The_Right_Rear_Door_391_S } } int main(int argc, char **argv) { Args args; if (!parseArgs(argc, argv, args)) { return 1; } std::signal(SIGINT, onSignal); std::signal(SIGTERM, onSignal); uint32_t pixfmt = (args.format == "UYVY") ? V4L2_PIX_FMT_UYVY : V4L2_PIX_FMT_YUYV; // 启动 CAN 收发(失败不致命, 可继续仅取图) if (args.can_enabled) { CanComm::set_msg_callback(deal_msg); // 注册报文解析回调 if (!CanComm::can_start()) { printf("CAN start failed, continue with camera only.\n"); args.can_enabled = false; } } V4l2Capture cap; if (!cap.open(args.device)) { return 1; } // 设置相机控制 if (args.sensor_mode >= 0) cap.setControl(CID_SENSOR_MODE, (int32_t)args.sensor_mode); if (args.trig_pin >= 0) cap.setControl(CID_TRIG_PIN, (int32_t)args.trig_pin); if (args.trig_mode >= 0) cap.setControl(CID_TRIG_MODE, (int32_t)args.trig_mode); if (args.gain >= 0) cap.setControl(CID_GAIN, (int32_t)args.gain); if (args.exposure >= 0) cap.setControl(CID_EXPOSURE, (int32_t)args.exposure); if (!cap.setFormat(args.width, args.height, pixfmt)) { return 1; } cap.setFps(args.fps); if (!cap.initMmap(4)) { return 1; } if (!cap.startStreaming()) { return 1; } printf("=========================================================\n"); printf("Capturing %s %ux%u @ %u fps from %s\n", args.format.c_str(), cap.width(), cap.height(), args.fps, args.device.c_str()); printf("Press Ctrl+C to stop.\n"); printf("=========================================================\n"); // 创建输出目录 if (args.save > 0) { std::string mkcmd = "mkdir -p " + args.out_dir; std::system(mkcmd.c_str()); } using clock = std::chrono::steady_clock; auto t_start = clock::now(); auto t_last_report = t_start; uint64_t frame_count = 0; uint64_t save_count = 0; uint32_t timeout_consecutive = 0; while (!g_stop) { if (args.count > 0 && frame_count >= (uint64_t)args.count) { break; } V4l2Capture::Frame frame; if (!cap.getFrame(frame, 2000)) { timeout_consecutive++; if (timeout_consecutive > 5) { printf("getFrame: timeout/empty for %u consecutive tries, aborting\n", timeout_consecutive); break; } continue; } timeout_consecutive = 0; frame_count++; // 保存帧 if (save_count < (uint64_t)args.save) { bool ok = false; char fname[512]; // 使用 OpenCV 转换并保存 cv::Mat bgr = frameToBgr(frame); if (!bgr.empty()) { std::snprintf(fname, sizeof(fname), "%s/frame_%06lu.%s", args.out_dir.c_str(), save_count, args.save_type.c_str()); std::vector params; if (args.save_type == "jpg" || args.save_type == "jpeg") { params = {cv::IMWRITE_JPEG_QUALITY, args.quality}; } else if (args.save_type == "png") { params = {cv::IMWRITE_PNG_COMPRESSION, 3}; } ok = cv::imwrite(fname, bgr, params); } if (ok) { printf("[saved] %s\n", fname); save_count++; } else { printf("[save failed] frame %lu\n", save_count); } } DebugInfo *pData = new DebugInfo(); // pData->CameraId = (ctx->cam_no == 1 ? 0 : 1); // pData->TimeStampMs = epochTimeStamp_ms; memcpy(pData->Img, frame.data, frame.size); bool res = send_debuginfo(pData); //printf("send %ld %d %d\n", pData->TimeStampMs, pData->CameraId, res); delete pData; pData = nullptr; cap.releaseFrame(frame); // if (args.verbose) { // printf("frame #%lu seq=%u size=%zu\n", // frame_count, frame.sequence, frame.size); // } auto now = clock::now(); auto since_report = std::chrono::duration_cast(now - t_last_report); if (since_report.count() >= 1000) { double total_sec = std::chrono::duration_cast(now - t_start).count() / 1000.0; double avg_fps = total_sec > 0 ? frame_count / total_sec : 0; printf("[stats] frames=%lu avg_fps=%.2f door_position[%ld %ld %ld %ld]\n", frame_count, avg_fps, g_Feedback_Of_The_Actual_Position_Of_The_Left_Front_Door_391_S.load(), g_Feedback_Of_The_Actual_Position_Of_The_Right_Front_Door_391_S.load(), g_Feedback_Of_The_Actual_Position_Of_The_Left_Rear_Door_391_S.load(), g_Feedback_Of_The_Actual_Position_Of_The_Right_Rear_Door_391_S.load()); t_last_report = now; } } auto t_end = clock::now(); double total_sec = std::chrono::duration_cast(t_end - t_start).count() / 1000.0; printf("=========================================================\n"); printf("Capture finished. frames=%lu duration=%.2fs avg_fps=%.2f\n", frame_count, total_sec, total_sec > 0 ? frame_count / total_sec : 0); if (args.save > 0) { printf("Saved %lu frames to %s/\n", save_count, args.out_dir.c_str()); } if (args.can_enabled && CanComm::is_open()) { printf("CAN received: %d frames\n", CanComm::receive_count()); } printf("=========================================================\n"); cap.stopStreaming(); cap.close(); if (args.can_enabled) { CanComm::can_stop(); } return 0; }