Bladeren bron

v0.8.18 优化茎识别(叶子误判成茎);优化遮挡识别(位置误判)

chenhongjiang 1 jaar geleden
bovenliggende
commit
ea06dbec03
6 gewijzigde bestanden met toevoegingen van 569 en 283 verwijderingen
  1. 2 1
      ReadMe.txt
  2. 401 218
      grab_occlusion.cpp
  3. 25 5
      grab_occlusion.h
  4. 126 56
      grab_point_rs.cpp
  5. 14 2
      grab_point_rs.h
  6. 1 1
      graft_cv_api.cpp

+ 2 - 1
ReadMe.txt

@@ -126,4 +126,5 @@ v0.8.13 优化茎粗检测功能,采用茎两侧边缘点构建直线,计算
 v0.8.14 优化茎粗检测功能,增加xx_grab_offset_cut, 在ybt、yup中间点进行偏移,在这个位置计算茎粗;茎edge直线拟合,剔除茎节的影响
 v0.8.15 优化茎节检测功能,增加xx_grab_fork_height,控制识别较长的茎节
 v0.8.16 修改茎节检测中while循环死循环的bug
-v0.8.17 修改内存报错的bug
+v0.8.17 修改内存报错的bug
+v0.8.18 优化茎识别(叶子误判成茎);优化遮挡识别(位置误判)

+ 401 - 218
grab_occlusion.cpp

@@ -139,8 +139,7 @@ CSeedlingStatus::CSeedlingStatus(
 {
 	int x0 = int(x_min);
 	int x1 = int(x_max);
-	m_hist_length = int((x1 - x0) / step);	
-	m_history_histogram = cv::Mat::zeros(m_max_size, m_hist_length, CV_32F);
+	m_hist_length = int((x1 - x0) / step);		
 	m_history_point_size = cv::Mat::zeros(m_max_size, 1, CV_32F);	
 }
 
@@ -165,25 +164,30 @@ void CSeedlingStatus::set_x_centers(std::vector<double>&cx)
 		m_idx_up.push_back(idx_up);
 	}
 	m_history_status = cv::Mat::zeros(m_max_size, m_center_x.size(), CV_8U);
+	m_history_histogram = cv::Mat::zeros(m_max_size, m_center_x.size(), CV_32F);
 
 }
 void CSeedlingStatus::append_hist(
-	std::vector<int>&xhist,		//input
-	std::vector<bool>&xstatus	//output
+	std::vector<int>&xhist		//input
+	//std::vector<bool>&xstatus	//output
 )
 {
 	assert(xhist.size() == m_hist_length);
+
+	std::vector<float>center_count;
+	calculate_center_size(xhist, center_count);
+	assert(center_count.size() == m_center_x.size());
+
 	m_global_cursor++;
 	if (m_record_cursor < m_max_size-1) {
 		m_record_cursor++;
 		float pc_size = 0.0;
-		for (int i = 0; i < xhist.size(); ++i) {
-			if (i >= m_hist_length) { continue; }
-			m_history_histogram.at<float>(m_record_cursor,i) = xhist.at(i);
-			pc_size += xhist.at(i);
+		for (int i = 0; i < center_count.size(); ++i) {			
+			m_history_histogram.at<float>(m_record_cursor,i) = center_count.at(i);
+			pc_size += center_count.at(i);
 		}
 		m_history_point_size.at<float>(m_record_cursor, 0) = pc_size;
-		get_status(xstatus);		
+		//get_status(xstatus);		
 	}
 	else {
 		//数据上移一行,数据放在最后一行
@@ -194,238 +198,424 @@ void CSeedlingStatus::append_hist(
 		}
 		for (int row = 0; row < m_history_point_size.rows - 1; ++row) {
 			m_history_point_size.at<float>(row, 0) = m_history_point_size.at<float>(row+1, 0);
-		}
-		/*memcpy_s(m_history_histogram.data,
-			m_history_histogram.step[0] * (m_max_size - 1),
-			m_history_histogram.data + m_history_histogram.step[0], 
-			m_history_histogram.step[0] * (m_max_size - 1));*/
-
-		/*memcpy_s(m_history_point_size.data,
-			m_history_point_size.step[0] * (m_max_size - 1),
-			m_history_point_size.data + m_history_point_size.step[0],
-			m_history_point_size.step[0] * (m_max_size - 1));*/
+		}		
 
 		float pc_size = 0.0;
-		for (int i = 0; i < xhist.size(); ++i) {
-			m_history_histogram.at<float>(m_history_histogram.rows - 1, i) = float(xhist.at(i));
-			pc_size += xhist.at(i);
+		for (int i = 0; i < center_count.size(); ++i) {
+			m_history_histogram.at<float>(m_history_histogram.rows - 1, i) = center_count.at(i);
+			pc_size += center_count.at(i);
 		}
 		m_history_point_size.at<float>(m_history_point_size.rows - 1, 0) = pc_size;
-		get_status(xstatus);
+		//get_status(xstatus);
 	}
 	
 
 }
-void CSeedlingStatus::get_status(std::vector<bool>&xstatus)
+void CSeedlingStatus::calculate_center_size(std::vector<int>&xhist, std::vector<float>&x_count)
 {
-	xstatus.clear();
-	xstatus.assign(m_center_x.size(), false);		
-	
-	//1 如果没有记录输入,返回没有苗
-	if (m_record_cursor < 0) { return; }
-
-	//2 如果是第一次,没有参考,用自身的分布阈值判断是否有苗(可能不准确,但没有其他办法)
-	//点云分布情况分析
-	// 每个bin的数量阈值设定m_bin_step宽度,至少有10毫米的点云,才认为有苗
-	float th_hist = m_bin_step * 10 / m_pc_mean_dist / m_pc_mean_dist; 
-	if (m_record_cursor == 0) {
-		std::vector<bool> hist_status;		
-		hist_status.assign(m_hist_length, false);
-		for (int i = 0; i < m_hist_length; ++i) {
-			if (m_history_histogram.at<float>(m_record_cursor, i) > th_hist) {
-				hist_status.at(i) = true;
-			}
-		}
-		for (int i = 0; i < m_center_x.size(); ++i) {
-			int idx_low = m_idx_low.at(i);
-			int idx_up = m_idx_up.at(i);
-			int  valid_bin_cnt = 0;
-			for (int k = idx_low; k < idx_up; ++k) {
-				if (hist_status.at(k)) { valid_bin_cnt++; }
-			}
-			double valid_ratio = (double)valid_bin_cnt / (double)(idx_up - idx_low);
-			xstatus.at(i) = valid_ratio > 0.5;			
-		}
-		//update m_history_status
-		for (int i = 0; i < m_history_status.cols; ++i) {
-			m_history_status.at<unsigned char>(m_record_cursor, i) = xstatus.at(i);
+	x_count.clear();
+	for (int i = 0; i < m_center_x.size(); ++i) {
+		int idx_low = m_idx_low.at(i);
+		int idx_up = m_idx_up.at(i);
+		float  valid_pc_cnt = 0;
+		for (int k = idx_low; k <= idx_up; ++k) {
+			if (k < 0 || k >= xhist.size()) { continue; }
+			valid_pc_cnt += xhist.at(k);
 		}
-		return;
+		x_count.push_back(valid_pc_cnt);
 	}
+}
+//void CSeedlingStatus::calculate_center_size(int cursor, std::vector<float>&x_count)
+//{
+//	x_count.clear();
+//	for (int i = 0; i < m_center_x.size(); ++i) {
+//		int idx_low = m_idx_low.at(i);
+//		int idx_up = m_idx_up.at(i);
+//		float  valid_pc_cnt = 0;
+//		for (int k = idx_low; k <= idx_up; ++k) {
+//			valid_pc_cnt += m_history_histogram.at<float>(cursor, i);
+//		}
+//		x_count.push_back(valid_pc_cnt);		
+//	}
+//}
+//void CSeedlingStatus::get_status(std::vector<bool>&xstatus)
+//{
+//	//根据历史信息评估:
+//	// 1)位置上是否有苗
+//	// 2)历史上是否已经取走苗
+//	// 3)是否有新进的苗
+//
+//	xstatus.clear();
+//	xstatus.assign(m_center_x.size(), false);		
+//	
+//	//1 如果没有记录输入,返回没有苗
+//	if (m_record_cursor < 0) { return; }
+//
+//	//2 如果是第一次,没有参考,用自身的分布阈值判断是否有苗(可能不准确,但没有其他办法)
+//	//点云分布情况分析
+//	// 每个bin的数量阈值设定m_bin_step宽度,至少有10毫米的点云,才认为有苗
+//	float th_hist = m_bin_step * 10 / m_pc_mean_dist / m_pc_mean_dist; 
+//	if (m_record_cursor == 0) {
+//		//std::vector<bool> hist_status;		
+//		//hist_status.assign(m_hist_length, false);
+//		//for (int i = 0; i < m_hist_length; ++i) {
+//		//	if (m_history_histogram.at<float>(m_record_cursor, i) > th_hist) {
+//		//		hist_status.at(i) = true;
+//		//	}
+//		//}
+//		//for (int i = 0; i < m_center_x.size(); ++i) {
+//		//	int idx_low = m_idx_low.at(i);
+//		//	int idx_up = m_idx_up.at(i);
+//		//	int  valid_bin_cnt = 0;
+//		//	for (int k = idx_low; k < idx_up; ++k) {
+//		//		if (hist_status.at(k)) { valid_bin_cnt++; }
+//		//	}
+//		//	double valid_ratio = (double)valid_bin_cnt / (double)(idx_up - idx_low);
+//		//	xstatus.at(i) = valid_ratio > 0.5;			
+//		//}
+//		////update m_history_status
+//		//for (int i = 0; i < m_history_status.cols; ++i) {
+//		//	m_history_status.at<unsigned char>(m_record_cursor, i) = xstatus.at(i);
+//		//}
+//		//return;
+//		
+//		std::vector<float> x_count;
+//		calculate_center_size(m_record_cursor, x_count);
+//		for (int i = 0; i < x_count.size(); ++i) {
+//			int idx_low = m_idx_low.at(i);
+//			int idx_up = m_idx_up.at(i);			
+//			xstatus.at(i) = x_count.at(i) > (idx_up - idx_low + 1) * th_hist;
+//		}
+//		//update m_history_status
+//		for (int i = 0; i < m_history_status.cols; ++i) {
+//			m_history_status.at<unsigned char>(m_record_cursor, i) = xstatus.at(i);
+//		}
+//		return;
+//	}
+//
+//	//3 2次或更多,通过前后2次差分析苗取走的情况
+//	//3.1 计算被取走的点云位置分布
+//	std::vector<float>hist_diff;
+//	hist_diff.assign(m_hist_length, 0.0);
+//	float sum_dn = 0.0;
+//	float sum_n = 0.0;
+//	float diff_cnt = 0.0;
+//	for (int i = 0; i < m_hist_length; ++i) {
+//		diff_cnt = m_history_histogram.at<float>(m_record_cursor - 1, i) -
+//			m_history_histogram.at<float>(m_record_cursor, i);
+//		hist_diff.at(i) = diff_cnt;
+//		sum_n += diff_cnt;
+//		sum_dn += diff_cnt * i;
+//	}
+//	/*if (m_record_cursor < m_max_size) {
+//		for (int i = 0; i < m_hist_length; ++i) {
+//			diff_cnt = m_history_histogram.at<float>(m_record_cursor - 1, i) - 
+//				m_history_histogram.at<float>(m_record_cursor, i);
+//			hist_diff.at(i) =  diff_cnt;
+//			sum_n += diff_cnt;
+//			sum_dn += diff_cnt * i;
+//		}
+//	}
+//	else {
+//		for (int i = 0; i < m_hist_length; ++i) {
+//			diff_cnt = m_history_histogram.at<float>(m_max_size - 2, i) -
+//				m_history_histogram.at<float>(m_max_size - 1, i);
+//			hist_diff.at(i) = diff_cnt;
+//			sum_n += diff_cnt;
+//			sum_dn += diff_cnt * i;
+//		}
+//
+//	}*/
+//	
+//	//3.2 统计增减点云的状态,区分点云增加,点云减小,点云没变化的部分
+//	std::vector<int> hist_status_2d;	//3种状态记录: -1取走,0没变化,1上苗
+//	hist_status_2d.assign(m_hist_length, 0);
+//	int add_cnt = 0;
+//	int sub_cnt = 0;
+//	for (int i = 0; i < m_hist_length; ++i) {
+//		if (hist_diff.at(i) > th_hist) {
+//			hist_status_2d.at(i) = -1;
+//			sub_cnt += 1;
+//		}
+//		if (hist_diff.at(i) < -th_hist) {
+//			hist_status_2d.at(i) = 1;
+//			add_cnt += 1;
+//		}
+//	}
+//
+//	//3.3 判断苗的整体情况
+//	double seedling_distance = m_center_x.at(1) - m_center_x.at(0); //株间距离
+//	double grid_one_seedling = seedling_distance / m_bin_step;		//每穴位占histogram的桶数
+//	//3.3.1进一排苗
+//	if (add_cnt > grid_one_seedling*3.0) {		
+//		xstatus.assign(m_center_x.size(), true);
+//		//update m_history_status
+//		if (m_record_cursor == m_global_cursor) {
+//			if (m_record_cursor < m_max_size) {
+//				for (int i = 0; i < m_history_status.cols; ++i) {
+//					m_history_status.at<unsigned char>(m_record_cursor, i) = xstatus.at(i);
+//				}
+//			}
+//			else {
+//				
+//			}
+//		}
+//		else {
+//			//数据上移一行,数据放在最后一行
+//			for (int row = 0; row < m_history_status.rows - 1; ++row) {
+//				for (int col = 0; col < m_history_status.cols; ++col) {
+//					m_history_status.at<float>(row, col) = m_history_status.at<float>(row + 1, col);
+//				}
+//			}
+//			/*memcpy_s(m_history_status.data,
+//				m_history_status.step[0] * (m_max_size - 1),
+//				m_history_status.data + m_history_status.step[0],
+//				m_history_status.step[0] * (m_max_size - 1));
+//			*/
+//			for (int i = 0; i < m_history_status.cols; ++i) {
+//				m_history_status.at<unsigned char>(m_max_size-1, i) = xstatus.at(i);
+//			}
+//		}		
+//		return;
+//	}
+//
+//	std::vector<size_t>sorted_idx;
+//	std::vector<float>sub_seedling_score;	//移出植株得分,记录每个穴位上点云变化得分
+//	//3.3.2 变化很小,说明没有改变(没能成功抓走)
+//	if (add_cnt + sub_cnt < 0.5 * grid_one_seedling) {
+//		goto no_change;
+//	}
+//	//3.3.3 否则的话,就是抓走过一个苗
+//	//找到被取走的苗的中心,然后根据dtype确定有苗的位置
+//	//找覆盖范围最大的区域
+//	double sub_cent_indx = sum_dn / sum_n;	//计算改变范围的中心,目前没用到	
+//	sub_seedling_score.assign(m_center_x.size(), 0.0);
+//	for (int idx = 0; idx < hist_status_2d.size(); ++idx) {
+//		if (hist_status_2d.at(idx) >= 0) {
+//			//这个histogram上没有移出,不统计, hist_status_2d的值域:-1取走,0没变化,1上苗
+//			continue; 
+//		}
+//		for (int i = 0; i < m_center_x.size(); ++i) {
+//			int idx_low = m_idx_low.at(i);
+//			int idx_up = m_idx_up.at(i);
+//			if (idx >= idx_low && idx < idx_up) {
+//				sub_seedling_score.at(i) += 1.0;
+//			}
+//		}
+//	}
+//	int sub_pos = -1;
+//	sorted_idx = sort_indexes_e(sub_seedling_score, false);
+//	for (auto& idx : sorted_idx) {
+//		if (sub_seedling_score.at(idx) < 0.25 * grid_one_seedling) {
+//			//如果改变量,不到穴位范围的一半,不认为是移走的
+//			continue; 
+//		}
+//		if (m_history_status.at<unsigned char>(m_record_cursor - 1, idx) == 0) { 
+//			//如果这个位置上一帧就没有苗,那判别也是错误的
+//			continue; 
+//		}
+//		sub_pos = idx;
+//		break;//找到得分最高,并且满足条件的位置,就是被抓走的位置,跳出
+//	}
+//	if (sub_pos >= 0) {
+//		xstatus.assign(m_center_x.size(), false);
+//		int cursor = m_record_cursor-1;		
+//		for (int i = 0; i < m_history_status.cols; ++i) {
+//			if (m_history_status.at<unsigned char>(cursor, i) == 1) {
+//				xstatus.at(i) = true;					
+//			}
+//		}
+//		xstatus.at(sub_pos) = false;
+//		
+//		//update m_history_status
+//		if (m_record_cursor == m_global_cursor) {
+//			for (int i = 0; i < m_history_status.cols; ++i) {
+//				m_history_status.at<unsigned char>(m_record_cursor, i) = xstatus.at(i);
+//			}
+//		}
+//		else{
+//			//数据上移一行,数据放在最后一行
+//			for (int row = 0; row < m_history_status.rows - 1; ++row) {
+//				for (int col = 0; col < m_history_status.cols; ++col) {
+//					m_history_status.at<float>(row, col) = m_history_status.at<float>(row + 1, col);
+//				}
+//			}
+//			/*memcpy_s(m_history_status.data,
+//				m_history_status.step[0] * (m_max_size - 1),
+//				m_history_status.data + m_history_status.step[0],
+//				m_history_status.step[0] * (m_max_size - 1));*/
+//			for (int i = 0; i < m_history_status.cols; ++i) {
+//				m_history_status.at<unsigned char>(m_max_size - 1, i) = xstatus.at(i);
+//			}
+//		}		
+//		return;
+//	}
+//	else {
+//		//如果没有找到有效位置,按没有变化处理
+//		goto no_change;
+//	}
+//
+//no_change:
+//	//没有改变,用上一次的结果
+//	xstatus.assign(m_center_x.size(), true);
+//	//update m_history_status
+//	if (m_record_cursor == m_global_cursor) {
+//		for (int i = 0; i < m_history_status.cols; ++i) {
+//			m_history_status.at<unsigned char>(m_record_cursor, i) = 
+//				m_history_status.at<unsigned char>(m_record_cursor - 1, i);
+//			if (m_history_status.at<unsigned char>(m_record_cursor - 1, i) == 0) {
+//				xstatus.at(i) = false;
+//			}
+//		}
+//	}
+//	else{
+//		//数据上移一行,数据放在最后一行
+//		for (int row = 0; row < m_history_status.rows - 1; ++row) {
+//			for (int col = 0; col < m_history_status.cols; ++col) {
+//				m_history_status.at<float>(row, col) = m_history_status.at<float>(row + 1, col);
+//			}
+//		}
+//		/*memcpy_s(m_history_status.data,
+//			m_history_status.step[0] * (m_max_size - 1),
+//			m_history_status.data + m_history_status.step[0],
+//			m_history_status.step[0] * (m_max_size - 1));*/
+//		for (int i = 0; i < m_history_status.cols; ++i) {
+//			m_history_status.at<unsigned char>(m_max_size - 1, i) = 
+//				m_history_status.at<unsigned char>(m_max_size - 2, i);
+//			if (m_history_status.at<unsigned char>(m_max_size - 2, i) == 0) {
+//				xstatus.at(i) = false;
+//			}
+//		}
+//	}
+//}
 
-	//3 2次或更多,通过前后2次差分析苗取走的情况
-	//3.1 计算被取走的点云位置分布
-	std::vector<float>hist_diff;
-	hist_diff.assign(m_hist_length, 0.0);
-	float sum_dn = 0.0;
-	float sum_n = 0.0;
-	float diff_cnt = 0.0;
-	for (int i = 0; i < m_hist_length; ++i) {
-		diff_cnt = m_history_histogram.at<float>(m_record_cursor - 1, i) -
-			m_history_histogram.at<float>(m_record_cursor, i);
-		hist_diff.at(i) = diff_cnt;
-		sum_n += diff_cnt;
-		sum_dn += diff_cnt * i;
+int CSeedlingStatus::get_status(int& max_idx, float&max_change)
+{
+	//根据历史信息评估:
+	// 1)评估是否新上苗
+	// 2)判断被取走的那个苗	
+
+	//状态 1 ---此次为新上苗, 0 无显著变化, -1 上一次取苗成功
+	int status = 1; 	
+
+	//1 如果没有记录输入,返回没有苗
+	if (m_record_cursor <= 0) { 
+		return status; 
 	}
-	/*if (m_record_cursor < m_max_size) {
-		for (int i = 0; i < m_hist_length; ++i) {
-			diff_cnt = m_history_histogram.at<float>(m_record_cursor - 1, i) - 
-				m_history_histogram.at<float>(m_record_cursor, i);
-			hist_diff.at(i) =  diff_cnt;
-			sum_n += diff_cnt;
-			sum_dn += diff_cnt * i;
-		}
+
+	float change_threshold = 500.0;//点云变化,是否有苗新加或取走的阈值
+	float change_count = m_history_point_size.at<float>(m_record_cursor, 0) - m_history_point_size.at<float>(m_record_cursor - 1, 0);
+	if (change_count > 2.0 * change_threshold) {
+		status = 1;		
 	}
 	else {
-		for (int i = 0; i < m_hist_length; ++i) {
-			diff_cnt = m_history_histogram.at<float>(m_max_size - 2, i) -
-				m_history_histogram.at<float>(m_max_size - 1, i);
-			hist_diff.at(i) = diff_cnt;
-			sum_n += diff_cnt;
-			sum_dn += diff_cnt * i;
+		if (change_count < -change_threshold) {
+			status = -1;
 		}
-
-	}*/
-	
-	//3.2 统计增减点云的状态,区分点云增加,点云减小,点云没变化的部分
-	std::vector<int> hist_status_2d;	//3种状态记录: -1取走,0没变化,1上苗
-	hist_status_2d.assign(m_hist_length, 0);
-	int add_cnt = 0;
-	int sub_cnt = 0;
-	for (int i = 0; i < m_hist_length; ++i) {
-		if (hist_diff.at(i) > th_hist) {
-			hist_status_2d.at(i) = -1;
-			sub_cnt += 1;
+		else {
+			status = 0;
 		}
-		if (hist_diff.at(i) < -th_hist) {
-			hist_status_2d.at(i) = 1;
-			add_cnt += 1;
+	}
+	max_change = 0;
+	max_idx = 0;
+	for (int i = 0; i < m_history_histogram.cols; ++i) {
+		float d = m_history_histogram.at<float>(m_record_cursor, i) - m_history_histogram.at<float>(m_record_cursor - 1, i);
+		if (fabs(d) > fabs(max_change)) {
+			max_change = d;
+			max_idx = i;
 		}
 	}
+	if (max_change < -change_threshold) {
+		status = -1;
+	}
+	return status;
+}
 
-	//3.3 判断苗的整体情况
-	double seedling_distance = m_center_x.at(1) - m_center_x.at(0); //株间距离
-	double grid_one_seedling = seedling_distance / m_bin_step;		//每穴位占histogram的桶数
-	//3.3.1进一排苗
-	if (add_cnt > grid_one_seedling*3.0) {		
-		xstatus.assign(m_center_x.size(), true);
-		//update m_history_status
-		if (m_record_cursor == m_global_cursor) {
-			if (m_record_cursor < m_max_size) {
-				for (int i = 0; i < m_history_status.cols; ++i) {
-					m_history_status.at<unsigned char>(m_record_cursor, i) = xstatus.at(i);
-				}
-			}
-			else {
-				
+
+void CSeedlingStatus::real_result_update(
+	std::vector<pcl::PointXYZ>& root
+)
+{	
+	m_stem_status.clear();
+	m_stem_status.assign(m_center_x.size(), 0);
+	
+	for (auto& p : root) {
+		int mini = -1;
+		float mind = 1000.0;
+		for (int i = 0; i < m_center_x.size();++i) {
+			float d = fabs(m_center_x.at(i) - p.x);
+			if (d < mind) {
+				mind = d;
+				mini = i;
 			}
 		}
-		else {
-			//数据上移一行,数据放在最后一行
-			for (int row = 0; row < m_history_status.rows - 1; ++row) {
-				for (int col = 0; col < m_history_status.cols; ++col) {
-					m_history_status.at<float>(row, col) = m_history_status.at<float>(row + 1, col);
-				}
-			}
-			/*memcpy_s(m_history_status.data,
-				m_history_status.step[0] * (m_max_size - 1),
-				m_history_status.data + m_history_status.step[0],
-				m_history_status.step[0] * (m_max_size - 1));
-			*/
-			for (int i = 0; i < m_history_status.cols; ++i) {
-				m_history_status.at<unsigned char>(m_max_size-1, i) = xstatus.at(i);
-			}
-		}		
-		return;
+		if (mini >= 0) {
+			m_stem_status.at(mini) = 1;
+		}
 	}
+}
 
-	std::vector<size_t>sorted_idx;
-	std::vector<float>sub_seedling_score;	//移出植株得分,记录每个穴位上点云变化得分
-	//3.3.2 变化很小,说明没有改变(没能成功抓走)
-	if (add_cnt + sub_cnt < 0.5 * grid_one_seedling) {
-		goto no_change;
-	}
-	//3.3.3 否则的话,就是抓走过一个苗
-	//找到被取走的苗的中心,然后根据dtype确定有苗的位置
-	//找覆盖范围最大的区域
-	double sub_cent_indx = sum_dn / sum_n;	//计算改变范围的中心,目前没用到	
-	sub_seedling_score.assign(m_center_x.size(), 0.0);
-	for (int idx = 0; idx < hist_status_2d.size(); ++idx) {
-		if (hist_status_2d.at(idx) >= 0) {
-			//这个histogram上没有移出,不统计, hist_status_2d的值域:-1取走,0没变化,1上苗
-			continue; 
-		}
-		for (int i = 0; i < m_center_x.size(); ++i) {
-			int idx_low = m_idx_low.at(i);
-			int idx_up = m_idx_up.at(i);
-			if (idx >= idx_low && idx < idx_up) {
-				sub_seedling_score.at(i) += 1.0;
-			}
+void CSeedlingStatus::occlusion_result_update(
+	std::vector<int>& leaf_occlusion
+)
+{
+	assert(m_center_x.size() == leaf_occlusion.size());
+	assert(m_center_x.size() == m_stem_status.size());
+	for (int i = 0; i < m_center_x.size(); ++i) {		
+		if (m_stem_status.at(i)==0 && leaf_occlusion.at(i)>0) {
+			m_stem_status.at(i) = 2;			
 		}
 	}
-	int sub_pos = -1;
-	sorted_idx = sort_indexes_e(sub_seedling_score, false);
-	for (auto& idx : sorted_idx) {
-		if (sub_seedling_score.at(idx) < 0.25 * grid_one_seedling) {
-			//如果改变量,不到穴位范围的一半,不认为是移走的
-			continue; 
-		}
-		if (m_history_status.at<unsigned char>(m_record_cursor - 1, idx) == 0) { 
-			//如果这个位置上一帧就没有苗,那判别也是错误的
-			continue; 
-		}
-		sub_pos = idx;
-		break;//找到得分最高,并且满足条件的位置,就是被抓走的位置,跳出
+	
+}
+//获取植株的状态,是否有苗
+// 在real_result_update()和occlusion_result_update()调用后
+// 调用此函数,获取茎的状态
+void CSeedlingStatus::get_stem_status(
+	std::vector<int>&stem_status
+)
+{
+	//状态 1 ---此次为新上苗, 0 无显著变化, -1 上一次取苗成功
+	float max_change_points_count;
+	int max_change_center_idx;
+	int status = get_status(max_change_center_idx, max_change_points_count);
+	if (status > 0) {
+		m_center_grabed_record.clear();
+		m_center_grabed_record.assign(m_center_x.size(), -1);
 	}
-	if (sub_pos >= 0) {
-		xstatus.assign(m_center_x.size(), false);
-		int cursor = m_record_cursor-1;		
-		for (int i = 0; i < m_history_status.cols; ++i) {
-			if (m_history_status.at<unsigned char>(cursor, i) == 1) {
-				xstatus.at(i) = true;					
-			}
-		}
-		xstatus.at(sub_pos) = false;
-		
-		//update m_history_status
-		if (m_record_cursor == m_global_cursor) {
-			for (int i = 0; i < m_history_status.cols; ++i) {
-				m_history_status.at<unsigned char>(m_record_cursor, i) = xstatus.at(i);
-			}
-		}
-		else{
-			//数据上移一行,数据放在最后一行
-			for (int row = 0; row < m_history_status.rows - 1; ++row) {
-				for (int col = 0; col < m_history_status.cols; ++col) {
-					m_history_status.at<float>(row, col) = m_history_status.at<float>(row + 1, col);
+
+	stem_status.clear();
+	if (status >= 0) {
+		//用当前识别的结果		
+	}
+	else {
+		m_center_grabed_record.at(max_change_center_idx) = 0;
+
+		//如果max_change_center_idx位置的苗上一次有苗,本次为2,那么久将2改成1
+		if (max_change_points_count < 0) {
+			if (m_history_status.at<unsigned char>(m_record_cursor - 1, max_change_center_idx) != 0) {
+				if (m_stem_status.at(max_change_center_idx) == 2) {
+					m_stem_status.at(max_change_center_idx) = 0;
 				}
 			}
-			/*memcpy_s(m_history_status.data,
-				m_history_status.step[0] * (m_max_size - 1),
-				m_history_status.data + m_history_status.step[0],
-				m_history_status.step[0] * (m_max_size - 1));*/
-			for (int i = 0; i < m_history_status.cols; ++i) {
-				m_history_status.at<unsigned char>(m_max_size - 1, i) = xstatus.at(i);
-			}
-		}		
-		return;
+		}
 	}
-	else {
-		//如果没有找到有效位置,按没有变化处理
-		goto no_change;
+
+	//更新根据: m_center_grabed_record更新m_stem_status中的叶子遮挡
+	for (int i = 0; i < m_stem_status.size(); ++i) {
+		if (m_center_grabed_record.at(i) == 0 && m_stem_status.at(i) == 2) {
+			m_stem_status.at(i) = 0;
+		}		
 	}
 
-no_change:
-	//没有改变,用上一次的结果
-	xstatus.assign(m_center_x.size(), true);
-	//update m_history_status
+	for (int i = 0; i < m_stem_status.size(); ++i) {
+		stem_status.push_back(m_stem_status.at(i));
+	}
+	
+	//更新m_history_status
 	if (m_record_cursor == m_global_cursor) {
 		for (int i = 0; i < m_history_status.cols; ++i) {
-			m_history_status.at<unsigned char>(m_record_cursor, i) = 
-				m_history_status.at<unsigned char>(m_record_cursor - 1, i);
-			if (m_history_status.at<unsigned char>(m_record_cursor - 1, i) == 0) {
-				xstatus.at(i) = false;
-			}
+			m_history_status.at<unsigned char>(m_record_cursor, i) = m_stem_status.at(i);
 		}
 	}
 	else{
@@ -434,19 +624,12 @@ no_change:
 			for (int col = 0; col < m_history_status.cols; ++col) {
 				m_history_status.at<float>(row, col) = m_history_status.at<float>(row + 1, col);
 			}
-		}
-		/*memcpy_s(m_history_status.data,
-			m_history_status.step[0] * (m_max_size - 1),
-			m_history_status.data + m_history_status.step[0],
-			m_history_status.step[0] * (m_max_size - 1));*/
+		}		
 		for (int i = 0; i < m_history_status.cols; ++i) {
-			m_history_status.at<unsigned char>(m_max_size - 1, i) = 
-				m_history_status.at<unsigned char>(m_max_size - 2, i);
-			if (m_history_status.at<unsigned char>(m_max_size - 2, i) == 0) {
-				xstatus.at(i) = false;
-			}
+			m_history_status.at<unsigned char>(m_max_size - 1, i) = m_stem_status.at(i);
 		}
-	}
+	}	
 }
 
+
 }

+ 25 - 5
grab_occlusion.h

@@ -104,20 +104,33 @@ namespace graft_cv {
 			CGcvLogger*pLog = 0);
 		~CSeedlingStatus();
 		void set_x_centers(std::vector<double>&cx);
-		void append_hist(std::vector<int>&xhist,	//input
-			std::vector<bool>&xstatus);				//output
-		
+		void append_hist(std::vector<int>&xhist	//input
+			//std::vector<bool>&xstatus				//output
+		);
+
+		//实际茎检测的结果,更新到历史记录中
+		void real_result_update(
+			std::vector<pcl::PointXYZ>& root);
+		//实际叶遮挡检测的结果,更新到历史记录中
+		void occlusion_result_update(
+			std::vector<int>& leaf_occlusion
+		);
+		//获取植株的状态,是否有苗
+		void get_stem_status(
+			std::vector<int>&xstatus
+		); 
 	protected:
 		CGcvLogger * m_pLogger;
 		int m_max_size;	//最大记录长度
 		int m_record_cursor;
 		int m_global_cursor;
-		cv::Mat m_history_histogram;	//历史点云x方向分布直方图
+		cv::Mat m_history_histogram;	//历史点云每个穴位的点云数量
 		cv::Mat m_history_point_size;	//历史点云框内数量
 		cv::Mat m_history_status;		//历史穴位是有苗状态
 		std::vector<int> m_idx_low;		//穴位对应的序号低位
 		std::vector<int> m_idx_up;		//穴位对应的序号低位
 
+
 		int m_dtype;					//1-砧木, 0-穗苗
 		double m_xmin;					//x的下限
 		double m_xmax;					//x的上限
@@ -125,8 +138,15 @@ namespace graft_cv {
 		double m_pc_mean_dist;			//点云,两点间平均距离
 		std::vector<double>m_center_x;
 		int m_hist_length;
+		std::vector<int>m_center_grabed_record;//植株被取走的记录, -1--不确定, 0--已抓取
+
+		int get_status(int& max_idx, float&max_change);
+		void calculate_center_size(
+			std::vector<int>&xhist, //input
+			std::vector<float>&x_count//output
+		);
 
-		void get_status(std::vector<bool>&xstatus);
+		std::vector<int> m_stem_status;//0--没有,1--茎检测有结果, 2---遮挡检测有结果
 		
 		
 	};

+ 126 - 56
grab_point_rs.cpp

@@ -202,7 +202,7 @@ namespace graft_cv {
 		}		
 		return rst;
 	}
-
+	
 	int CRootStockGrabPoint::grab_point_detect(
 		PositionInfo& posinfo
 		)
@@ -336,9 +336,14 @@ namespace graft_cv {
 			m_pSeedlingStatus->set_x_centers(root_cxs);
 		}
 		
+		/*std::vector<int> center_counts;
+		std::vector<Eigen::Vector4f> centroids;
+		get_point_centriod(m_root_centers, center_counts, centroids);*/
+
 		std::vector<int>xhist_inbox;		
 		get_point_x_hist(xhist_inbox);			
-		m_pSeedlingStatus->append_hist(xhist_inbox, m_root_center_with_seedling_history);		
+		m_pSeedlingStatus->append_hist(xhist_inbox);	
+		
 		////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 		// 4 对截取的点云进行ror滤除大面积联通区域,剔除叶片
 		pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_dowm_sampled(new pcl::PointCloud<pcl::PointXYZ>);
@@ -359,14 +364,15 @@ namespace graft_cv {
 		////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 		//判断m_root_centers位置上是否有叶片遮挡
 		occluded_seedling_detect_by_leaf(cloud_ror, leaf_indices);
-		//用m_root_center_with_seedling_history对叶子遮挡的结果进行过滤
-		/*for (int j = 0; j < m_root_center_with_seedling.size(); ++j) {
-			if (m_root_center_with_seedling.at(j) && !m_root_center_with_seedling_history.at(j)) {
-				m_root_center_with_seedling.at(j) = false;
+		if (m_pLogger) {
+			stringstream buff;
+			buff << m_pcdId << ": leafoccl status: ";
+			for (auto&st : m_root_center_with_occlusion) {
+					buff <<st<< "\t";
 			}
-		}*/
-		m_root_center_with_seedling = m_root_center_with_seedling_history;
-		
+			m_pLogger->INFO(buff.str());
+		}
+
 
 		////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 		//5 seedling position,找到第一个位置上的植株
@@ -579,12 +585,12 @@ namespace graft_cv {
 		selected_idx = -1;
 		if (m_dtype == 0) {
 			for (int i = 0; i < m_root_center_with_seedling.size(); ++i) {
-				if (m_root_center_with_seedling[i]) { selected_idx = i; }
+				if (m_root_center_with_seedling[i]>0) { selected_idx = i; }
 			}
 		}
 		else {
 			for (int i = 0; i < m_root_center_with_seedling.size(); ++i) {
-				if (m_root_center_with_seedling[i]) {
+				if (m_root_center_with_seedling[i]>0) {
 					selected_idx = i;
 					break;
 				}
@@ -707,8 +713,8 @@ namespace graft_cv {
 	{	
 		//仅用叶子点云数量,有时会出现因叶子误识别(和ror_ratio相关),造成叶子点云数量小,造成误判
 		//所以改成整个空间点云数量做判别--2024-3-2
-		m_root_center_with_seedling.clear();
-		m_root_center_with_seedling.assign(m_root_centers.size(), false);
+		m_root_center_with_occlusion.clear();
+		m_root_center_with_occlusion.assign(m_root_centers.size(), 0);
 
 		m_root_center_leaf_cx.clear();
 		m_root_center_leaf_cx.assign(m_root_centers.size(), 1.0e6);
@@ -753,7 +759,10 @@ namespace graft_cv {
 			}
 			pcd_cnt.push_back(total_cnt);
 			bool has_seedling = total_cnt > th_pcd_size;
-			m_root_center_with_seedling.at(i) = has_seedling;
+			if (has_seedling) {
+				m_root_center_with_occlusion.at(i) = 2;
+			}
+			
 			aabb_mins_maxs.push_back(aabb_min);
 			aabb_mins_maxs.push_back(aabb_max);			
 		}
@@ -786,40 +795,72 @@ namespace graft_cv {
 		}		
 
 	}
-	//int CRootStockGrabPoint::get_point_count_inbox(const CStemResult& sr, 
-	//	pcl::PointXYZ& aabb_min,
-	//	pcl::PointXYZ& aabb_max)
-	//{
-	//	double seedling_distance = m_cparam.rs_grab_seedling_dist;
-	//	double min_y = m_cparam.rs_grab_ymin;
-	//	if (m_dtype == 0) {
-	//		seedling_distance = m_cparam.sc_grab_seedling_dist;
-	//		min_y = m_cparam.sc_grab_ymin;
-	//	}
-	//	double min_x = sr.root_x - 0.5 * seedling_distance;
-	//	double max_x = sr.root_x + 0.5 * seedling_distance;
-	//	double min_z = sr.root_z - 0.75 * seedling_distance;
-	//	double max_z = sr.root_z + 0.25 * seedling_distance;
-	//	double max_y = min_y + 150.0; //假定苗高150mm
-
-	//	int count = 0;
-	//	for (auto&pt : m_raw_cloud->points) {
-	//		if(pt.y >= min_y && pt.y <= max_y &&
-	//			pt.x >=min_x && pt.x<=max_x &&
-	//			pt.z >= min_z && pt.z <= max_z)
-	//		{ 
-	//			count++;
-	//		}
-	//	}
-	//	aabb_min.x = min_x;
-	//	aabb_min.y = min_y;
-	//	aabb_min.z = min_z;
-	//	aabb_max.x = max_x;
-	//	aabb_max.y = max_y;
-	//	aabb_max.z = max_z;
-
-	//	return count;
-	//}
+	int CRootStockGrabPoint::get_point_count_inbox(const CStemResult& sr, 
+		pcl::PointXYZ& aabb_min,
+		pcl::PointXYZ& aabb_max,
+		Eigen::Vector4f& centroid)
+	{
+		double seedling_distance = m_cparam.rs_grab_seedling_dist;
+		double min_y = m_cparam.rs_grab_ymin;
+		if (m_dtype == 0) {
+			seedling_distance = m_cparam.sc_grab_seedling_dist;
+			min_y = m_cparam.sc_grab_ymin;
+		}
+		double min_x = sr.root_x - 0.5 * seedling_distance;
+		double max_x = sr.root_x + 0.5 * seedling_distance;
+		double min_z = sr.root_z - 1.5 * seedling_distance;
+		double max_z = sr.root_z + 0.5 * seedling_distance;
+		double max_y = min_y + 150.0; //假定苗高150mm
+
+		pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_inbox(new pcl::PointCloud<pcl::PointXYZ>);
+		pcl::CropBox<pcl::PointXYZ> box_filter;
+		
+		box_filter.setMin(Eigen::Vector4f(min_x, min_y, min_z, 1));
+		box_filter.setMax(Eigen::Vector4f(max_x, max_y, max_z, 1));
+		
+		box_filter.setNegative(false);
+		box_filter.setInputCloud(m_raw_cloud);
+		box_filter.filter(*cloud_inbox);
+
+		int count = cloud_inbox->points.size();
+		if (count > 0) {
+			pcl::compute3DCentroid(*cloud_inbox, centroid);
+		}
+		else {
+			centroid(0) = 0.5 * (min_x + max_x);
+			centroid(1) = min_y;
+			centroid(2) = 0.5 * (min_z + max_z);
+			centroid(3) = 1.0;
+		}
+		
+		aabb_min.x = min_x;
+		aabb_min.y = min_y;
+		aabb_min.z = min_z;
+		aabb_max.x = max_x;
+		aabb_max.y = max_y;
+		aabb_max.z = max_z;
+
+		return count;
+	}
+	// 统计inbox点云数量和重心
+	void CRootStockGrabPoint::get_point_centriod(
+		std::vector<CStemResult>& root_centers,	//input
+		std::vector<int>&counts,	//output
+		std::vector<Eigen::Vector4f>& centroids	//output
+	)
+	{
+		counts.clear();
+		centroids.clear();
+		for (auto& sr : root_centers) {
+			pcl::PointXYZ aabb_min, aabb_max;
+			Eigen::Vector4f centroid;
+			int cnt = get_point_count_inbox(sr, aabb_min, aabb_max, centroid);
+			counts.push_back(cnt);
+			centroids.push_back(centroid);
+		}
+
+	}
+
 
 	//统计inbox点云在x方向的分布情况
 	void CRootStockGrabPoint::get_point_x_hist(
@@ -1432,6 +1473,22 @@ void CRootStockGrabPoint::line_filter(
 		pcl::PointCloud<pcl::PointXYZ>::Ptr stem_cloud(new pcl::PointCloud<pcl::PointXYZ>);
 		pcl::copyPointCloud(*seedling_inbox, *inliers, *stem_cloud);
 
+		//稳定性检测:通过扩大直径检测范围,得到直线上的点应基本保持一致,如果不一致,就是茎不显著,可能是叶子内找到的
+		pcl::ModelCoefficients::Ptr coefficients_ex(new pcl::ModelCoefficients);
+		pcl::PointIndices::Ptr inliers_ex(new pcl::PointIndices);
+		seg.setDistanceThreshold(1.5*stem_radius);		
+		seg.segment(*inliers_ex, *coefficients_ex);
+		float ratio_ex = inliers_ex->indices.size() / (float)(inliers->indices.size());
+		float rex_th = 1.5;
+		if (m_pLogger) {
+			stringstream buff;
+			buff << m_pcdId << ": stem externed line points ext_ratio= " << ratio_ex << ", threshold is "<< rex_th;
+			m_pLogger->INFO(buff.str());
+		}
+		if (ratio_ex > rex_th) {
+			continue;
+		}
+
 		//点数过滤
 		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; }
@@ -1451,7 +1508,7 @@ void CRootStockGrabPoint::line_filter(
 			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; }
+		if (a < 60.0) { continue; }
 		target_filtered.push_back(p);
 
 		//有效茎长计算
@@ -1597,7 +1654,7 @@ void CRootStockGrabPoint::line_filter(
 		if (m_dtype == 0) {
 			hole_step = m_cparam.sc_grab_seedling_dist - 5.0;
 		}
-		float hole_step_radius = hole_step / 2.0;
+		float hole_step_radius = 2.0 * hole_step / 3.0;
 
 		// 点云降维到xz平面,y=0
 		pcl::PointCloud<pcl::PointXYZ>::Ptr cloud2d(new pcl::PointCloud < pcl::PointXYZ>);
@@ -1732,10 +1789,10 @@ void CRootStockGrabPoint::line_filter(
 			clt_box_v.push_back(max_point_aabb_ex);
 		}
 		// 显示			
-		if (m_cparam.image_show) {
+		/*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);
 		
@@ -1772,6 +1829,19 @@ void CRootStockGrabPoint::line_filter(
 			target_member,
 			target_filtered_models);
 
+		//根据实际得到的茎的位置,更新当前茎状态
+		m_pSeedlingStatus->real_result_update(target_filtered_root);
+		m_pSeedlingStatus->occlusion_result_update(m_root_center_with_occlusion);
+		m_pSeedlingStatus->get_stem_status(m_root_center_with_seedling);
+		if (m_pLogger) {
+			stringstream buff;
+			buff << m_pcdId << ": stem status: ";
+			for (auto&st : m_root_center_with_seedling) {
+				buff << st << "\t";
+			}
+			m_pLogger->INFO(buff.str());
+		}
+
 		if (target_filtered_root.size() == 0) {
 			if (m_pLogger) {
 				stringstream buff;
@@ -1781,7 +1851,7 @@ void CRootStockGrabPoint::line_filter(
 			//统计苗数量
 			first_row_size = 0;
 			for (auto&has_seedling : m_root_center_with_seedling) {
-				if (has_seedling) { first_row_size += 1; }
+				if (has_seedling>0) { first_row_size += 1; }
 			}
 			return false;
 		}
@@ -1885,7 +1955,7 @@ void CRootStockGrabPoint::line_filter(
 			//统计苗数量
 			first_row_size = 0;
 			for (auto&has_seedling : m_root_center_with_seedling) {
-				if (has_seedling) { first_row_size += 1; }
+				if (has_seedling>0) { first_row_size += 1; }
 			}
 			return false;
 		}
@@ -1908,13 +1978,13 @@ void CRootStockGrabPoint::line_filter(
 				}
 			}
 			if (min_i >= 0) {
-				m_root_center_with_seedling.at(min_i) = true;
+				m_root_center_with_seedling.at(min_i) = 1;
 			}
 		}
 		//统计苗数量
 		first_row_size = 0;
 		for (auto&has_seedling : m_root_center_with_seedling) {
-			if (has_seedling) { first_row_size += 1; }
+			if (has_seedling>0) { first_row_size += 1; }
 		}
 
 		

+ 14 - 2
grab_point_rs.h

@@ -32,8 +32,8 @@ namespace graft_cv {
 		std::vector<CStemResult> m_root_centers;  //通过m_pStemInfos获取到历史根中心位置
 		std::vector<int> m_root_center_pcd_size;  //穴位上整体点云数量
 		std::vector<double> m_root_center_leaf_cx;//穴位上,如有叶子,叶子中心,或按整体点云中心,默认1.0e6
-		std::vector<bool> m_root_center_with_seedling;	//m_root_centers位置上是否含有植株
-		std::vector<bool> m_root_center_with_seedling_history;	//m_root_centers位置上是否含有植株(通过历史信息获取)
+		std::vector<int> m_root_center_with_seedling;	//m_root_centers位置上是否含有植株,0--没有, 1--有茎, 2--有叶子遮挡
+		std::vector<int> m_root_center_with_occlusion;	//m_root_center_with_occlusion位置上是否含有植株,0--没有, 2--有叶子遮挡		
 		
 		//用于记录第一排z均值,用来辅助判别1、2排的苗
 		float m_1st_row_zmean_rs = -1.0;
@@ -211,6 +211,18 @@ namespace graft_cv {
 		void get_point_x_hist(
 			std::vector<int>& x_hist	//output			
 		);
+		void get_point_centriod(
+			std::vector<CStemResult>& root_centers,	//input
+			std::vector<int>&counts,	//output
+			std::vector<Eigen::Vector4f>& centroids	//output
+		);
+		//指定茎周围点云数量、重心位置
+		int CRootStockGrabPoint::get_point_count_inbox(
+			const CStemResult& sr,	//input
+			pcl::PointXYZ& aabb_min,//output
+			pcl::PointXYZ& aabb_max,//output
+			Eigen::Vector4f& centoid);//output
+
 		void get_leaf_point_count_inbox(
 			const CStemResult& sr, //input
 			pcl::PointCloud<pcl::PointXYZ>::Ptr in_cloud,	//input 输入点云数据			

+ 1 - 1
graft_cv_api.cpp

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