#include <opencv2/opencv.hpp> #include <opencv2/fe...
Créé le : 17 mars 2025
Créé le : 17 mars 2025
#include <opencv2/opencv.hpp>
#include <opencv2/features2d.hpp>
#include <iostream>
// wiringPi & LCD
#include <wiringPi.h>
#include <lcd.h>
#include <unistd.h> // for sleep(), usleep()
// ---------------------【LCD1602 接口定义】---------------------
// 这里使用 BCM GPIO 编号 + wiringPiSetupGpio() 初始化
#define LCD_RS 6 // RS 引脚
#define LCD_E 13 // E 引脚
#define LCD_D4 19
#define LCD_D5 26
#define LCD_D6 12
#define LCD_D7 16
static cv::VideoCapture cap; // 全局摄像头
// -------------------- 初始化摄像头 ---------------------
bool setupCamera(int width, int height)
{
cap.open(0);
if(!cap.isOpened()) {
std::cerr << "Unable to open camera!" << std::endl;
return false;
}
cap.set(cv::CAP_PROP_FRAME_WIDTH, width);
cap.set(cv::CAP_PROP_FRAME_HEIGHT, height);
return true;
}
// -------------------- 捕获一帧图像 ---------------------
cv::Mat captureFrame()
{
cv::Mat frame;
cap >> frame;
return frame;
}
// -------------------- 读取参考图 -----------------------
cv::Mat readImage(const std::string &path)
{
cv::Mat img = cv::imread(path, cv::IMREAD_COLOR);
if (img.empty()) {
std::cerr << "Cannot load image: " << path << std::endl;
}
return img;
}
// -------------------- ORB + KNN + RANSAC 检测 ----------------
cv::Point detectObject(const cv::Mat &scene, const cv::Mat &object)
{
if (scene.empty() || object.empty()) return cv::Point(-1, -1);
textstatic cv::Ptr<cv::Feature2D> orb = cv::ORB::create(); if(!orb) { std::cerr << "ORB not supported in this OpenCV build." << std::endl; return cv::Point(-1, -1); } // 提取特征 std::vector<cv::KeyPoint> kpObj, kpScene; cv::Mat descObj, descScene; orb->detectAndCompute(object, cv::noArray(), kpObj, descObj); orb->detectAndCompute(scene, cv::noArray(), kpScene, descScene); if(descObj.empty() || descScene.empty()) { return cv::Point(-1, -1); } // BFMatcher + KNN 匹配 cv::BFMatcher matcher(cv::NORM_HAMMING); std::vector<std::vector<cv::DMatch>> knnMatches; matcher.knnMatch(descObj, descScene, knnMatches, 2); // 比例测试 const float ratioThresh = 0.75f; std::vector<cv::DMatch> goodMatches; for(size_t i = 0; i < knnMatches.size(); i++) { if(knnMatches[i].size() == 2) { const cv::DMatch &best = knnMatches[i][0]; const cv::DMatch &second = knnMatches[i][1]; if(best.distance < ratioThresh * second.distance) { goodMatches.push_back(best); } } } if(goodMatches.size() < 10) { return cv::Point(-1, -1); } // RANSAC Homography std::vector<cv::Point2f> objPoints, scenePoints; for(const auto &m : goodMatches) { objPoints.push_back(kpObj[m.queryIdx].pt); scenePoints.push_back(kpScene[m.trainIdx].pt); } std::vector<unsigned char> inlierMask; cv::Mat H = cv::findHomography(objPoints, scenePoints, cv::RANSAC, 5.0, inlierMask); if(H.empty()) { return cv::Point(-1, -1); } // 统计内点 int inliersCount = 0; for(size_t i=0; i<inlierMask.size(); i++) { if(inlierMask[i]) inliersCount++; } if(inliersCount < 10) { return cv::Point(-1, -1); } // 计算所有内点的平均坐标 double sumX = 0, sumY = 0; int validCount = 0; for(size_t i=0; i<inlierMask.size(); i++) { if(inlierMask[i]) { sumX += scenePoints[i].x; sumY += scenePoints[i].y; validCount++; } } if(validCount==0) return cv::Point(-1, -1); cv::Point center((int)(sumX/validCount), (int)(sumY/validCount)); return center;
}
// -------------------- 主函数 ---------------------------
int main()
{
// 1. 初始化 wiringPi (BCM模式)
if(wiringPiSetupGpio() == -1) {
std::cerr << "wiringPiSetupGpio failed.\n";
return -1;
}
text// 2. 初始化 LCD1602 (2 行, 16 列, 4 位接口) int lcdHandle = lcdInit(2, 16, 4, LCD_RS, LCD_E, LCD_D4, LCD_D5, LCD_D6, LCD_D7, 0,0,0,0); // 剩余参数用不到时填0 if(lcdHandle < 0) { std::cerr << "lcdInit failed!\n"; return -1; } lcdClear(lcdHandle); // 3. 初始化摄像头 if(!setupCamera(640, 480)) { return -1; } // 4. 读取参考图片 (Music.png),匹配后播放 Sun.mp3 std::string musicPath = "/home/pi/Desktop/Music.png"; cv::Mat refImage = readImage(musicPath); if(refImage.empty()) { std::cerr << "No reference image found at " << musicPath << std::endl; return -1; } cv::namedWindow("Camera", cv::WINDOW_AUTOSIZE); std::cout << "Press ESC or Q to quit.\n"; // 额外添加:只在“从未检测到->检测到”时才触发 bool hasTriggered = false; // 记录当前是否处于“已检测”状态 while(true) { // a) 采集当前帧 cv::Mat frame = captureFrame(); if(frame.empty()) { std::cerr << "Empty frame.\n"; break; } // b) 检测是否出现 Music.png cv::Point matchCenter = detectObject(frame, refImage); bool isDetected = (matchCenter.x >= 0 && matchCenter.y >= 0); if(isDetected && !hasTriggered) { // “从未检测”到“检测”的瞬间 hasTriggered = true; // 1. 终端输出 + 画面上画圈 std::cout << "Play Music" << std::endl; cv::circle(frame, matchCenter, 20, cv::Scalar(0,255,0), 3); // 2. LCD1602 显示 "Play Music" 5 秒 lcdClear(lcdHandle); lcdPosition(lcdHandle, 0, 0); lcdPuts(lcdHandle, "Play Music"); // 3. 后台播放 mp3 system("omxplayer /home/pi/Desktop/Sun.mp3 &"); // 先等待 5 秒,让 LCD 显示 delay(5000); lcdClear(lcdHandle); // 如果你要播放19秒: // 其实上面已经播放了5秒 + 14秒 =19秒 delay(14000); // 手动结束 omxplayer system("pkill omxplayer"); } else if(!isDetected) { // 当检测不到图像时,重置触发标记 hasTriggered = false; } // c) 显示结果 cv::imshow("Camera", frame); // d) 按键退出 char c = (char) cv::waitKey(30); if(c == 27 || c == 'q' || c == 'Q') { std::cout << "Exit.\n"; break; } } cap.release(); cv::destroyAllWindows(); lcdClear(lcdHandle); return 0;
}
现在这个程序有个问题就是每次我在codeblock(内置Open CV库上运行了一遍之后,这个程序就没要办法第二次执行,必须在树莓派终端上输入sudo fuser /dev/video0
sudo fuser -k /dev/video0
ps aux | grep -E 'v4l|video|cam|opencv'
sudo rmmod bcm2835_v4l2
sudo modprobe bcm2835_v4l2这一串命令行才能再运行一次。 你有没有什么可以一劳永逸的方法让这个程序每次运行之后不用在终端输入这些了
Shannon, this is kind of a classic gotcha when running camera code repeatedly on a Pi. The short story: something (usually the V4L2 driver) is still hanging on to the camera even after your program ends. Then Code::Blocks tries to run it again, and boom, “Device or resource busy.”
Below are a few ways to handle this so you’re not stuck typing those fuser and modprobe commands every time:
1. Make sure Code::Blocks really stops the program.
/dev/video0
.2. Add a post-exec (or post-build) script in Code::Blocks.
bashsudo fuser -k /dev/video0 sudo rmmod bcm2835_v4l2 sudo modprobe bcm2835_v4l2
3. Wrap those commands in a single shell script.
bash#!/bin/bash sudo fuser -k /dev/video0 sudo rmmod bcm2835_v4l2 sudo modprobe bcm2835_v4l2
reset_cam.sh
, make it executable (chmod +x reset_cam.sh
), and run it whenever you want a clean slate. You could also add it as a custom menu option in Code::Blocks or run it just before launching the program again.4. Double-check you’re releasing everything properly in code.
cap.release()
and cv::destroyAllWindows()
at the end, so that’s good. Just make sure there aren’t any other references to the camera that might be alive (for instance, a separate thread or something else that never got shut down).5. Consider the new Raspberry Pi “libcamera” approach (if you’re using the Pi cam module).
bcm2835_v4l2
method can be a bit finicky, especially if you’re not specifically using the old Pi Camera stack.libcamera
and OpenCV’s GStreamer-based pipeline, though that can be a bigger change in your code.Bottom line: The simplest route is probably a script or a Code::Blocks “post-exec” command that forcibly resets the camera driver. Then you can just run your program over and over without manually typing out the same kill/rmmod/modprobe sequence. Good luck!