Просмотр исходного кода

v0.6.3 增加茄科切后切割点识别接口(一个接口支持砧木和穗苗)

chenhongjiang 2 лет назад
Родитель
Сommit
c82ff4f3b6
8 измененных файлов с 458 добавлено и 4 удалено
  1. 2 1
      ReadMe.txt
  2. 321 0
      cut_point_rs_reid.cpp
  3. 34 0
      cut_point_rs_reid.h
  4. 1 1
      data_def.h
  5. 16 0
      data_def_api.h
  6. 31 1
      graft_cv_api.cpp
  7. 19 1
      graft_cv_api.h
  8. 34 0
      utils.h

+ 2 - 1
ReadMe.txt

@@ -70,4 +70,5 @@ v0.5.9.20 
 		  修改穗苗识别方法(用于标定位置的夹子被遮挡)
 v0.6.0 修改旋转角度识别算法,采用顶部拍照,一次识别旋转角度
 v0.6.1 移植到vs2015平台(vc14),选用OpenCV v4.55
-v0.6.2 增加上苗点云识别夹取位置接口及功能
+v0.6.2 增加上苗点云识别夹取位置接口及功能
+v0.6.3 增加茄科切后识别接口及功能(同一个接口支持砧木和穗苗)

+ 321 - 0
cut_point_rs_reid.cpp

@@ -269,4 +269,325 @@ void CRootStockCutPointReid::img_preprocess(cv::Mat&img)
 	
 }
 
+///////////////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////////////
+CSolaCutPointReid::CSolaCutPointReid(ConfigParam&cp, int stemType, CGcvLogger*pLog)
+	:m_cparam(cp),
+	m_pLogger(pLog),	
+	m_stem_type(stemType),
+	m_imgId(""),
+	m_ppImgSaver(0),
+	m_pImginfoBin(0),
+	m_pImgCutPoint(0)
+{
+
+}
+
+CSolaCutPointReid::~CSolaCutPointReid()
+{}
+void CSolaCutPointReid::clear_imginfo() {
+	if (m_pImginfoBin) {
+		imginfo_release(&m_pImginfoBin);
+		m_pImginfoBin = 0;
+	}	
+	if (m_pImgCutPoint) {
+		imginfo_release(&m_pImgCutPoint);
+		m_pImgCutPoint = 0;
+	}
+}
+int CSolaCutPointReid::cut_point_reid(
+	ImgInfo* imginfo,
+	cv::Mat&cimg,	
+	PositionInfo& posinfo
+)
+{
+	if (m_stem_type == 0) {
+		m_imgId = getImgId(img_type::sola_rs_reid);
+	}
+	else {
+		m_imgId = getImgId(img_type::sola_sc_reid);
+	}
+	
+	//1 image segment	
+	clock_t t;
+	clock_t t0 = clock();
+
+	cv::Mat img;
+	if (imginfo) {
+		if (m_pLogger) {
+			stringstream buff;
+			buff << m_imgId<<" "<<get_stem_type_name() << " image, width=" << imginfo->width
+				<< "\theight=" << imginfo->height;
+			m_pLogger->INFO(buff.str());
+		}
+		if (!isvalid(imginfo)) {
+			if (m_pLogger) {
+				m_pLogger->ERRORINFO(m_imgId +string(" ")+get_stem_type_name() + " input image invalid.");
+			}
+			throw_msg(m_imgId + string(" ") + get_stem_type_name() + " invalid input image");
+
+		}
+		img = imginfo2mat(imginfo);
+	}
+	else {
+		if (m_pLogger) {
+			stringstream buff;
+			buff << m_imgId << " " << get_stem_type_name() << " image, width=" << cimg.cols
+				<< "\theight=" << cimg.rows;
+			m_pLogger->INFO(buff.str());
+		}
+		if (cimg.empty()) {
+			if (m_pLogger) {
+				m_pLogger->ERRORINFO(m_imgId +string(" ")+ get_stem_type_name() + " input image invalid.");
+			}
+			throw_msg(m_imgId + string(" ") +get_stem_type_name() + " invalid input image");
+
+		}
+		img = cimg;
+	}
+
+	/*if (m_cparam.self_camera) {
+		image_set_bottom(img, 20, 8);
+		if (m_pLogger) {
+			m_pLogger->DEBUG(m_imgId + " image set bottom with pixel value 20.");
+		}
+	}*/
+	if (m_cparam.rs_y_flip) {
+		flip(img, img, 0);
+		if (m_pLogger) {
+			m_pLogger->DEBUG(m_imgId + string(" ") + get_stem_type_name() + " image y fliped.");
+		}
+	}
+
+	//image saver
+	if (m_ppImgSaver && *m_ppImgSaver) {
+		(*m_ppImgSaver)->saveImage(img, m_imgId);
+	}
+	if (m_pLogger) {
+		m_pLogger->DEBUG(m_imgId + string(" ") + get_stem_type_name() + " before image segment.");
+	}
+	///////////////////////////////////////////////////////
+	// image segment
+	this->img_preprocess(img);
+	if (m_pLogger) {
+		m_pLogger->DEBUG(m_imgId + " after image gray.");
+	}
+
+	if (m_cparam.image_show) {
+		cv::destroyAllWindows();		
+		imshow_wait("gray", m_grayImg);
+		imshow_wait("binary", m_binImg);
+	}
+	else {
+		t = clock();
+		if (1000.0*((float)(t - t0)) / CLOCKS_PER_SEC>(float)m_cparam.timeout_proc) {
+			if (m_pLogger) {
+				m_pLogger->ERRORINFO(m_imgId + " rootstock reid timeout.");
+			}
+			throw_msg(m_imgId + " time out");
+		}
+	}
+	if (m_pLogger) {
+		m_pLogger->DEBUG(m_imgId + " after gray image show.");
+	}
+
+   // find object
+	vector<vector<cv::Point>> contours;
+	vector<cv::Vec4i> hierarchy;	
+	contours.clear();
+	hierarchy.clear();
+	int obj_area_th = 100;//m_cparam
+	double max_area = 0;
+	int max_idx = -1;
+	
+	findContours(m_binImg, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE);
+	for (int i = 0; i<contours.size(); ++i) {
+		double area = contourArea(contours[i]);
+		if (area < obj_area_th) { continue; }
+		if(area<max_area) { continue; }
+		max_area = area;
+		max_idx = i;
+	}
+
+	if (max_idx < 0 || contours[max_idx].size()==0) {
+		throw_msg(m_imgId + " no valid object");
+	}
+
+	vector<cv::Point> cut_curve;
+	cv::RotatedRect retval;
+	find_cut_curve(contours[max_idx], cut_curve, retval);
+	if (m_cparam.image_show) { 		
+		cv::Mat tmp = m_grayImg.clone();
+		cv::ellipse(tmp, retval, cv::Scalar(200));
+		imshow_wait("gray_ellipse", tmp);
+	}
+
+	posinfo.rs_reid_sola_upoint_x = retval.center.x;
+	posinfo.rs_reid_sola_upoint_y = retval.center.y - retval.size.height / 2;
+	posinfo.rs_reid_sola_cpoint_x = retval.center.x;
+	posinfo.rs_reid_sola_cpoint_y = retval.center.y;
+	posinfo.rs_reid_sola_lpoint_x = retval.center.x;
+	posinfo.rs_reid_sola_lpoint_y = retval.center.y + retval.size.height/2;
+
+	
+
+	if(m_pLogger){		
+		stringstream buff;
+		buff<<m_imgId<<" rootstock image, rs_cut_upoint("<< posinfo.rs_reid_sola_upoint_x
+			<<","<< posinfo.rs_reid_sola_upoint_y <<")"
+			<<", rs_cut_cpoint=("<< posinfo.rs_reid_sola_cpoint_x
+			<< "," << posinfo.rs_reid_sola_cpoint_y << ")"
+			<<", rs_cut_lpoint(mm)("<< posinfo.rs_reid_sola_lpoint_x
+			<<","<< posinfo.rs_reid_sola_lpoint_y <<")";
+		m_pLogger->INFO(buff.str());
+	}
+
+	//  return images:	posinfo.pp_images
+	if(m_cparam.image_return){
+		this->clear_imginfo();
+		//0) image id
+		strcpy(posinfo.rs_img_id,m_imgId.c_str());
+
+		//1) bin image				
+		m_pImginfoBin=mat2imginfo(m_binImg);	
+
+		//2 cut point int gray image
+		cv::Mat tmp = m_grayImg.clone();
+		cv::ellipse(tmp, retval, cv::Scalar(200));
+	
+		m_pImgCutPoint = mat2imginfo(tmp);
+
+		posinfo.pp_images[0] = m_pImginfoBin;
+		posinfo.pp_images[1] = m_pImgCutPoint;
+		
+		if(m_ppImgSaver && *m_ppImgSaver){
+			(*m_ppImgSaver)->saveImage(m_binImg, m_imgId+"_rst_0");
+			(*m_ppImgSaver)->saveImage(tmp, m_imgId+"_rst_1");
+		}		
+	}
+	if (m_pLogger) {
+		m_pLogger->INFO(m_imgId + " rootstock cut reid detect finished.");
+	}
+	return 0;
+};
+void CSolaCutPointReid::find_cut_curve(vector<cv::Point>&curve_points, 
+	vector<cv::Point>&cut_curve, cv::RotatedRect& retval)
+{
+	int max_y = curve_points[0].y;
+	int min_y = curve_points[0].y;
+	int max_x = curve_points[0].x;
+	int min_x = curve_points[0].x;
+	for (auto&pt : curve_points) {
+		if (pt.y > max_y) {
+			max_y = pt.y;
+		}
+		if (pt.y < min_y) {
+			min_y = pt.y;
+		}
+		if (pt.x > max_x) {
+			max_x = pt.x;
+		}
+		if (pt.x < min_x) {
+			min_x = pt.x;
+		}
+	}
+	vector<int>stem_width;
+	int line_min_x, line_max_x;
+	for (int y = min_y; y <= max_y;++y) {
+		line_min_x = max_x;
+		line_max_x = min_x;
+		for (auto&pt : curve_points) {
+			if (pt.y != y) { continue; }
+			if (pt.x < line_min_x) { line_min_x = pt.x; }
+			if (pt.x > line_max_x) { line_max_x = pt.x; }
+		}
+		stem_width.push_back(line_max_x - line_min_x + 1);		
+	}
+	// find y range curve
+	int min_yc = min_y;
+	int max_yc = max_y;
+	if (m_stem_type == 0) {
+		int pos = trend_detect_pos(stem_width, 15);
+		if (pos > 0) {
+			max_yc = min_yc + pos;
+		}
+		else {
+			throw(m_imgId + " not found cut curve range");
+		}
+	}
+	else {
+		vector<int> stem_width_r;
+		vector<int>::reverse_iterator rit = stem_width.rbegin();
+		for (; rit != stem_width.rend(); ++rit) {
+			stem_width_r.push_back(*rit);
+		}
+		int pos = trend_detect_pos(stem_width_r, 15);
+		if (pos > 0) {
+			min_yc = max_yc - pos;
+		}
+		else {
+			throw(m_imgId + " not found cut curve range");
+		}
+	}
+
+	// copy cut curve points
+	vector<cv::Point>cut_curve_points;
+	for (auto&pt : curve_points) {
+		if (pt.y >= min_yc && pt.y <= max_yc) {
+			cut_curve_points.push_back(cv::Point(pt));
+		}
+	}
+
+	// ecllipse fit
+	retval = cv::fitEllipse(cut_curve_points);
+	//if (m_cparam.image_show) {
+	//	//cv::destroyAllWindows();
+	//	cv::Mat tmp = m_grayImg.clone();
+	//	for (auto&pt : cut_curve_points) {
+	//		tmp.at<cv::uint8_t>(pt.y, pt.x+500) = 200;
+	//	}
+
+	//	//cv::ellipse(tmp, retval, cv::Scalar(200));
+	//	imshow_wait("cut_curve", tmp);
+	//}
+	
+}
+string CSolaCutPointReid::get_stem_type_name() {
+	if (m_stem_type == 0) {
+		return string("solanaceae rootstock");
+	}
+	else {
+		return string("solanaceae scion");
+	}
+}
+void CSolaCutPointReid::img_preprocess(cv::Mat&img)
+{
+	//»Ò¶È»¯
+	cv::Mat b_img;
+	if (img.channels() != 1) {
+		//color image ,bgr, for testing		
+		cvtColor(img, m_grayImg, cv::COLOR_BGR2GRAY);
+	}
+	else {
+		m_grayImg = img.clone();
+	}	
+
+	cv::Mat kernel = cv::getStructuringElement(
+		cv::MORPH_ELLIPSE,
+		cv::Size(5, 5),
+		cv::Point(2,2)
+	);	
+
+	double th = threshold(m_grayImg, b_img, 255, 255, cv::THRESH_OTSU);
+
+	morphologyEx(
+		b_img,
+		m_binImg,
+		cv::MORPH_CLOSE,
+		kernel,
+		cv::Point(-1, -1),
+		2
+	);
+
+}
 }

+ 34 - 0
cut_point_rs_reid.h

@@ -43,5 +43,39 @@ private:
 	void img_preprocess(cv::Mat&);
 	void clear_imginfo();
 	
+};
+
+// 茄科切割点识别
+// 切后识别
+class CSolaCutPointReid {
+public:
+	CSolaCutPointReid(ConfigParam&c, int stemType=0, CGcvLogger*pLog=0);
+	~CSolaCutPointReid();
+	int cut_point_reid(			//切后切割点识别
+		ImgInfo*,				//切后图片
+		cv::Mat&,					//切后图片,测试用		
+		PositionInfo& posinfo
+	);
+private:
+	int m_stem_type; //植株类别:0--砧木;1--穗苗
+					 //global configure parameters
+	ConfigParam& m_cparam;
+	CGcvLogger * m_pLogger;
+	string m_imgId;
+	CImStoreManager** m_ppImgSaver;
+	cv::Mat m_grayImg;// gray image
+	cv::Mat m_binImg; // bin image
+
+	//返回图片,用于调试
+	ImgInfo* m_pImginfoBin;//	
+	ImgInfo* m_pImgCutPoint;//reference-point, cutpoint
+
+	void clear_imginfo();
+	inline string get_stem_type_name();
+	void img_preprocess(cv::Mat&img);
+	void find_cut_curve(vector<cv::Point>&points,
+		vector<cv::Point>&cut_curve,
+		cv::RotatedRect& retval);
+	
 };
 };

+ 1 - 1
data_def.h

@@ -3,7 +3,7 @@
 namespace graft_cv{
 	extern char *g_version_str;
 	//enum camera {rs_clamp_rad, rs_clamp_tan, rs_cut_rad, rs_cut_tan, sc_clamp_rad, sc_clamp_tan, sc_cut_rad, sc_cut_tan};
-	enum img_type {oa,rs,rs_reid,sc };
+	enum img_type {oa,rs,rs_reid,sc,sola_rs_reid, sola_sc_reid };
 	template<class T>
 	class roi_box{
 	public:

+ 16 - 0
data_def_api.h

@@ -142,6 +142,22 @@ typedef struct
 	double rs_reid_lpoint_x;//切后砧木下切割点x位置,毫米
 	double rs_reid_lpoint_y;//切后砧木下切割点y位置,毫米
 
+	double rs_reid_sola_upoint_x;//茄科切后砧木上切割点x位置,毫米
+	double rs_reid_sola_upoint_y;//茄科切后砧木上切割点y位置,毫米
+	double rs_reid_sola_cpoint_x;//茄科切后砧木中切割点x位置,毫米
+	double rs_reid_sola_cpoint_y;//茄科切后砧木中切割点y位置,毫米
+	double rs_reid_sola_lpoint_x;//茄科切后砧木下切割点x位置,毫米
+	double rs_reid_sola_lpoint_y;//茄科切后砧木下切割点y位置,毫米
+
+	double sc_reid_sola_upoint_x;//茄科穗苗切后上切割点x位置,毫米
+	double sc_reid_sola_upoint_y;//茄科穗苗切后上切割点y位置,毫米
+	double sc_reid_sola_cpoint_x;//茄科穗苗切后中切割点x位置,毫米
+	double sc_reid_sola_cpoint_y;//茄科穗苗切后中切割点y位置,毫米
+	double sc_reid_sola_lpoint_x;//茄科穗苗切后下切割点x位置,毫米
+	double sc_reid_sola_lpoint_y;//茄科穗苗切后下切割点y位置,毫米
+
+
+
 
 	double sc_cut_upoint_x;//穗苗上切割点x位置,毫米
 	double sc_cut_upoint_y;//穗苗上切割点y位置,毫米

+ 31 - 1
graft_cv_api.cpp

@@ -19,7 +19,7 @@ extern CRITICAL_SECTION g_cs;
 namespace graft_cv
 {
 
-	char *g_version_str = "0.6.2";
+	char *g_version_str = "0.6.3";
 
 	//configure
 	string g_conf_file = "./gcv_conf.yml";	
@@ -46,6 +46,9 @@ namespace graft_cv
 
 	CRootStockGrabPoint g_rs_gp(g_cp, &g_logger);
 
+	CSolaCutPointReid g_sola_rs = CSolaCutPointReid(g_cp, 0, &g_logger);
+	CSolaCutPointReid g_sola_sc = CSolaCutPointReid(g_cp, 1, &g_logger);
+
 	//
 	map<string, cv::Mat> g_img_cache;
 	
@@ -388,6 +391,33 @@ namespace graft_cv
 		return 0;		
 	}
 
+	// 
+	int sola_cut_point_reid(ImgInfo*imginfo, int sola_type, PositionInfo& posinfo)
+	{
+		memset(&posinfo, 0, sizeof(PositionInfo));
+		try {
+			if (sola_type == 0) {
+				g_sola_rs.cut_point_reid(imginfo, cv::Mat(), posinfo);
+			}
+			else {
+				g_sola_sc.cut_point_reid(imginfo, cv::Mat(), posinfo);
+			}			
+		}
+		catch (std::exception &err) {
+			g_logger.ERRORINFO(err.what());
+			return 1;
+		}
+		catch (string& msg) {
+			g_logger.ERRORINFO(msg);
+			return 1;
+		}
+		catch (...) {
+			g_logger.ERRORINFO("unknown error");
+			return 1;
+		}
+		return 0;
+	}
+
 	//12
 	int sc_cut_point(
 		ImgInfo* imginfo, 

+ 19 - 1
graft_cv_api.h

@@ -120,7 +120,25 @@ GCV_API int rs_cut_point(ImgInfo*, PositionInfo& posinfo);
 //   返回: 0- 正常; 1- 失败
 GCV_API int rs_cut_point_reid(ImgInfo*, const char* pre_img_id, PositionInfo& posinfo);
 
-//16 穗苗上切割点识别,返回
+//16 茄科切割点切后识别
+//  input:
+//       ImgInfo*  --- 输入图像
+//       sola_type --- 0--砧木; 1---穗苗
+//       posinfo---- 返回信息
+//
+// 返回
+//		上切点,中切点,下切点
+//   	
+//  	 posinfo.pp_images   (return image 为 true时)
+//               返回2张图片:图0:二值图像
+//                           图1: 灰度图像,拟合椭圆
+//                            
+//   返回: 0- 正常; 1- 失败
+GCV_API int sola_cut_point_reid(ImgInfo*, int sola_type, PositionInfo& posinfo);
+
+
+
+//17 穗苗上切割点识别,返回
 //		 posinfo.sc_cut_upoint_x;
 //		 posinfo.sc_cut_upoint_y;
 //		 posinfo.sc_cut_cpoint_x;

+ 34 - 0
utils.h

@@ -1,6 +1,7 @@
 #pragma once
 
 #include <opencv2\imgproc\imgproc.hpp>
+#include <boost\math\distributions\binomial.hpp>
 #include <vector>
 #include <time.h>
 #include <string>
@@ -430,6 +431,39 @@ namespace graft_cv{
 		return mu;
 	}
 
+	template<typename T>
+	int trend_detect_pos(const vector<T>&data, int step=15) {
+		int pos = -1;
+		if (data.size() < 2 * step) {
+			return pos;
+		}		
+		int radius = int(step/2);
+		boost::math::binomial_distribution<> binomal(radius, 0.5);
+		pos = radius;
+		int max_v = 0;
+		int pos_n, neg_n;
+		for (size_t i = radius; i < data.size() - radius; ++i) {
+			pos_n = neg_n = 0;
+			for (int j = 1; j <= radius; ++j) {
+				int diff = data[i + j] - data[i + j -radius];
+				if (diff > 0) {
+					pos_n++;
+				}
+				else {
+					neg_n++;
+				}
+			}
+			int k = min(pos_n, neg_n);			
+			double p = 2.0 * boost::math::cdf(binomal, k);
+			std::cout <<"idx="<<i<< ", posn=" << pos_n << ", negn=" << neg_n << ", pvalue=" << p << endl;
+			if (p > 0.05) {
+				pos = i;
+				break;
+			}
+		}
+		return pos;
+
+	}