一、前言
- 在工作中往往出现些奇葩需求。
- 上一篇介绍了一些奇葩需求奇葩需求如何处理(一) ,今天给大家分享一些更奇葩的需求。
二、奇葩需求
2.1、井盖
昨天突然接到一个需求,识别井盖且判断是否有井盖或无井盖。而且时间紧急,比赛突然加的需求,只给一天时间。一天时间用深度学习方法大概率是来不及了,采集数据标注数据训练模型都要花时间。
下面是现场用手机拍的图片,给可以看看。图片中一个有井盖、一个无井盖
![图片名称](https://i-blog.csdnimg.cn/blog_migrate/567f990dd9eb69990fc8c2262171fbe7.jpeg)
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;
}
上述代码注释很清晰,大概思路也很明了。
- 转换为灰度图像
- 取一定区域进行操作
- 高斯滤波去噪
- Canny 边缘检测
- HoughCircles 霍夫曼圆找圆
- 画图
在找到圆中可以添加一些过滤条件,过滤一些误检的圆。这里可以根据具体需求操作,比如分割特征、形状、纹理、颜色等方式。
分割效果图
![图片名称](https://i-blog.csdnimg.cn/blog_migrate/969cb5d4807b06d19f4e22d02f003bb5.png)
结果图
![图片名称](https://i-blog.csdnimg.cn/blog_migrate/05db26fc1f0c772ff00c4a1eda54c42b.png)
看看效果还不错,第二步我们要区分是否真的有井盖。第一个想到的是利用灰度分布,毕竟受光照影响小。
灰度分布结果
![图片名称](https://i-blog.csdnimg.cn/blog_migrate/2c4c41fd0582346a030049454cf10130.png)
看到这里其实结果就显而易见了。找出相应的特征计算。最终通过计算结果,270帧图片检测结果,共400左右个井盖,分类正确率高达99%
2.2、管线
识别管线、跟踪+定位、发送消息给规控。
如图中绳子、管子等。
- 深度学习分割出绳子如 segformer 模型,后处理找出像素包络框,
- 计算最小矩形框,跟踪,赋值id。
- 发送凸包以及相应的距离信息。
流程图
(一)最小矩形框
由于检测分割管线,输入的是管线像素的包络点。点的输入可能会大于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;
}
三、后记
除了上面这些需求,博主还遇到更加奇葩的,今天就到这,下次分享更奇葩的。欢迎大家交流。