赞
踩
本题的主要重点就是识别矩形框,和识别在矩形框上面的红色激光点,然后控制红色激光点。当然也可以不用识别激光点的坐标直接识别矩形框的位置和姿态后直接控制云台走相应的距离也行(虽然凭借这种方式侥幸获得国一),但为了更可加靠这里我还是介绍一下闭环的方案。方案我当初采用的是Sipeed M2dock(主云台)+K210(跟随云台)+stm32控制步进电机云台。
1.当初比赛时采用的官方的
micropython
库可以直接识别矩形的边框,经过一番折腾后可以调到能用,但在非常极限的位置的时候仍然不可避免的会走偏。好在裁判没有放在极其偏的位置上面。
2.当然,光识别一个矩形时比赛的要求(几天内完成压力还算大了),如果是识别任意图形呢?
cvtColor(image, imgGray, COLOR_BGR2GRAY);
threshold(imgGray, binImage, 100, 255, cv::THRESH_BINARY_INV); // 转换二值图,设置阈值,高于100认为255`
/** * @brief : 检测出物体的轮廓 * @param Mat imgDil 输入图像 * @param contours contours,定义为“vector<vector<Point>> contours”, * 是一个向量,并且是一个双重向量,向量内每个元素保存了一组由连续的Point点构成的点的集合的向量, * 每一组Point点集就是一个轮廓。有多少轮廓,向量contours就有多少元素。 * @param hierarchy 定义为“vector<Vec4i> hierarchy”,先来看一下Vec4i的定义: typedef Vec<int, 4> Vec4i; * Vec4i是Vec<int,4>的别名,定义了一个“向量内每一个元素包含了4个int型变量”的向量。所以从定义上看, * hierarchy也是一个向量,向量内每个元素保存了一个包含4个int整型的数组。向量hiararchy内的元素和轮廓向量contours * 内的元素是一一对应的,向量的容量相同。hierarchy向量内每一个元素的4个int型变量——hierarchy[i][0] ~hierarchy[i][3], * 分别表示第 i个轮廓的后一个轮廓、前一个轮廓、父轮廓、内嵌轮廓的索引编号。如果当前轮廓没有对应的后一个轮廓、前一个轮廓、 * 父轮廓或内嵌轮廓的话,则hierarchy[i][0] ~hierarchy[i][3]的相应位被设置为默认值-1。 * @param mode 定义轮廓的检索模式: * 取值一:CV_RETR_EXTERNAL只检测最外围轮廓,包含在外围轮廓内的内围轮廓被忽略 * 取值二:CV_RETR_LIST 检测所有的轮廓,包括内围、外围轮廓,但是检测到的轮廓不建立等级关系,彼此之间独立,没有等级关系, * 这就意味着这个检索模式下不存在父轮廓或内嵌轮廓,所以hierarchy向量内所有元素的第3、第4个分量都会被置为-1,具体下文会讲到 * 取值三:CV_RETR_CCOMP 检测所有的轮廓,但所有轮廓只建立两个等级关系,外围为顶层,若外围内的内围轮廓还包含了其他的轮廓信息, * 则内围内的所有轮廓均归属于顶层。 * 取值四:CV_RETR_TREE, 检测所有轮廓,所有轮廓建立一个等级树结构。外层轮廓包含内层轮廓,内层轮廓还可以继续包含内嵌轮廓。 * @param method 定义轮廓的近似方法: * 取值一:CV_CHAIN_APPROX_NONE 保存物体边界上所有连续的轮廓点到contours向量内 * 取值二:CV_CHAIN_APPROX_SIMPLE 仅保存轮廓的拐点信息,把所有轮廓拐点处的点保存入contours * 向量内,拐点与拐点之间直线段上的信息点不予保留 * 取值三和四:CV_CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS使用teh-Chinl chain 近似算法 * @param offset Point偏移量: * 所有的轮廓信息相对于原始图像对应点的偏移量,相当于在每一个检测出的轮廓点上加上该偏移量,并且Point还可以是负值! * @retval: None */ findContours(Mat image, OutputArrayOfArrays contours, OutputArray hierarchy, int mode, int method, Point offset = Point());
// 该函数计算轮廓的长度,后面的bool值表面轮廓曲线是否闭合若为true 则轮廓曲线闭合
arcLength(contours[i], true);
//计算轮廓面积
contourArea(contours[i]);
// conpoly 同样为轮廓点集但它第二个数组中只有1-9个参数为了描述各个轮廓的拐角点
// conpoly[i]是输出array 0.02*peri 这个参数理解不了就不要理解!!!最后一个参数仍然是询问是否闭合
approxPolyDP(contours[i], conpoly[i], 0.02f * peri, true);
// 输出图像轮廓中的拐角点数和面积
cout << "conpoly:" << conpoly[i].size() << "area:" << area << endl;
判断是矩形的条件为 面积合适 && 角的个数==4
/** * @brief : 对cv::findContours(...,cv::CHAIN_APPROX_SIMPLE)找到的轮廓进行放大、缩小处理(注意最后的参数必须为cv::CHAIN_APPROX_SIMPLE) * @param vector<cv::Point> &in 为输入轮廓 * @param vector<cv::Point> &out 为输出轮廓 * @param float scalar 负数为内缩,正数为外扩 * @retval: None */ static void contours_handle(std::vector<cv::Point> &in, std::vector<cv::Point> &out, const float scalar) { float SAFELINE = scalar; std::vector<cv::Point2f> dpList, ndpList; int count = in.size(); for (int i = 0; i < count; ++i) { int next = (i == (count - 1) ? 0 : (i + 1)); dpList.emplace_back(in.at(next) - in.at(i)); float unitLen = 1.0f / sqrt(dpList.at(i).dot(dpList.at(i))); ndpList.emplace_back(dpList.at(i) * unitLen); } for (int i = 0; i < count; ++i) { int startIndex = (i == 0 ? (count - 1) : (i - 1)); int endIndex = i; float sinTheta = ndpList.at(startIndex).cross(ndpList.at(endIndex)); cv::Point2f orientVector = ndpList.at(endIndex) - ndpList.at(startIndex); // i.e. PV2-V1P=PV2+PV1 if (std::isinf(SAFELINE / sinTheta * orientVector.x) || std::isinf(SAFELINE / sinTheta * orientVector.y)) { continue; // 过滤掉离谱数据 } out.emplace_back(cv::Point2f(in.at(i).x + SAFELINE / sinTheta * orientVector.x, in.at(i).y + SAFELINE / sinTheta * orientVector.y)); } }
然后就可以对识别到的轮廓进行偏移到中心位置了
至此,矩形的处理就算完成了,后面就是串口输出矩形的坐标,不同的开发板的方法也不一样.我这里使用的是OrangePi 5B CPU仅仅占用了5%. C++可以使用boost库来调用串口输出数据,网上有很多教程
// 设定彩色图像的阈值范围
inRange(image, Scalar(ColorB, ColorG, ColorR), Scalar(ColorBM, ColorGM, ColorRM), RGB_binImage);
可以看到微调阈值就只剩下红色光斑的地方了。然后重复之前的识别轮廓的操作就可以获取红色激光点的坐标了。
/** * @brief : 步进电机脉冲控制 * @param float yaw_step_Freq YAW轴脉冲频率控制 * @param float pitch_step_Freq PITCH轴脉冲频率控制 * @retval: None */ static void Step_Motor_Server(float yaw_step_Freq, float pitch_step_Freq) { // Yaw static uint32_t count = 0; uint32_t step_Cycle; if (yaw_step_Freq >= 0) { step_Cycle = (uint32_t)(yaw_step_Freq); HAL_GPIO_WritePin(DIR_GPIO_Port, DIR_Pin, GPIO_PIN_SET); } else if (yaw_step_Freq < 0) { step_Cycle = (uint32_t)(-yaw_step_Freq); HAL_GPIO_WritePin(DIR_GPIO_Port, DIR_Pin, GPIO_PIN_RESET); } count++; if ((count % (10000 / step_Cycle)) == 0) { count = 0; HAL_GPIO_TogglePin(PUL_GPIO_Port, PUL_Pin); } // Pitch static uint32_t count1; static uint32_t step_Cycle1; if (pitch_step_Freq >= 0) { step_Cycle1 = (uint32_t)(pitch_step_Freq); HAL_GPIO_WritePin(DIR1_GPIO_Port, DIR1_Pin, GPIO_PIN_SET); } else if (pitch_step_Freq < 0) { step_Cycle1 = (uint32_t)(-pitch_step_Freq); HAL_GPIO_WritePin(DIR1_GPIO_Port, DIR1_Pin, GPIO_PIN_RESET); } count1++; if ((count1 % (10000 / step_Cycle1)) == 0) { count1 = 0; HAL_GPIO_TogglePin(PUL1_GPIO_Port, PUL1_Pin); } }
/** * @brief : 轮廓的识别处理 * @FilePath : example.cpp * @Author : fubingyan * @Date : 2023-09-16 19:51:02 * @LastEditTime : 2023-09-18 14:39:29 * @version : V1.0.0 * @Copyright (c) 2023 by fubingyan, All Rights Reserved. */ #include <iostream> #include <opencv2/highgui.hpp> // 说是说gui 具体什么gui 不清楚 #include <opencv2/imgcodecs.hpp> // 图像头文件 #include <opencv2/imgproc.hpp> // 图像处理头文件 #include <opencv2/imgproc/types_c.h> using namespace std; using namespace cv; // 今天又是美好的一天,我到底在干什么呢? /*要进行图像形貌检测之前 *首先要二值化,阈值处理,再进行滤波处理,再进行Canny边缘检测 *最后才能检测出图形轮廓 */ Mat imgGray; // 灰度图像 Mat binImage; // 灰度图二值化的图像 Mat RGB_binImage; // RGB图像阈值后的图像 static void getContours(Mat imgDil, Mat &img); // 获取轮廓 static void morphTreat(Mat &binImg); // 形态学处理 static void contours_handle(std::vector<cv::Point> &in, std::vector<cv::Point> &out, const float scalar); // 缩放轮廓 static void RGB_adjust(void); // 控件调参 // 帧率数据 static float t, lt; uint16_t fps; // RGB颜色阈值(小) 只有在最小值和最大值区间的才会被保留 static int ColorR = 0; static int ColorG = 0; static int ColorB = 0; // RGB颜色阈值(大) static int ColorRM = 255; static int ColorGM = 255; static int ColorBM = 240; uint8_t Step = 0; uint16_t step_cnt = 0; int main() { Mat image; // 在opencv 中所有的图像信息都使用Mat VideoCapture capture; namedWindow("Sample", WINDOW_AUTOSIZE); capture.open(0); RGB_adjust(); if (capture.isOpened()) { // 更改图像的捕获像素 这里为320*240大小 capture.set(CAP_PROP_FRAME_WIDTH, 320); capture.set(CAP_PROP_FRAME_HEIGHT, 240); capture.set(CAP_PROP_FPS, 30); // capture.set(cv::CAP_PROP_AUTO_EXPOSURE, 0.25); //关闭相机的自动曝光 启用是-1 // capture.set(cv::CAP_PROP_EXPOSURE, -3); //设置相机的曝光值 cout << "Capture is opened" << endl; for (;;) { capture >> image; if (image.empty()) break; // 显示帧率 t = ((float)cv::getTickCount() - lt) / cv::getTickFrequency(); lt = (float)cv::getTickCount(); fps = 1.0f / t; cout << "FPS:" << fps << endl; if (step == 0) { step_cnt++; if (step_cnt > 100) // 识别100桢矩形框 { step == 1; } cvtColor(image, imgGray, COLOR_BGR2GRAY); // 将图像转化为灰度图像 threshold(imgGray, binImage, 100, 255, cv::THRESH_BINARY_INV); // 转换二值图,设置阈值,高于100认为255 getContours(binImage, image); // 第一个参数 是寻找轮廓的参数, 第二个参数是显示图案的参数 /* code */ } else if (step == 1) { inRange(image, Scalar(ColorB, ColorG, ColorR), Scalar(ColorBM, ColorGM, ColorRM), RGB_binImage); // 设定彩色图像的阈值范围 getContours(RGB_binImage, image); } imshow("Sample", image); // 在原始图像上显示处理识别到的轮廓 imshow("BinIMG", RGB_binImage); // 显示阈值后的图像 if (waitKey(5) >= 0) break; } } else { cout << "No capture" << endl; image = Mat::zeros(240, 320, CV_8UC1); imshow("Sample", image); waitKey(0); } return 0; // pre-processing image 图像预处理 } /** * @brief : 因为一开始参数不同,所以电脑直接将其视为重载函数 * @param Mat imgDil * @param Mat &img * @retval: None */ static void getContours(Mat imgDil, Mat &img) { /* contour is a vector inside that vector there is more vector * {{Point(20,30),Point(50,60)},{},{}} each vector like a contour and each contour have some points * **/ vector<vector<Point>> contours; vector<Vec4i> hierarchy; // Vec4i 即代表该向量内有4个 int 变量typedef Vec<int, 4> Vec4i; 这四个向量每一层级代表一个轮廓 findContours(imgDil, contours, hierarchy, CV_RETR_TREE, CV_CHAIN_APPROX_NONE); // CV_CHAIN_APPROX_SIMPLE - 简单的链式接近法 vector<vector<Point>> conpoly(contours.size()); // conpoly(paprameter1) ,paprameter1便代表vector对象的行数,而其列数中的vector 是使用了point点集但其只包含图形的拐角点集 vector<Rect> boundRect(contours.size()); // 记录各图形的拟合矩形 string objType; // 记录物体形状 // 为了滤除微小噪声,因此计算area 的面积 // 关于contours.size()为什么是返回二维数组的行,因为 vector::size()函数只接受vector 对象的调用而contours的所有行(不管列)均为其对象 for (int i = 0; i < contours.size(); i++) { int area = contourArea(contours[i]); if (area > 1000) { float peri = arcLength(contours[i], true); // 该函数计算轮廓的长度,后面的bool值表面轮廓曲线是否闭合若为true 则轮廓曲线闭合 // 寻找角点 // conpoly 同样为轮廓点集但它第二个数组中只有1-9个参数为了描述各个轮廓的拐角点 // conpoly[i]是输出array 0.02*peri 这个参数理解不了就不要理解!!! 最后一个参数仍然是询问是否闭合 approxPolyDP(contours[i], conpoly[i], 0.02f * peri, true); // 通过conpoly 而绘制的轮廓中只存在程序认为应该存在的点 cout << "conpoly:" << conpoly[i].size() << "area:" << area << endl; // 输出图像轮廓中的拐角点 boundRect[i] = boundingRect(conpoly[i]); // 针对conpoly[i] 进行boundingRect 以便拟合相切矩形 if ((int)conpoly[i].size() > 3 && (int)conpoly[i].size() < 5) // 寻找四边形 { float aspRatio = (float)boundRect[i].width / (float)boundRect[i].height; // 计算float对象,一定要记得使用 float 强转符号 if (aspRatio < 1.05f && aspRatio > 0.95f) objType = "Square"; else objType = "Rectangle"; std::vector<std::vector<cv::Point>> Hulls(contours.size()); // 保存凸包 cv::convexHull(contours[0], Hulls[0], true, true); // 寻找轮廓的凸包,输出点集为顺时针 std::vector<std::vector<cv::Point>> outs(contours.size()); // 保存放大、缩小后的轮廓 contours_handle(Hulls[i], outs[0], -4.0f); // 将轮廓缩小15个像素 putText(img, objType, Point(boundRect[i].x, boundRect[i].y - 5), FONT_HERSHEY_PLAIN, 1, Scalar(255, 255, 255), 1); cout << "PointNUM:" << outs[0].size() << "Pointx[0]:" << outs[0][0].x << "Pointy[0]:" << outs[0][0].y << endl; // 输出图像轮廓中的拐角点 drawContours(img, contours, i, Scalar(0, 0, 255), 1, 8, hierarchy, 3); drawContours(img, outs, 0, Scalar(255, 255, 255), 1); // 使用圆圈圈出矩形的四个点的坐标 // cv::circle(img,Point(conpoly[i][0].x,conpoly[i][0].y),2,Scalar(0, 0, 255),2); // cv::circle(img,Point(conpoly[i][1].x,conpoly[i][1].y),2,Scalar(0, 255, 0),2); // cv::circle(img,Point(conpoly[i][2].x,conpoly[i][2].y),2,Scalar(255, 0, 0),2); // cv::circle(img,Point(conpoly[i][3].x,conpoly[i][3].y),2,Scalar(255, 255, 255),2); break; } } } } /** * @brief : 形态学处理 * @param Mat &binImg * @retval: None */ static void morphTreat(Mat &binImg) { Mat BinOriImg; // 形态学处理结果图像 Mat element = getStructuringElement(MORPH_RECT, Size(5, 5)); // 设置形态学处理窗的大小 GaussianBlur(binImg, binImg, Size(3, 3), 3, 0); dilate(binImg, binImg, element); // 进行多次膨胀操作 dilate(binImg, binImg, element); erode(binImg, binImg, element); // 进行多次腐蚀操作 erode(binImg, binImg, element); // imshow("形态学处理后", BinOriImg); //显示形态学处理之后的图像 cvtColor(binImg, binImg, CV_BGR2GRAY); // 将形态学处理之后的图像转化为灰度图像 threshold(binImg, binImg, 100, 255, THRESH_BINARY); // 灰度图像二值化 } /** * @brief : 控件调参 * @param Mat &image * @retval: None */ static void RGB_adjust(void) { createTrackbar("R:", "Sample", &ColorR, 255, 0); createTrackbar("G:", "Sample", &ColorG, 255, 0); createTrackbar("B:", "Sample", &ColorB, 255, 0); createTrackbar("RM:", "Sample", &ColorRM, 255, 0); createTrackbar("GM:", "Sample", &ColorGM, 255, 0); createTrackbar("BM:", "Sample", &ColorBM, 255, 0); } /** * @brief : 对cv::findContours(...,cv::CHAIN_APPROX_SIMPLE)找到的轮廓进行放大、缩小处理(注意最后的参数必须为cv::CHAIN_APPROX_SIMPLE) * @param vector<cv::Point> &in 为输入轮廓 * @param vector<cv::Point> &out 为输出轮廓 * @param float scalar 负数为内缩,正数为外扩 * @retval: None */ static void contours_handle(std::vector<cv::Point> &in, std::vector<cv::Point> &out, const float scalar) { float SAFELINE = scalar; std::vector<cv::Point2f> dpList, ndpList; int count = in.size(); for (int i = 0; i < count; ++i) { int next = (i == (count - 1) ? 0 : (i + 1)); dpList.emplace_back(in.at(next) - in.at(i)); float unitLen = 1.0f / sqrt(dpList.at(i).dot(dpList.at(i))); ndpList.emplace_back(dpList.at(i) * unitLen); } for (int i = 0; i < count; ++i) { int startIndex = (i == 0 ? (count - 1) : (i - 1)); int endIndex = i; float sinTheta = ndpList.at(startIndex).cross(ndpList.at(endIndex)); cv::Point2f orientVector = ndpList.at(endIndex) - ndpList.at(startIndex); // i.e. PV2-V1P=PV2+PV1 if (std::isinf(SAFELINE / sinTheta * orientVector.x) || std::isinf(SAFELINE / sinTheta * orientVector.y)) { continue; // 过滤掉离谱数据 } out.emplace_back(cv::Point2f(in.at(i).x + SAFELINE / sinTheta * orientVector.x, in.at(i).y + SAFELINE / sinTheta * orientVector.y)); } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。