【目标跟踪】奇葩需求如何处理(二)-二、奇葩需求

时间:2024-03-22 08:46:34

2.1、井盖

昨天突然接到一个需求,识别井盖且判断是否有井盖或无井盖。而且时间紧急,比赛突然加的需求,只给一天时间。一天时间用深度学习方法大概率是来不及了,采集数据标注数据训练模型都要花时间。

下面是现场用手机拍的图片,给可以看看。图片中一个有井盖、一个无井盖

图片名称

1、首先要判断前方井盖位置。2、其次要判断是否真的存在井盖。

传统的方法,那无疑是分割了,分割然后判断圆形,最后统计分布,寻找能区分的特征量,能够有简单区分的值是最好的。

然后花了 20min 得出一个检测圆的代码。

python 代码

import cv2
import numpy as np


def cover_detect(image):
    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)  # 转换为灰度图像
    roi_x, roi_y, roi_w, roi_h = 0, height // 2, width - 1, height // 2 - 1
    roi = gray[roi_y:roi_y + roi_h, roi_x:roi_x + roi_w]
    blurred = cv2.GaussianBlur(roi, (5, 5), 0)  # 应用高斯滤波去噪
    edges = cv2.Canny(blurred, 100, 200)  # 应用Canny边缘检测
    cv2.imshow("show", edges)
    cv2.waitKey(100)

    # 应用霍夫圆变换检测圆形物体
    circles = cv2.HoughCircles(edges, cv2.HOUGH_GRADIENT, 2, 1000, param1=50, param2=35, minRadius=80, maxRadius=100)
    # 确保检测到了圆
    if circles is not None:
        # 将坐标和半径转换为整数
        circles = np.uint16(np.around(circles))
        # 遍历检测到的每个圆
        for i in circles[0, :]:
            # 在原图上绘制圆形轮廓和圆心
            cv2.circle(image, (i[0], i[1] + roi_y), i[2], (0, 255, 0), 2)
            cv2.circle(image, (i[0], i[1] + roi_y), 2, (0, 0, 255), 3)

            x, y, r = i
            mask = np.zeros_like(roi)
            cv2.circle(mask, (x, y), r, (255, 255, 255), -1)
            cv2.putText(image, "Well Cover", (i[0] - 50, i[1] - 50 + roi_y), cv2.FONT_HERSHEY_SIMPLEX, 0.75,
                        (0, 255, 0), 2)
    return image

c++ 代码

#include <opencv2/opencv.hpp>  
#include <iostream>  
  
using namespace std;  
using namespace cv;  

Mat cover_detect(Mat image) {  
    Mat gray;  
    cvtColor(image, gray, COLOR_BGR2GRAY); // 转换为灰度图像  
  
    int height = image.rows;  
    int width = image.cols;  
    int roi_x = 0;  
    int roi_y = height / 2;  
    int roi_w = width;  
    int roi_h = height / 2; // 注意:这里应该是height / 2而不是height / 2 - 1,除非你有特别的理由要减去1  
  
    Mat roi(gray, Rect(roi_x, roi_y, roi_w, roi_h));  
    GaussianBlur(roi, roi, Size(5, 5), 0); // 应用高斯滤波去噪  
  
    Mat edges;  
    Canny(roi, edges, 100, 200); // 应用Canny边缘检测,注意:这里应该是Canny而不是Canny  
  
    // 显示边缘检测结果(如果需要)  
    // namedWindow("show", WINDOW_AUTOSIZE);  
    // imshow("show", edges);  
    // waitKey(100);  
  
    // 应用霍夫圆变换检测圆形物体  
    vector<Vec3f> circles;  
    HoughCircles(edges, circles, HOUGH_GRADIENT, 1, 100, 50, 35, 80, 100); // 注意:这里是HOUGH_GRADIENT而不是HOUGH_GRADIENT  
  
    // 遍历检测到的每个圆  
    for (size_t i = 0; i < circles.size(); i++) {  
        Vec3f c = circles[i];  
        Point center(cvRound(c[0]), cvRound(c[1]) + roi_y);  
        int radius = cvRound(c[2]);  
  
        // 在原图上绘制圆形轮廓和圆心  
        circle(image, center, radius, Scalar(0, 255, 0), 2);  
        circle(image, center, 2, Scalar(0, 0, 255), 3);  
  
        // 在图像上添加文本  
        putText(image, "Well Cover", Point(center.x - 50, center.y - 50), FONT_HERSHEY_SIMPLEX, 0.75, Scalar(0, 255, 0), 2);  
    }  
  
    return image;  
}  
  
int main() {  
    // 读取图像  
    Mat image = imread("path_to_your_image.jpg");  
    if (image.empty()) {  
        cerr << "Error: Could not open or find the image!" << endl;  
        return -1;  
    }  
  
    // 调用函数并显示结果  
    Mat result = cover_detect(image);  
    namedWindow("Result", WINDOW_AUTOSIZE);  
    imshow("Result", result);  
    waitKey(0);  
  
    return 0;  
}

上述代码注释很清晰,大概思路也很明了。

  1. 转换为灰度图像
  2. 取一定区域进行操作
  3. 高斯滤波去噪
  4. Canny 边缘检测
  5. HoughCircles 霍夫曼圆找圆
  6. 画图

在找到圆中可以添加一些过滤条件,过滤一些误检的圆。这里可以根据具体需求操作,比如分割特征、形状、纹理、颜色等方式。

分割效果图

图片名称

结果图

图片名称

看看效果还不错,第二步我们要区分是否真的有井盖。第一个想到的是利用灰度分布,毕竟受光照影响小。

灰度分布结果

图片名称

看到这里其实结果就显而易见了。找出相应的特征计算。最终通过计算结果,270帧图片检测结果,共400左右个井盖,分类正确率高达99%

2.2、管线

识别管线、跟踪+定位、发送消息给规控。

在这里插入图片描述

如图中绳子、管子等。

  1. 深度学习分割出绳子如 segformer 模型,后处理找出像素包络框,
  2. 计算最小矩形框,跟踪,赋值id。
  3. 发送凸包以及相应的距离信息。

流程图

在这里插入图片描述
(一)最小矩形框
由于检测分割管线,输入的是管线像素的包络点。点的输入可能会大于2000,单纯对点的跟踪耗时长且不稳定。 首先对输入的点求最小矩形框,用最小矩形框去跟踪与航迹管理(分配id)。

红色框为检测后的最小矩形框
在这里插入图片描述

cv::Rect_<float> Tracking::GetMinBox(std::vector<cv::Point> points)
{
    cv::Point_<float> minPoint, maxPoint;
    minPoint.x = minPoint.y = FLT_MAX;
    maxPoint.x = maxPoint.y = -FLT_MAX;
    for (cv::Point point:points) {
        minPoint.x = std::min(minPoint.x, float(point.x));
        maxPoint.x = std::max(maxPoint.x, float(point.x));
        minPoint.y = std::min(minPoint.y, float(point.y));
        maxPoint.y = std::max(maxPoint.y, float(point.y));
    }
    cv::Rect_<float> res = cv::Rect_<float>(minPoint, maxPoint);
    return res;
}

(二)凸包计算

box可以跟踪,但是最终输出给下游的应该是世界坐标系的点。而点应该是包络框的形式,则需要计算凸包减少点的传递,避免增加无效的计算。(你要是一次性传上千个点,你看规控的人打不打你[坏笑.jpg])。

在这里插入图片描述
蓝色框是跟踪框包络点的最小凸包。获得了凸包的像素点,直接输出像素点的世界坐标,最终得到的包络框输出给规控。

计算凸包可以利用 opencv 中 cv::convexHull 函数,输入所有点像素,得出凸包点像素。根据凸包点像素发送俯视图下的位置就可。

#include <opencv2/opencv.hpp>  
#include <vector>  
  
int main() {  
    std::vector<cv::Point2f> points = {  
        {0, 0}, {10, 0}, {10, 10}, {5, 4}, {0, 10}  
    };  
  
    std::vector<int> hullIndices;  
    cv::convexHull(points, hullIndices, false, false);  
  
    std::vector<cv::Point2f> hullPoints(hullIndices.size());  
    for (size_t i = 0; i < hullIndices.size(); ++i) {  
        hullPoints[i] = points[hullIndices[i]];  
    }  
  
    // Draw the points and the convex hull  
    cv::Mat img(200, 200, CV_8UC3, cv::Scalar(255, 255, 255));  
    for (const auto& pt : points) {  
        cv::circle(img, pt, 2, cv::Scalar(0, 0, 255), -1);  
    }  
    for (size_t i = 0; i < hullPoints.size(); ++i) {  
        const auto& pt1 = hullPoints[i];  
        const auto& pt2 = hullPoints[(i + 1) % hullPoints.size()];  
        cv::line(img, pt1, pt2, cv::Scalar(0, 255, 0), 2);  
    }  
  
    cv::imshow("Convex Hull", img);  
    cv::waitKey(0);  
  
    return 0;  
}