概述
这篇文章简单的介绍了RoboMaster机器人的装甲识别。方法是通过边缘检测的方法将灯柱作为标识选中装甲板区域。在本篇博客中将提供坐标点,作为后续机甲调试使用。
所使用到的技术:颜色通道分离,高斯滤波,lambda表达式,轮廓检测
训练素材&最终效果
训练材料
训练素材放在以下的百度网盘链接中,有需要的朋友自提一下。
链接:https://pan.baidu.com/s/17-WpcpQ409eRNsL9pHSrfw
提取码:yvi6
最终效果
关键部分
颜色通道分离
b, g, r = cv2.split(image)
在OpenCV中图片颜色通道顺序为BGR,而非大家熟悉的RBG,cv2.split(src)返回值为三个通道,与灰度图一样,用0~255表示颜色的深度。
轮廓检测
轮廓检测的前提条件为二值图,因此我们需要那单通道图片进行二值化。
ret, binary = cv2.threshold(src, threshold, maxval, type)
二值化的四个参数分别为:输入的单通道图片src,二值化阈值threshold,超过阈值的赋值maxval,二值化类型type。
其中type的类型常用为:
- cv2.THRESH_BINARY(黑白二值)
- cv2.THRESH_BINARY_INV(黑白二值反转)
高斯滤波
Gaussian = cv2.GaussianBlur(binary, ksize, 0)
高斯卷积操作的参数分别为:输入的二值图像binary,卷积核尺寸ksize(例如(5,5)),影响高斯分布的参数,一般设为0。
通过高斯滤波可以减小二值图中的噪声的影响,因为在二值化的过程中由于指定的阈值,可能会导致在一块白色中间出现黑点,导致在进行cv2.findCotours()轮廓时,将这些噪音点单独标出。下图为未进行高斯滤波的其中一帧效果。
轮廓筛选
在进行cv2.findCotours()后,我们需要对检测出来的轮廓进行判定筛选出装甲板灯条的轮廓。
ontours, hierarchy = cv2.findContours(image=draw_img, mode=cv2.RETR_TREE, method=cv2.CHAIN_APPROX_NONE)
contours = list(contours)
contours.sort(key=lambda c: cv2.contourArea(c), reverse=True)
这里通过lambda表达式将选中的轮廓按照面积大小进行排序。
width_array = []
height_array = []
point_array = []
for cont in contours[:5]:
x, y, w, h = cv2.boundingRect(cont)
# cv2.rectangle(img, (x,y), (x+w,y+h), (0, 255, 0), 2)
try:
if h / w >= 2 and h / whole_h > 0.1:
# if height / h > 0.05:
width_array.append(w)
height_array.append(h)
point_array.append([x, y])
except:
continue
这里通过外接矩形的思路将这些提取到轮廓信息,然后通过对灯条条件的筛选去除一部分的轮廓。这里的筛选条件为:
- 外接矩形的高度至少是宽度的两倍
- 外接矩形的高度不小于画面大小的1/10
这里可能有小伙伴有疑问通过外接最小矩形在效果上最好吗,其实外接最小矩形的思路就是通过拟合,寻找出四个点。首先我们来看下最小矩形的代码
for cont in contours:
rect = cv2.minAreaRect(cont)
# cv2.boxPoints可以将轮廓点转换为四个角点坐标
box = cv2.boxPoints(rect)
# print(type(box))
# 这一步不影响后面的画图,但是可以保证四个角点坐标为顺时针
startidx = box.sum(axis=1).argmin()
box = np.roll(box, 4 - startidx, 0)
# 在原图上画出预测的外接矩形
box = box.reshape((-1, 1, 2)).astype(np.int32)
cv2.polylines(img, [box], True, (0, 255, 0), 2)
这是一段大家能在网上找到比较通用的代码了。在这些代码中,它定义的四个点为顺时针旋转,并且最低点永远为0号点,这就意味着咱们很难得出这个最小矩形的长和宽。这是因为角度不同会导致长宽组成的点也不同。因此选择外接矩形会更加简单也更加方便。
跳过上面的插曲。在我们获得这么多轮廓之后我们需要来通过两个单独灯条轮廓将整个装甲板框起来。此时我用到的方法就是寻找两个最接近的轮廓。
point_near = [0, 0]
min = 10000
for i in range(len(width_array) - 1):
for j in range(i + 1, len(width_array)):
value = abs(width_array[i] * height_array[i] - width_array[j] * height_array[j])
if value < min:
min = value
point_near[0] = i
point_near[1] = j
try:
rectangle1 = point_array[point_near[0]]
rectangle2 = point_array[point_near[1]]
point1 = [rectangle1[0] + width_array[point_near[0]] / 2, rectangle1[1]]
point2 = [rectangle1[0] + width_array[point_near[0]] / 2, rectangle1[1] + height_array[point_near[0]]]
point3 = [rectangle2[0] + width_array[point_near[1]] / 2, rectangle2[1]]
point4 = [rectangle2[0] + width_array[point_near[1]] / 2, rectangle2[1] + height_array[point_near[1]]]
print(point1, point2, point3, point4)
x = np.array([point1, point2, point4, point3], np.int32)
box = x.reshape((-1, 1, 2)).astype(np.int32)
cv2.polylines(img, [box], True, (0, 255, 0), 2)
point_near数组保存的是最接近的两个轮廓在数组中的index索引值。min变量保存着轮廓面积的最下差值。
完整代码
python
# 开发作者 :Tian.Z.L
# 开发时间 :2022/2/17 18:11
# 文件名称 :assignment3.PY
# 开发工具 :PyCharm
import cv2
import numpy as np
import math
video = cv2.VideoCapture(r'C:\Users\TianZhonglin\Documents\Tencent Files\765808965\FileRecv\MobileFile\1234567.mp4')
# video = cv2.VideoCapture(r'C:\Users\TianZhonglin\Documents\Tencent Files\765808965\FileRecv\23456789.avi')
def img_show(name, src):
cv2.imshow(name, src)
cv2.waitKey(0)
cv2.destroyAllWindows()
while True:
ret, img = video.read()
blue, g, r = cv2.split(img) # 分离通道,在opencv中图片的存储通道为BGR非RBG
# 绘制轮廓 是在原图像上进行画轮廓
ret2, binary = cv2.threshold(blue, 220, 255, 0)
Gaussian = cv2.GaussianBlur(binary, (5, 5), 0) # 高斯滤波
# edge = cv2.Canny(binary, 50, 150) # 边缘检测
draw_img = Gaussian.copy()
whole_h, whole_w = binary.shape[:2]
# 输出的第一个值为图像,第二个值为轮廓信息,第三个为层级信息
contours, hierarchy = cv2.findContours(image=draw_img, mode=cv2.RETR_TREE, method=cv2.CHAIN_APPROX_NONE)
contours = list(contours)
contours.sort(key=lambda c: cv2.contourArea(c), reverse=True)
width_array = []
height_array = []
point_array = []
for cont in contours[:5]:
x, y, w, h = cv2.boundingRect(cont)
try:
if h / w >= 2 and h / whole_h > 0.1 and h > w:
# if height / h > 0.05:
width_array.append(w)
height_array.append(h)
point_array.append([x, y])
except:
continue
point_near = [0, 0]
min = 10000
for i in range(len(width_array) - 1):
for j in range(i + 1, len(width_array)):
value = abs(width_array[i] * height_array[i] - width_array[j] * height_array[j])
if value < min:
min = value
point_near[0] = i
point_near[1] = j
try:
rectangle1 = point_array[point_near[0]]
rectangle2 = point_array[point_near[1]]
point1 = [rectangle1[0] + width_array[point_near[0]] / 2, rectangle1[1]]
point2 = [rectangle1[0] + width_array[point_near[0]] / 2, rectangle1[1] + height_array[point_near[0]]]
point3 = [rectangle2[0] + width_array[point_near[1]] / 2, rectangle2[1]]
point4 = [rectangle2[0] + width_array[point_near[1]] / 2, rectangle2[1] + height_array[point_near[1]]]
print(point1, point2, point3, point4)
x = np.array([point1, point2, point4, point3], np.int32)
box = x.reshape((-1, 1, 2)).astype(np.int32)
cv2.polylines(img, [box], True, (0, 255, 0), 2)
except:
continue
cv2.imshow('name', img)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
# 释放资源
video.release()
cv2.destroyAllWindows()
C++
// OpenCVtest.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include "stdio.h"
#include<iostream>
#include <opencv2/opencv.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
using namespace std;
using namespace cv;
const int kThreashold = 220;
const int kMaxVal = 255;
const Size kGaussianBlueSize = Size(5, 5);
int main()
{
VideoCapture video;
video.open("C:/Users/TianZhonglin/Documents/Tencent Files/765808965/FileRecv/MobileFile/1234567.mp4");
Mat frame,channels[3],binary,Gaussian;
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
Rect boundRect;
RotatedRect box;
vector<Point2f> boxPts(4);
for (;;) {
Rect point_array[20];
video >> frame;
if (frame.empty()) {
break;
}
split(frame,channels);
threshold(channels[0], binary, kThreashold, kMaxVal, 0);
GaussianBlur(binary, Gaussian, kGaussianBlueSize, 0);
findContours(Gaussian, contours, hierarchy, RETR_TREE, CHAIN_APPROX_NONE);
int index = 0;
for (int i = 0; i < contours.size(); i++) {
//box = minAreaRect(Mat(contours[i]));
//box.points(boxPts.data());
boundRect = boundingRect(Mat(contours[i]));
//rectangle(frame, boundRect.tl(), boundRect.br(), (0, 255, 0), 2,8 ,0);
try
{
if (double(boundRect.height / boundRect.width) >= 1.3 && boundRect.height > 36 && boundRect.height>20) {
point_array[index] = boundRect;
index++;
}
}
catch (const char* msg)
{
cout << printf(msg) << endl;
//continue;
}
}
int point_near[2];
int min = 10000;
for (int i = 0; i < index-1; i++)
{
for (int j = i + 1; j < index; j++) {
int value = abs(point_array[i].area() - point_array[j].area());
if (value < min)
{
min = value;
point_near[0] = i;
point_near[1] = j;
}
}
}
try
{
Rect rectangle_1 = point_array[point_near[0]];
Rect rectangle_2 = point_array[point_near[1]];
if (rectangle_2.x == 0 || rectangle_1.x == 0) {
throw "not enough points";
}
Point point1 = Point(rectangle_1.x + rectangle_1.width / 2, rectangle_1.y);
Point point2 = Point(rectangle_1.x + rectangle_1.width / 2, rectangle_1.y + rectangle_1.height);
Point point3 = Point(rectangle_2.x + rectangle_2.width / 2, rectangle_2.y);
Point point4 = Point(rectangle_2.x + rectangle_2.width / 2, rectangle_2.y + rectangle_2.height);
Point p[4] = { point1,point2,point4,point3 };
cout << p[0]<<p[1]<<p[2]<<p[3] << endl;
for (int i = 0; i < 4; i++) {
line(frame, p[i%4], p[(i+1)%4], Scalar(0, 255, 0), 2);
}
}
catch (const char* msg)
{
cout << msg << endl;
//continue;
}
imshow("video", frame);
if (waitKey(10) >= 0) {
break;
}
}
video.release();
cv::destroyAllWindows();
return 0;
}