RLL Computer Vision Code: code/armour_detector.cpp 源文件

RLL Computer Vision

RLL Computer Vision Code  1.0
江苏科技大学HLL战队机器视觉代码
armour_detector.cpp
浏览该文件的文档.
1 #include "armour_detector.h"
2 
3 namespace HCVC
4 {
6 {
7  FileStorage fs("statics/params.xml", FileStorage::READ);
8  if(!fs.isOpened())
9  {
10  cout << "Open file failed" << endl;
11  }
12 
13  FileNode node = fs["armour_detector"];
14 
15  node["angleRange"] >> params.angleRange;
16  node["minArea"] >> params.minArea;
17  node["maxHeightWidthRat"] >> params.maxHeightWidthRat;
18  node["minHeightWidthRat"] >> params.minHeightWidthRat;
19 
20  node["maxWidthRat"] >> params.maxWidthRat;
21  node["maxHeightRat"] >> params.maxHeightRat;
22  node["maxAngleDiff"] >> params.maxAngleDiff;
23  node["maxHeightGapRat"] >> params.maxHeightGapRat;
24  node["minHeightGapRat"] >> params.minHeightGapRat;
25 
26  fs.release();
27 }
28 
29 bool ArmourDetector::detect(const Mat& srcImage)
30 {
31  Mat dstImage = preprocess(srcImage);
32 
33  //存储初步找到的团块
34  vector<vector<Point> > blocks = searchBlocks(dstImage.clone());
35 
36  //存储找到的所有灯柱块
37  vector<RotatedRect> lampBlocks = calcBlocksInfo(blocks);
38 
39  //为中间结果显示准备图像
40  Mat drawImage = srcImage.clone();
41 
42  //查看搜索出的每一个灯柱块
43  drawBlocks(drawImage, lampBlocks, Scalar(200, 150, 100));
44 
45  //存储筛选过符合条件的所有对灯柱对最小包围矩形即装甲板区域
46  vector<RotatedRect> armourBlocks = extracArmourBlocks(lampBlocks);
47 
48  //查看搜索出的每一个独立的团块
49  drawBlocks(drawImage, armourBlocks, Scalar(100, 150, 200));
50 
51  if(armourBlocks.empty())
52  {
53  return false;
54  }
55 
56  //对每个装甲板区域评分
57  markArmourBlocks(srcImage, dstImage, armourBlocks);
58 
59  drawBlocks(drawImage, vector<RotatedRect>(1, optimalArmourBlocks.front().block), Scalar(180, 200, 220));
60 
61  if(armourBlocks.empty())
62  {
63  return false;
64  }
65 
66  return true;
67 }
68 
70 {
71  return optimalArmourBlocks.front().block.boundingRect();
72 }
73 
74 void ArmourDetector::drawBlocks(Mat srcImage, const vector<RotatedRect>& minRotatedRects, const Scalar& color) const
75 {
76  for(unsigned int i = 0; i < minRotatedRects.size(); i++)
77  {
78  Point2f points[4];
79  minRotatedRects[i].points(points);
80 
81  for(unsigned int j = 0; j < 4; j++)
82  {
83  line(srcImage, points[j], points[(j+1)%4], color, 2);
84  }
85  }
86 
87  imshow("detectBlocks", srcImage);
88 }
89 
90 
91 
92 void ArmourDetector::fillLampBlock(Mat& srcImage, vector<vector<Point> >& blocks, int row, int col)
93 {
94  //如果这个像素越界或者为零则不需要进一步访问它的相邻像素
95  if(row < 0 || row >= srcImage.rows || col < 0 || col >= srcImage.cols || srcImage.at<uchar>(row, col) == 0)
96  {
97  return ;
98  }
99 
100  //向当前正在填充的团块中加入数据,注意点的坐标是(x,y),指针访问的坐标顺序是(y,x),x为横轴,y为纵轴
101  blocks.back().push_back(Point(col, row));
102  //避免已访问像素的重复访问,将其置零
103  srcImage.at<uchar>(row, col) = 0;
104 
105  for(int x = -1; x <= 1; x++)
106  {
107  for(int y = -1; y <= 1; y++)
108  {
109  //避免重复访问自身
110  if(x == 0 && y == 0)
111  {
112  continue;
113  }
114 
115  fillLampBlock(srcImage, blocks, row+x, col+y);
116  }
117  }
118 }
119 
120 vector<vector<Point> > ArmourDetector::searchBlocks(Mat srcImage)
121 {
122  vector<vector<Point> > blocks;
123 
124  int rowNum = srcImage.rows;
125  int colNum = srcImage.cols;
126 
127  //如果图像在内存空间中存储是连续的,则把图像当作一行连续的矩阵来访问,提高访问效率
128  if (srcImage.isContinuous())
129  {
130  rowNum = 1;
131  colNum = colNum * srcImage.rows * srcImage.channels();
132  }
133 
134  for (int row = 0; row < rowNum; row++)
135  {
136  uchar* srcImagePtr = srcImage.ptr<uchar>(row);
137 
138  for (int col = 0; col < colNum; col++)
139  {
140  //按行优先的顺序访问图像矩阵后,找到的每一个非零数据一定是一个新的独立团块的起点
141  if(*srcImagePtr++)
142  {
143  //根据找到的起点,递归遍历所有它的相邻像素
144  blocks.push_back(vector<Point>());
145  //由于存在两种不同的访问方式,需要进行坐标转换
146  if(row == 0)
147  {
148  fillLampBlock(srcImage, blocks, col/srcImage.cols, col%srcImage.cols);
149  }
150  else
151  {
152  fillLampBlock(srcImage, blocks, row, col);
153  }
154  }
155  }
156  }
157 
158  return blocks;
159 }
160 
161 vector<RotatedRect> ArmourDetector::calcBlocksInfo(const vector<vector<Point> >& blocks)
162 {
163  vector<RotatedRect> lampBlocks;
164 
165  for(unsigned int i = 0; i < blocks.size(); i++)
166  {
167  RotatedRect minRotatedRect = minAreaRect(blocks[i]);
168 
169  if(minRotatedRect.size.area() > params.minArea
170  &&((minRotatedRect.angle > -params.angleRange
171  &&((minRotatedRect.size.height/minRotatedRect.size.width >= params.minHeightWidthRat)
172  &&(minRotatedRect.size.height/minRotatedRect.size.width <= params.maxHeightWidthRat)))
173  ||(minRotatedRect.angle < params.angleRange-90
174  &&((minRotatedRect.size.width/minRotatedRect.size.height >= params.minHeightWidthRat)
175  &&(minRotatedRect.size.width/minRotatedRect.size.height <= params.maxHeightWidthRat)))))
176  {
177  lampBlocks.push_back(minRotatedRect);
178  }
179  }
180 
181  return lampBlocks;
182 }
183 
184 vector<RotatedRect> ArmourDetector::extracArmourBlocks(const vector<RotatedRect>& lampBlocks)
185 {
186  vector<RotatedRect> armourBlocks;
187 
188  //非空判定,如果为空的话在下面遍历的时候会出现一个bug,i-1溢出成2^32-1,使循环卡死
189  if(lampBlocks.empty())
190  {
191  return armourBlocks;
192  }
193 
194  for(unsigned int i = 0; i < lampBlocks.size() - 1; i++)
195  {
196  for(unsigned int j = i + 1; j < lampBlocks.size(); j++)
197  {
198  if(fabs(lampBlocks[i].boundingRect2f().width-lampBlocks[j].boundingRect2f().width) <= params.maxWidthRat * lampBlocks[i].boundingRect2f().width
199  && fabs(lampBlocks[i].boundingRect2f().height-lampBlocks[j].boundingRect2f().height) <= params.maxHeightRat * lampBlocks[i].boundingRect2f().height
200  /*&&fabs(minRotatedRects[i].angle-minRotatedRects[j].angle) <= maxAngleDiff*/)
201  {
202  float distance = 0;
203  distance += powf((lampBlocks[i].center.x-lampBlocks[j].center.x), 2);
204  distance += powf((lampBlocks[i].center.y-lampBlocks[j].center.y), 2);
205  distance = sqrt(distance);
206  if(distance / lampBlocks[i].boundingRect2f().height < params.maxHeightGapRat
207  && distance / lampBlocks[i].boundingRect2f().height > params.minHeightGapRat
208  && fabs(lampBlocks[i].center.y-lampBlocks[j].center.y) < 2 * lampBlocks[i].boundingRect2f().height)
209  {
210  vector<Point> points;
211  Point2f iPoints[4], jPoints[4];
212  lampBlocks[i].points(iPoints);
213  lampBlocks[j].points(jPoints);
214  for(unsigned int k = 0; k < 4; k++)
215  {
216  points.push_back(iPoints[k]);
217  points.push_back(jPoints[k]);
218  }
219 
220  RotatedRect minRotatedRect = minAreaRect(points);
221  if((minRotatedRect.size.height > minRotatedRect.size.width && minRotatedRect.angle < -60)
222  || (minRotatedRect.size.height < minRotatedRect.size.width && minRotatedRect.angle > -30))
223  {
224  armourBlocks.push_back(minRotatedRect);
225  }
226  }
227  }
228 
229  }
230  }
231 
232  cout << "Num of lampBlocksRects: " << armourBlocks.size() << endl;
233 
234  return armourBlocks;
235 }
236 
237 void ArmourDetector::markArmourBlocks(const Mat& srcImage, const Mat& dstImage, const vector<RotatedRect> &armourBlocks)
238 {
239  //清除之前运算的结果
240  optimalArmourBlocks.clear();
241 
242  //去除灯柱灯光区域影响
243  Mat invDstImage;
244  threshold(dstImage, invDstImage, 0, 255, THRESH_BINARY_INV);
245 
246  for(unsigned int id = 0; id < armourBlocks.size(); id++)
247  {
248  Point2f fpoints[4];
249  armourBlocks[id].points(fpoints);
250 
251  //剪去旋转矩形的多余边角,得到装甲板的平行四边形区域
252  //cutEdgeOfRect(fpoints);
253 
254  //浮点数转换整数
255  Point points[4];
256  for(unsigned int i = 0; i < 4; i++)
257  {
258  points[i] = Point(static_cast<int>(fpoints[i].x), static_cast<int>(fpoints[i].y));
259  }
260 
261  const Point* pts = points;
262  const int npts = 4;
263 
264  //创建掩码区域为包含装甲板的旋转矩形
265  Mat mask(srcImage.size(), CV_8UC1, Scalar(0));
266  //多边形填充
267  fillConvexPoly(mask, pts, npts, Scalar(255));
268 
269  bitwise_and(mask, invDstImage, mask);
270 
271  Scalar armourBlockMean, armourBlockStdDev;
272  meanStdDev(srcImage, armourBlockMean, armourBlockStdDev, mask);
273  double grade = sqrt((pow(armourBlockMean[0], 2) + pow(armourBlockMean[1], 2) + pow(armourBlockMean[2], 2))/3.0) + 5 * sqrt((pow(armourBlockStdDev[0], 2) + pow(armourBlockStdDev[1], 2) + pow(armourBlockStdDev[2], 2))/3.0);
274  imshow("mask", mask);
275 
276  optimalArmourBlocks.push_back(OptimalArmourBlock(armourBlocks[id], grade));
277  }
278 
279  //将装甲板区域按分从小到大排序,找出最佳区域
280  sort(optimalArmourBlocks.begin(), optimalArmourBlocks.end());
281 }
282 
283 void ArmourDetector::cutEdgeOfRect(Point2f* points)
284 {
285  //求出四个点的横坐标中点
286  float centerx = 0;
287  for(unsigned int i = 0; i < 4; i++)
288  {
289  centerx += points[i].x;
290  }
291  centerx /= 4;
292 
293  //通过横坐标中点将这组点分为左右两对
294  vector<Point2f> leftPoints;
295  vector<Point2f> rightPoints;
296  for(unsigned int i = 0; i < 4; i++)
297  {
298  if(points[i].x < centerx)
299  {
300  leftPoints.push_back(points[i]);
301  }
302  else
303  {
304  rightPoints.push_back(points[i]);
305  }
306  }
307 
308  //组内分别按高度排序,方便之后处理
309  if(leftPoints[0].y < leftPoints[1].y)
310  {
311  reverse(leftPoints.begin(), leftPoints.end());
312  }
313 
314  if(rightPoints[0].y < rightPoints[1].y)
315  {
316  reverse(rightPoints.begin(), rightPoints.end());
317  }
318 
319  //如果左边这对比右边高,则矩形为倒向左侧状态,否则为倒向右侧状态
320  if(leftPoints[1].y > rightPoints[1].y)
321  {
322  Point2f newPoint;
323 
324  //两条直线相交的交点
325  newPoint.x = leftPoints[0].x;
326  newPoint.y = (leftPoints[1].y-rightPoints[1].y)/(leftPoints[1].x-rightPoints[1].x)
327  * (leftPoints[0].x-rightPoints[1].x) + rightPoints[1].y;
328  leftPoints[1] = newPoint;
329 
330  newPoint.x = rightPoints[1].x;
331  newPoint.y = (leftPoints[0].y-rightPoints[0].y)/(leftPoints[0].x-rightPoints[0].x)
332  * (rightPoints[1].x-leftPoints[0].x) + leftPoints[0].y;
333  rightPoints[0] = newPoint;
334  }
335  else
336  {
337  Point2f newPoint;
338 
339  //两条直线相交的交点
340  newPoint.x = leftPoints[1].x;
341  newPoint.y = (leftPoints[0].y-rightPoints[0].y)/(leftPoints[0].x-rightPoints[0].x)
342  * (leftPoints[1].x-rightPoints[0].x) + rightPoints[0].y;
343  leftPoints[0] = newPoint;
344 
345  newPoint.x = rightPoints[0].x;
346  newPoint.y = (leftPoints[1].y-rightPoints[1].y)/(leftPoints[1].x-rightPoints[1].x)
347  * (rightPoints[0].x-leftPoints[1].x) + leftPoints[1].y;
348  rightPoints[1] = newPoint;
349  }
350 
351  //拼接两对点
352  points[0] = leftPoints[0];
353  points[1] = leftPoints[1];
354  points[2] = rightPoints[1];
355  points[3] = rightPoints[0];
356 }
357 }
Mat preprocess(const Mat &srcImage)
对读取到的图片预处理
bool detect(const Mat &srcImage)
检测图像中是否存在装甲板区域
vector< vector< Point > > searchBlocks(Mat srcImage)
搜寻图中所有独立的团块
HLL Computer Vision Code namepace.
void cutEdgeOfRect(Point2f *points)
减去旋转矩形的边角,使旋转矩形的左右两条边与竖直方向平行,成为平行四边形
void markArmourBlocks(const Mat &srcImage, const Mat &dstImage, const vector< RotatedRect > &armourBlocks)
对最后提取出的灯柱区域评分,选出最优区域
最佳装甲板区域结构体
struct HCVC::ArmourDetector::Params params
void drawBlocks(Mat srcImage, const vector< RotatedRect > &minRotatedRects, const Scalar &color) const
在原图像上画出旋转矩形,便于调试
vector< RotatedRect > calcBlocksInfo(const vector< vector< Point > > &blocks)
计算每一个团块的信息,并进行初步的筛选即去除掉一些不符合条件的团块
void fillLampBlock(Mat &srcImage, vector< vector< Point > > &blocks, int row, int col)
用广度优先搜索填充每一个连通块
ArmourDetector()
加载装甲板区域判定参数
Rect2d getBestArmourBlock() const
获取上一次图像中检测出的最佳装甲板区域
vector< OptimalArmourBlock > optimalArmourBlocks
vector< RotatedRect > extracArmourBlocks(const vector< RotatedRect > &minRotatedRects)
进一步筛选,匹配团块即灯柱对,提取出最优目标
制作者   doxygen 1.8.13