region_detect.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
  1. //
  2. // Created by zx on 2019/12/6.
  3. //
  4. #include "region_detect.h"
  5. /**
  6. * 有参构造函数
  7. * */
  8. Region_detector::Region_detector(int id, wj::Region region) : m_region_id(-1)
  9. {
  10. m_region_param.CopyFrom(region);
  11. m_region_id = id;
  12. }
  13. /**
  14. * 析构函数
  15. * */
  16. Region_detector::~Region_detector()
  17. {
  18. }
  19. /**
  20. * 获取区域id
  21. * */
  22. int Region_detector::get_region_id()
  23. {
  24. return m_region_id;
  25. }
  26. /**
  27. * 预处理,xy直通滤波与离群点过滤
  28. * 返回 PARAMETER_ERROR,WJ_REGION_EMPTY_CLOUD 或 SUCCESS
  29. * */
  30. Error_manager Region_detector::preprocess(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_in, pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_out)
  31. {
  32. // 1.参数合法性检验
  33. if (!m_region_param.has_maxx() || !m_region_param.has_maxy() || !m_region_param.has_minx() || !m_region_param.has_miny())
  34. return Error_manager(Error_code::PARAMETER_ERROR);
  35. if (cloud_in->size() <= 0)
  36. return Error_manager(Error_code::WJ_REGION_EMPTY_CLOUD);
  37. cloud_out->clear();
  38. pcl::copyPointCloud(*cloud_in, *cloud_out);
  39. if (cloud_out->size() <= 0)
  40. return Error_manager(Error_code::WJ_REGION_EMPTY_CLOUD);
  41. // 2.直通滤波, 筛选xy
  42. pcl::PassThrough<pcl::PointXYZ> pass;
  43. pass.setInputCloud(cloud_out);
  44. pass.setFilterFieldName("x"); //设置想在哪个坐标轴上操作
  45. pass.setFilterLimits(m_region_param.minx(), m_region_param.maxx()); //将x轴的0到1范围内
  46. pass.setFilterLimitsNegative(false); //保留(true就是删除,false就是保留而删除此区间外的)
  47. pass.filter(*cloud_out); //输出到结果指针
  48. if (cloud_out->size() <= 0)
  49. return Error_manager(Error_code::WJ_REGION_EMPTY_CLOUD);
  50. pass.setInputCloud(cloud_out);
  51. pass.setFilterFieldName("y"); //设置想在哪个坐标轴上操作
  52. pass.setFilterLimits(m_region_param.miny(), m_region_param.maxy()); //将x轴的0到1范围内
  53. pass.setFilterLimitsNegative(false); //保留(true就是删除,false就是保留而删除此区间外的)
  54. pass.filter(*cloud_out); //输出到结果指针
  55. if (cloud_out->size() <= 0)
  56. return Error_manager(Error_code::WJ_REGION_EMPTY_CLOUD);
  57. // 3.离群点过滤
  58. pcl::StatisticalOutlierRemoval<pcl::PointXYZ> sor;
  59. sor.setInputCloud(cloud_out);
  60. sor.setMeanK(15); //K近邻搜索点个数
  61. sor.setStddevMulThresh(3.0); //标准差倍数
  62. sor.setNegative(false); //保留未滤波点(内点)
  63. sor.filter(*cloud_out); //保存滤波结果到cloud_filter
  64. if (cloud_out->size() <= 0)
  65. return Error_manager(Error_code::WJ_REGION_EMPTY_CLOUD);
  66. else
  67. return Error_manager(Error_code::SUCCESS);
  68. }
  69. /**
  70. * 返回二维点之间距离
  71. * */
  72. double distance(cv::Point2f p1, cv::Point2f p2)
  73. {
  74. return sqrt(pow(p1.x - p2.x, 2.0) + pow(p1.y - p2.y, 2.0));
  75. }
  76. /**
  77. * 判断是否符合标准矩形,
  78. * 传入矩形四个角点,以及是否打印
  79. * 返回 WJ_REGION_RECTANGLE_ANGLE_ERROR, WJ_REGION_RECTANGLE_SIZE_ERROR,
  80. * WJ_REGION_RECTANGLE_SYMMETRY_ERROR, WJ_REGION_CLUSTER_SIZE_ERROR, SUCCESS
  81. * */
  82. Error_manager Region_detector::isRect(std::vector<cv::Point2f> &points, bool print)
  83. {
  84. if (points.size() == 4)
  85. {
  86. double L[3] = {0.0};
  87. L[0] = distance(points[0], points[1]);
  88. L[1] = distance(points[1], points[2]);
  89. L[2] = distance(points[0], points[2]);
  90. double max_l = L[0];
  91. double l1 = L[1];
  92. double l2 = L[2];
  93. int max_index = 0;
  94. cv::Point2f ps = points[0], pt = points[1];
  95. // 顶点
  96. cv::Point2f pc = points[2];
  97. for (int i = 1; i < 3; ++i)
  98. {
  99. if (L[i] > max_l)
  100. {
  101. max_index = i;
  102. max_l = L[i];
  103. l1 = L[abs(i + 1) % 3];
  104. l2 = L[abs(i + 2) % 3];
  105. ps = points[i % 3];
  106. pt = points[(i + 1) % 3];
  107. pc = points[(i + 2) % 3];
  108. }
  109. }
  110. // 顶角大小
  111. double cosa = (l1 * l1 + l2 * l2 - max_l * max_l) / (2.0 * l1 * l2);
  112. // 顶角相对于90度过大或小
  113. if (fabs(cosa) >= 0.15 /* || std::min(l1, l2) > 2.0 || std::max(l1, l2) > 3.3*/)
  114. {
  115. if (print)
  116. {
  117. LOG(WARNING) << " angle cos >0.13 =" << cosa << " i=" << max_index;
  118. // LOG(WARNING) << "L1:" << l1 << " L2:" << l2 << " L3:" << max_l;
  119. }
  120. return Error_manager(Error_code::WJ_REGION_RECTANGLE_ANGLE_ERROR);
  121. }
  122. float width = std::min(l1, l2);
  123. float length = std::max(l1, l2);
  124. // 车宽应位于[1.4, 2.0],车长应位于[2.2, 3.0]
  125. if (width < 1.400 || width > 2.000 || length > 3.000 || length < 2.200)
  126. {
  127. if (print)
  128. {
  129. LOG(WARNING) << "\t width<1400 || width >2100 || length >3300 ||length < 2100 "
  130. << " length:" << length << " width:" << width;
  131. }
  132. return Error_manager(Error_code::WJ_REGION_RECTANGLE_SIZE_ERROR);
  133. }
  134. double d = distance(pc, points[3]);
  135. cv::Point2f center1 = (ps + pt) * 0.5;
  136. cv::Point2f center2 = (pc + points[3]) * 0.5;
  137. // 对角线形变超过0.15倍,或中心点距离偏差0.15m
  138. if (fabs(d - max_l) > max_l * 0.15 || distance(center1, center2) > 0.150)
  139. {
  140. if (print)
  141. {
  142. LOG(WARNING) << "d:" << d << " maxl:" << max_l << " center1:" << center1 << " center2:" << center2
  143. << " center distance=" << distance(center1, center2);
  144. }
  145. return Error_manager(Error_code::WJ_REGION_RECTANGLE_SYMMETRY_ERROR);
  146. }
  147. if (print)
  148. {
  149. LOG(INFO) << " rectangle verify OK cos angle=" << cosa << " length off=" << fabs(d - max_l)
  150. << " center distance=" << distance(center1, center2);
  151. }
  152. return Error_manager(Error_code::SUCCESS);
  153. }
  154. // 以三个点进行验证
  155. else if (points.size() == 3)
  156. {
  157. double L[3] = {0.0};
  158. L[0] = distance(points[0], points[1]);
  159. L[1] = distance(points[1], points[2]);
  160. L[2] = distance(points[0], points[2]);
  161. double max_l = L[0];
  162. double l1 = L[1];
  163. double l2 = L[2];
  164. int max_index = 0;
  165. cv::Point2f ps = points[0], pt = points[1];
  166. cv::Point2f pc = points[2];
  167. for (int i = 1; i < 3; ++i)
  168. {
  169. if (L[i] > max_l)
  170. {
  171. max_index = i;
  172. max_l = L[i];
  173. l1 = L[abs(i + 1) % 3];
  174. l2 = L[abs(i + 2) % 3];
  175. ps = points[i % 3];
  176. pt = points[(i + 1) % 3];
  177. pc = points[(i + 2) % 3];
  178. }
  179. }
  180. double cosa = (l1 * l1 + l2 * l2 - max_l * max_l) / (2.0 * l1 * l2);
  181. // 顶角应接近90度
  182. if (fabs(cosa) >= 0.15)
  183. {
  184. if (print)
  185. {
  186. LOG(WARNING) << "3 wheels angle cos >0.12 =" << cosa << " i=" << max_index;
  187. LOG(WARNING) << "L1:" << l1 << " L2:" << l2 << " L3:" << max_l;
  188. }
  189. return Error_manager(Error_code::WJ_REGION_RECTANGLE_ANGLE_ERROR);
  190. }
  191. double length = std::max(l1, l2);
  192. double width = std::min(l1, l2);
  193. // 车宽应位于[1.4, 2.0],车长应位于[2.2, 3.0]
  194. if (length > 2.100 && length < 3.000 && width > 1.400 && width < 2.100)
  195. {
  196. //生成第四个点
  197. cv::Point2f vec1 = ps - pc;
  198. cv::Point2f vec2 = pt - pc;
  199. cv::Point2f point4 = (vec1 + vec2) + pc;
  200. points.push_back(point4);
  201. if (print)
  202. {
  203. LOG(WARNING) << "3 wheels rectangle verify OK cos angle=" << cosa << " L=" << length
  204. << " w=" << width;
  205. }
  206. return Error_manager(Error_code::SUCCESS);
  207. }
  208. else
  209. {
  210. if (print)
  211. {
  212. LOG(WARNING) << "3 wheels rectangle verify Failed cos angle=" << cosa << " L=" << length
  213. << " w=" << width;
  214. }
  215. return Error_manager(Error_code::WJ_REGION_RECTANGLE_SIZE_ERROR);
  216. }
  217. }
  218. else
  219. {
  220. return Error_manager(Error_code::WJ_REGION_CLUSTER_SIZE_ERROR);
  221. }
  222. }
  223. /**
  224. * 聚类并通过矩形判断函数
  225. * 传入原始点云,传出角点与聚类出的点云
  226. * 返回 WJ_REGION_EMPTY_CLOUD, WJ_REGION_RECTANGLE_ANGLE_ERROR, WJ_REGION_RECTANGLE_SIZE_ERROR,
  227. * WJ_REGION_RECTANGLE_SYMMETRY_ERROR, WJ_REGION_CLUSTER_SIZE_ERROR, SUCCESS
  228. * */
  229. Error_manager Region_detector::clustering(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_in, std::vector<cv::Point2f> &corner_points, std::vector<pcl::PointCloud<pcl::PointXYZ>::Ptr> &seg_clouds, bool print)
  230. {
  231. // 1.判断参数合法性
  232. if (cloud_in->size() <= 0)
  233. return Error_manager(Error_code::WJ_REGION_EMPTY_CLOUD);
  234. // 2.聚类
  235. pcl::search::KdTree<pcl::PointXYZ>::Ptr kdtree(new pcl::search::KdTree<pcl::PointXYZ>);
  236. kdtree->setInputCloud(cloud_in);
  237. pcl::EuclideanClusterExtraction<pcl::PointXYZ> clustering;
  238. // 设置聚类的最小值 2cm (small values may cause objects to be divided
  239. // in several clusters, whereas big values may join objects in a same cluster).
  240. clustering.setClusterTolerance(0.2);
  241. // 设置聚类的小点数和最大点云数
  242. clustering.setMinClusterSize(10);
  243. clustering.setMaxClusterSize(500);
  244. clustering.setSearchMethod(kdtree);
  245. clustering.setInputCloud(cloud_in);
  246. std::vector<pcl::PointIndices> clusters;
  247. clustering.extract(clusters);
  248. if(clusters.size() <= 0)
  249. return Error_manager(Error_code::WJ_REGION_EMPTY_CLOUD);
  250. for (int i = 0; i < clusters.size(); ++i)
  251. {
  252. seg_clouds.push_back(pcl::PointCloud<pcl::PointXYZ>::Ptr(new pcl::PointCloud<pcl::PointXYZ>));
  253. }
  254. int j = 0;
  255. for (std::vector<pcl::PointIndices>::const_iterator it = clusters.begin(); it != clusters.end(); ++it)
  256. {
  257. pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_cluster(new pcl::PointCloud<pcl::PointXYZ>);
  258. //创建新的点云数据集cloud_cluster,将所有当前聚类写入到点云数据集中
  259. for (std::vector<int>::const_iterator pit = it->indices.begin(); pit != it->indices.end(); ++pit)
  260. {
  261. cloud_cluster->points.push_back(cloud_in->points[*pit]);
  262. cloud_cluster->width = cloud_cluster->points.size();
  263. cloud_cluster->height = 1;
  264. cloud_cluster->is_dense = true;
  265. }
  266. seg_clouds[j] = cloud_cluster;
  267. j++;
  268. }
  269. if (print)
  270. {
  271. LOG(INFO) << " cluster classes:" << clusters.size();
  272. }
  273. // 3.分别计算每团点云几何中心,作为矩形角点
  274. corner_points.clear();
  275. for (int i = 0; i < clusters.size(); ++i)
  276. {
  277. if (seg_clouds.size() <= i || seg_clouds[i]->size() == 0)
  278. continue;
  279. float sumX = 0, sumY = 0;
  280. for (int j = 0; j < seg_clouds[i]->size(); ++j)
  281. {
  282. sumX += seg_clouds[i]->points[j].x;
  283. sumY += seg_clouds[i]->points[j].y;
  284. }
  285. float center_x = sumX / float(seg_clouds[i]->size());
  286. float center_y = sumY / float(seg_clouds[i]->size());
  287. corner_points.push_back(cv::Point2f(center_x, center_y));
  288. }
  289. /*char buf[255]={0};
  290. for (int k = 0; k < corner_points.size(); ++k) {
  291. sprintf(buf+strlen(buf), "point %d: (%.2f, %.2f)__", k, corner_points[k].x, corner_points[k].y);
  292. }
  293. LOG(WARNING) << buf;*/
  294. cv::RotatedRect t_rect=cv::minAreaRect(corner_points);
  295. //display(t_rect,cv::Scalar(255,0,0));
  296. return isRect(corner_points, print);
  297. }
  298. /**
  299. * 省略传出参数的四轮检测算法函数
  300. * 传入待检测点云
  301. * 返回值检测结果
  302. * */
  303. Error_manager Region_detector::detect(pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_in)
  304. {
  305. pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered = pcl::PointCloud<pcl::PointXYZ>::Ptr(new pcl::PointCloud<pcl::PointXYZ>);
  306. // 1.预处理
  307. Error_manager result = preprocess(cloud_in, cloud_filtered);
  308. if (result == SUCCESS)
  309. {
  310. std::vector<cv::Point2f> corner_points;
  311. std::vector<pcl::PointCloud<pcl::PointXYZ>::Ptr> seg_clouds;
  312. // 2.聚类与矩形校验
  313. result = clustering(cloud_filtered, corner_points, seg_clouds);
  314. }
  315. return result;
  316. }
  317. /**
  318. * 输出中心点、角度、轮距与宽度的四轮点云检测函数
  319. * */
  320. Error_manager Region_detector::detect(pcl::PointCloud<pcl::PointXYZ>::Ptr &cloud_in, double &x, double &y, double &c, double &wheelbase, double &width, bool print)
  321. {
  322. Error_manager result = Error_manager(Error_code::SUCCESS);
  323. pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_filtered(pcl::PointCloud<pcl::PointXYZ>::Ptr(new pcl::PointCloud<pcl::PointXYZ>));
  324. // 1.预处理
  325. result = preprocess(cloud_in, cloud_filtered);
  326. if (result != SUCCESS)
  327. return result;
  328. // 2.聚类计算角点
  329. std::vector<cv::Point2f> corner_points;
  330. std::vector<pcl::PointCloud<pcl::PointXYZ>::Ptr> seg_clouds;
  331. result = clustering(cloud_filtered, corner_points, seg_clouds, print);
  332. if (result != SUCCESS)
  333. return result;
  334. // 修改原始点云为预处理后点云,并加入角点供查验
  335. cloud_in->clear();
  336. cloud_in->operator+=(*cloud_filtered);
  337. for (size_t i = 0; i < corner_points.size(); i++)
  338. {
  339. cloud_in->points.push_back(pcl::PointXYZ(corner_points[i].x, corner_points[i].y, 0.0));
  340. }
  341. // convert all points after preprocessed into 2d
  342. std::vector<cv::Point2f> all_points;
  343. // car center
  344. for (int j = 0; j < cloud_filtered->size(); ++j)
  345. {
  346. all_points.push_back(cv::Point2f(cloud_filtered->points[j].x, cloud_filtered->points[j].y));
  347. }
  348. // find bounding rectangle of all wheel points
  349. cv::RotatedRect wheel_box = cv::minAreaRect(all_points);
  350. x = wheel_box.center.x;
  351. y = wheel_box.center.y;
  352. // add center point to the input cloud for further check
  353. cloud_in->points.push_back(pcl::PointXYZ(x, y, 0.0));
  354. // 长边向量
  355. cv::Point2f vec;
  356. cv::Point2f vertice[4];
  357. wheel_box.points(vertice);
  358. float len1 = pow(vertice[0].x - vertice[1].x, 2.0) + pow(vertice[0].y - vertice[1].y, 2.0);
  359. float len2 = pow(vertice[1].x - vertice[2].x, 2.0) + pow(vertice[1].y - vertice[2].y, 2.0);
  360. // 寻找长边,倾角为长边与x轴夹角
  361. if (len1 > len2)
  362. {
  363. vec.x = vertice[0].x - vertice[1].x;
  364. vec.y = vertice[0].y - vertice[1].y;
  365. }
  366. else
  367. {
  368. vec.x = vertice[1].x - vertice[2].x;
  369. vec.y = vertice[1].y - vertice[2].y;
  370. }
  371. float angle_x = 180.0 / M_PI * acos(vec.x / sqrt(vec.x * vec.x + vec.y * vec.y));
  372. c = angle_x;
  373. // get line formula, normal is (cos(theta), sin(theta)), towards the long side
  374. // the line formula is: nx * x + ny * y + (-(nx*cx+ny*cy)) = 0
  375. Eigen::Vector2f normal, center_point;
  376. normal << vec.x / sqrt(vec.x * vec.x + vec.y * vec.y), vec.y / sqrt(vec.x * vec.x + vec.y * vec.y);
  377. center_point << x, y;
  378. float line_param_c = -1 * center_point.transpose() * normal;
  379. // get rotation matrix, get the angle towards normal vector, rather than x axis
  380. // R = [ cos -sin]
  381. // [ sin cos]
  382. float rotate_angle = M_PI_2 - acos(vec.x / sqrt(vec.x * vec.x + vec.y * vec.y));
  383. Eigen::Matrix2f rotation_matrix;
  384. rotation_matrix << cos(rotate_angle), -sin(rotate_angle), sin(rotate_angle), cos(rotate_angle);
  385. // find min x and max x, separate y values according to the line, calculate difference between mean of y values as wheelbase
  386. float min_x = 20, max_x = 0;
  387. float y_values0 = 0, y_values1 = 0;
  388. int count0 = 0, count1 = 0;
  389. for (size_t i = 0; i < seg_clouds.size(); i++)
  390. {
  391. if (seg_clouds[i]->size() <= 0)
  392. continue;
  393. for (size_t j = 0; j < seg_clouds[i]->size(); j++)
  394. {
  395. // origin point and the point rotated around the origin
  396. Eigen::Vector2f vec, vec_rot;
  397. vec << seg_clouds[i]->points[j].x, seg_clouds[i]->points[j].y;
  398. vec_rot = rotation_matrix * (vec - center_point) + center_point;
  399. // find min max x
  400. if (vec_rot[0] < min_x)
  401. min_x = vec_rot[0];
  402. if (vec_rot[0] > max_x)
  403. max_x = vec_rot[0];
  404. // separate point as two clusters(front and back), calc y values respectively
  405. if (normal.transpose() * vec + line_param_c > 0)
  406. {
  407. y_values0 += vec_rot[1];
  408. count0 += 1;
  409. }
  410. else
  411. {
  412. y_values1 += vec_rot[1];
  413. count1 += 1;
  414. }
  415. }
  416. }
  417. // check if front and back both has points
  418. if (count0 > 0 && count1 > 0)
  419. {
  420. y_values0 /= count0;
  421. y_values1 /= count1;
  422. wheelbase = fabs(y_values1 - y_values0);
  423. width = fabs(min_x - max_x);
  424. // LOG(INFO) << "--------detector find width, wheelbase: " << width << ", " << wheelbase << ", y means: [" << y_values0 << ", " << y_values1 << "] -----";
  425. cloud_in->points.push_back(pcl::PointXYZ(min_x, y, 0.0));
  426. cloud_in->points.push_back(pcl::PointXYZ(max_x, y, 0.0));
  427. cloud_in->points.push_back(pcl::PointXYZ(x, y_values0, 0.0));
  428. cloud_in->points.push_back(pcl::PointXYZ(x, y_values1, 0.0));
  429. }
  430. else
  431. {
  432. // calculate wheelbase according to corner points
  433. // wheelbase = std::max(wheel_box.size.width, wheel_box.size.height);
  434. // LOG(INFO) << "--------detector find border: [" << wheel_box.size.width << ", " << wheel_box.size.height << "] -----";
  435. cv::RotatedRect wheel_center_box = cv::minAreaRect(corner_points);
  436. wheelbase = std::max(wheel_center_box.size.width, wheel_center_box.size.height);
  437. width = std::min(wheel_box.size.width, wheel_box.size.height);
  438. }
  439. return Error_manager(Error_code::SUCCESS);
  440. }