#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
#include <serial/serial.h> // 需要安装serial库
// 定义固定的头和尾
const char HEADER = 0x0a;
const char FOOTER = 0x0b;
// 定义初始的红色HSV范围
int h_min1 = 0, s_min1 = 120, v_min1 = 70;
int h_max1 = 10, s_max1 = 255, v_max1 = 255;
int h_min2 = 160, s_min2 = 120, v_min2 = 70;
int h_max2 = 180, s_max2 = 255, v_max2 = 255;
// 滑动条回调函数,这里可以为空,因为我们在主循环中直接读取滑动条的值
void on_trackbar(int, void*) {}
int main() {
// 打开串口
serial::Serial my_serial("/dev/ttyUSB0", 115200, serial::Timeout::simpleTimeout(1000));
if (!my_serial.isOpen()) {
std::cerr << "无法打开串口!" << std::endl;
return -1;
}
// 打开摄像头
cv::VideoCapture cap(0);
if (!cap.isOpened()) {
std::cerr << "无法打开摄像头!" << std::endl;
return -1;
}
cv::namedWindow("检测结果", cv::WINDOW_AUTOSIZE);
cv::namedWindow("HSV设置", cv::WINDOW_AUTOSIZE);
// 创建滑动条
cv::createTrackbar("H Min1", "HSV设置", &h_min1, 180, on_trackbar);
cv::createTrackbar("S Min1", "HSV设置", &s_min1, 255, on_trackbar);
cv::createTrackbar("V Min1", "HSV设置", &v_min1, 255, on_trackbar);
cv::createTrackbar("H Max1", "HSV设置", &h_max1, 180, on_trackbar);
cv::createTrackbar("S Max1", "HSV设置", &s_max1, 255, on_trackbar);
cv::createTrackbar("V Max1", "HSV设置", &v_max1, 255, on_trackbar);
cv::createTrackbar("H Min2", "HSV设置", &h_min2, 180, on_trackbar);
cv::createTrackbar("S Min2", "HSV设置", &s_min2, 255, on_trackbar);
cv::createTrackbar("V Min2", "HSV设置", &v_min2, 255, on_trackbar);
cv::createTrackbar("H Max2", "HSV设置", &h_max2, 180, on_trackbar);
cv::createTrackbar("S Max2", "HSV设置", &s_max2, 255, on_trackbar);
cv::createTrackbar("V Max2", "HSV设置", &v_max2, 255, on_trackbar);
while (true)
{
cv::Mat frame, hsv, red_mask1, red_mask2, red_mask;
// 读取帧
cap >> frame;
if (frame.empty())
break;
// 转换为HSV色彩空间
cv::cvtColor(frame, hsv, cv::COLOR_BGR2HSV);
// 创建红色掩模(两个范围,因为红色在HSV色环的两端)
cv::Scalar RED_LOWER1(h_min1, s_min1, v_min1);
cv::Scalar RED_UPPER1(h_max1, s_max1, v_max1);
cv::Scalar RED_LOWER2(h_min2, s_min2, v_min2);
cv::Scalar RED_UPPER2(h_max2, s_max2, v_max2);
cv::inRange(hsv, RED_LOWER1, RED_UPPER1, red_mask1);
cv::inRange(hsv, RED_LOWER2, RED_UPPER2, red_mask2);
cv::bitwise_or(red_mask1, red_mask2, red_mask);
// 形态学操作(去除噪声)
cv::Mat kernel = cv::getStructuringElement(cv::MORPH_ELLIPSE, cv::Size(5, 5));
cv::morphologyEx(red_mask, red_mask, cv::MORPH_OPEN, kernel);
cv::morphologyEx(red_mask, red_mask, cv::MORPH_CLOSE, kernel);
// 查找轮廓
std::vector<std::vector<cv::Point>> contours;
cv::findContours(red_mask.clone(), contours, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);
// 检测圆形
for (size_t i = 0; i < contours.size(); i++)
{
double area = cv::contourArea(contours[i]);
// 过滤小区域
if (area < 300)
continue;
// 计算最小外接圆
cv::Point2f center;
float radius;
cv::minEnclosingCircle(contours[i], center, radius);
// 计算圆形度
double perimeter = cv::arcLength(contours[i], true);
double circularity = (4 * CV_PI * area) / (perimeter * perimeter);
// 如果是圆形(圆形度接近1)
if (circularity > 0.7) {
// 绘制圆形和中心点
cv::circle(frame, center, static_cast<int>(radius), cv::Scalar(0, 255, 0), 2);
cv::circle(frame, center, 3, cv::Scalar(0, 0, 255), -1);
// 显示半径
std::string radiusText = "R: " + std::to_string(static_cast<int>(radius));
cv::putText(frame, radiusText, cv::Point(center.x, center.y - 20),
cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(255, 255, 255), 1);
// 显示中心点坐标
std::string centerText = "(" + std::to_string(static_cast<int>(center.x)) + ", " + std::to_string(static_cast<int>(center.y)) + ")";
cv::putText(frame, centerText, cv::Point(center.x, center.y + 20),
cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(255, 255, 255), 1);
// 发送头
my_serial.write(&HEADER, 1);
// 发送坐标到串口
float x = center.x;
float y = center.y;
my_serial.write((char*)&x, sizeof(float));
my_serial.write((char*)&y, sizeof(float));
// 发送尾
my_serial.write(&FOOTER, 1);
}
}
// 显示结果
cv::imshow("检测结果", frame);
cv::imshow("红色掩模", red_mask);
// 按'q'退出
if (cv::waitKey(30) == 'q') break;
}
cap.release();
cv::destroyAllWindows();
my_serial.close();
return 0;
}