Переглянути джерело

Merge branch 'dev-v0.6.16' of chenhongjiang/graft_cv into dev

chenhongjiang 1 рік тому
батько
коміт
e41c26b72c
7 змінених файлів з 597 додано та 31 видалено
  1. 2 1
      ReadMe.txt
  2. 13 3
      config.cpp
  3. 4 0
      data_def_api.h
  4. 10 6
      gcv_conf.yml
  5. 531 20
      grab_point_rs.cpp
  6. 36 0
      grab_point_rs.h
  7. 1 1
      graft_cv_api.cpp

+ 2 - 1
ReadMe.txt

@@ -83,4 +83,5 @@ v0.6.11 
 v0.6.12 增加茄科抓取目标大小判断,太小的目标跳出;增加自动ply数据保存功能;茄科切后重识别图片保存功能
 v0.6.13 增加聚类结果中点数小于20的类别剔除功能;修改降采样后整个点云数量小于50退出(原来是200)
 v0.6.14 修改茎识别方法,通过2d高密度点找到茎的位置,然后提取株的最大空间内点云,用直线分割的方法得到茎的位置,避免识别位置错误
-v0.6.15 修改抓取点识别方法,通过比较直线点云和原始点云相同位置邻域内xz的范围,确定此点是否是无干扰点的茎
+v0.6.15 修改抓取点识别方法,通过比较直线点云和原始点云相同位置邻域内xz的范围,确定此点是否是无干扰点的茎
+v0.6.16 加强苗的识别,增加倾斜苗的检测,修改排序错乱问题

+ 13 - 3
config.cpp

@@ -103,6 +103,8 @@ namespace graft_cv{
 			<< "rs_grab_zmax" << m_cparam->rs_grab_zmax
 			<< "rs_grab_stem_diameter" << m_cparam->rs_grab_stem_diameter
 			<< "rs_grab_y_opt" << m_cparam->rs_grab_y_opt
+			<< "rs_grab_seedling_dist" << m_cparam->rs_grab_seedling_dist
+			<< "rs_grab_stem_min_pts" << m_cparam->rs_grab_stem_min_pts
 
 			<< "sc_grab_xmin" << m_cparam->sc_grab_xmin
 			<< "sc_grab_xmax" << m_cparam->sc_grab_xmax
@@ -112,7 +114,8 @@ namespace graft_cv{
 			<< "sc_grab_zmax" << m_cparam->sc_grab_zmax
 			<< "sc_grab_stem_diameter" << m_cparam->sc_grab_stem_diameter
 			<< "sc_grab_y_opt" << m_cparam->sc_grab_y_opt
-
+			<< "sc_grab_seedling_dist" << m_cparam->sc_grab_seedling_dist
+			<< "sc_grab_stem_min_pts" << m_cparam->sc_grab_stem_min_pts
 			<< "}"; 	
 	};
 	void CGCvConfig::read(const cv::FileNode& node){ //Read serialization for this class
@@ -199,6 +202,8 @@ namespace graft_cv{
 		m_cparam->rs_grab_zmax = (double)node["rs_grab_zmax"];
 		m_cparam->rs_grab_stem_diameter = (double)node["rs_grab_stem_diameter"];
 		m_cparam->rs_grab_y_opt = (double)node["rs_grab_y_opt"];
+		m_cparam->rs_grab_seedling_dist = (double)node["rs_grab_seedling_dist"];
+		m_cparam->rs_grab_stem_min_pts = (double)node["rs_grab_stem_min_pts"];
 
 		m_cparam->sc_grab_xmin = (double)node["sc_grab_xmin"];
 		m_cparam->sc_grab_xmax = (double)node["sc_grab_xmax"];
@@ -208,7 +213,8 @@ namespace graft_cv{
 		m_cparam->sc_grab_zmax = (double)node["sc_grab_zmax"];
 		m_cparam->sc_grab_stem_diameter = (double)node["sc_grab_stem_diameter"];
 		m_cparam->sc_grab_y_opt = (double)node["sc_grab_y_opt"];
-    
+		m_cparam->sc_grab_seedling_dist = (double)node["sc_grab_seedling_dist"];
+        m_cparam->sc_grab_stem_min_pts = (double)node["sc_grab_stem_min_pts"];
   }
 	string get_cparam_info(ConfigParam*m_cparam)
 	{
@@ -300,6 +306,9 @@ namespace graft_cv{
 			<< "rs_grab_zmax:\t" << m_cparam->rs_grab_zmax << endl
 			<< "rs_grab_stem_diameter:\t" << m_cparam->rs_grab_stem_diameter << endl
 			<< "rs_grab_y_opt:\t" << m_cparam->rs_grab_y_opt << endl
+            << "rs_grab_seedling_dist:\t" << m_cparam->rs_grab_seedling_dist << endl
+			<< "rs_grab_stem_min_pts:\t" << m_cparam->rs_grab_stem_min_pts << endl
+
 
 			<< "sc_grab_xmin:\t" << m_cparam->sc_grab_xmin << endl
 			<< "sc_grab_xmax:\t" << m_cparam->sc_grab_xmax << endl
@@ -309,7 +318,8 @@ namespace graft_cv{
 			<< "sc_grab_zmax:\t" << m_cparam->sc_grab_zmax << endl
 			<< "sc_grab_stem_diameter:\t" << m_cparam->sc_grab_stem_diameter << endl
 			<< "sc_grab_y_opt:\t" << m_cparam->sc_grab_y_opt << endl
-
+			<< "sc_grab_seedling_dist:\t" << m_cparam->sc_grab_seedling_dist << endl
+			<< "sc_grab_stem_min_pts:\t" << m_cparam->sc_grab_stem_min_pts << endl
 			<< "}" << endl; 	
 		return buff.str();
 	}

+ 4 - 0
data_def_api.h

@@ -113,6 +113,8 @@ typedef struct{
 
 	double rs_grab_stem_diameter;	
 	double rs_grab_y_opt;
+	double rs_grab_seedling_dist;
+	int rs_grab_stem_min_pts;
 
 	// scion grab based points cloud
 	double sc_grab_xmin;
@@ -124,6 +126,8 @@ typedef struct{
 
 	double sc_grab_stem_diameter;
 	double sc_grab_y_opt;
+	double sc_grab_seedling_dist;
+	int sc_grab_stem_min_pts;
 
 
 

+ 10 - 6
gcv_conf.yml

@@ -68,18 +68,22 @@ conf_parameters:
    rs_grab_xmin: -200
    rs_grab_xmax: 200
    rs_grab_ymin: -45
-   rs_grab_ymax: 50
+   rs_grab_ymax: 30
    rs_grab_zmin: 300
    rs_grab_zmax: 450
    rs_grab_stem_diameter: 5.0
-   rs_grab_y_opt: -20
+   rs_grab_y_opt: -30
+   rs_grab_seedling_dist: 40.0
+   rs_grab_stem_min_pts: 40
    sc_grab_xmin: -200
    sc_grab_xmax: 200
-   sc_grab_ymin: -45
-   sc_grab_ymax: 50
+   sc_grab_ymin: -50
+   sc_grab_ymax: 30
    sc_grab_zmin: 300
-   sc_grab_zmax: 450
+   sc_grab_zmax: 500
    sc_grab_stem_diameter: 5.0
-   sc_grab_y_opt: -30
+   sc_grab_y_opt: -10
+   sc_grab_seedling_dist: 40.0
+   sc_grab_stem_min_pts: 35
    
 

+ 531 - 20
grab_point_rs.cpp

@@ -33,7 +33,9 @@ namespace graft_cv {
 		m_pLogger(pLog),
 		m_dtype(0),
 		m_pcdId(""),
-		m_ppImgSaver(0)
+		m_ppImgSaver(0),
+		m_1st_row_zmean_rs(-1.0),
+		m_1st_row_zmean_sc(-1.0)
 	{
 	}
 
@@ -219,9 +221,8 @@ namespace graft_cv {
 		pcl::PointXYZ xz_center;					//存储找到的第一个位置上植株根部坐标
 		if (m_pLogger) {
 			m_pLogger->INFO("begin find seedling position");
-		}
-		//find_seedling_position_line_based(cloud_ror, first_seedling_cloud_idx, xz_center);
-		find_seedling_position(cloud_dowm_sampled, first_seedling_cloud_idx, xz_center);
+		}		
+		find_seedling_position_key(cloud_dowm_sampled, first_seedling_cloud_idx, xz_center);
 		if (m_pLogger) {
 			stringstream buff;
 			buff << "after find_seedling_position(), foud first seedling seeds points size " << first_seedling_cloud_idx.size();
@@ -450,16 +451,518 @@ namespace graft_cv {
 
 	//}
 	////////////////////////////////////////////////////////////
-	void CRootStockGrabPoint::find_seedling_position(		
+void CRootStockGrabPoint::gen_all_seedling_positions(
+	pcl::PointXYZ&key_center,	//输入,已知的苗的坐标
+	std::vector<pcl::PointXYZ>&candidate_center	//输出,有倾斜苗的坐标
+)
+{
+	//生成所有的植株位置,并排序
+	candidate_center.clear();
+	
+	//找到z最小的那一株,并找出和它一排的那些
+	float step_harf = m_cparam.rs_grab_seedling_dist / 2.0;
+	float xmin = m_cparam.rs_grab_xmin;
+	float xmax = m_cparam.rs_grab_xmax;
+	float zmin = m_cparam.rs_grab_zmin;
+	float zmax = m_cparam.rs_grab_zmax;
+	int col_from = -8;
+	int col_to = 8;
+	int col_step = 1;
+
+	if (m_dtype == 0) {
+		step_harf = m_cparam.sc_grab_seedling_dist / 2.0;
+		xmin = m_cparam.sc_grab_xmin;
+		xmax = m_cparam.sc_grab_xmax;
+		zmin = m_cparam.sc_grab_zmin;
+		zmax = m_cparam.sc_grab_zmax;		
+	}
+
+	// 生成所有可能的植株位置中心点	
+	for (int row = -8; row <= 4; row += 1) {
+		float cz = key_center.z + row * step_harf/2.0;
+		if (cz < zmin || cz > zmax) { continue; }
+		for (int col = 4*col_from; col <= 4*col_to; col += col_step) {
+			float cx = key_center.x + col * step_harf/2;			
+			if (cx < xmin || cx > xmax) { continue; }			
+			//保存
+			pcl::PointXYZ pc(cx, 0.0, cz);
+			candidate_center.push_back(pc);
+		}
+	}
+}
+
+void CRootStockGrabPoint::nms_box(
+	std::vector<pcl::PointXYZ>&clt_root_v,	
+	std::vector<float>&valid_box_cc_dist,
+	float hole_step_radius, 
+	std::vector<pcl::PointXYZ>& target_toot)
+{	
+	target_toot.clear();
+	pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
+	for (auto&p : clt_root_v) { cloud->points.push_back(p); }
+	
+	pcl::KdTreeFLANN<pcl::PointXYZ> kdtree;
+	kdtree.setInputCloud(cloud);
+	std::vector<int>idx;
+	std::vector<float>dist_sqr;
+	std::vector<int> tar_idx;
+	for (size_t i = 0; i < cloud->points.size(); ++i) {
+		int k = kdtree.radiusSearch(cloud->points.at(i), hole_step_radius, idx, dist_sqr);
+		std::vector<float> cc_tmp;
+		for (auto& j : idx) { cc_tmp.push_back(valid_box_cc_dist.at(j)); }
+		int min_id = std::min_element(cc_tmp.begin(), cc_tmp.end()) - cc_tmp.begin();
+		if (min_id == 0) {
+			tar_idx.push_back(i);
+		}		
+	}
+	for (auto& i : tar_idx) {
+		target_toot.push_back(clt_root_v.at(i));
+	}
+
+}
+//通过指定位置内,取部分点云分析是否存在真正的茎,真茎位置保存到target_filtered
+void CRootStockGrabPoint::line_filter(
+	pcl::PointCloud<pcl::PointXYZ>::Ptr in_cloud,  //输入点云
+	float radius,												//输入,取点半径
+	float ymin,
+	float ymax,
+	std::vector<pcl::PointXYZ>&target_root,						//输入,位置
+	std::vector<pcl::PointXYZ>&target_filtered,					//输出,位置
+	std::vector<pcl::PointXYZ>&target_filtered_root,			//输出,茎上根部点坐标
+	std::vector<std::vector<int>>&target_filtered_element		//输出,茎上点index
+	
+
+)
+{
+	target_filtered.clear();
+	target_filtered_element.clear();
+	target_filtered_root.clear();
+	if (target_root.size() == 0) { return; }
+
+	for (auto&p : target_root) {
+		// 构建box,获取植株点云
+		pcl::PointCloud<pcl::PointXYZ>::Ptr seedling_inbox(new pcl::PointCloud<pcl::PointXYZ>);
+		pcl::CropBox<pcl::PointXYZ> box_filter;
+		std::vector<int>inbox_idx;
+		pcl::PointXYZ min_point_aabb_ex;
+		pcl::PointXYZ max_point_aabb_ex;
+
+		min_point_aabb_ex.x = p.x - radius;
+		min_point_aabb_ex.y = ymin;
+		min_point_aabb_ex.z = p.z - radius;
+		max_point_aabb_ex.x = p.x + radius;
+		max_point_aabb_ex.y = ymax;
+		max_point_aabb_ex.z = p.z + radius;
+
+		box_filter.setMin(Eigen::Vector4f(min_point_aabb_ex.x, min_point_aabb_ex.y, min_point_aabb_ex.z, 1));
+		box_filter.setMax(Eigen::Vector4f(max_point_aabb_ex.x, max_point_aabb_ex.y, max_point_aabb_ex.z, 1));
+
+		box_filter.setNegative(false);
+		box_filter.setInputCloud(in_cloud);
+		box_filter.filter(inbox_idx);
+		pcl::copyPointCloud(*in_cloud, inbox_idx, *seedling_inbox);
+
+		//植株点云直线查找
+		//找到inbox点云中的直线
+		float stem_radius = m_cparam.rs_grab_stem_diameter / 2.0;
+		if (m_dtype == 0) {
+			stem_radius = m_cparam.sc_grab_stem_diameter / 2.0;
+		}
+		pcl::ModelCoefficients::Ptr coefficients(new pcl::ModelCoefficients);
+		pcl::PointIndices::Ptr inliers(new pcl::PointIndices);
+		pcl::SACSegmentation<pcl::PointXYZ> seg;
+		seg.setOptimizeCoefficients(true);
+		seg.setModelType(pcl::SACMODEL_LINE);
+		seg.setDistanceThreshold(stem_radius);
+		seg.setInputCloud(seedling_inbox);
+		seg.segment(*inliers, *coefficients);
+
+
+		pcl::PointCloud<pcl::PointXYZ>::Ptr stem_cloud(new pcl::PointCloud<pcl::PointXYZ>);
+		pcl::copyPointCloud(*seedling_inbox, *inliers, *stem_cloud);
+		
+		//点数过滤
+		int min_stem_pts = m_cparam.rs_grab_stem_min_pts;
+		if (m_dtype == 0) { min_stem_pts = m_cparam.sc_grab_stem_min_pts; }
+		if (inliers->indices.size() < int(min_stem_pts / 2)) { continue; }
+		//y方向分布范围滤波
+		pcl::PointXYZ min_v;
+		pcl::PointXYZ max_v;
+		pcl::getMinMax3D(*stem_cloud, min_v, max_v);
+		float dy = max_v.y - min_v.y;
+		if (dy / (ymax - ymin) < 0.5) { continue; }
+		//y方向分布中心滤波
+		float dy_c = 0.5*(max_v.y + min_v.y);
+		if ((dy_c - ymin) / (ymax - ymin) > 0.75) { continue; }
+		//倾斜角度过滤
+		float xz = std::sqrt(coefficients->values.at(3) * coefficients->values.at(3) +
+			coefficients->values.at(5) * coefficients->values.at(5));
+		float a = std::atan2f(coefficients->values.at(4), xz);
+		a = std::fabs(a) * 180.0 / 3.14159;
+		if (a < 45.0) { continue; }
+		target_filtered.push_back(p);
+
+		float min_y = 10000.0;
+		int min_y_idx = -1;
+		for (size_t j = 0; j < stem_cloud->points.size();++j) {
+			pcl::PointXYZ &spt = stem_cloud->at(j);
+			if (spt.y < min_y) {
+				min_y = spt.y;
+				min_y_idx = j;
+			}
+		}
+		pcl::PointXYZ tr_pt(stem_cloud->points.at(min_y_idx).x, stem_cloud->points.at(min_y_idx).y, stem_cloud->points.at(min_y_idx).z);
+		target_filtered_root.push_back(tr_pt);
+		
+		int idx_raw = 0;
+		std::vector<int> out_idx;
+		for (auto& idx : inliers->indices) {
+			idx_raw = inbox_idx.at(idx);
+			out_idx.push_back(idx_raw);
+		}
+		target_filtered_element.push_back(out_idx);
+		
+		// debug show
+		/*if (m_cparam.image_show) {
+			pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_line(new pcl::PointCloud<pcl::PointXYZ>);
+			pcl::copyPointCloud(*seedling_inbox, *inliers, *cloud_line);
+
+			pcl::visualization::PCLVisualizer::Ptr viewer(new pcl::visualization::PCLVisualizer("line in cluster"));
+			viewer->addPointCloud<pcl::PointXYZ>(seedling_inbox, "cluster");
+			viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_COLOR, 1, 1, 1, "cluster");
+			viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 1, "cluster");
+
+			viewer->addPointCloud(cloud_line, "fit_line");
+			viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_COLOR, 0, 1, 0, "fit_line");
+			viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 3, "fit_line");
+			while (!viewer->wasStopped()) {
+				viewer->spinOnce(100);
+			}
+		}*/
+	}
+}
+
+
+	void CRootStockGrabPoint::find_seedling_position_key(		
 		pcl::PointCloud<pcl::PointXYZ>::Ptr in_cloud,
 		std::vector<int> &first_seedling_cloud_idx,
 		pcl::PointXYZ&xz_center
 	)
 	{
+		// 确定植株inbox范围
+		float hole_step = m_cparam.rs_grab_seedling_dist - 5.0; //穴盘中穴间距
+		if (m_dtype == 0) {
+			hole_step = m_cparam.sc_grab_seedling_dist - 5.0;
+		}
+		float hole_step_radius = hole_step / 2.0;
+
+		// 点云降维到xz平面,y=0
+		pcl::PointCloud<pcl::PointXYZ>::Ptr cloud2d(new pcl::PointCloud < pcl::PointXYZ>);
+		pcl::copyPointCloud(*in_cloud, *cloud2d);
+		for (auto&pt : *cloud2d) {
+			pt.y = 0.0;
+		}	
+
+		// 在xz平面内统计点的密度
+		double radius = m_cparam.rs_grab_stem_diameter;
+		if (m_dtype == 0) {
+			radius = m_cparam.sc_grab_stem_diameter;
+		}
+		std::vector<int> counter;
+		pcl::KdTreeFLANN<pcl::PointXYZ> kdtree;
+		kdtree.setInputCloud(cloud2d);
+		std::vector<int>idx;
+		std::vector<float>dist_sqr;
+		for (size_t i = 0; i < cloud2d->points.size(); ++i) {
+			int k = kdtree.radiusSearch(cloud2d->points.at(i), radius, idx, dist_sqr);
+			counter.push_back(k);
+		}
+		int max_density_idx = std::max_element(counter.begin(), counter.end()) - counter.begin();
+		int max_density = counter.at(max_density_idx);
+		
+		if (m_cparam.image_show) {
+			//显示密度最大的点的位置
+			pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud_tmp(new pcl::PointCloud<pcl::PointXYZRGB>);
+			pcl::copyPointCloud(*in_cloud, *cloud_tmp);
+			for (auto& pt : *cloud_tmp) {
+				pt.r = 255;
+				pt.g = 255;
+				pt.b = 255;
+			}
+			std::vector<pcl::PointXYZ>cc;
+			cc.push_back(in_cloud->points.at(max_density_idx));
+			viewer_cloud_cluster(cloud_tmp, cc, string("key"));
+		}
+		//  生成植株的中心及box
+		std::vector<pcl::PointXYZ>clt_root;
+		std::vector<pcl::PointXYZ> clt_box;
+		float ymin = m_cparam.rs_grab_ymin;
+		float ymax = m_cparam.rs_grab_ymax;
+		if (m_dtype == 0) {			
+			ymin = m_cparam.sc_grab_ymin;
+			ymax = m_cparam.sc_grab_ymax;			
+		}
+		gen_all_seedling_positions(in_cloud->points.at(max_density_idx), clt_root);		
+
+		// 每个位置点云情况,过滤		
+		std::vector<int> valid_index;		//初步有效的矩形index
+		std::vector<int> valid_box_pts;		//立方体内点云数量
+		std::vector<float>valid_box_cc_dist;//重心和矩形中心距离
+		for (size_t i = 0; i < clt_root.size();++i) {
+			pcl::PointXYZ &p = clt_root.at(i);
+			pcl::PointCloud<pcl::PointXYZ>::Ptr seedling_inbox(new pcl::PointCloud<pcl::PointXYZ>);
+			pcl::CropBox<pcl::PointXYZ> box_filter;
+			std::vector<int>inbox_idx;
+			pcl::PointXYZ min_point_aabb_ex;
+			pcl::PointXYZ max_point_aabb_ex;
+
+			min_point_aabb_ex.x = p.x - hole_step_radius;
+			min_point_aabb_ex.y = ymin;
+			min_point_aabb_ex.z = p.z - hole_step_radius;
+			max_point_aabb_ex.x = p.x + hole_step_radius;
+			max_point_aabb_ex.y = ymax;
+			max_point_aabb_ex.z = p.z + hole_step_radius;
+
+			box_filter.setMin(Eigen::Vector4f(min_point_aabb_ex.x, min_point_aabb_ex.y, min_point_aabb_ex.z, 1));
+			box_filter.setMax(Eigen::Vector4f(max_point_aabb_ex.x, max_point_aabb_ex.y, max_point_aabb_ex.z, 1));
+
+			box_filter.setNegative(false);
+			box_filter.setInputCloud(in_cloud);
+			box_filter.filter(inbox_idx);
+			pcl::copyPointCloud(*in_cloud, inbox_idx, *seedling_inbox);
+			//点数过滤
+			if (inbox_idx.size() < int(max_density/2)) { continue; }
+			//y方向分布范围滤波
+			pcl::PointXYZ min_v;
+			pcl::PointXYZ max_v;
+			pcl::getMinMax3D(*seedling_inbox, min_v, max_v);
+			float dy = max_v.y - min_v.y;
+			if (dy / (ymax - ymin) < 0.5) { continue; }
+			//y方向分布中心滤波
+			float dy_c = 0.5*(max_v.y + min_v.y);
+			if ((dy_c-ymin) / (ymax - ymin) > 0.75) { continue; }
+			//计算中心
+			Eigen::Vector4f mean;
+			pcl::compute3DCentroid(*seedling_inbox, mean);
+			double cc_dist = std::sqrt((mean[0] - p.x)*(mean[0] - p.x) + (mean[2] - p.z)*(mean[2] - p.z));
+			valid_index.push_back(i);
+			valid_box_pts.push_back(inbox_idx.size());
+			valid_box_cc_dist.push_back((float)cc_dist);			
+		}
+		// 找到局部最大值,找出真正的位置
+		std::vector<pcl::PointXYZ> clt_root_v;
+		std::vector<pcl::PointXYZ> clt_box_v;
+		for (auto&i : valid_index) {
+			pcl::PointXYZ&p = clt_root.at(i);
+			pcl::PointXYZ p_show(p.x, ymin, p.z);
+			clt_root_v.push_back(p_show);
+			pcl::PointXYZ min_point_aabb_ex;
+			pcl::PointXYZ max_point_aabb_ex;
+			min_point_aabb_ex.x = p.x - hole_step_radius;
+			min_point_aabb_ex.y = ymin;
+			min_point_aabb_ex.z = p.z - hole_step_radius;
+			max_point_aabb_ex.x = p.x + hole_step_radius;
+			max_point_aabb_ex.y = ymax;
+			max_point_aabb_ex.z = p.z + hole_step_radius;
+			clt_box_v.push_back(min_point_aabb_ex);
+			clt_box_v.push_back(max_point_aabb_ex);
+		}
+		// 显示			
+		if (m_cparam.image_show) {
+			std::vector<std::vector<int> > clt_line_idx;
+			viewer_cloud_cluster_box(in_cloud, clt_root_v, clt_box_v, clt_line_idx, std::string("valid_box"));
+		}
+		std::vector<pcl::PointXYZ> target_root;
+		nms_box(clt_root_v, valid_box_cc_dist, hole_step_radius, target_root);
+		
+		// 显示			
+		if (m_cparam.image_show) {
+			std::vector<std::vector<int> > clt_line_idx;
+			std::vector<pcl::PointXYZ>clt_box_tmp;
+			for (auto&p : target_root) {
+				pcl::PointXYZ p_show(p.x, ymin, p.z);
+				clt_root_v.push_back(p_show);
+				pcl::PointXYZ min_point_aabb_ex;
+				pcl::PointXYZ max_point_aabb_ex;
+				min_point_aabb_ex.x = p.x - hole_step_radius;
+				min_point_aabb_ex.y = ymin;
+				min_point_aabb_ex.z = p.z - hole_step_radius;
+				max_point_aabb_ex.x = p.x + hole_step_radius;
+				max_point_aabb_ex.y = ymax;
+				max_point_aabb_ex.z = p.z + hole_step_radius;
+				clt_box_tmp.push_back(min_point_aabb_ex);
+				clt_box_tmp.push_back(max_point_aabb_ex);
+			}
+
+			viewer_cloud_cluster_box(in_cloud, target_root, clt_box_tmp, clt_line_idx, std::string("nms_box"));
+		}
+		// 根据得到的位置,直线检测,并过滤
+		std::vector<pcl::PointXYZ>target_filtered;
+		std::vector<std::vector<int>> target_member;
+		std::vector<pcl::PointXYZ>target_filtered_root;
+		line_filter(in_cloud, hole_step_radius,ymin,ymax, target_root, target_filtered, target_filtered_root, target_member);
+
+		if (m_cparam.image_show) {
+			std::vector<pcl::PointXYZ> final_box;
+			for (auto&pt : target_filtered_root) {
+				//扩展矩形范围
+				pcl::PointXYZ min_point_aabb_ex;
+				pcl::PointXYZ max_point_aabb_ex;
+				min_point_aabb_ex.x = pt.x - hole_step_radius;
+				min_point_aabb_ex.y = ymin;				
+				min_point_aabb_ex.z = pt.z - hole_step_radius;
+
+				max_point_aabb_ex.x = pt.x + hole_step_radius;
+				max_point_aabb_ex.y = ymax;
+				max_point_aabb_ex.z = pt.z + hole_step_radius;
+				final_box.push_back(min_point_aabb_ex);
+				final_box.push_back(max_point_aabb_ex);
+			}
+			std::vector<std::vector<int> > clt_line_idx_;
+			viewer_cloud_cluster_box(in_cloud, target_filtered_root, final_box, clt_line_idx_, std::string("line_filter"));
+		}
+
+		//sort cluster center, get the frontest leftest one
+		//找最小z,然后计算平均z
+		float min_root_z = 10000.0;
+		for(auto&pt : target_filtered_root) {
+			if (pt.z < min_root_z) { min_root_z = pt.z; }
+		}
+		//通过最小z,保守的找到和他一排的植株
+		std::vector<int> first_row_index;
+		float mean_z = 0.0;
+		for (int idx_r = 0; idx_r < target_filtered_root.size();++idx_r) {
+			pcl::PointXYZ&pt = target_filtered_root.at(idx_r);
+			if (std::fabs(pt.z - min_root_z) < hole_step) { 
+				first_row_index.push_back(idx_r); 
+				mean_z += pt.z;
+			}
+		}
+		//如果第一排的植株大于3,计算平均值
+		float first_row_num = (float)first_row_index.size();
+		mean_z /= first_row_num;
+		if (first_row_num >= 4) {
+			if (m_dtype == 0) {
+				m_1st_row_zmean_sc = mean_z;
+			}
+			else{ m_1st_row_zmean_rs = mean_z; }
+		}
+		else {
+			if( m_dtype == 0) {
+				if (m_1st_row_zmean_sc > 0) { mean_z = m_1st_row_zmean_sc; }
+			}
+			else {
+				if (m_1st_row_zmean_rs > 0) { mean_z = m_1st_row_zmean_rs; }
+			}
+		}
+		//通过mean_z再次寻找第一排的植株
+		first_row_index.clear();		
+		for (int idx_r = 0; idx_r < target_filtered_root.size(); ++idx_r) {
+			pcl::PointXYZ&pt = target_filtered_root.at(idx_r);
+			if (std::fabs(pt.z - mean_z) < hole_step) {
+				first_row_index.push_back(idx_r);				
+			}
+		}
+		std::vector<float> cluster_index;
+		for (int i=0; i<first_row_index.size();++i){
+			int raw_idx = first_row_index.at(i);
+			pcl::PointXYZ&pt = target_filtered_root.at(raw_idx);			
+			cluster_index.push_back(pt.x);
+		}
+		int first_idx = std::min_element(cluster_index.begin(), cluster_index.end()) - cluster_index.begin();
+		if (m_dtype == 0) {
+			first_idx = std::max_element(cluster_index.begin(), cluster_index.end()) - cluster_index.begin();
+		}
+		first_idx = first_row_index.at(first_idx);
+
+		first_seedling_cloud_idx.clear();
+		for (auto&v : target_member.at(first_idx)) {
+			first_seedling_cloud_idx.push_back(v);
+		}
+		xz_center.x = target_filtered_root.at(first_idx).x;
+		xz_center.y = target_filtered_root.at(first_idx).y;
+		xz_center.z = target_filtered_root.at(first_idx).z;
+
+		if (m_pLogger) {
+			stringstream buff;
+			buff << "euclidean_clustering_ttsas, find cluster center(" << xz_center.x
+				<<", "<< xz_center.y<<", "<< xz_center.z<<")";
+			m_pLogger->INFO(buff.str());
+		}
+	}
+	void CRootStockGrabPoint::tilted_seedling_discover(
+		std::vector<pcl::PointXYZ>&cluster_center,	//输入,已知的苗的坐标
+		std::vector<pcl::PointXYZ>&tilted_center	//输出,有倾斜苗的坐标
+	)
+	{
+		tilted_center.clear();
+		if (cluster_center.size() == 0) { return; }
+		//找到z最小的那一株,并找出和它一排的那些
+		float step_harf = m_cparam.rs_grab_seedling_dist / 2.0;
+		float xmin = m_cparam.rs_grab_xmin;		
+		float xmax = m_cparam.rs_grab_xmax;
+		float zmin = m_cparam.rs_grab_zmin;
+		float zmax = m_cparam.rs_grab_zmax;
+		if (m_dtype == 0) { 
+			step_harf = m_cparam.sc_grab_seedling_dist / 2.0;
+			xmin = m_cparam.sc_grab_xmin;
+			xmax = m_cparam.sc_grab_xmax;
+			zmin = m_cparam.sc_grab_zmin;
+			zmax = m_cparam.sc_grab_zmax;
+		}
+		auto minz_it = std::min_element(cluster_center.begin(), cluster_center.end(),
+			[](const pcl::PointXYZ& p1, const pcl::PointXYZ& p2)-> bool {return p1.z < p2.z; });
+		float minz = minz_it->z;
+		float meanz = 0.0;
+		float cnt = 0.0;
+		for (auto&p : cluster_center) {
+			if (fabs(p.z - minz) < step_harf) { meanz += p.z; cnt += 1.0; }
+		}
+		if(cnt>0.0){meanz /= cnt;}
+		else { meanz = minz; }
+		// 生成所有可能的植株位置中心点
+		std::vector<pcl::PointXYZ>candidate_centers;
+		for (int row = -1; row <= 1; row += 1) {
+			for (int col = -6; col <= 6; col += 1) {
+				float cx = minz_it->x + col * 2.0 * step_harf;
+				float cz = meanz + row * 2.0 * step_harf;
+				if (cx < xmin || cx > xmax) { continue; }
+				if (cz < zmin || cz > zmax) { continue; }
+				// 邻域内已经存在植株的跳过
+				bool with_seedling = false;
+				for (auto&p : cluster_center) {
+					float d = std::sqrt((p.x - cx) * (p.x - cx) + (p.z - cz) * (p.z - cz));
+					if (d < 2.0*step_harf) {
+						with_seedling=true; 
+						break;
+					}
+				}
+				if (!with_seedling) {//保存
+					pcl::PointXYZ pc(cx,0.0,cz);
+					candidate_centers.push_back(pc);
+				}
+			}
+		}
+		//针对每一个candidat_centers的邻域进行检查,检测是否有茎目标
+		for (auto&pc : candidate_centers) {
+		}
+		
+	}
 
-		float hole_step = 40.0; //穴盘中穴间距
+	void CRootStockGrabPoint::find_seedling_position(
+		pcl::PointCloud<pcl::PointXYZ>::Ptr in_cloud,
+		std::vector<int> &first_seedling_cloud_idx,
+		pcl::PointXYZ&xz_center
+	)
+	{
+		// 确定植株inbox范围
+		float hole_step = m_cparam.rs_grab_seedling_dist - 10.0; //穴盘中穴间距
+		if (m_dtype == 0) {
+			hole_step = m_cparam.sc_grab_seedling_dist - 10.0;
+		}
 		float hole_step_radius = hole_step / 2.0;
 
+		// 点云降维到xz平面,y=0
 		pcl::PointCloud<pcl::PointXYZ>::Ptr cloud2d(new pcl::PointCloud < pcl::PointXYZ>);
 		pcl::copyPointCloud(*in_cloud, *cloud2d);
 		for (auto&pt : *cloud2d) {
@@ -467,9 +970,10 @@ namespace graft_cv {
 		}
 
 		/*if(m_cparam.image_show){
-			viewer_cloud(cloud2d, std::string("cloud2d"));
+		viewer_cloud(cloud2d, std::string("cloud2d"));
 		}		*/
 
+		// 在xz平面内统计点的密度
 		double radius = m_cparam.rs_grab_stem_diameter;
 		if (m_dtype == 0) {
 			radius = m_cparam.sc_grab_stem_diameter;
@@ -483,27 +987,28 @@ namespace graft_cv {
 			int k = kdtree.radiusSearch(cloud2d->points.at(i), radius, idx, dist_sqr);
 			counter.push_back(k);
 		}
-		int th = (int)(otsu(counter));		
+		// 计算根据密度分割的自动阈值
+		int th = (int)(otsu(counter));
+		// 得到茎目标的点云序号,和对应的点云cloud2d_pos
 		std::vector<int> index;
 		for (size_t i = 0; i < counter.size(); ++i) {
 			if (counter.at(i) >= th) {
 				index.push_back(i);
 			}
 		}
-
 		pcl::PointCloud<pcl::PointXYZ>::Ptr cloud2d_pos(new pcl::PointCloud < pcl::PointXYZ>);
 		pcl::copyPointCloud(*cloud2d, index, *cloud2d_pos);
 
 		if (m_pLogger) {
 			stringstream buff;
-			buff << "get 2d seedling seed point cloud " << index.size()<< " data points with thrreshold "<<th;
+			buff << "get 2d seedling seed point cloud " << index.size() << " data points with thrreshold " << th;
 			m_pLogger->INFO(buff.str());
 		}
 		if (m_cparam.image_show) {
 			viewer_cloud(cloud2d_pos, std::string("cloud2d_pos"));
 		}
-		
-		//clustering
+
+		//对茎的点云进行clustering,得到不同的茎的分组
 		double d1 = m_cparam.rs_grab_stem_diameter;
 		double d2 = m_cparam.rs_grab_stem_diameter * 3.0;
 		if (m_dtype == 0) {
@@ -530,8 +1035,12 @@ namespace graft_cv {
 				pt.g = 255;
 				pt.b = 255;
 			}
-			viewer_cloud_cluster(cloud_cluster, cluster_center,  std::string("cloud2d_cluster_pos"));
+			viewer_cloud_cluster(cloud_cluster, cluster_center, std::string("cloud2d_cluster_pos"));
 		}
+		// 扩展检测第一排的可能位置,找到倾斜的苗
+		std::vector<pcl::PointXYZ> tilted_center;
+		tilted_seedling_discover(cluster_center, tilted_center);
+
 		//对每个类别进行检测,剔除不是茎的类别
 		std::vector<pcl::PointXYZ> clt_root;
 		std::vector<pcl::PointXYZ> clt_box;
@@ -573,7 +1082,7 @@ namespace graft_cv {
 			//找到line_idx中y最小的那个点的idx
 			int line_root_idx = -1;
 			float line_root_y_min = 10000.0;
-			for (size_t lidx = 0; lidx < line_idx.size();++lidx) {
+			for (size_t lidx = 0; lidx < line_idx.size(); ++lidx) {
 				int raw_idx = line_idx.at(lidx);
 				if (in_cloud->at(raw_idx).y < line_root_y_min) {
 					line_root_y_min = in_cloud->at(raw_idx).y;
@@ -583,14 +1092,14 @@ namespace graft_cv {
 			//找到底部中心点,然后找到根部坐标			
 			pcl::PointXYZ pt_root;
 			pt_root = in_cloud->at(line_root_idx);
-		
+
 			clt_root.push_back(pt_root);
 			clt_box.push_back(min_point_aabb_ex);
 			clt_box.push_back(max_point_aabb_ex);
 			//viewer_cloud(clt_cloud, std::string("cluster"));			
 		}
-		if (m_cparam.image_show) {			
-			viewer_cloud_cluster_box(in_cloud, clt_root, clt_box, clt_line_idx, std::string("cloud2d_cluster_box"));			
+		if (m_cparam.image_show) {
+			viewer_cloud_cluster_box(in_cloud, clt_root, clt_box, clt_line_idx, std::string("cloud2d_cluster_box"));
 		}
 
 		//sort cluster center, get the frontest leftest one
@@ -605,7 +1114,7 @@ namespace graft_cv {
 		}
 
 		first_seedling_cloud_idx.clear();
-		for (auto&v : clt_line_idx.at(first_idx)) {			
+		for (auto&v : clt_line_idx.at(first_idx)) {
 			first_seedling_cloud_idx.push_back(v);
 		}
 		xz_center.x = clt_root.at(first_idx).x;
@@ -615,10 +1124,12 @@ namespace graft_cv {
 		if (m_pLogger) {
 			stringstream buff;
 			buff << "euclidean_clustering_ttsas, find cluster center(" << xz_center.x
-				<<", "<< xz_center.y<<", "<< xz_center.z<<")";
+				<< ", " << xz_center.y << ", " << xz_center.z << ")";
 			m_pLogger->INFO(buff.str());
 		}
 	}
+
+
 	void CRootStockGrabPoint::find_line_in_cube(
 		pcl::PointCloud<pcl::PointXYZ>::Ptr in_cloud,	//输入,整体点云
 		pcl::PointXYZ& min_pt_ex,				//输入,
@@ -1263,7 +1774,7 @@ namespace graft_cv {
 				pcl::visualization::PCL_VISUALIZER_REPRESENTATION_WIREFRAME, "AABB_" + string(_itoa(cnt, buf, 10)));
 
 			pcl::PointCloud<pcl::PointXYZ>::Ptr line_cloud(new pcl::PointCloud<pcl::PointXYZ>);
-			pcl::copyPointCloud(*cloud, clt_line_idx.at(i), *line_cloud);
+			//pcl::copyPointCloud(*cloud, clt_line_idx.at(i), *line_cloud);
 			viewer.addPointCloud(line_cloud, "fit_line" + string(_itoa(cnt, buf, 10)));
 			viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_COLOR, 0, 1, 0, "fit_line" + string(_itoa(cnt, buf, 10)));
 			viewer.setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 3, "fit_line" + string(_itoa(cnt, buf, 10)));

+ 36 - 0
grab_point_rs.h

@@ -23,6 +23,10 @@ namespace graft_cv {
 		int m_dtype;
 		string m_pcdId;
 
+		//用于记录第一排z均值,用来辅助判别1、2排的苗
+		float m_1st_row_zmean_rs = -1.0;
+		float m_1st_row_zmean_sc = -1.0;
+
 		CImStoreManager** m_ppImgSaver;
 
 		pcl::PointCloud<pcl::PointXYZ>::Ptr m_raw_cloud;
@@ -46,6 +50,33 @@ namespace graft_cv {
 			pcl::PointCloud<pcl::PointXYZ>::Ptr in_cloud
 		);*/
 		//<---------
+		void gen_all_seedling_positions(
+			pcl::PointXYZ&key_center,	//输入,已知的苗的坐标
+			std::vector<pcl::PointXYZ>&candidate_center	//输出,有倾斜苗的坐标
+		);
+		void find_seedling_position_key(
+			pcl::PointCloud<pcl::PointXYZ>::Ptr in_cloud,
+			std::vector<int> &first_seedling_cloud_idx,
+			pcl::PointXYZ&xz_center
+		);
+		// 邻域最小抑制
+		void nms_box(
+			std::vector<pcl::PointXYZ>&clt_root_v,	//目标点的可能位置
+			std::vector<float>&valid_box_cc_dist,	//目标点的重心到中心的距离,距离越近越好
+			float hole_step_radius,					//目标点搜索半径
+			std::vector<pcl::PointXYZ>& target_toot // 返回值
+		);
+		//通过指定位置内,取部分点云分析是否存在真正的茎,真茎位置保存到target_filtered
+		void line_filter(pcl::PointCloud<pcl::PointXYZ>::Ptr in_cloud,  //输入点云
+			float radius,												//输入,取点半径
+			float ymin,													//输入,y最小值
+			float ymax,													//输入,y最大值
+			std::vector<pcl::PointXYZ>&target_root,						//输入,位置
+			std::vector<pcl::PointXYZ>&target_filtered,					//输出,位置
+			std::vector<pcl::PointXYZ>&target_filtered_root,			//输出,茎上根部点坐标
+			std::vector<std::vector<int>>&target_filtered_element		//输出,茎上点index
+		);
+
 		/////////////////////////////////////////////////
 		void find_seedling_position(
 			pcl::PointCloud<pcl::PointXYZ>::Ptr in_cloud,
@@ -77,6 +108,11 @@ namespace graft_cv {
 			pcl::PointCloud<pcl::PointXYZ>::Ptr,
 			pcl::PointXYZ&pt,
 			int &pt_idx);
+        //已知检测出苗的位置,找出有可能有苗的位置
+		void tilted_seedling_discover(
+			std::vector<pcl::PointXYZ>&cluster_center,	//输入,已知的苗的坐标
+			std::vector<pcl::PointXYZ>&tilted_center	//输出,有倾斜苗的坐标
+		);
 
 		//通过比较直线点云和原始点云相同位置邻域内xz的范围,确定此点是否是无干扰点的茎
 		void get_optimal_seed_compare(

+ 1 - 1
graft_cv_api.cpp

@@ -19,7 +19,7 @@ extern CRITICAL_SECTION g_cs;
 namespace graft_cv
 {
 
-	char *g_version_str = "0.6.15";
+	char *g_version_str = "0.6.16";
 
 	//configure
 	string g_conf_file = "./gcv_conf.yml";