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

修改顶部识别旋转角度、砧木切割点识别接口,新增砧木切后切割点识别接口(未完全)

chenhongjiang 2 роки тому
батько
коміт
43081578b8
13 змінених файлів з 1058 додано та 178 видалено
  1. 2 1
      ReadMe.txt
  2. 4 1
      config.cpp
  3. 8 1
      cut_point_rs.cpp
  4. 2 1
      cut_point_rs.h
  5. 264 0
      cut_point_rs_reid.cpp
  6. 47 0
      cut_point_rs_reid.h
  7. 3 1
      data_def_api.h
  8. 252 154
      demo.cpp
  9. 2 1
      gcv_conf.yml
  10. 25 14
      graft_cv_api.cpp
  11. 5 4
      graft_cv_api.h
  12. 391 0
      optimal_angle.cpp
  13. 53 0
      optimal_angle.h

+ 2 - 1
ReadMe.txt

@@ -67,4 +67,5 @@ v0.5.9.20 增加砧木分叉点检测优化,增加fork_rs.h,cpp(还没有全
 		  修改砧木茎中心最大内接园半径的方法,不采用flann查找(出现误差较大),改用pointPolygonTest()
 		  去掉opencv_flann2410d.lib
 		  按测试,修改rs_cut_point_offset_ratio: 9.0000000000000000e-001
-		  修改穗苗识别方法(用于标定位置的夹子被遮挡)
+		  修改穗苗识别方法(用于标定位置的夹子被遮挡)
+v0.6.0 修改旋转角度识别算法,采用顶部拍照,一次识别旋转角度

+ 4 - 1
config.cpp

@@ -35,6 +35,7 @@ namespace graft_cv{
 			<< "oa_y_flip"<<  m_cparam->oa_y_flip 	
 			<< "oa_morph_radius"<<  m_cparam->oa_morph_radius 	
 			<< "oa_morph_iteration" << m_cparam->oa_morph_iteration
+			<< "oa_min_leaf_area"<< m_cparam->oa_min_leaf_area
 			<< "oa_min_hist_value"<< m_cparam->oa_min_hist_value
 			<< "oa_morph_radius_base"<<  m_cparam->oa_morph_radius_base	
 			<< "oa_morph_iteration_base" << m_cparam->oa_morph_iteration_base
@@ -114,6 +115,7 @@ namespace graft_cv{
 		m_cparam->rs_min_hist_value = (int)node["rs_min_hist_value"];
 	    m_cparam->oa_morph_radius = (int)node["oa_morph_radius"];
 		m_cparam->oa_morph_iteration  = (int)node["oa_morph_iteration"];
+		m_cparam->oa_min_leaf_area = (int)node["oa_min_leaf_area"];
 		m_cparam->oa_min_hist_value = (int)node["oa_min_hist_value"];
 		m_cparam->oa_morph_radius_base = (int)node["oa_morph_radius_base"];
 		m_cparam->oa_morph_iteration_base  = (int)node["oa_morph_iteration_base"];
@@ -193,7 +195,8 @@ namespace graft_cv{
 			<< "oa_y_flip:\t"<<  m_cparam->oa_y_flip 	 << endl
 			<< "oa_morph_radius:\t"<<  m_cparam->oa_morph_radius 	 << endl
 			<< "oa_morph_iteration:\t" << m_cparam->oa_morph_iteration << endl
-			<< "oa_min_hist_value:\t"<< m_cparam->oa_min_hist_value << endl
+			<< "oa_min_leaf_area:\t"<< m_cparam->oa_min_leaf_area << endl
+			<< "oa_min_hist_value:\t"<< m_cparam->oa_min_hist_value<<endl
 			<< "oa_morph_radius_base:\t"<<  m_cparam->oa_morph_radius_base	 << endl
 			<< "oa_morph_iteration_base:\t" << m_cparam->oa_morph_iteration_base << endl
 			<< "oa_min_hist_value_base:\t"<< m_cparam->oa_min_hist_value_base << endl

+ 8 - 1
cut_point_rs.cpp

@@ -51,7 +51,8 @@ void CRootStockCutPoint::clear_imginfo(){
 int CRootStockCutPoint::up_point_detect(
 	ImgInfo* imginfo, 
 	Mat&cimg, 
-	PositionInfo& posinfo
+	PositionInfo& posinfo,
+	map<string, Mat>& img_cache
 	)
 {
 	// cimg --- color image, bgr
@@ -130,6 +131,9 @@ int CRootStockCutPoint::up_point_detect(
 	///////////////////////////////////////////////////////
 	// image segment
 	this->img_segment(img);
+	// image save to cache
+	img_cache.insert(make_pair<string, Mat>(m_imgId, m_grayImg));
+
     if(m_pLogger){	
 		m_pLogger->DEBUG(m_imgId+" after image segment.");				
 	}
@@ -395,6 +399,9 @@ int CRootStockCutPoint::up_point_detect(
 	//  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) 		
 		//stem x-range
 		line(m_binImg,Point(stem_x0,0),Point(stem_x0,m_binImg.cols-1),Scalar(100),2);

+ 2 - 1
cut_point_rs.h

@@ -17,7 +17,8 @@ public:
 	int up_point_detect(//上切割点,切前识别
 		ImgInfo*, 
 		Mat&,
-		PositionInfo& posinfo
+		PositionInfo& posinfo,
+		map<string, Mat>& img_cache
 		);
 
 	//int up_point_reid( // 上切割点,切后重识别

+ 264 - 0
cut_point_rs_reid.cpp

@@ -0,0 +1,264 @@
+/*
+  砧木切割点后识别
+
+*/
+#include <opencv2\imgproc\imgproc.hpp>
+#include <opencv2\features2d\features2d.hpp>
+#include <opencv2\nonfree\features2d.hpp>
+#include <math.h>
+#include <algorithm>
+
+#include "cut_point_rs_reid.h"
+#include "utils.h"
+#include "data_def.h"
+#include "logger.h"
+using namespace cv;
+
+namespace graft_cv{
+
+CRootStockCutPointReid::CRootStockCutPointReid(ConfigParam&cp,CGcvLogger*pLog/*=0*/)
+:m_cparam(cp),
+m_pLogger(pLog),
+m_pImginfoBinFork(0),
+m_pImgCorners(0),
+m_pImgCutPoint(0),
+m_imgId(""),
+m_ppImgSaver(0)
+{	
+}
+CRootStockCutPointReid::~CRootStockCutPointReid()
+{
+	this->clear_imginfo();	
+}
+
+void CRootStockCutPointReid::clear_imginfo(){
+	if (m_pImginfoBinFork){
+		imginfo_release(&m_pImginfoBinFork);
+		m_pImginfoBinFork=0;
+	}
+	if (m_pImgCorners){
+		imginfo_release(&m_pImgCorners);
+		m_pImgCorners=0;
+	}
+	if (m_pImgCutPoint){
+		imginfo_release(&m_pImgCutPoint);
+		m_pImgCutPoint=0;
+	}
+}
+
+int CRootStockCutPointReid::cut_point_reid(
+	ImgInfo* imginfo, 
+	Mat&cimg, 
+	const char * pre_img_id,
+	PositionInfo& posinfo,
+	map<string, Mat>& img_cache
+	)
+{
+	// cimg --- color image, bgr
+	
+	if(m_pLogger){				
+			m_pLogger->INFO(m_imgId +" rootstock cut_pt reid begin");
+	}
+	if(!pre_img_id){
+		if(m_pLogger){				
+			m_pLogger->ERRORINFO(m_imgId +" pre-image id is NULL");
+		}	
+		return 1;
+	} 
+	string pre_imgid(pre_img_id);
+	map<string, Mat>::iterator iter = img_cache.find(pre_imgid);
+	if(iter==img_cache.end()){
+		if(m_pLogger){				
+			m_pLogger->ERRORINFO(m_imgId +" pre-image NOT in cache");
+		}	
+		return 1;
+	}
+	m_preGrayImg = iter->second;
+    m_imgId = getImgId(img_type::rs_reid);
+	 
+	//1 image segment	
+	clock_t t;
+	clock_t t0 = clock();	
+
+	Mat img;
+	if(imginfo){
+		if(m_pLogger){		
+			stringstream buff;
+			buff<<m_imgId<<" rootstock image, width="<<imginfo->width
+				<<"\theight="<<imginfo->height;
+			m_pLogger->INFO(buff.str());
+		}
+		if(!isvalid(imginfo)){
+			if(m_pLogger){				
+				m_pLogger->ERRORINFO(m_imgId+" rootstock input image invalid.");				
+			}
+			throw_msg(m_imgId+" invalid input image");	
+			
+		}	
+		img = imginfo2mat(imginfo);
+	}
+	else{
+		if(m_pLogger){		
+			stringstream buff;
+			buff<<m_imgId<<"rootstock image, width="<<cimg.cols
+				<<"\theight="<<cimg.rows;
+			m_pLogger->INFO(buff.str());
+		}
+		if(cimg.empty()){
+			if(m_pLogger){				
+				m_pLogger->ERRORINFO(m_imgId+" rootstock input image invalid.");				
+			}
+			throw_msg(m_imgId +" 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+" image y fliped.");				
+		}
+	}
+	
+	//image saver
+	if(m_ppImgSaver && *m_ppImgSaver){
+		(*m_ppImgSaver)->saveImage(img, m_imgId);
+	}
+    if(m_pLogger){				
+		m_pLogger->DEBUG(m_imgId+" 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){
+		destroyAllWindows();	
+		imshow_wait("rs_pre_gray",m_preGrayImg);	
+		imshow_wait("rs_gray",m_grayImg);		
+	}
+	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 pre- and cur- gray image show.");				
+	}
+	
+	//特征提取
+	int max_feature = 500;
+	OrbFeatureDetector fts_detector(max_feature);
+	//SurfFeatureDetector fts_detector(max_feature);
+	std::vector<KeyPoint> keypoints_pre, keypoints_cur;
+	fts_detector.detect( m_preGrayImg, keypoints_pre );
+	fts_detector.detect( m_grayImg, keypoints_cur );
+
+	SurfDescriptorExtractor extractor;
+	Mat descriptors_pre, descriptors_cur;
+	extractor.compute( m_preGrayImg, keypoints_pre, descriptors_pre );
+	extractor.compute( m_grayImg, keypoints_cur, descriptors_cur );
+	//-- Step 3: Matching descriptor vectors with a brute force matcher
+	BFMatcher matcher(NORM_L2);
+	std::vector< DMatch > matches;
+	matcher.match( descriptors_pre, descriptors_cur, matches );
+	//-- Draw matches
+	Mat img_matches;
+	drawMatches( m_preGrayImg, keypoints_pre, m_grayImg, keypoints_cur, matches, img_matches );
+	//-- Show detected matches
+	imshow("Matches", img_matches );
+	waitKey(-1);
+
+	//if(m_pLogger){		
+	//	stringstream buff;
+	//	buff<<m_imgId<<" rootstock image, rs_cut_upoint(mm)("<<rs_cut_upoint_x
+	//		<<","<<rs_cut_upoint_y<<")"
+	//		<<", rs_stem_diameter(mm)="<<rs_stem_diameter
+	//		<<", lower_cut_pt(mm)("<<rs_cut_lpoint_x
+	//		<<","<<rs_cut_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) 		
+	//	//stem x-range
+	//	line(m_binImg,Point(stem_x0,0),Point(stem_x0,m_binImg.cols-1),Scalar(100),2);
+	//	line(m_binImg,Point(stem_x1,0),Point(stem_x1,m_binImg.cols-1),Scalar(100),2);
+	//	//fork right point
+	//	circle(m_binImg, Point(stem_fork_left_x,stem_fork_y),5, Scalar(128,0,128), -1, 8,0);
+	//	m_pImginfoBinFork=mat2imginfo(m_binImg);	
+
+	//	//3 cut point int gray image	
+	//	circle(m_grayImg, Point(stem_fork_left_x,stem_fork_y),5, Scalar(128,0,128), -1, 8,0);
+	//	circle(m_grayImg, Point(stem_fork_right_x,stem_fork_y),5, Scalar(128,0,128), -1, 8,0);//v0.5.9.3 reference point
+	//	circle(m_grayImg, Point(cut_pt.x,stem_fork_y),5, Scalar(128,0,128), -1, 8,0);//v0.5.9.3 reference point
+	//	circle(m_grayImg, Point(cut_pt.x,stem_fork_y),2, Scalar(255,0,255), -1, 8,0);
+	//	circle(m_grayImg, Point(lower_cut_pt.x,lower_cut_pt.y),5, Scalar(200,0,200), -1, 8,0);
+	//	image_draw_line(m_grayImg,cut_pt.x,cut_pt.y,lower_cut_pt.x,lower_cut_pt.y);
+	//
+	//	m_pImgCutPoint = mat2imginfo(m_grayImg);			
+	//	posinfo.pp_images[0]=m_pImginfoBinFork;
+	//	posinfo.pp_images[1]=m_pImgCutPoint;
+
+	//	if(m_ppImgSaver && *m_ppImgSaver){
+	//		(*m_ppImgSaver)->saveImage(m_binImg, m_imgId+"_rst_0");
+	//		(*m_ppImgSaver)->saveImage(m_grayImg, m_imgId+"_rst_1");
+	//	}		
+	//}
+	if(m_pLogger){				
+		m_pLogger->INFO(m_imgId +" rootstock cut reid detect finished.");				
+	}
+	return 0;
+};
+
+void CRootStockCutPointReid::img_preprocess(Mat&img)
+{
+	//灰度化
+	Mat b_img;
+	if(img.channels()!=1){
+		//color image ,bgr, for testing		
+		cvtColor(img,m_grayImg,COLOR_BGR2GRAY);
+	}
+	else{
+		m_grayImg = img.clone();
+	}
+
+	/*Mat kernel = getStructuringElement(
+		MORPH_ELLIPSE, 
+		Size( 2*m_cparam.rs_morph_radius + 1, 2*m_cparam.rs_morph_radius+1 ),
+		Point( m_cparam.rs_morph_radius, m_cparam.rs_morph_radius )
+		);    	
+	double th = threshold(m_grayImg, b_img, 255, 255,THRESH_OTSU);		
+
+	morphologyEx(
+		b_img, 
+		m_binImg, 
+		MORPH_CLOSE,
+		kernel,
+		Point(-1,-1),
+		m_cparam.rs_morph_iteration
+		);*/
+
+
+	
+}
+
+}

+ 47 - 0
cut_point_rs_reid.h

@@ -0,0 +1,47 @@
+//cut point reid for rootstock plant
+#pragma once
+#include <time.h>
+#include <opencv.hpp>
+#include "data_def_api.h"
+#include "data_def.h"
+#include "logger.h"
+#include "imstorage_manager.h"
+
+using namespace cv;
+namespace graft_cv{
+
+class CRootStockCutPointReid{
+public:
+	CRootStockCutPointReid(ConfigParam&c,CGcvLogger*pLog=0);
+	~CRootStockCutPointReid();
+	int cut_point_reid(			//切后切割点识别
+		ImgInfo*,				//切后图片
+		Mat&,					//切后图片,测试用
+		const char * pre_img_id,		//切前图像id
+		PositionInfo& posinfo,	
+		map<string, Mat>& img_cache
+		);	
+	void set_image_saver(CImStoreManager** ppis){m_ppImgSaver=ppis;}
+private:
+	Mat m_preGrayImg;// gray image
+	//Mat m_binImg;// binary image
+	Mat m_grayImg;// gray image
+	Mat m_edgeImg;
+	string m_imgId;
+	CImStoreManager** m_ppImgSaver;
+
+	//返回图片,用于调试
+	ImgInfo* m_pImginfoBinFork;//fork-y, right-x
+	ImgInfo* m_pImgCorners;//corners, reference-point, candidate box
+	ImgInfo* m_pImgCutPoint;//reference-point, cutpoint
+
+
+	//global configure parameters
+	ConfigParam& m_cparam;
+	CGcvLogger * m_pLogger;
+	// image segment
+	void img_preprocess(Mat&);
+	void clear_imginfo();
+	
+};
+};

+ 3 - 1
data_def_api.h

@@ -32,6 +32,7 @@ typedef struct{
 	bool oa_y_flip;
 	int oa_morph_radius;//optimal-angle, open-operation morph-size, = 1;-->COptimalAngle::imgproc(Mat& img)
 	int oa_morph_iteration; //optimal-angle, open-operation times, = 5; -->COptimalAngle::imgproc(Mat& img)
+	int oa_min_leaf_area; //最小叶片像素面积
 	int oa_min_hist_value;// optimal-angle, columns-histogram, threshold; -->COptimalAngle::imgproc(Mat& img)
 	double oa_col_th_ratio;//  = 0.7, rootstock binary image column histogram, threshold ratio for max-value, for 
 	                       // detect stem x-range
@@ -75,7 +76,7 @@ typedef struct{
 	double rs_corner_qaulity_level;// = 0.1;
 	double rs_corner_min_distance;// = 10;
 	double rs_cut_angle; //=-135 以上切割点为中心,切削茎的方向,(-180.0,180.0)
-	double rs_cut_point_offset_ratio;//向下偏移比例,内接圆半径的倍数0-1.0
+	double rs_cut_point_offset_ratio;//向下偏移比例,内接圆半径的倍数0-1.0	
 
 	//scion cut parameters (14)
 	bool sc_y_flip;
@@ -119,6 +120,7 @@ typedef struct
 	double rs_stem_diameter;//砧木茎粗,毫米	
 	double rs_cut_lpoint_x;//砧木下切割点x位置,毫米
 	double rs_cut_lpoint_y;//砧木下切割点y位置,毫米
+	char rs_img_id[64]; 
 
 	double sc_cut_upoint_x;//穗苗上切割点x位置,毫米
 	double sc_cut_upoint_y;//穗苗上切割点y位置,毫米

+ 252 - 154
demo.cpp

@@ -19,6 +19,7 @@
 #include "optimal_angle.h"
 #include "cut_point_sc.h"
 #include "cut_point_rs.h"
+#include "cut_point_rs_reid.h"
 #include "graft_cv_api.h"
 #include "logger.h"
 
@@ -680,149 +681,149 @@ void test_sc_cut_point_simulate()
 	}	
 	
 };
-void test_rs_cut_point(){
-	ConfigParam cp;
-	test_init_cp(cp);	
-
-	 namedWindow("pic", CV_WINDOW_NORMAL);
-	 CRootStockCutPoint rscp(cp);
-	 ofstream g_logger_ofs;
-	CGcvLogger g_logger = CGcvLogger(
-		g_logger_ofs,
-		CGcvLogger::file_and_terminal,
-		CGcvLogger::warning,
-		"./gcv_debug.log");
-	 clock_t t;
-	for(int i=4;i<193;++i){
-		//if (/*i !=11 &&*/ D:\private\grafting_robot\samples\rs_cut_simulate
-		//	i !=32 && 
-		//	i !=48 && 
-		//	i != 49 && 
-		//	i != 53 && 
-		//	i != 103 && 
-		//	i != 104 && 
-		//	i != 106 && 
-		//	i != 110 && 
-		//	i != 125 && 
-		//	i !=182 && 
-		//	i != 187 && 
-		//	i != 191)
-		//{
-		//	continue;
-		//}
-
-		stringstream buff;
-		buff<<setw(3) << setfill('0') << i;
-		cout<<buff.str()<<endl;
-
-		//string img_file= "E:/projects/grafting_robots/samples/rootstlock_pumpkin/"+buff.str()+"/6.jpg";
-		//string img_file= "D:/private/grafting_robot/samples/rootstlock_pumpkin/"+buff.str()+"/6.jpg";
-		string img_file= "D:/grafting_robot/samples/rootstlock_pumpkin/"+buff.str()+"/6.jpg";
-	
-		
-		Mat img = imread(img_file,CV_LOAD_IMAGE_COLOR  );
-		//ImgInfo* imginfo = mat2imginfo(img);
-		PositionInfo pinfo;
-		try{
-		t = clock();
-		int fold_y = rscp.up_point_detect(0,img, pinfo);
-		t = clock() - t;	
-		}
-		catch(exception &err){
-			cout<<err.what()<<endl;
-		}
-		catch(string msg){
-		 cout<<msg<<endl;
-		 cout<<img_file<<endl<<endl;
-		 g_logger.ERRORINFO(msg);
-		 g_logger.INFO(buff.str());
-		}
-		catch(...){
-			cout<<"============================================unknown error"<<endl;
-			cout<<img_file<<endl<<endl;
-		}
-		//cv::line(img,Point(0,fold_y), Point(img.cols,fold_y),Scalar(0,0,255));
-
-		imshow("pic", img);
-		waitKey(1);
-			
-		cout<<"time(seconds): "<<((float)t)/CLOCKS_PER_SEC<<endl;
-
-		cout<<"\n\n";
-
-	}
-
-
-};
-void test_rs_cut_point_simulate(){
-	ConfigParam cp;
-	test_init_cp(cp);
-	cp.image_show=true;
-	cp.image_return = true;
-
-	 namedWindow("pic", CV_WINDOW_NORMAL);
-	 CRootStockCutPoint rscp(cp,&g_logger);
-	 /*ofstream g_logger_ofs;
-	Logger g_logger = Logger(
-		g_logger_ofs,
-		Logger::file_and_terminal,
-		Logger::warning,
-		"./gcv_debug.log");*/
-
-	//string src_folder = "D:\\private\\grafting_robot\\samples\\rs_cut_simulate";	
-	//string src_folder = "D:\\private\\grafting_robot\\samples\\rootstock_hold_down";
-	//string src_folder = "D:\\grafting_robot\\samples\\rootstock_hold_down";
-	 string src_folder = "D:\\private\\grafting_robot\\samples\\20211215\\rootstock";
-	vector<cv::String>filenames;
-	cv::glob(src_folder, filenames);
-
-	 clock_t t;
-	 for(int i=0;i<filenames.size();++i){
-		
-		string img_file= filenames[i];
-		
-		Mat img = imread(img_file,CV_LOAD_IMAGE_COLOR  );
-		//ImgInfo* imginfo = mat2imginfo(img);
-		PositionInfo pinfo;
-		memset(&pinfo,0,sizeof(PositionInfo));
-		try{
-		t = clock();
-		int fold_y = rscp.up_point_detect(0,img, pinfo);
-		t = clock() - t;	
-		}
-		catch(exception &err){
-			cout<<err.what()<<endl;
-		}
-		catch(string msg){
-		 cout<<msg<<endl;
-		 cout<<img_file<<endl<<endl;
-		 g_logger.ERRORINFO(msg);
-		 g_logger.INFO(img_file);
-		}
-		catch(...){
-			cout<<"============================================unknown error"<<endl;
-			cout<<img_file<<endl<<endl;
-		}
-		//cv::line(img,Point(0,fold_y), Point(img.cols,fold_y),Scalar(0,0,255));
-
-		imshow("pic", img);
-		waitKey(1);
-
-		//show return images
-		for (int i =0;i<5;++i){
-			if (!pinfo.pp_images[i]){continue;}
-			Mat tmp_img = imginfo2mat(pinfo.pp_images[i]);
-			imshow("pic", tmp_img);
-			waitKey(-1);
-		}			
-		cout<<"time(seconds): "<<((float)t)/CLOCKS_PER_SEC<<endl;
-
-		cout<<"\n\n";
-
-	}
-
-
-};
+//void test_rs_cut_point(){
+//	ConfigParam cp;
+//	test_init_cp(cp);	
+//
+//	 namedWindow("pic", CV_WINDOW_NORMAL);
+//	 CRootStockCutPoint rscp(cp);
+//	 ofstream g_logger_ofs;
+//	CGcvLogger g_logger = CGcvLogger(
+//		g_logger_ofs,
+//		CGcvLogger::file_and_terminal,
+//		CGcvLogger::warning,
+//		"./gcv_debug.log");
+//	 clock_t t;
+//	for(int i=4;i<193;++i){
+//		//if (/*i !=11 &&*/ D:\private\grafting_robot\samples\rs_cut_simulate
+//		//	i !=32 && 
+//		//	i !=48 && 
+//		//	i != 49 && 
+//		//	i != 53 && 
+//		//	i != 103 && 
+//		//	i != 104 && 
+//		//	i != 106 && 
+//		//	i != 110 && 
+//		//	i != 125 && 
+//		//	i !=182 && 
+//		//	i != 187 && 
+//		//	i != 191)
+//		//{
+//		//	continue;
+//		//}
+//
+//		stringstream buff;
+//		buff<<setw(3) << setfill('0') << i;
+//		cout<<buff.str()<<endl;
+//
+//		//string img_file= "E:/projects/grafting_robots/samples/rootstlock_pumpkin/"+buff.str()+"/6.jpg";
+//		//string img_file= "D:/private/grafting_robot/samples/rootstlock_pumpkin/"+buff.str()+"/6.jpg";
+//		string img_file= "D:/grafting_robot/samples/rootstlock_pumpkin/"+buff.str()+"/6.jpg";
+//	
+//		
+//		Mat img = imread(img_file,CV_LOAD_IMAGE_COLOR  );
+//		//ImgInfo* imginfo = mat2imginfo(img);
+//		PositionInfo pinfo;
+//		try{
+//		t = clock();
+//		int fold_y = rscp.up_point_detect(0,img, pinfo);
+//		t = clock() - t;	
+//		}
+//		catch(exception &err){
+//			cout<<err.what()<<endl;
+//		}
+//		catch(string msg){
+//		 cout<<msg<<endl;
+//		 cout<<img_file<<endl<<endl;
+//		 g_logger.ERRORINFO(msg);
+//		 g_logger.INFO(buff.str());
+//		}
+//		catch(...){
+//			cout<<"============================================unknown error"<<endl;
+//			cout<<img_file<<endl<<endl;
+//		}
+//		//cv::line(img,Point(0,fold_y), Point(img.cols,fold_y),Scalar(0,0,255));
+//
+//		imshow("pic", img);
+//		waitKey(1);
+//			
+//		cout<<"time(seconds): "<<((float)t)/CLOCKS_PER_SEC<<endl;
+//
+//		cout<<"\n\n";
+//
+//	}
+//
+//
+//};
+//void test_rs_cut_point_simulate(){
+//	ConfigParam cp;
+//	test_init_cp(cp);
+//	cp.image_show=true;
+//	cp.image_return = true;
+//
+//	 namedWindow("pic", CV_WINDOW_NORMAL);
+//	 CRootStockCutPoint rscp(cp,&g_logger);
+//	 /*ofstream g_logger_ofs;
+//	Logger g_logger = Logger(
+//		g_logger_ofs,
+//		Logger::file_and_terminal,
+//		Logger::warning,
+//		"./gcv_debug.log");*/
+//
+//	//string src_folder = "D:\\private\\grafting_robot\\samples\\rs_cut_simulate";	
+//	//string src_folder = "D:\\private\\grafting_robot\\samples\\rootstock_hold_down";
+//	//string src_folder = "D:\\grafting_robot\\samples\\rootstock_hold_down";
+//	 string src_folder = "D:\\private\\grafting_robot\\samples\\20211215\\rootstock";
+//	vector<cv::String>filenames;
+//	cv::glob(src_folder, filenames);
+//
+//	 clock_t t;
+//	 for(int i=0;i<filenames.size();++i){
+//		
+//		string img_file= filenames[i];
+//		
+//		Mat img = imread(img_file,CV_LOAD_IMAGE_COLOR  );
+//		//ImgInfo* imginfo = mat2imginfo(img);
+//		PositionInfo pinfo;
+//		memset(&pinfo,0,sizeof(PositionInfo));
+//		try{
+//		t = clock();
+//		int fold_y = rscp.up_point_detect(0,img, pinfo);
+//		t = clock() - t;	
+//		}
+//		catch(exception &err){
+//			cout<<err.what()<<endl;
+//		}
+//		catch(string msg){
+//		 cout<<msg<<endl;
+//		 cout<<img_file<<endl<<endl;
+//		 g_logger.ERRORINFO(msg);
+//		 g_logger.INFO(img_file);
+//		}
+//		catch(...){
+//			cout<<"============================================unknown error"<<endl;
+//			cout<<img_file<<endl<<endl;
+//		}
+//		//cv::line(img,Point(0,fold_y), Point(img.cols,fold_y),Scalar(0,0,255));
+//
+//		imshow("pic", img);
+//		waitKey(1);
+//
+//		//show return images
+//		for (int i =0;i<5;++i){
+//			if (!pinfo.pp_images[i]){continue;}
+//			Mat tmp_img = imginfo2mat(pinfo.pp_images[i]);
+//			imshow("pic", tmp_img);
+//			waitKey(-1);
+//		}			
+//		cout<<"time(seconds): "<<((float)t)/CLOCKS_PER_SEC<<endl;
+//
+//		cout<<"\n\n";
+//
+//	}
+//
+//
+//};
 //void test_rs_cp_reid()
 //{
 //	ConfigParam cp;
@@ -1249,7 +1250,7 @@ void test_oa_batch(){
 	//cv_set_param(cp_ret);
 	//namedWindow("pic", CV_WINDOW_NORMAL);
 	
-	string work_folder = "E:\\projects\\grafting_robot\\samples\\20220223\\rotate";
+	string work_folder = "E:\\projects\\grafting_robot\\samples\\20220831\\top";
 	//string work_folder = "D:\\private\\grafting_robot\\samples\\20220220\\rotate";
 	vector<cv::String>filenames;
 	cv::glob(work_folder, filenames);			
@@ -1258,20 +1259,22 @@ void test_oa_batch(){
 			if(filename.find(".rst_")!=string::npos){continue;}
 			//if(filename.find("\\249.bmp")==string::npos){continue;}
 			PositionInfo posinfo;
-			Mat img = imread(filename, cv::IMREAD_GRAYSCALE);
+			Mat img = imread(filename, cv::IMREAD_COLOR);//IMREAD_GRAYSCALE
 			if(img.empty()){continue;}
-			image_set_top(img,20,8);
-			ImgInfo* imginfo = mat2imginfo(img);
+			//image_set_top(img,20,8);
+			//ImgInfo* imginfo = mat2imginfo(img);
+			ImgInfo* imginfo = (ImgInfo*)(&img);
 			//imginfo->angle = j*30;
 			try{
-				rs_oa_init();
-				rs_oa_append(imginfo, posinfo);
-				
+				//rs_oa_init();
+				//rs_oa_append(imginfo, posinfo);
+				rs_oa_get_result(imginfo, posinfo);
+
 				for (int i =0;i<5;++i){
 					if (!posinfo.pp_images[i]){continue;}
 					Mat tmp_img = imginfo2mat(posinfo.pp_images[i]);			
-					/*imshow("pic", tmp_img);
-					waitKey(-1);*/
+					imshow("pic", tmp_img);
+					waitKey(-1);
 
 					stringstream bbu;
 					bbu<<filename<<".rst_"<<i<<".jpg";
@@ -1283,7 +1286,101 @@ void test_oa_batch(){
 				std::cout<<"some error ..."<<endl;
 			}			
 			
-			imginfo_release(&imginfo);
+			//imginfo_release(&imginfo);
+			
+
+		
+	}
+	cv_release();
+}
+void test_rs_batch_reid(){
+
+	// 0 version;
+	char* ver = new char[10];
+	get_version(ver);
+	cout<<ver<<endl;
+	delete [] ver;
+	ver=0;
+	
+	//2 cv_init()
+	//char *conf_file = "D:\\private\\grafting_robot\\cpp_code\\demo\\demo\\gcv_conf.yml";
+	//char *conf_file = "D:\\grafting_robot\\cpp_code\\demo\\demo\\gcv_conf.yml";
+	char *conf_file = "E:\\projects\\grafting_robot\\cpp_code\\demo\\demo\\gcv_conf.yml";
+	int rst = cv_init(conf_file);
+	ConfigParam cp_ret;	
+	cv_get_param(cp_ret);	
+	cv_set_loglevel(0);
+	cv_init_image_saver();
+
+	//cp_ret.image_show=true;
+	//cv_set_param(cp_ret);
+	//namedWindow("pic", CV_WINDOW_NORMAL);
+	
+	string work_folder = "E:\\projects\\grafting_robot\\samples\\20221003";
+	//string work_folder = "D:\\private\\grafting_robot\\samples\\20220220\\rotate";
+	vector<cv::String>filenames;
+	cv::glob(work_folder, filenames);	
+	map<string, Mat> img_cache;
+	for(size_t idx=0; idx<filenames.size();++idx){
+			
+		    if(idx %2 ==0){
+				continue;
+			}
+			img_cache.clear();
+
+			string filename_pre = filenames[idx-1];
+			string filename = filenames[idx];
+			if(filename_pre.find(".rst_")!=string::npos){continue;}
+			if(filename.find(".rst_")!=string::npos){continue;}
+			//if(filename.find("\\249.bmp")==string::npos){continue;}
+			PositionInfo posinfo;
+			memset(&posinfo,0,sizeof(PositionInfo));
+			Mat img_pre = imread(filename_pre, cv::IMREAD_GRAYSCALE);
+			Mat img = imread(filename, cv::IMREAD_GRAYSCALE);//IMREAD_GRAYSCALE  IMREAD_COLOR
+			if(img_pre.empty() || img.empty()){continue;}
+			//image_set_top(img,20,8);
+			//ImgInfo* imginfo = mat2imginfo(img);
+			//ImgInfo* imginfo = (ImgInfo*)(&img);
+			//imginfo->angle = j*30;
+
+			//for DEBUG			
+			int m_width = img.cols/2;
+			int m_height = img.rows/2;
+			cv::resize(img, img, cv::Size(m_width,m_height ));
+			cv::resize(img_pre, img_pre, cv::Size(m_width,m_height ));
+			//for DEBUG end
+
+			string imgid_pre("1");
+			img_cache.insert(make_pair<string, Mat>(imgid_pre,img_pre ));
+
+			try{
+				//rs_oa_init();
+				//rs_oa_append(imginfo, posinfo);
+
+				CRootStockCutPointReid rs_reid(cp_ret,0);
+				rs_reid.cut_point_reid(NULL, 
+					img, 
+					imgid_pre.c_str(),
+					posinfo,
+					img_cache);				
+
+				for (int i =0;i<5;++i){
+					if (!posinfo.pp_images[i]){continue;}
+					Mat tmp_img = imginfo2mat(posinfo.pp_images[i]);			
+					imshow("pic", tmp_img);
+					waitKey(-1);
+
+					//stringstream bbu;
+					//bbu<<filename<<".rst_"<<i<<".jpg";
+					//string fnn = bbu.str();
+					//cv::imwrite(fnn,tmp_img);
+				}			
+			}
+			catch(...){
+				std::cout<<"some error ..."<<endl;
+			}			
+			
+			//imginfo_release(&imginfo);
 			
 
 		
@@ -1509,8 +1606,9 @@ int _tmain(int argc, _TCHAR* argv[])
 	//api test
 	//test_api_scion();	
 	//test_sc_batch();
-	test_rs_batch();
+	//test_rs_batch();
 	//test_rs_batch_camera();
+	test_rs_batch_reid();
 	//test_oa_batch();
 	//test_api_batch();
 	//drawline_rs();

+ 2 - 1
gcv_conf.yml

@@ -1,6 +1,6 @@
 %YAML:1.0
 conf_parameters:
-   image_show: 0
+   image_show: 1
    image_return: 1
    image_row_grid: 20
    image_col_grid: 100
@@ -13,6 +13,7 @@ conf_parameters:
    oa_y_flip: 0
    oa_morph_radius: 1
    oa_morph_iteration: 2
+   oa_min_leaf_area: 10000
    oa_min_hist_value: 5
    oa_morph_radius_base: 1
    oa_morph_iteration_base: 2

+ 25 - 14
graft_cv_api.cpp

@@ -11,11 +11,12 @@
 #include "logger.h"
 #include "utils.h"
 #include "imstorage_manager.h"
+#include "cut_point_rs_reid.h"
 
 extern CRITICAL_SECTION g_cs;
 namespace graft_cv
 {
-	char *g_version_str = "0.5.9.20";
+	char *g_version_str = "0.6.0";
 
 	//configure
 	string g_conf_file = "./gcv_conf.yml";	
@@ -34,9 +35,14 @@ namespace graft_cv
 	CImStoreManager* g_pImStore=0;
 
 	//implement
-	COptimalAnglePart g_oa = COptimalAnglePart(g_cp,&g_logger);
+	//COptimalAnglePart g_oa = COptimalAnglePart(g_cp,&g_logger);
+	CCotyledonAngle g_oa = CCotyledonAngle(g_cp,&g_logger);
 	CRootStockCutPoint g_rs_cp = CRootStockCutPoint(g_cp,&g_logger);
+	CRootStockCutPointReid g_rs_cp_reid = CRootStockCutPointReid(g_cp,&g_logger);
 	CScionCutPoint g_sc_cp = CScionCutPoint(g_cp,&g_logger);
+
+	//
+	map<string, Mat> g_img_cache;
 	
 
 	//0 log path
@@ -227,12 +233,12 @@ namespace graft_cv
 	};
 
 	//7
-	int rs_oa_init()
+	/*int rs_oa_init()
 	{
 		return g_oa.reset();		
-	};
+	};*/
 	//8
-	int rs_oa_append(
+	/*int rs_oa_append(
 		ImgInfo* imginfo,
 		PositionInfo& posinfo
 		)
@@ -255,14 +261,17 @@ namespace graft_cv
 			return 1;
 		}
 		return 0;
-	};
+	};*/
 
 	//9
-	int rs_oa_get_result(PositionInfo& posinfo)
+	int rs_oa_get_result(
+		ImgInfo* imginfo,
+		PositionInfo& posinfo
+		)
 	{
 		memset(&posinfo,0,sizeof(PositionInfo));
 		try{
-			double oa = g_oa.infer(posinfo);
+			double oa = g_oa.angle_recognize(imginfo, posinfo);
 			
 		}
 		catch(std::exception &err){
@@ -292,7 +301,8 @@ namespace graft_cv
 			g_rs_cp.up_point_detect(
 				imginfo,
 				Mat(),
-				posinfo
+				posinfo, 
+				g_img_cache
 				);
 		}
 		catch(std::exception &err){
@@ -312,15 +322,16 @@ namespace graft_cv
 	}
 
 	//11
-	/*int rs_cut_point_reid(ImgInfo*imginfo , double edge_length, PositionInfo& posinfo)
+	int rs_cut_point_reid(ImgInfo*imginfo , const char* pre_img_id, PositionInfo& posinfo)
 	{
 		memset(&posinfo,0,sizeof(PositionInfo));
 		try{
-			g_rs_cp.up_point_reid(
+			g_rs_cp_reid.cut_point_reid(
 				imginfo,
 				Mat(),
-				edge_length,
-				posinfo
+				pre_img_id,
+				posinfo,
+				g_img_cache
 				);
 		}
 		catch(std::exception &err){
@@ -336,7 +347,7 @@ namespace graft_cv
 			return 1;
 		}
 		return 0;		
-	}*/
+	}
 
 	//12
 	int sc_cut_point(

+ 5 - 4
graft_cv_api.h

@@ -64,7 +64,7 @@ GCV_API void get_version(char* buf);
 
 //11 砧木最优角度识别初始化,每一株均需初始化一次
 //  返回: 0- 正常; 其他- 失败 
-GCV_API int rs_oa_init();
+//GCV_API int rs_oa_init();
 
 //12  砧木最优角度识别,追加图像,要求图像的角度按顺序,第一帧图像角度为0,最后一帧图像角度为180
 //   每次追加图片,通过posinfo返回:
@@ -75,15 +75,16 @@ GCV_API int rs_oa_init();
 //            5)2帧图像:posinfo.pp_images[0]--砧木分割二值图像 (return image 为 true时)
 //                        posinfo.pp_images[1]--基质分割二值图像
 //   返回: 0- 正常; 1- 失败
-GCV_API int rs_oa_append(ImgInfo*, PositionInfo& posinfo);
+//GCV_API int rs_oa_append(ImgInfo*, PositionInfo& posinfo);
 
 //13 获取砧木最优角度
+//  ImgInfo* 为输出顶部拍照的图片
 //  返回posinfo.rs_oa,
 //      posinfo.rs_oa_base
 //      posinfo.rs_oa_stem_y_fork,
 //      posinfo.rs_oa_clamp_y_end,
 //   返回: 0- 正常; 1- 失败
-GCV_API int rs_oa_get_result(PositionInfo& posinfo);
+GCV_API int rs_oa_get_result(ImgInfo*, PositionInfo& posinfo);
 
 //14 砧木切割点识别,返回
 //       posinfo.rs_cut_edge_length
@@ -105,7 +106,7 @@ GCV_API int rs_cut_point(ImgInfo*, PositionInfo& posinfo);
 //               返回1张图片:图0:二值图像,含茎x-range,上切点(circle中心),下切点(circle中心),根点(circle中心)
 //                            
 //   返回: 0- 正常; 1- 失败
-//GCV_API int rs_cut_point_reid(ImgInfo*, double edge_length, PositionInfo& posinfo);
+GCV_API int rs_cut_point_reid(ImgInfo*, const char* pre_img_id, PositionInfo& posinfo);
 
 //16 穗苗上切割点识别,返回
 //		 posinfo.sc_cut_upoint_x;

+ 391 - 0
optimal_angle.cpp

@@ -811,4 +811,395 @@ double COptimalAnglePart::opt_angle_part_impl(
 	}
     return oa;
 }
+
+CCotyledonAngle::CCotyledonAngle(ConfigParam&cp, CGcvLogger* pLog)
+	:m_cparam(cp),
+	m_pLogger(pLog),	
+	m_imgId(""),
+	m_ppImgSaver(0),
+	m_height(0),
+	m_width(0)
+{
+}
+
+CCotyledonAngle::~CCotyledonAngle(void)
+{	
+}
+
+void CCotyledonAngle::clear_imginfo(){
+	if (m_pImginfoOpen){
+		imginfo_release(&m_pImginfoOpen);
+		m_pImginfoOpen=0;
+	}
+	if (m_pImginfoAngle){
+		imginfo_release(&m_pImginfoAngle);
+		m_pImginfoAngle=0;
+	}
+}
+int CCotyledonAngle::angle_recognize(
+	ImgInfo* imginfo,
+	PositionInfo& posinfo	
+	)
+{
+	//clear opencv windows
+	if(m_cparam.image_show){destroyAllWindows();}
+
+	clock_t t;
+	clock_t t0 = clock();	
+	m_imgId = getImgId(img_type::oa);	
+
+	/*if(m_pLogger){
+		m_pLogger->INFO(m_imgId + " cotyledon angle recognize begin.");
+	}	
+	if(!isvalid(imginfo)){
+		throw_msg(m_imgId+" invalid input image");
+	}
+	if(m_width==0 || m_height==0){
+		m_width = imginfo->width;
+		m_height = imginfo->height;
+	}	
+	if(m_pLogger){
+		stringstream buff;
+		buff<<m_imgId<<" load image\twidth="<<imginfo->width
+			<<"\theight="<<imginfo->height;
+		m_pLogger->INFO(buff.str());
+	}
+	Mat img = imginfo2mat(imginfo);*/
+
+
+
+	//for DEBUG
+	Mat img = *(Mat*)(imginfo);
+	m_width = img.cols/2;
+	m_height = img.rows/2;
+	cv::resize(img, img, cv::Size(m_width,m_height ));
+	//end DEBUG
+
+
+	//imshow_wait("src", img);
+	if(m_cparam.self_camera){
+		image_set_bottom(img,20,8);
+		if(m_pLogger){				
+			m_pLogger->DEBUG(m_imgId+" oa_recognize image set bottom with pixel value 20.");				
+		}
+	}
+	if(m_cparam.oa_y_flip){
+		flip(img,img,0);
+		if(m_pLogger){				
+			m_pLogger->DEBUG(m_imgId+" oa_recognize image y fliped.");				
+		}
+	}
+	if(m_ppImgSaver && *m_ppImgSaver){
+		(*m_ppImgSaver)->saveImage(img, m_imgId);
+		if(m_pLogger){				
+			m_pLogger->DEBUG(m_imgId+" oa_recognize after image save.");				
+		}
+	}
+	if(m_cparam.image_show){
+		Mat tmp_b = img.clone();			
+		imshow_wait("oa_image", tmp_b);
+	}
+
+	//1图像分割
+	this->img_segment(img);
+	if(m_cparam.image_show){					
+		imshow_wait("oa_image_bin", m_binImg);
+	}
+	Mat img_median, img_base, img_open, img_xor;
+	cv::medianBlur(m_binImg,img_median, 5);	
+
+	Mat kernel = getStructuringElement(
+			MORPH_ELLIPSE,
+			Size( 2*m_cparam.oa_morph_radius + 1, 2*m_cparam.oa_morph_radius+1),
+			Point( m_cparam.oa_morph_radius, m_cparam.oa_morph_radius)
+			);  
+	morphologyEx(
+		img_median,
+		img_base,
+		MORPH_CLOSE,
+		kernel,
+		Point(-1,-1),
+		1);
+
+	if(m_cparam.image_show){					
+		imshow_wait("img_base", img_base);
+	}
+
+	//2 找到2片叶子,计算各自重心,主方向,面积,横纵比,椭圆度,选择最优,计算需要旋转角度
+	int iter_step = 3;
+	int iter_open = 10;
+	
+	double leaf_area_th = (double)m_cparam.oa_min_leaf_area;
+	vector<vector<Point>> contours;
+	vector<Vec4i> hierarchy;
+	vector<t_leaf> leafs;
+
+	while( iter_open >0 && iter_open < 50){
+
+		morphologyEx(
+			img_base,
+			img_open,
+			MORPH_OPEN,
+			kernel,
+			Point(-1,-1),
+			iter_open);
+		imshow_wait("img_base", img_base);
+		imshow_wait("img_open", img_open);
+		m_openImg = img_open.clone();
+	
+		contours.clear();
+		hierarchy.clear();
+		leafs.clear();
+		findContours(img_open,contours,hierarchy,RETR_EXTERNAL,CHAIN_APPROX_NONE);
+		for(int i=0;i<contours.size();++i){
+			double area = contourArea(contours[i]);
+			if (area < leaf_area_th){continue;}			
+			RotatedRect minrect_ellips = fitEllipse(contours[i]);
+			Moments moment = moments(contours[i]);
+			double centx = moment.m10 / moment.m00;
+			double centy = moment.m01 / moment.m00;
+
+			t_leaf leaf;
+			leaf.area = area;
+			leaf.centx = centx;
+			leaf.centy = centy;			
+			leaf.minrect_ellips = minrect_ellips;
+			leaf.contours = contours[i];
+
+			leafs.push_back(leaf);
+		}	
+
+		if(leafs.size()<2){
+			iter_open += iter_step;
+		}
+		if(leafs.size()>2){
+			iter_open -= iter_step;
+		}
+		if(leafs.size()==2){
+			break;
+		}
+
+	}
+	if(leafs.size()!=2){
+		throw_msg(m_imgId+" leafs detect failed");
+	}
+
+	//select one leaf for angle detection
+	double min_err = 1.0e16;
+	int min_err_idx = -1;
+	for(size_t i =0; i<leafs.size();++i){		
+		double err = fit_error_ellipse(leafs[i].minrect_ellips, leafs[i].contours);
+		if(err<min_err){
+			min_err = err;
+			min_err_idx = i;
+		}
+	}
+	if(min_err_idx<0){
+		throw_msg(m_imgId+" no normal leaf");
+	}
+
+	//计算旋转角度
+	double all_area = leafs[0].area+leafs[1].area;
+    double r0 = leafs[0].area/all_area;
+    double all_centx = leafs[0].centx * r0 + leafs[1].centx * (1-r0);
+    double all_centy = leafs[0].centy * r0 + leafs[1].centy * (1 - r0);
+
+	float tar_angle1 = 90 + leafs[min_err_idx].minrect_ellips.angle;//长轴与x轴的夹角(图像坐标系下),90-270间
+	float tar_angle2 =  leafs[min_err_idx].minrect_ellips.angle - 90;//-90 ~ 90
+	if(tar_angle2<0){
+		tar_angle2+=360;
+	}
+	//叶尖方向(相对于两个叶片中心)
+	float leaf_apex_angle = atan2(leafs[min_err_idx].centy - all_centy, leafs[min_err_idx].centx - all_centx) * 180 / CV_PI;
+	if(leaf_apex_angle<0){
+		leaf_apex_angle += 360;
+	}
+
+	float diff1 = fabs(leaf_apex_angle - tar_angle1);
+	if(diff1>180){
+		diff1 = 360 - diff1;
+	}
+	float diff2 = fabs(leaf_apex_angle - tar_angle2);
+	if(diff2>180){
+		diff2 = 360 - diff2;
+	}
+	float rot_angle = tar_angle1;
+	if(diff2 < diff1){
+		rot_angle = tar_angle2;
+	}
+
+	rot_angle -= 180;//需要旋转的角度
+
+	//draw image
+	Mat ellipse_img = m_openImg.clone();
+	for(size_t j=0; j<leafs.size(); ++j){
+		stringstream buff;
+		buff<<"wa="<<(float)((int)(leafs[j].minrect_ellips.angle*10.0)/10.0);	
+		Scalar color = Scalar(100);
+		if(j == min_err_idx){
+			color[0] = 180;
+			buff<<",rot="<<(float)((int)(rot_angle*10.0)/10.0);
+		}
+			
+		Point2f vertices[4];
+		leafs[j].minrect_ellips.points(vertices);
+		for (int i = 0; i < 4; i++)
+			line(ellipse_img, vertices[i], vertices[(i+1)%4], color);
+					
+		putText(ellipse_img,buff.str(),leafs[j].minrect_ellips.center, 0,0.5,color);
+			
+	}
+	if(m_cparam.image_show){		
+		imshow_wait("ellipse_img", ellipse_img);			
+	}
+
+	// 3 返回结果	
+	posinfo.rs_oa=(double)rot_angle;
+    
+	if(m_pLogger){
+		stringstream buff;
+		buff<<m_imgId<<" detect rotat angle "<<posinfo.rs_oa;
+		m_pLogger->INFO(buff.str());
+	}
+	//4 返回图像
+	if(m_cparam.image_return){
+		this->clear_imginfo();
+		m_pImginfoOpen = mat2imginfo(m_openImg);
+		m_pImginfoAngle = mat2imginfo(ellipse_img);
+		posinfo.pp_images[0]=m_pImginfoOpen;
+		posinfo.pp_images[1]=m_pImginfoAngle;
+
+		if(m_ppImgSaver && *m_ppImgSaver){
+			(*m_ppImgSaver)->saveImage(m_binImg, m_imgId+"_rst_0");	
+			(*m_ppImgSaver)->saveImage(ellipse_img, m_imgId+"_rst_1");
+			if(!m_grayImg.empty()){
+				(*m_ppImgSaver)->saveImage(m_grayImg, m_imgId+"_rst_2");
+			}
+		}
+	}
+	
+	if(m_pLogger){
+		m_pLogger->INFO(m_imgId + " oa_recognize image finished.");
+	}	
+	return 0;
+}
+double CCotyledonAngle::fit_error_ellipse(
+	RotatedRect& ellipse_rect,
+	vector<Point>& pts)
+{
+	/*
+	  ref:https://blog.csdn.net/fangyan90617/article/details/89486331
+	  ellipse common function: A * x^2 + B * x*y + C * y^2 + D * x + E * y + F = 0
+	  reformed function: 
+	              A * x'^2 + B * x'*y' + C * y'^2 + F = 0
+				  x' = x - x0
+				  y' = y - y0
+	  from ellipse_rect, calculate A,B,C,F
+
+	*/
+	if(pts.size()==0){
+		return 1.0e6;
+	}
+	float A, B,C, F;
+	get_ellipse_param(ellipse_rect, A, B,C, F);
+	double mean_err = 0.0;
+	double x,y,err;
+	for(size_t i=0; i<pts.size(); ++i){
+		x = (double)pts[i].x - ellipse_rect.center.x;
+		y = (double)pts[i].y - ellipse_rect.center.y;
+		err = A * x * x + B * x * y + C * y * y + F;
+		mean_err += err*err;
+	}
+	mean_err /= (double)pts.size();
+	mean_err = sqrt(mean_err);
+	return mean_err;	
+}
+void CCotyledonAngle::get_ellipse_param(
+	RotatedRect&ellipse_rect,
+	float&A, 
+	float&B, 
+	float&C, 
+	float&F)
+{
+	float theta = ellipse_rect.angle * CV_PI / 180.0;
+	float a = ellipse_rect.size.width / 2.0;
+	float b = ellipse_rect.size.height / 2.0;
+	A = a * a * sin(theta) * sin(theta) + b * b * cos(theta) * cos(theta);
+	B = (-2.0) * (a * a - b * b) * sin(theta) * cos(theta);
+	C = a * a * cos(theta) * cos(theta) + b * b * sin(theta) * sin(theta);
+    F = (-1.0) * a * a * b * b;
+}
+void CCotyledonAngle::img_segment(Mat&img)
+{
+	if(img.channels()!=1){
+		//color image ,bgr, for testing
+		/*Mat img_gray,b_img;
+		cvtColor(img,img_gray,COLOR_BGR2GRAY);
+		double th = threshold(
+			img_gray,
+			b_img,
+			255,
+			255,
+			THRESH_OTSU
+			);
+	
+		b_img/= 255;*/
+
+		Mat img_hsv, b_img_hsv;		
+		cvtColor(
+			img, 
+			img_hsv,
+			COLOR_BGR2HSV
+			);
+
+		inRange(
+			img_hsv,
+			Scalar(30 , 30, 30),
+			Scalar(75, 255, 255),
+			b_img_hsv
+			);
+
+		/*imshow_wait("oa_image_range", b_img_hsv);
+		b_img_hsv/=255;
+		b_img = b_img.mul(b_img_hsv);	*/
+
+		//int morph_size = 1;
+		Mat kernel = getStructuringElement(
+			MORPH_RECT,
+			Size( 2*m_cparam.oa_morph_radius + 1, 2*m_cparam.oa_morph_radius+1),
+			Point( m_cparam.oa_morph_radius, m_cparam.oa_morph_radius)
+			);  
+		morphologyEx(
+			b_img_hsv,
+			m_binImg,
+			MORPH_CLOSE,
+			kernel,
+			Point(-1,-1),
+			m_cparam.oa_morph_iteration);
+		
+	}
+	else{
+		//from imginfo image, gray image 
+		//int morph_size = 1;	
+
+		Mat b_img;
+		m_grayImg = img.clone();
+		double th = threshold(img, b_img, 255, 255,THRESH_OTSU);
+		Mat kernel = getStructuringElement( 
+			MORPH_RECT, 
+			Size( 2*m_cparam.oa_morph_radius + 1, 2*m_cparam.oa_morph_radius+1 ), 
+			Point( m_cparam.oa_morph_radius, m_cparam.oa_morph_radius)
+			);    
+		morphologyEx(
+			b_img, 
+			m_binImg, 
+			MORPH_OPEN,
+			kernel,
+			Point(-1,-1),
+			m_cparam.oa_morph_iteration
+			);
+	}	
+}
+
+
 };

+ 53 - 0
optimal_angle.h

@@ -124,4 +124,57 @@ protected:
 		vector<double>&y,
 		bool is_base=false);
 };
+
+// CCotyledonAngle
+// 顶部拍照砧木,通过子叶角度识别,提供机构旋转角度
+class CCotyledonAngle{
+public:
+
+	CCotyledonAngle(ConfigParam&, CGcvLogger* pLog=0);
+	virtual ~CCotyledonAngle();
+	int angle_recognize(ImgInfo*, 
+		PositionInfo& posinfo
+		);
+	void set_image_saver(CImStoreManager** ppis)
+	{
+		m_ppImgSaver=ppis;
+	}
+
+protected:
+	//global configure parameters
+	ConfigParam& m_cparam;
+	CGcvLogger* m_pLogger;
+
+	string m_imgId;
+	CImStoreManager** m_ppImgSaver;
+
+
+	//all image size
+	Mat m_grayImg;
+	Mat m_binImg;
+	int m_width;
+	int m_height;
+	Mat m_openImg; // for save	
+
+	//返回图片,用于调试
+	ImgInfo* m_pImginfoOpen;//fork-y, right-x
+	ImgInfo* m_pImginfoAngle;//corners, reference-point, candidate box
+	
+
+	//
+	typedef struct {
+		double area;
+		double centx;
+		double centy;		
+		RotatedRect minrect_ellips;
+		vector<Point> contours;
+	} t_leaf;
+
+	void img_segment(Mat&img);
+	double fit_error_ellipse(RotatedRect&ellipse_rect,vector<Point>& pts);
+	void get_ellipse_param(RotatedRect&ellipse_rect,   //input
+		float&A, float&B, float&C, float&F); //output
+	void clear_imginfo();
+};
+
 };