赞
踩
昨天突然接到一个需求,识别井盖且判断是否有井盖或无井盖。而且时间紧急,比赛突然加的需求,只给一天时间。一天时间用深度学习方法大概率是来不及了,采集数据标注数据训练模型都要花时间。
下面是现场用手机拍的图片,给可以看看。图片中一个有井盖、一个无井盖
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; }
上述代码注释很清晰,大概思路也很明了。
在找到圆中可以添加一些过滤条件,过滤一些误检的圆。这里可以根据具体需求操作,比如分割特征、形状、纹理、颜色等方式。
分割效果图
结果图
看看效果还不错,第二步我们要区分是否真的有井盖。第一个想到的是利用灰度分布,毕竟受光照影响小。
灰度分布结果
看到这里其实结果就显而易见了。找出相应的特征计算。最终通过计算结果,270帧图片检测结果,共400左右个井盖,分类正确率高达99%
识别管线、跟踪+定位、发送消息给规控。
如图中绳子、管子等。
流程图
(一)最小矩形框
由于检测分割管线,输入的是管线像素的包络点。点的输入可能会大于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; }
除了上面这些需求,博主还遇到更加奇葩的,今天就到这,下次分享更奇葩的。欢迎大家交流。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。