//#include "StdAfx.h" #include #include "optimal_angle.h" #include "utils.h" #include namespace graft_cv{ COptimalAngle::COptimalAngle(ConfigParam&cp, CGcvLogger* pLog/*=0*/) :m_cparam(cp), m_pLogger(pLog), m_pImginfo(0), m_pImginfoBase(0), m_height(0), m_width(0), m_patch_id(""), m_imgIndex(0), m_imgId(""), m_ppImgSaver(0) { } COptimalAngle::~COptimalAngle(void) { this->clear_imginfo(); } void COptimalAngle::clear_imginfo(){ if (m_pImginfo){ imginfo_release(&m_pImginfo); m_pImginfo=0; } if (m_pImginfoBase){ imginfo_release(&m_pImginfoBase); m_pImginfoBase=0; } } int COptimalAngle::reset() { if(m_pLogger){ m_pLogger->DEBUG("optimal angle reset begin."); } m_fork_ys.clear(); m_end_ys.clear(); m_widths.clear(); m_an2width.clear(); m_an2widthBase.clear(); m_width=0; m_height=0; m_patch_id=""; m_imgIndex=0; if(m_pLogger){ m_pLogger->DEBUG("optimal angle reset finished."); } return 0; } int COptimalAngle::append( ImgInfo* imginfo, PositionInfo& posinfo ) { //clear opencv windows if(m_cparam.image_show){destroyAllWindows();} clock_t t; clock_t t0 = clock(); if(m_imgIndex==0){ //new patch m_patch_id = getImgId(img_type::oa); } m_imgId = getImgIdOa(m_patch_id,m_imgIndex); m_imgIndex+=1; if(m_pLogger){ m_pLogger->INFO(m_imgId + " append image 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; } int angle = imginfo->angle; if(m_pLogger){ stringstream buff; buff<DEBUG(m_imgId+" oa_append 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_append image y fliped."); } } if(m_ppImgSaver && *m_ppImgSaver){ (*m_ppImgSaver)->saveImage(img, m_imgId); if(m_pLogger){ m_pLogger->DEBUG(m_imgId+" oa_append after image save."); } } if(m_cparam.image_show){ Mat tmp_b = img.clone(); cv::line(tmp_b,Point(0,m_cparam.oa_clip_y_min), Point(tmp_b.cols,m_cparam.oa_clip_y_min),Scalar(200),3); cv::line(tmp_b,Point(0,m_cparam.oa_clip_y_max), Point(tmp_b.cols,m_cparam.oa_clip_y_max),Scalar(200),3); imshow_wait("oa_sub_image_range", tmp_b); } Rect rect_rs(Point(0,0),Point(img.cols,m_cparam.oa_clip_y_min)); //Rect rect_base(Point(0,m_cparam.oa_clip_y_max),Point(img.cols,img.rows)); Mat rs_img = img(rect_rs); //Mat bs_img = img(rect_base); // 1 rootstock image processing // 1.1 calculate the image, get binary image and get object width int min_idx, max_idx; vector hist_col; int width = imgproc(rs_img, hist_col, min_idx, max_idx); m_an2width.insert(make_pair(angle, width)); posinfo.rs_oa_width=width; if(m_cparam.image_show){ Mat hist_col_img; hist2image(hist_col,hist_col_img,1,m_cparam.image_col_grid,m_cparam.image_row_grid); imshow_wait("oa_hist_col", hist_col_img); } if(m_pLogger){ stringstream buff; buff<hist_row_leaf; mat_histogram(m_binImg,hist_row_leaf,1); for(size_t i=0;i=m_cparam.oa_min_hist_value && r0<0){r0=i;} if(hist_row_leaf[i]>=m_cparam.oa_min_hist_value){r1=i;} } if(r0<0 || r1<0){throw_msg(string(m_imgId+" invalid image, with no object in binary image"));} vector hist_col_yfork; mat_histogram_yfork(m_binImg,hist_col_yfork,r0,r1); if(m_cparam.image_show){ Mat tmp; hist2image(hist_col_yfork,tmp,1,m_cparam.image_col_grid,m_cparam.image_row_grid); imshow_wait("oa_hist_col_yfork", tmp); } int x0,x1,stem_x0, stem_x1; int centx = (int)((min_idx+ max_idx)/2.0); get_stem_x_range_oa( hist_col_yfork, m_cparam.oa_col_th_ratio, m_cparam.oa_stem_x_padding, centx, width, x0, x1, stem_x0, stem_x1); if(m_pLogger){ stringstream buff; buff<(float)m_cparam.timeout_append){ throw_msg(m_imgId+" time out"); } } vector hist_row; mat_histogram(m_binImg,hist_row,1,-1,-1,x0,x1+1); if(m_cparam.image_show){ Mat hist_row_img; hist2image(hist_row,hist_row_img, 0,m_cparam.image_col_grid,m_cparam.image_row_grid); imshow_wait("oa_hist_row", hist_row_img); } // 1.2.2 茎分叉点检测,y方向检测 int stem_fork_y=-1,stem_end_y=-1, stem_dia=-1; get_stem_y_fork( hist_row, m_cparam.oa_row_th_ratio, m_cparam.oa_stem_dia_min, m_cparam.oa_stem_fork_y_min, m_cparam.oa_stem_dia_mp, stem_fork_y, stem_end_y, stem_dia); if(m_pLogger){ stringstream buff; buff< hist_col_base; int width_base = imgprocBase(bs_img, hist_col_base, min_idx_base, max_idx_base); m_an2widthBase.insert(make_pair(angle, width_base)); posinfo.rs_oa_width_base = width_base; if(m_pLogger){ stringstream buff; buff<INFO(m_imgId + " append image finished."); } return 0; } double COptimalAngle::infer(PositionInfo& posinfo){ if(m_pLogger){ m_pLogger->INFO(m_patch_id + " optimal angle infer begin."); } double oa = this->angle_fit(this->m_an2width); /*double oa_base = 0.0; try{ oa_base = this->angle_fit_base(this->m_an2widthBase); } catch(...){ if(m_pLogger){ m_pLogger->WARNING("angle_fit_base() error"); } }*/ posinfo.rs_oa = oa; //posinfo.rs_oa_base=oa_base; if(m_fork_ys.size()==0 ||m_end_ys.size()==0){ throw_msg(m_patch_id+ " empty fork_ys or end_ys, NEED append image"); } vectordes_idx = sort_indexes_e(m_widths,false); int e_idx = (int)((float)m_end_ys.size() * 0.5); sort(m_end_ys.begin(), m_end_ys.end()); double fork_y = ((m_fork_ys[des_idx[0]] + m_fork_ys[des_idx[1]])/2.0); double end_y = m_end_ys[e_idx]; if(m_pLogger){ stringstream buff; buff<& hist, int& min_idx, int& max_idx ) { //int morph_size = 1; //int min_hist_value = 10; hist.clear(); Mat b_img; double th = threshold( img, b_img, 255, 255, THRESH_OTSU);//+THRESH_BINARY_INV 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_CLOSE, kernel, Point(-1,-1), m_cparam.oa_morph_iteration); mat_histogram(m_binImg, hist, 0); /*int min_idx, max_idx;*/ min_idx = hist.size(); max_idx = 0; vector::const_iterator it0 = hist.begin(); for(vector::const_iterator it = hist.begin();it!=hist.end();++it){ if(*it>=m_cparam.oa_min_hist_value){ int idx = it - it0; if(idx max_idx) {max_idx = idx;} } } if(max_idx<=min_idx){ throw_msg(m_imgId+" invalid binary image, not exist valid objects"); } if(m_cparam.image_show){ Mat tmp = m_binImg.clone(); cv::line(tmp,Point(min_idx,0), Point(min_idx,tmp.rows),Scalar(156),3); cv::line(tmp,Point(max_idx,0), Point(max_idx,tmp.rows),Scalar(156),3); imshow_wait("oa_bin", tmp); } int width = max_idx-min_idx+1; return width; } int COptimalAngle::imgprocBase( Mat& img, vector& hist, int& min_idx, int& max_idx ) { //int morph_size = 1; //int min_hist_value = 10; hist.clear(); Mat b_img; double th = threshold( img, b_img, 255, 255, THRESH_OTSU+THRESH_BINARY_INV); Mat kernel = getStructuringElement( MORPH_RECT, Size( 2*m_cparam.oa_morph_radius_base+1, 2*m_cparam.oa_morph_radius_base+1), Point( m_cparam.oa_morph_radius_base, m_cparam.oa_morph_radius_base) ); morphologyEx( b_img, m_binImgBase, MORPH_OPEN, kernel, Point(-1,-1), m_cparam.oa_morph_iteration_base); if(m_cparam.image_show){ destroyAllWindows(); imshow_wait("oa_bin_base", m_binImgBase); } mat_histogram(m_binImgBase, hist, 0); /*int min_idx, max_idx;*/ min_idx = hist.size(); max_idx = 0; vector::const_iterator it0 = hist.begin(); for(vector::const_iterator it = hist.begin()+5;it!=hist.end();++it){ if(*it>=m_cparam.oa_min_hist_value_base){ int idx = it - it0; if(idx max_idx) {max_idx = idx;} } } if(max_idx<=min_idx){ throw_msg(m_imgId+" invalid binary image, not exist valid objects"); } int width = max_idx-min_idx+1; return width; } //angle_fit()方法 //append()加载的图片,满足下述条件:0-180度采样,其中0和180度都有数值 double COptimalAngle::angle_fit(map& an2width,bool is_base/*=false*/) { if(an2width.size()<3){ throw_msg(m_patch_id+" not enough valid images, NEED append image"); } double oa = 0.0; std::vector x;//angle std::vector y;//width map::iterator it = an2width.begin(); for(it=an2width.begin(); it!=an2width.end(); ++it) { x.push_back((double)(it->first)); y.push_back((double)(it->second)); } //mean value of first and latest y double y_mu = 0.5*(y[0]+ y[y.size()-1]); y[0] = y[y.size()-1] = y_mu; size_t ap_times = x.size()-1; for (size_t i=0; i::iterator smallest = min_element(begin(y), end(y)); size_t min_idx, max_idx; min_idx = distance(y.begin(), smallest); for(size_t i = min_idx+1;ixx; vectoryy; for(size_t i=min_idx;i<=max_idx;++i){ xx.push_back(x[i]); yy.push_back(y[i]); } // fit quadratic function with opencv Mat A = cv::Mat::zeros(cv::Size(3,xx.size()), CV_64FC1); for(int i=0; i(i,0) = 1; A.at(i,1) = xx[i]; A.at(i,2) = xx[i] * xx[i]; } Mat b = cv::Mat::zeros(cv::Size(1,yy.size()), CV_64FC1); for(int i = 0; i(i,0) = yy[i]; } Mat c, d; c = A.t() * A; d = A.t() * b; Mat result = cv::Mat::zeros(cv::Size(1,3),CV_64FC1); cv::solve(c,d,result); double a0 = result.at(0, 0); double a1 = result.at(1, 0); double a2 = result.at(2, 0); if(fabs(a2)<1.0e-6){ throw_msg(m_patch_id+" a2 = 0 in solver"); } oa = -a1 / a2 / 2; if(oa >180.0){oa -= 180.0;} return oa; } double COptimalAngle::angle_fit_base(map&){ return 0.0; } /////////////////////////////////////////////////////////////////////////////// COptimalAnglePart::COptimalAnglePart(ConfigParam&cp, CGcvLogger* pLog/*=0*/) :COptimalAngle(cp,pLog) { } COptimalAnglePart::~COptimalAnglePart() { COptimalAngle::clear_imginfo(); } // append()加载的图片,满足不是0-180度采样,但保证部分采样中一定存在最大值, // 直接利用这些数值进行二次函数的拟合 double COptimalAnglePart::angle_fit(map& an2width,bool is_base/*=false*/) { double oa = 0.0; std::vector xx;//angle std::vector yy;//width map::iterator it = an2width.begin(); for(it=an2width.begin(); it!=an2width.end(); ++it) { xx.push_back((double)(it->first)); yy.push_back((double)(it->second)); } //sort for(size_t i =0;iINFO(buff.str()); } if(an2width.size()<3){ throw_msg(m_patch_id+" not enough valid images, NEED append image"); } // angle field check if((xx.back()-xx.front())<90){ if(m_pLogger){ stringstream buff; buff<ERRORINFO(buff.str()); } throw_msg(m_patch_id+" not enough angle field, small than 90 degree"); } oa = opt_angle_part_impl(xx,yy,is_base); if(m_pLogger){ stringstream buff; buff<INFO(buff.str()); } return oa; } double COptimalAnglePart::angle_fit_base(map& an2width){ double oa = 0.0; //find the maximun angle oa = angle_fit(an2width,true); //get the minimum angle if(oa>45.0){oa -= 45.0;} else{oa +=45.0;} return oa; } void COptimalAnglePart::zero_cross_detect( vector&d_sign, //input bool& is_local_max, // whether exists local max int& local_limit_idx // local limit index of original vector ) { int min_zc_idx = -1; int max_zc_idx = -1; bool b_local_min =false; bool b_local_max =false; bool is_all_down=true; bool is_all_up = true; for (size_t i=0; i0){is_all_down=false;} if (d_sign[i] <0){is_all_up=false;} if (i == d_sign.size()-1){continue;} if (d_sign[i] >=0 && d_sign[i+1] <=0){ //is_local_max=true; max_zc_idx = i+1; b_local_max=true; } if (d_sign[i] <=0 && d_sign[i+1] >=0){ //is_local_max=false; min_zc_idx=i+1; b_local_min=true; } } if(b_local_min){ is_local_max=false; local_limit_idx = min_zc_idx; return; } if(b_local_max){ is_local_max=true; local_limit_idx = max_zc_idx; return; } if (is_all_down){ is_local_max=true; local_limit_idx=0; return; } if( is_all_up){ is_local_max=true; local_limit_idx=d_sign.size(); return; } is_local_max=true; local_limit_idx=-1; return; } double COptimalAnglePart::limit_min_angle( vector&x, //sorted, ascending vector&y, size_t min_idx ) { size_t i = min_idx; if(fabs(x[i+1]-x[i])<1.0e-6 || fabs(x[i]-x[i-1])<1.0e-6){ throw_msg(m_patch_id+" limit_min_angle, diff_x < 1.0e-6"); } double k_r = (y[i+1]-y[i])/(x[i+1]-x[i]); double k_l = (y[i]-y[i-1])/(x[i]-x[i-1]); double oa = 0.0; double b_r = 0.0; double b_l = 0.0; double cross_x = 0; if (fabs(k_r) > fabs(k_l)){ // right line b_r = y[i]-k_r*x[i]; b_l = y[i-1] + k_r*x[i-1]; cross_x = line_cross(k_r,b_r,-k_r,b_l); oa = cross_x; } else{ //# left line b_l = y[i]-k_l*x[i]; b_r = y[i+1] + k_l*x[i+1]; cross_x = line_cross(k_l,b_l,-k_l,b_r); oa = cross_x; } return oa; } double COptimalAnglePart::line_cross(double k1,double b1,double k2,double b2) { double x = (b2-b1) / (k1-k2); return x; } double COptimalAnglePart::opt_angle_part_impl( vector&x, //sorted, ascending vector&y, bool is_base/*=false*/) { // sign of difference of y vector dy_sign; for(size_t i=1; i0){ dy_sign.push_back(1); } else{ if(dd<0){dy_sign.push_back(-1);} else{dy_sign.push_back(0);} } } //local limit search bool is_local_max = false; int local_limit_idx = -1; zero_cross_detect(dy_sign,is_local_max,local_limit_idx); if(local_limit_idx<0){ throw_msg(m_patch_id+" not found local limit"); } // optimal angle calculation vector xx; vector yy; double oa=0.0; if(is_local_max){ //存在局部最大 if( local_limit_idx == 0 || local_limit_idx==y.size()-1){ //#单调 oa = x[local_limit_idx]; /*if (local_limit_idx == 0){ for(size_t i=0;i<3;++i){ xx.push_back(x[i]); yy.push_back(y[i]); } } else { for(size_t i=y.size()-3;iINFO(m_patch_id+ " angle fit -- signle --- max_angle"); } } else{ // 极大值 for(size_t i=local_limit_idx-1;iINFO(m_patch_id+" angle fit -- local max --- 3p insert"); } } } else{ // 极小值 oa = limit_min_angle(x,y,local_limit_idx); if(is_base){ //for base, offset 45 degree if(oa >45){oa = oa-45.0;} else{oa = oa+45.0;} } else{ // for rootstock, offset 90 degree if(oa >90){oa = oa-90.0;} else{oa = oa+90.0;} } if(m_pLogger){ m_pLogger->INFO(m_patch_id+" angle fit -- local min --- 3p insert"); } } return oa; } };