utils.cpp 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860
  1. #include "utils.h"
  2. #include <opencv.hpp>
  3. #include <time.h>
  4. #include <algorithm>
  5. #include <math.h>
  6. //#include "fork_rs.h"
  7. namespace graft_cv{
  8. cv::Mat imginfo2mat(ImgInfo* imginfo)
  9. {
  10. assert(imginfo->data);
  11. assert(imginfo->height>0 && imginfo->width>0);
  12. assert(imginfo->channel == 1 || imginfo->channel == 3);
  13. cv::Mat m;
  14. if (imginfo->channel == 1) {
  15. m = cv::Mat(imginfo->height, imginfo->width, CV_8UC1);
  16. }
  17. if (imginfo->channel == 3) {
  18. m = cv::Mat(imginfo->height, imginfo->width, CV_8UC3);
  19. }
  20. for (int h = 0; h<imginfo->height; ++h)
  21. {
  22. memcpy((void*)(m.ptr(h)),
  23. (void*)(imginfo->data + h*imginfo->width * imginfo->channel),
  24. imginfo->width * imginfo->channel);
  25. };
  26. return m;
  27. };
  28. ImgInfo* mat2imginfo(const cv::Mat&img, ImgInfo** ppimginfo){
  29. assert(img.channels() == 1 || img.channels() == 3);
  30. bool is_usable = false;
  31. ImgInfo* imginfo = 0;
  32. if (ppimginfo != 0 && isvalid(*ppimginfo) && (*ppimginfo)->channel == img.channels() &&
  33. (*ppimginfo)->height == img.rows && (*ppimginfo)->width == img.cols) {
  34. is_usable = true;
  35. }
  36. else {
  37. if (ppimginfo != 0 && isvalid(*ppimginfo)) {
  38. imginfo_release(ppimginfo);
  39. }
  40. }
  41. if (is_usable) {
  42. imginfo = *ppimginfo;
  43. }
  44. else {
  45. imginfo = new ImgInfo();
  46. imginfo->channel = img.channels();
  47. imginfo->height = img.rows;
  48. imginfo->width = img.cols;
  49. imginfo->data = 0;
  50. imginfo->data = new byte[imginfo->height * imginfo->width * imginfo->channel];
  51. }
  52. memset(imginfo->data, 0, imginfo->height * imginfo->width * imginfo->channel);
  53. //IplImage ipl_img = cvIplImage(img);
  54. for (int i = 0; i<imginfo->height; ++i) {
  55. memcpy(imginfo->data + i*imginfo->width * imginfo->channel, img.ptr(i), imginfo->width * imginfo->channel);
  56. }
  57. return imginfo;
  58. };
  59. void imginfo_release(ImgInfo**ppImgInfo){
  60. ImgInfo* pImgInfo = *ppImgInfo;
  61. if(pImgInfo->data){
  62. delete [] pImgInfo->data;
  63. pImgInfo->data=0;
  64. }
  65. delete pImgInfo;
  66. pImgInfo = 0;
  67. };
  68. bool isvalid(const ImgInfo*imginfo)
  69. {
  70. if(!imginfo){return false;}
  71. if(!imginfo->data){return false;}
  72. if(imginfo->height*imginfo->width<=0){return false;}
  73. return true;
  74. }
  75. void image_set_bottom(
  76. cv::Mat& img,
  77. unsigned char value,
  78. int rows_cnt)
  79. {
  80. for(size_t r=(img.rows-rows_cnt);r<img.rows;++r){
  81. for(size_t c=0;c<img.cols;++c){
  82. img.at<unsigned char>(r,c) = value;
  83. }
  84. }
  85. }
  86. void image_set_top(
  87. cv::Mat& img,
  88. unsigned char value,
  89. int rows_cnt)
  90. {
  91. for(size_t r=0;r<rows_cnt;++r){
  92. for(size_t c=0;c<img.cols;++c){
  93. img.at<unsigned char>(r,c) = value;
  94. }
  95. }
  96. }
  97. void image_draw_line(
  98. cv::Mat& img,
  99. int x0,int y0,
  100. int x1,int y1)
  101. {
  102. cv::Point p0,p1;
  103. if(x0==x1){
  104. p0.x=x0;
  105. p0.y=0;
  106. p1.x=x0;
  107. p1.y=img.rows-1;
  108. }
  109. else{
  110. double k = (double)(y1-y0)/(double)(x1-x0);
  111. double b = (double)y0 - k * x0;
  112. p0.x=0;
  113. p0.y=b;
  114. p1.x=img.cols;
  115. p1.y = (int)(b + k * p1.x);
  116. }
  117. cv::line(img,p0,p1, cv::Scalar(255,255,255));
  118. }
  119. string currTime(){
  120. char tmp[64];
  121. struct tm ptime;
  122. time_t time_seconds = time(0);
  123. localtime_s(&ptime, &time_seconds);
  124. strftime(
  125. tmp,
  126. sizeof(tmp),
  127. "%Y-%m-%d %H:%M:%S",
  128. &ptime
  129. );
  130. return tmp;
  131. }
  132. // function getImgId
  133. // input im_type, img_type
  134. static unsigned int ID_COUNTER = 0;
  135. string getImgId(int im_type)
  136. {
  137. char tmp[64];
  138. struct tm ptime;
  139. time_t time_seconds = time(0);
  140. localtime_s(&ptime, &time_seconds);
  141. strftime(
  142. tmp,
  143. sizeof(tmp),
  144. "%Y%m%d%H%M%S",
  145. &ptime
  146. );
  147. unsigned int id_serial = ID_COUNTER % 100;
  148. stringstream buff;
  149. buff<<tmp;
  150. buff.width(2);
  151. buff.fill('0');
  152. buff<<id_serial;
  153. buff<<"_"<<im_type;
  154. ID_COUNTER+=1;
  155. return buff.str();
  156. }
  157. string getImgIdOa(string iid, int im_idx)
  158. {
  159. stringstream buff;
  160. buff<<im_idx;
  161. return iid+"_"+buff.str();
  162. }
  163. inline void throw_msg(string& msg){
  164. stringstream buff;
  165. buff<<"Error:"<<msg<<"\tfile:"<<__FILE__<<"\tline:"<<__LINE__;
  166. throw(buff.str());
  167. }
  168. void drawgrid(
  169. cv::Mat&img,
  170. int grid_col/*=50*/,
  171. int grid_row/*=50*/,
  172. int line_thick/*=2*/
  173. )
  174. {
  175. if(img.empty()){return;}
  176. //horizontal grid
  177. for(int row=img.rows-1; row>=0; row-=grid_row){
  178. cv::line(img, cv::Point(0,row), cv::Point(img.cols-1,row), cv::Scalar(100),line_thick);
  179. }
  180. //veritcal grid
  181. for(int col=0; col<img.cols; col+=grid_col){
  182. cv::line(img, cv::Point(col,0), cv::Point(col,img.rows-1), cv::Scalar(100),line_thick);
  183. }
  184. }
  185. void hist2image(
  186. vector<int>& hist,
  187. cv::Mat&img,
  188. int aix, /*=1*/
  189. int grid_col/*=50*/,
  190. int grid_row/*=50*/
  191. )
  192. {
  193. vector<int>::iterator maxit = max_element(begin(hist),end(hist));
  194. if( grid_col<10){grid_col=10;}
  195. if( grid_row<10){grid_row=10;}
  196. // aix = 0, column hist, aix=1, row histogtam
  197. if (aix==0){
  198. img = cv::Mat::zeros(hist.size(),*maxit, CV_8UC1);
  199. drawgrid(img,grid_col, grid_row,1);
  200. for(size_t i=0; i<hist.size();++i){
  201. if(i>=img.rows){break;}
  202. for(size_t j=0;j<=hist[i];++j){
  203. if(j>=img.cols){break;}
  204. img.at<unsigned char>(i,j)=255;
  205. }
  206. }
  207. }
  208. else{
  209. img = cv::Mat::zeros(*maxit,hist.size(), CV_8UC1);
  210. drawgrid(img,grid_col, grid_row,1);
  211. for(size_t i=0; i<hist.size();++i){
  212. int y = *maxit-hist[i]-1;
  213. if (y<0){y=0;}
  214. for(size_t j=y;j<img.rows;++j){
  215. img.at<unsigned char>(j,i)=255;
  216. }
  217. }
  218. }
  219. };
  220. //matHistogram
  221. // axis == 0, statistic histogram for columns;
  222. // others, statistic histogram for rows
  223. void mat_histogram(
  224. cv::Mat& img,
  225. std::vector<int>&hist,
  226. int axis,
  227. int r0,
  228. int r1,
  229. int c0,
  230. int c1
  231. )
  232. {
  233. hist.clear();
  234. if(r0<0){r0 = 0;}
  235. if(r1<0){r1 = img.rows;}
  236. if(c0<0){c0 = 0;}
  237. if(c1<0){c1 = img.cols;}
  238. if (axis==0){
  239. for(int c=c0;c<c1;++c){
  240. int cnt = 0;
  241. for(int r=r0;r<r1; ++r){
  242. if (img.at<unsigned char>(r,c)>0){cnt+=1;}
  243. }
  244. hist.push_back(cnt);
  245. }
  246. }
  247. else{
  248. for(int r=r0;r<r1; ++r){
  249. int cnt = 0;
  250. for(int c=c0;c<c1;++c){
  251. if (img.at<unsigned char>(r,c)>0){cnt+=1;}
  252. }
  253. hist.push_back(cnt);
  254. }
  255. }
  256. };
  257. //matHistogram
  258. // axis == 0, statistic histogram for columns;
  259. // others, statistic histogram for rows
  260. // with weight
  261. void mat_histogram_w(
  262. cv::Mat& img,
  263. std::vector<int>&hist,
  264. int axis,
  265. int r0,
  266. int r1,
  267. int c0,
  268. int c1,
  269. bool asc_w //true---ascending weight, [0-1.0], false --- descending weight [1.0--0.0]
  270. )
  271. {
  272. hist.clear();
  273. if(r0<0){r0 = 0;}
  274. if(r1<0){r1 = img.rows;}
  275. if(c0<0){c0 = 0;}
  276. if(c1<0){c1 = img.cols;}
  277. if (axis==0){
  278. double step = 1.0/(double)(r1-1);
  279. for(int c=c0;c<c1;++c){
  280. double cnt = 0.0;
  281. double hi=0.0;
  282. for(int r=r0;r<r1; ++r){
  283. if (img.at<unsigned char>(r,c)>0){
  284. if(asc_w){
  285. hi = (r-r0)*step;
  286. if(hi>=0.66666){
  287. cnt+=hi;
  288. }
  289. }
  290. else{
  291. hi = (r1-r+r0)*step;
  292. if(hi>=0.66666){
  293. cnt+=hi;
  294. }
  295. }
  296. }
  297. }
  298. hist.push_back((int)cnt);
  299. }
  300. }
  301. else{
  302. double step = 1.0/(double)(c1-1);
  303. for(int r=r0;r<r1; ++r){
  304. double cnt = 0.0;
  305. double hi=0.0;
  306. for(int c=c0;c<c1;++c){
  307. if (img.at<unsigned char>(r,c)>0){
  308. if(asc_w){
  309. hi = (c-c0)*step;
  310. if(hi>=0.66666){
  311. cnt+=hi;
  312. }
  313. }
  314. else{
  315. hi = (c1-c+c0)*step;
  316. if(hi>=0.66666){
  317. cnt+=hi;
  318. }
  319. }
  320. }
  321. }
  322. hist.push_back((int)cnt);
  323. }
  324. }
  325. };
  326. void mat_histogram_yfork(
  327. cv::Mat& img,
  328. std::vector<int>&hist,
  329. int r0,
  330. int r1
  331. )
  332. {
  333. hist.clear();
  334. if(r0<0){r0 = 0;}
  335. if(r1<0){r1 = img.rows;}
  336. double step = 1.0/(double)(r1-r0);
  337. for(int c=0;c<img.cols;++c){
  338. double cnt = 0.0;
  339. double hi=0.0;
  340. for(int r=r0;r<r1; ++r){
  341. if (img.at<unsigned char>(r,c)>0){
  342. hi = (r-r0)*step;
  343. //if(hi>=0.66666){
  344. cnt+=hi;
  345. //}
  346. }
  347. }
  348. hist.push_back((int)cnt);
  349. }
  350. };
  351. //get_stem_x_range() 确定茎的位置,x方向
  352. // 2022/1/4 chenhj 修改,大叶子下垂,将叶子识别成茎,增加histogram满足阈值情况下,两侧数据方差检测
  353. void get_stem_x_range_oa(
  354. const std::vector<int>&hist_h,
  355. double h_ratio,
  356. int padding,
  357. int cent_x,
  358. int width_x,
  359. int&x0,
  360. int&x1,
  361. int&stem_x0,
  362. int&stem_x1
  363. )
  364. {
  365. //hist_h: histogram in column
  366. //h_ratio: ratio for generating threshold
  367. // x0,x1: sub-image x-range
  368. // stem_x0,1: stem x-range
  369. // padding: pad for stem x-range
  370. // calculate the max-value of hist_h
  371. int min_segment_len = 0;//最小线段长度,如果小于此数值,不计为有效数据,跳过
  372. int min_segment_dist = 20;//多峰情况下,间隔像素数量
  373. int var_padding = 20;
  374. auto max_it = max_element(hist_h.begin(), hist_h.end());
  375. size_t max_idx = max_it - hist_h.begin();
  376. int th = int(h_ratio * (double)(*max_it));
  377. if(th<=0){
  378. throw_msg(string("error: threshold is 0 in get_stem_x_range()"));
  379. }
  380. // 1 计算小线段的起始位置
  381. vector<gcv_point<int>>segments;
  382. get_hist_segment(hist_h,th,segments);
  383. // 2 获取多峰位置
  384. vector<gcv_point<int>>peaks;
  385. if(segments.size()==1){
  386. peaks = segments; //单峰
  387. }
  388. else{// 小线段删除
  389. vector<gcv_point<int>>big_segments;
  390. for(size_t i=0;i<segments.size();++i){
  391. if(segments[i].y - segments[i].x>=min_segment_len){
  392. big_segments.push_back(segments[i]);
  393. }
  394. }
  395. if(big_segments.size()==1){
  396. peaks = big_segments;//单峰
  397. }
  398. else{// 合并
  399. peaks.push_back(big_segments[0]);
  400. for(size_t j=1;j<big_segments.size();++j){
  401. int dist = big_segments[j].x - peaks.back().y;
  402. if(dist>min_segment_dist){
  403. peaks.push_back(big_segments[j]);
  404. }
  405. else{
  406. peaks.back().y = big_segments[j].y;
  407. }
  408. }
  409. }
  410. }
  411. // 3 多峰情况,先计算center_ratio, 统计峰段两侧数据的方差,方差大的为茎
  412. int opt_index=0;
  413. if(peaks.size()>1){
  414. vector<double>center_r;
  415. for(size_t i=0;i<peaks.size();++i){
  416. double r = fabs(0.5*(double)(peaks[i].x+ peaks[i].y) - cent_x)/(double)(width_x/2.0);
  417. center_r.push_back(1.0-r);
  418. }
  419. vector<size_t>sort_idx = sort_indexes_e(center_r,false);
  420. double cr_1 = center_r[sort_idx[0]];
  421. double cr_2 = center_r[sort_idx[1]];
  422. if(cr_1-cr_2>0.2 || cr_1>=0.65){//如果存在居中显著的,按居中优先
  423. opt_index = sort_idx[0];
  424. }
  425. else{
  426. vector<int> candidate_idx;
  427. candidate_idx.push_back(sort_idx[0]);
  428. candidate_idx.push_back(sort_idx[1]);
  429. for(size_t id = 2;id<sort_idx.size();++id){
  430. if(cr_1 - center_r[sort_idx[id]]<=0.3){
  431. candidate_idx.push_back(sort_idx[id]);
  432. }
  433. }
  434. vector<double>vars;
  435. int vx0,vx1;
  436. for(size_t i=0;i<peaks.size();++i){
  437. vector<int>::iterator it = find(candidate_idx.begin(),candidate_idx.end(),i);
  438. if(it==candidate_idx.end()){
  439. vars.push_back(0.0);
  440. continue;
  441. }
  442. vx0 = peaks[i].x - var_padding;
  443. vx1 = peaks[i].y + var_padding;
  444. if(vx0<0){vx0=0;}
  445. if(vx1>hist_h.size()-1){vx1=hist_h.size()-1;}
  446. double mean,var;
  447. mean = 0.0;
  448. var=-1;
  449. get_hist_mean_var_local(hist_h,vx0,vx1,mean,var);
  450. if(var<0){var=0.0;}
  451. vars.push_back(var);
  452. }
  453. auto var_max_it = max_element(vars.begin(), vars.end());
  454. size_t var_max_idx = var_max_it - vars.begin();
  455. opt_index = var_max_idx;
  456. }
  457. }
  458. stem_x0 = peaks[opt_index].x;
  459. stem_x1 = peaks[opt_index].y;
  460. x0 = stem_x0 - padding;
  461. x1 = stem_x1 + padding;
  462. if(x0<0){x0=0;};
  463. if(x1>hist_h.size()-1){x1 = hist_h.size()-1;};
  464. //////////////////////////////////////
  465. //int global_start_idx=-1;
  466. //int global_end_idx=-1;
  467. //int start_idx=-1;
  468. //int end_idx=-1;
  469. //for(size_t i=0;i<hist_h.size();++i){
  470. // if(i==0){
  471. // if(hist_h[i]>=th){
  472. // start_idx=i;
  473. // }
  474. // continue;
  475. // }
  476. // if(hist_h[i]>=th && hist_h[i-1]<th){
  477. // start_idx=i;
  478. // continue;
  479. // }
  480. // if(i==hist_h.size()-1){
  481. // if(hist_h[i]>=th && hist_h[i-1]>=th){
  482. // if((i-start_idx+1)>=min_segment_len){
  483. // global_end_idx=i;
  484. // }
  485. // }
  486. // }
  487. // if(hist_h[i]<th && hist_h[i-1]>=th){
  488. // if((i-start_idx+1)>=min_segment_len){
  489. // if( global_start_idx<0){
  490. // global_start_idx = start_idx;
  491. // }
  492. // global_end_idx = i;
  493. // }
  494. // }
  495. //}
  496. ///*stem_x0 = 0;
  497. //stem_x1 = hist_h.size()-1;
  498. //for(size_t i=max_idx; i < hist_h.size(); ++i){
  499. // if(hist_h[i]>=th){
  500. // stem_x1 = i;
  501. // }
  502. // else{
  503. // break;
  504. // }
  505. //}
  506. //for(int i=max_idx; i >= 0; i--){
  507. // if(hist_h[i]>=th){
  508. // stem_x0 = i;
  509. // }
  510. // else{
  511. // break;
  512. // }
  513. //}*/
  514. //stem_x0 = global_start_idx;
  515. //stem_x1 = global_end_idx;
  516. //x0 = stem_x0 - padding;
  517. //x1 = stem_x1 + padding;
  518. //if(x0<0){x0=0;};
  519. //if(x1>hist_h.size()-1){x1 = hist_h.size()-1;};
  520. };
  521. void get_stem_x_range_rscut(
  522. const std::vector<int>&hist_h,
  523. double h_ratio,
  524. int padding,
  525. int cent_x,
  526. int width_x,
  527. int&x0,
  528. int&x1,
  529. int&stem_x0,
  530. int&stem_x1
  531. )
  532. {
  533. //hist_h: histogram in column
  534. //h_ratio: ratio for generating threshold
  535. // x0,x1: sub-image x-range
  536. // stem_x0,1: stem x-range
  537. // padding: pad for stem x-range
  538. // calculate the max-value of hist_h
  539. int min_segment_len = 0;//最小线段长度,如果小于此数值,不计为有效数据,跳过
  540. int min_segment_dist = 20;//多峰情况下,间隔像素数量
  541. int var_padding = 20;
  542. auto max_it = max_element(hist_h.begin(), hist_h.end());
  543. size_t max_idx = max_it - hist_h.begin();
  544. int th = int(h_ratio * (double)(*max_it));
  545. if(th<=0){
  546. throw_msg(string("error: threshold is 0 in get_stem_x_range()"));
  547. }
  548. // 1 计算小线段的起始位置
  549. vector<gcv_point<int>>segments;
  550. get_hist_segment(hist_h,th,segments);
  551. // 2 获取多峰位置
  552. vector<gcv_point<int>>peaks;
  553. if(segments.size()==1){
  554. peaks = segments; //单峰
  555. }
  556. else{// 小线段删除
  557. vector<gcv_point<int>>big_segments;
  558. for(size_t i=0;i<segments.size();++i){
  559. if(segments[i].y - segments[i].x>=min_segment_len){
  560. big_segments.push_back(segments[i]);
  561. }
  562. }
  563. if(big_segments.size()==1){
  564. peaks = big_segments;//单峰
  565. }
  566. else{// 合并
  567. peaks.push_back(big_segments[0]);
  568. for(size_t j=1;j<big_segments.size();++j){
  569. int dist = big_segments[j].x - peaks.back().y;
  570. if(dist>min_segment_dist){
  571. peaks.push_back(big_segments[j]);
  572. }
  573. else{
  574. peaks.back().y = big_segments[j].y;
  575. }
  576. }
  577. }
  578. }
  579. // 3 多峰情况,先计算center_ratio, 统计峰段两侧数据的方差,方差大的为茎
  580. int opt_index=0;
  581. if(peaks.size()>1){
  582. vector<double>center_r;
  583. for(size_t i=0;i<peaks.size();++i){
  584. double r = fabs(0.5*(double)(peaks[i].x+ peaks[i].y) - cent_x)/(double)(width_x/2.0);
  585. center_r.push_back(1.0-r);
  586. }
  587. vector<size_t>sort_idx = sort_indexes_e(center_r,false);
  588. double cr_1 = center_r[sort_idx[0]];
  589. double cr_2 = center_r[sort_idx[1]];
  590. if(cr_1-cr_2>0.2 || cr_1>=0.65){//如果存在居中显著的,按居中优先
  591. opt_index = sort_idx[0];
  592. }
  593. else{
  594. vector<int> candidate_idx;
  595. candidate_idx.push_back(sort_idx[0]);
  596. candidate_idx.push_back(sort_idx[1]);
  597. for(size_t id = 2;id<sort_idx.size();++id){
  598. if(cr_1 - center_r[sort_idx[id]]<=0.3){
  599. candidate_idx.push_back(sort_idx[id]);
  600. }
  601. }
  602. vector<double>vars;
  603. int vx0,vx1;
  604. for(size_t i=0;i<peaks.size();++i){
  605. vector<int>::iterator it = find(candidate_idx.begin(),candidate_idx.end(),i);
  606. if(it==candidate_idx.end()){
  607. vars.push_back(0.0);
  608. continue;
  609. }
  610. vx0 = peaks[i].x - var_padding;
  611. vx1 = peaks[i].y + var_padding;
  612. if(vx0<0){vx0=0;}
  613. if(vx1>hist_h.size()-1){vx1=hist_h.size()-1;}
  614. double mean,var;
  615. mean = 0.0;
  616. var=-1;
  617. get_hist_mean_var_local(hist_h,vx0,vx1,mean,var);
  618. if(var<0){var=0.0;}
  619. vars.push_back(var);
  620. }
  621. auto var_max_it = max_element(vars.begin(), vars.end());
  622. size_t var_max_idx = var_max_it - vars.begin();
  623. opt_index = var_max_idx;
  624. }
  625. }
  626. stem_x0 = peaks[opt_index].x;
  627. stem_x1 = peaks[opt_index].y;
  628. x0 = stem_x0 - padding;
  629. x1 = stem_x1 + padding;
  630. if(x0<0){x0=0;};
  631. if(x1>hist_h.size()-1){x1 = hist_h.size()-1;};
  632. //////////////////////////////////////
  633. //int global_start_idx=-1;
  634. //int global_end_idx=-1;
  635. //int start_idx=-1;
  636. //int end_idx=-1;
  637. //for(size_t i=0;i<hist_h.size();++i){
  638. // if(i==0){
  639. // if(hist_h[i]>=th){
  640. // start_idx=i;
  641. // }
  642. // continue;
  643. // }
  644. // if(hist_h[i]>=th && hist_h[i-1]<th){
  645. // start_idx=i;
  646. // continue;
  647. // }
  648. // if(i==hist_h.size()-1){
  649. // if(hist_h[i]>=th && hist_h[i-1]>=th){
  650. // if((i-start_idx+1)>=min_segment_len){
  651. // global_end_idx=i;
  652. // }
  653. // }
  654. // }
  655. // if(hist_h[i]<th && hist_h[i-1]>=th){
  656. // if((i-start_idx+1)>=min_segment_len){
  657. // if( global_start_idx<0){
  658. // global_start_idx = start_idx;
  659. // }
  660. // global_end_idx = i;
  661. // }
  662. // }
  663. //}
  664. ///*stem_x0 = 0;
  665. //stem_x1 = hist_h.size()-1;
  666. //for(size_t i=max_idx; i < hist_h.size(); ++i){
  667. // if(hist_h[i]>=th){
  668. // stem_x1 = i;
  669. // }
  670. // else{
  671. // break;
  672. // }
  673. //}
  674. //for(int i=max_idx; i >= 0; i--){
  675. // if(hist_h[i]>=th){
  676. // stem_x0 = i;
  677. // }
  678. // else{
  679. // break;
  680. // }
  681. //}*/
  682. //stem_x0 = global_start_idx;
  683. //stem_x1 = global_end_idx;
  684. //x0 = stem_x0 - padding;
  685. //x1 = stem_x1 + padding;
  686. //if(x0<0){x0=0;};
  687. //if(x1>hist_h.size()-1){x1 = hist_h.size()-1;};
  688. };
  689. void get_stem_x_range_scion(
  690. const std::vector<int>&hist_h,
  691. double h_ratio,
  692. int padding,
  693. int&x0,
  694. int&x1,
  695. int&stem_x0,
  696. int&stem_x1
  697. )
  698. {
  699. //hist_h: histogram in column
  700. //h_ratio: ratio for generating threshold
  701. // x0,x1: sub-image x-range
  702. // stem_x0,1: stem x-range
  703. // padding: pad for stem x-range
  704. // calculate the max-value of hist_h
  705. auto max_it = max_element(hist_h.begin(), hist_h.end());
  706. size_t max_idx = max_it - hist_h.begin();
  707. int th = int(h_ratio * (double)(*max_it));
  708. stem_x0 = 0;
  709. stem_x1 = hist_h.size()-1;
  710. for(size_t i=max_idx; i < hist_h.size(); ++i){
  711. if(hist_h[i]>=th){
  712. stem_x1 = i;
  713. }
  714. else{
  715. break;
  716. }
  717. }
  718. for(int i=max_idx; i >= 0; i--){
  719. if(hist_h[i]>=th){
  720. stem_x0 = i;
  721. }
  722. else{
  723. break;
  724. }
  725. }
  726. x0 = stem_x0 - padding;
  727. x1 = stem_x1 + padding;
  728. if(x0<0){x0=0;};
  729. if(x1>hist_h.size()-1){x1 = hist_h.size()-1;};
  730. };
  731. void get_hist_segment(
  732. const std::vector<int>& hist,
  733. int th,//threshold
  734. std::vector<gcv_point<int>>& segments
  735. )
  736. {
  737. segments.clear();
  738. int start_idx=-1;
  739. int end_idx=-1;
  740. for(size_t i=0;i<hist.size();++i){
  741. if(i==0){
  742. if(hist[i]>=th){
  743. start_idx=i;
  744. }
  745. continue;
  746. }
  747. if(hist[i]>=th && hist[i-1]<th){
  748. start_idx=i;
  749. continue;
  750. }
  751. if(i==hist.size()-1){
  752. if(hist[i]>=th && hist[i-1]>=th){
  753. segments.push_back(gcv_point<int>(start_idx,i));
  754. }
  755. }
  756. if(hist[i]<th && hist[i-1]>=th){
  757. segments.push_back(gcv_point<int>(start_idx,i-1));
  758. }
  759. }
  760. }
  761. void get_hist_mean_var_local(
  762. const std::vector<int>& hist,
  763. int x0,
  764. int x1,
  765. double& mean,
  766. double& var
  767. )
  768. {
  769. mean=0.0;
  770. var = 0.0;
  771. if(hist.size()==0){
  772. return;
  773. }
  774. if(x0<0){x0=0;}
  775. if(x1<0 || x1>hist.size()-1){hist.size()-1;}
  776. for(int i=x0;i<=x1;++i){
  777. mean+=hist[i];
  778. }
  779. mean /=(double)(x1-x0+1);
  780. for(int i=x0;i<=x1;++i){
  781. var+=((double)(hist[i])-mean) * ((double)(hist[i])-mean);
  782. }
  783. var /= (double)(x1-x0+1);
  784. }
  785. void get_stem_y_fork(
  786. const std::vector<int>& hist,
  787. double ratio,
  788. int stem_dia_min,
  789. int stem_fork_y_min,
  790. double stem_dia_mp,
  791. int& fork_y, //output
  792. int& end_y, //output
  793. int& stem_dia //output
  794. )
  795. {
  796. //# 由y的底部向上检测是否有茎,有的话计算移动均值
  797. //# 通过当前值和移动均值的比值识别分叉点
  798. //# stem_dia_min = 10 # 茎粗最小值
  799. //# stem_fork_y_min = 10 # 茎分叉位置与茎根的最小像素高度
  800. //# stem_dia_mp = 0.8 # moving power
  801. fork_y = end_y = stem_dia = -1;
  802. std::vector<int>reversed_hist;
  803. for(int ii=hist.size()-1; ii>=0;ii--){
  804. reversed_hist.push_back(hist[ii]);
  805. }
  806. int stem_root_y = -1;
  807. double stem_dia_ma = -1;//# stem diameter moving-average
  808. size_t i=0;
  809. for(; i<hist.size(); ++i){
  810. if (i==0){continue;}
  811. if ((reversed_hist[i-1]<stem_dia_min && reversed_hist[i]>=stem_dia_min && stem_root_y<0)
  812. ||(reversed_hist[i-1]>=stem_dia_min && reversed_hist[i]>=stem_dia_min && stem_root_y<0))
  813. {
  814. //# stem root begin
  815. stem_root_y = i;
  816. stem_dia_ma = (double)reversed_hist[i];
  817. }
  818. else {
  819. if (reversed_hist[i-1]>=stem_dia_min &&
  820. reversed_hist[i]>=stem_dia_min)
  821. {
  822. double fork_ratio = reversed_hist[i] / stem_dia_ma;
  823. //if (i<150){
  824. // cout<<fork_ratio<<"\t"<<reversed_hist[i]<<"\t"<<stem_dia_ma<<endl;
  825. //}
  826. if( i - stem_root_y > stem_fork_y_min &&
  827. fork_ratio >= ratio)
  828. {
  829. //# found the fork y level
  830. fork_y = hist.size() - i;
  831. stem_dia = reversed_hist[i-1];
  832. break;
  833. }
  834. //# update stem_dia_ma
  835. stem_dia_ma = stem_dia_mp *stem_dia_ma + (1.0-stem_dia_mp) * reversed_hist[i];
  836. }
  837. }
  838. };
  839. if(stem_root_y<0){
  840. throw_msg(string("not exists diameter bigger than stem_dia_min"));
  841. }
  842. if(fork_y<0){
  843. throw_msg(string("not exists diameter fork_ratio bigger than threshold"));
  844. }
  845. //end_y
  846. end_y = hist.size() - stem_root_y-1;
  847. //统计stem_root_y 到 i 间reversed_hist的数值,得到stem_dia。方法可用平均值,75%分位数
  848. vector<int> tmp;
  849. for(size_t j=stem_root_y; j<=i;++j){
  850. tmp.push_back(reversed_hist[j]);
  851. }
  852. sort(tmp.begin(), tmp.end());
  853. int tar_idx = (int)((float)tmp.size()*0.75);
  854. stem_dia = tmp[tar_idx];
  855. };
  856. void get_stem_y_fork_3sigma(
  857. const std::vector<int>& hist,
  858. double ratio,
  859. int stem_dia_min,
  860. int stem_fork_y_min,
  861. double stem_dia_mp,
  862. int& fork_y, //output
  863. int& end_y, //output
  864. int& stem_dia //output
  865. )
  866. {
  867. fork_y = end_y = stem_dia = -1;
  868. std::vector<int>reversed_hist;
  869. for(int ii=hist.size()-1; ii>=0;ii--){
  870. reversed_hist.push_back(hist[ii]);
  871. }
  872. int mean_radius =2;
  873. double mean_dia = 2.0*mean_radius +1.0;
  874. double mean = 0.0;
  875. double min_m, max_m;
  876. min_m = max_m = 0.0;
  877. std::vector<double>reversed_mean;
  878. for(int ii=0; ii<reversed_hist.size();ii++){
  879. if(ii-mean_radius<0 || ii + mean_radius>=reversed_hist.size()){
  880. reversed_mean.push_back(0.0);
  881. continue;
  882. }
  883. mean = 0.0;
  884. for(int k = -mean_radius;k<=mean_radius;++k){
  885. mean+=(double)reversed_hist[ii+k];
  886. }
  887. mean /= mean_dia;
  888. if(mean>max_m){max_m = mean;}
  889. reversed_mean.push_back(mean);
  890. }
  891. cv::Mat tmp;
  892. hist2image_scale_line(reversed_mean,tmp,min_m,max_m,1.0,min_m,50,50);
  893. imshow_wait("mean5",tmp);
  894. return;
  895. int stem_root_y = -1;
  896. double stem_dia_ma = -1;//# stem diameter moving-average
  897. size_t i=0;
  898. for(; i<reversed_mean.size(); ++i){
  899. if (i==0){continue;}
  900. if ((reversed_hist[i-1]<stem_dia_min && reversed_hist[i]>=stem_dia_min && stem_root_y<0)
  901. ||(reversed_hist[i-1]>=stem_dia_min && reversed_hist[i]>=stem_dia_min && stem_root_y<0))
  902. {
  903. //# stem root begin
  904. stem_root_y = i;
  905. stem_dia_ma = (double)reversed_hist[i];
  906. }
  907. else {
  908. if (reversed_hist[i-1]>=stem_dia_min &&
  909. reversed_hist[i]>=stem_dia_min)
  910. {
  911. double fork_ratio = reversed_hist[i] / stem_dia_ma;
  912. //if (i<150){
  913. // cout<<fork_ratio<<"\t"<<reversed_hist[i]<<"\t"<<stem_dia_ma<<endl;
  914. //}
  915. if( i - stem_root_y > stem_fork_y_min &&
  916. fork_ratio >= ratio)
  917. {
  918. //# found the fork y level
  919. fork_y = hist.size() - i;
  920. stem_dia = reversed_hist[i-1];
  921. break;
  922. }
  923. //# update stem_dia_ma
  924. stem_dia_ma = stem_dia_mp *stem_dia_ma + (1.0-stem_dia_mp) * reversed_hist[i];
  925. }
  926. }
  927. };
  928. //r2index(
  929. /*
  930. int stem_root_y = -1;
  931. double stem_dia_ma = -1;//# stem diameter moving-average
  932. size_t i=0;
  933. for(; i<hist.size(); ++i){
  934. if (i==0){continue;}
  935. if ((reversed_hist[i-1]<stem_dia_min && reversed_hist[i]>=stem_dia_min && stem_root_y<0)
  936. ||(reversed_hist[i-1]>=stem_dia_min && reversed_hist[i]>=stem_dia_min && stem_root_y<0))
  937. {
  938. //# stem root begin
  939. stem_root_y = i;
  940. stem_dia_ma = (double)reversed_hist[i];
  941. }
  942. else {
  943. if (reversed_hist[i-1]>=stem_dia_min &&
  944. reversed_hist[i]>=stem_dia_min)
  945. {
  946. double fork_ratio = reversed_hist[i] / stem_dia_ma;
  947. //if (i<150){
  948. // cout<<fork_ratio<<"\t"<<reversed_hist[i]<<"\t"<<stem_dia_ma<<endl;
  949. //}
  950. if( i - stem_root_y > stem_fork_y_min &&
  951. fork_ratio >= ratio)
  952. {
  953. //# found the fork y level
  954. fork_y = hist.size() - i;
  955. stem_dia = reversed_hist[i-1];
  956. break;
  957. }
  958. //# update stem_dia_ma
  959. stem_dia_ma = stem_dia_mp *stem_dia_ma + (1.0-stem_dia_mp) * reversed_hist[i];
  960. }
  961. }
  962. };
  963. if(stem_root_y<0){
  964. throw_msg(string("not exists diameter bigger than stem_dia_min"));
  965. }
  966. if(fork_y<0){
  967. throw_msg(string("not exists diameter fork_ratio bigger than threshold"));
  968. }
  969. //end_y
  970. end_y = hist.size() - stem_root_y-1;
  971. //统计stem_root_y 到 i 间reversed_hist的数值,得到stem_dia。方法可用平均值,75%分位数
  972. vector<int> tmp;
  973. for(size_t j=stem_root_y; j<=i;++j){
  974. tmp.push_back(reversed_hist[j]);
  975. }
  976. sort(tmp.begin(), tmp.end());
  977. int tar_idx = (int)((float)tmp.size()*0.75);
  978. stem_dia = tmp[tar_idx];
  979. */
  980. };
  981. void get_stem_y_fork_rs(
  982. const std::vector<int>& hist,
  983. double ratio,
  984. int stem_dia_min,
  985. int stem_fork_y_min,
  986. double stem_dia_mp,
  987. int& fork_y, //output
  988. int& end_y, //output
  989. int& stem_dia, //output
  990. int& roi_max_y //output
  991. )
  992. {
  993. //# 由y的底部向上检测是否有茎,有的话计算移动均值
  994. //# 通过当前值和移动均值的比值识别分叉点
  995. //# stem_dia_min = 10 # 茎粗最小值
  996. //# stem_fork_y_min = 10 # 茎分叉位置与茎根的最小像素高度
  997. //# stem_dia_mp = 0.8 # moving power
  998. fork_y = end_y = stem_dia = -1;
  999. std::vector<int>reversed_hist;
  1000. for(int ii=hist.size()-1; ii>=0;ii--){
  1001. reversed_hist.push_back(hist[ii]);
  1002. }
  1003. int stem_root_y = -1;
  1004. double stem_dia_ma = -1;//# stem diameter moving-average
  1005. int max_y = 0;
  1006. int max_val = reversed_hist[0];
  1007. size_t i=0;
  1008. vector<double> index_of_diachange;
  1009. vector<double> index_of_diameter;
  1010. for(; i<reversed_hist.size(); ++i){
  1011. //find max value index
  1012. if(reversed_hist[i]>max_val){
  1013. max_val = reversed_hist[i];
  1014. max_y = i;
  1015. }
  1016. if (i==0){
  1017. index_of_diachange.push_back(0.0);
  1018. continue;
  1019. }
  1020. if ((reversed_hist[i-1]<stem_dia_min && reversed_hist[i]>=stem_dia_min && stem_root_y<0)
  1021. ||(reversed_hist[i-1]>=stem_dia_min && reversed_hist[i]>=stem_dia_min && stem_root_y<0))
  1022. {
  1023. //# stem root begin
  1024. stem_root_y = i;
  1025. stem_dia_ma = (double)reversed_hist[i];
  1026. index_of_diachange.push_back(0.0);
  1027. }
  1028. else {
  1029. if (reversed_hist[i-1]>=stem_dia_min &&
  1030. reversed_hist[i]>=stem_dia_min)
  1031. {
  1032. double fork_ratio = reversed_hist[i] / stem_dia_ma;
  1033. if(fork_ratio>1.5){fork_ratio=1.5;}
  1034. index_of_diachange.push_back(fork_ratio);
  1035. stem_dia_ma = stem_dia_mp *stem_dia_ma + (1.0-stem_dia_mp) * reversed_hist[i];
  1036. }
  1037. else{
  1038. index_of_diachange.push_back(0.0);
  1039. }
  1040. }
  1041. };
  1042. if(stem_root_y<0){
  1043. throw_msg(string("not exists diameter bigger than stem_dia_min"));
  1044. }
  1045. //end_y
  1046. end_y = hist.size() - stem_root_y-1;
  1047. //统计stem_root_y 到 max_y 间reversed_hist的数值,得到stem_dia。方法可用平均值,40%分位数
  1048. vector<int> tmp;
  1049. for(size_t j=stem_root_y; j<max_y;++j){
  1050. if(j<0 || j>=reversed_hist.size()){continue;}
  1051. tmp.push_back(reversed_hist[j]);
  1052. }
  1053. sort(tmp.begin(), tmp.end());
  1054. int tar_idx = (int)((float)tmp.size()*0.40);
  1055. if(tmp.size()==0 || tar_idx>=tmp.size()){
  1056. throw_msg(string("not exists y fork point"));
  1057. }
  1058. stem_dia = tmp[tar_idx];
  1059. ////////////////////////////////////////
  1060. //update 重新检验max_y,因为max_y是全局最大,由于苗的倾斜,可能出现局部最大为分叉点位置
  1061. // 2022-2-21 将比例参数1.5调整至2.0
  1062. int local_max_y = max_y;
  1063. for(size_t j=stem_root_y; j<max_y;++j){
  1064. if((double)(reversed_hist[j]/(double)stem_dia) >=2.0){
  1065. local_max_y = j;
  1066. break;
  1067. }
  1068. }
  1069. max_y = local_max_y ;
  1070. roi_max_y = hist.size() - max_y-1;
  1071. //////////////////////////////////////////////////////////////////
  1072. // 计算vector<double>index_of_diameter
  1073. for(int idx = 0;idx<reversed_hist.size();++idx){
  1074. if(reversed_hist[idx]==0){
  1075. index_of_diameter.push_back(0.0);
  1076. }
  1077. else {
  1078. if(idx>=max_y){
  1079. index_of_diameter.push_back(0.0);
  1080. }
  1081. else{
  1082. double r = yfork_validity_stemdiaratio(stem_dia,reversed_hist[idx],ratio);
  1083. index_of_diameter.push_back(r);
  1084. }
  1085. }
  1086. }
  1087. //////////////////////////////////////////////////////////////////
  1088. // 计算fork_y
  1089. vector<double> index_yfork;
  1090. for(int idx = 0;idx<reversed_hist.size();++idx){
  1091. index_yfork.push_back(index_of_diachange[idx] * index_of_diameter[idx]);
  1092. }
  1093. vector<double>::iterator max_it = max_element(begin(index_yfork),end(index_yfork));
  1094. int max_idx_y = max_it - index_yfork.begin();
  1095. fork_y = hist.size() - max_idx_y;
  1096. };
  1097. void get_stem_y_fork_rs_update(
  1098. const cv::Mat& bin_img,
  1099. int stem_x_padding,
  1100. int x0,
  1101. int x1,
  1102. int max_y,
  1103. int stem_root_y,
  1104. int stem_dia,
  1105. bool image_show,
  1106. double cut_point_offset_ratio,
  1107. cv::Point& fork_cent,
  1108. double& max_radius,
  1109. double& stem_angle
  1110. )
  1111. {
  1112. //1 通过bin_img的二值图像,找到茎中心线
  1113. //2 通过中心线,更新二值图像采集区域(有可能倾斜)
  1114. //3 在max_y附近找最大内接圆中心(在茎中心线上)
  1115. fork_cent.x = fork_cent.y = -1;
  1116. max_radius = 0.0;
  1117. stem_angle = 90.0;
  1118. // 茎中心点提取, 在图像max_y 和 stem_root_y之间
  1119. int max_len,start_x,cent_x, pre_val, cur_val;
  1120. vector<int>cent_xs;
  1121. vector<int>cent_ys;
  1122. for(int i=0;i<bin_img.rows;++i){
  1123. if(i<=max_y || i>=stem_root_y){continue;}
  1124. max_len = start_x = cent_x = -1;
  1125. for(int j=x0;j<=x1;++j){
  1126. if(j==x0 && bin_img.at<unsigned char>(i,j)==0){continue;}
  1127. if(j==x0 && bin_img.at<unsigned char>(i,j)>0){start_x=j;continue;}
  1128. pre_val = bin_img.at<unsigned char>(i,j-1);
  1129. cur_val = bin_img.at<unsigned char>(i,j);
  1130. if(pre_val==0 && cur_val>0){
  1131. start_x = j;
  1132. }
  1133. if((pre_val>0 && cur_val==0) ||(j==x1 && cur_val>0)){
  1134. int len = j-start_x +1;
  1135. if(len>max_len){
  1136. max_len = len;
  1137. cent_x = (start_x+j)/2;
  1138. //cout<<i<<"\t"<<"x0="<<start_x<<", x1="<<j<<", centx="<<cent_x<<endl;
  1139. }
  1140. }
  1141. }
  1142. if(cent_x>0){
  1143. cent_xs.push_back(cent_x);
  1144. cent_ys.push_back(i);
  1145. }
  1146. }
  1147. if(cent_xs.size()!=cent_ys.size() || cent_xs.size()<3){
  1148. throw_msg(string("stem length < 3, in function get_stem_y_fork_rs_update()"));
  1149. }
  1150. //茎中心线拟合
  1151. double beta0, beta1;
  1152. beta0 = beta1 = 0.0;
  1153. // x = beta0 + beta1 * y
  1154. double r2 = r2index(cent_ys,cent_xs,0,cent_xs.size()-1, beta0, beta1);
  1155. double th = (double)stem_dia/2.0 + stem_x_padding;
  1156. cv::Mat roi_img = bin_img.clone();
  1157. //提取兴趣区域图片
  1158. stem_angle = atan(beta1)*57.2957795130 + 90.0;
  1159. if(fabs(stem_angle-90.0)<2.0){
  1160. for(int r=0;r<roi_img.rows;++r){
  1161. for(int c=0;c<roi_img.cols;++c){
  1162. if(c<x0 || c>x1){
  1163. roi_img.at<unsigned char>(r,c) = 0;
  1164. }
  1165. }
  1166. }
  1167. }
  1168. else{
  1169. for(int r=0;r<roi_img.rows;++r){
  1170. double cx = beta0 + beta1 * r;
  1171. int cx0 = (int)(cx-th);
  1172. int cx1 = (int)(cx+th);
  1173. for(int c=0;c<roi_img.cols;++c){
  1174. if(c<cx0 || c>cx1){
  1175. roi_img.at<unsigned char>(r,c) = 0;
  1176. }
  1177. }
  1178. }
  1179. }
  1180. ////////////////////////////////////////////////////////////
  1181. //
  1182. if(image_show){
  1183. cv::Mat tmp = roi_img.clone();
  1184. cv::Point p0((int)(beta0),0);
  1185. cv::Point p1((int)((double)tmp.rows *beta1 +beta0),tmp.rows);
  1186. cv::line(tmp,p0,p1, cv::Scalar(128,0,0));
  1187. cv::line(tmp, cv::Point(cent_xs[0],cent_ys[0]),
  1188. cv::Point(cent_xs.back(),cent_ys.back()), cv::Scalar(128,0,0));
  1189. imshow_wait("rs_bin_roi", tmp);
  1190. }
  1191. ///////////////////////////////////////////////////////////
  1192. //兴趣区域图片findContours
  1193. vector<vector<cv::Point>> contours;
  1194. vector<cv::Vec4i> hierarchy;
  1195. findContours(roi_img,contours,hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE);
  1196. //找到周长最长的区域作为目标区域
  1197. int max_length_idx = 0;
  1198. int max_length_val = contours[0].size();
  1199. for(int i=0;i<contours.size();++i){
  1200. if(i==0){continue;}
  1201. if(contours[i].size()>max_length_val){
  1202. max_length_val = contours[i].size();
  1203. max_length_idx = i;
  1204. }
  1205. }
  1206. vector<float>inner_max_radius;
  1207. vector<int> nnindex;
  1208. vector<int> xs;
  1209. /*
  1210. //////////////////////////////////////////
  1211. //基于flann的方法
  1212. vector<Point> contours_serial;
  1213. for(size_t i=0;i<contours.size();++i){
  1214. if(contours_serial.size()==0){
  1215. contours_serial.assign(contours[i].begin(),contours[i].end());
  1216. }
  1217. else{
  1218. for(size_t j=0;j<contours[i].size();++j){
  1219. contours_serial.push_back(contours[i][j]);
  1220. }
  1221. }
  1222. }
  1223. //find inner max radius
  1224. Mat source = Mat(contours_serial).reshape(1);
  1225. source.convertTo(source,CV_32F);
  1226. flann::KDTreeIndexParams indexParams(2);
  1227. flann::Index kdtree(source, indexParams);
  1228. unsigned queryNum=1;
  1229. vector<float> vecQuery(2);
  1230. vector<int> vecIndex(queryNum);
  1231. vector<float> vecDist(queryNum);
  1232. flann::SearchParams params(32);
  1233. //在茎中心线上计算最优点指标:
  1234. // 1) inner_max_radius --- 中心点对应的最小内接圆半径
  1235. for(size_t r =0; r<roi_img.rows;++r){
  1236. if(r < max_y - 50){
  1237. inner_max_radius.push_back(0.0);
  1238. nnindex.push_back(-1);
  1239. xs.push_back(-1);
  1240. continue;
  1241. }
  1242. // x = beta0 + beta1 * y
  1243. float x = (float) (beta0 + beta1 * r);
  1244. int xi = (int)(x+0.5);
  1245. if(xi<0 || xi>=roi_img.cols){
  1246. inner_max_radius.push_back(0.0);
  1247. nnindex.push_back(-1);
  1248. xs.push_back(-1);
  1249. continue;
  1250. }
  1251. if(roi_img.at<unsigned char>(r,xi)==0){
  1252. inner_max_radius.push_back(0.0);
  1253. nnindex.push_back(-1);
  1254. xs.push_back(xi);
  1255. continue;
  1256. }
  1257. vecQuery[0] = x;//x
  1258. vecQuery[1] = (float)r;//y
  1259. kdtree.knnSearch(vecQuery, vecIndex, vecDist,queryNum, params);
  1260. if(vecDist.size()<1 || vecIndex.size()<1){
  1261. inner_max_radius.push_back(0.0);
  1262. nnindex.push_back(-1);
  1263. xs.push_back(xi);
  1264. continue;
  1265. }
  1266. inner_max_radius.push_back(vecDist[0]);
  1267. nnindex.push_back(vecIndex[0]);
  1268. xs.push_back(xi);
  1269. }
  1270. */
  1271. ////////////////////////////////////////////////////////////////
  1272. //基于pointPolygonTest的方法
  1273. //在茎中心线上计算最优点指标:
  1274. // 1) inner_max_radius --- 中心点对应的最小内接圆半径
  1275. for(size_t r =0; r<roi_img.rows;++r){
  1276. if(r < max_y - 50){
  1277. inner_max_radius.push_back(0.0);
  1278. nnindex.push_back(-1);
  1279. xs.push_back(-1);
  1280. continue;
  1281. }
  1282. // x = beta0 + beta1 * y
  1283. float x = (float) (beta0 + beta1 * r);
  1284. int xi = (int)(x+0.5);
  1285. if(xi<0 || xi>=roi_img.cols){
  1286. inner_max_radius.push_back(0.0);
  1287. nnindex.push_back(-1);
  1288. xs.push_back(-1);
  1289. continue;
  1290. }
  1291. if(roi_img.at<unsigned char>(r,xi)==0){
  1292. inner_max_radius.push_back(0.0);
  1293. nnindex.push_back(-1);
  1294. xs.push_back(xi);
  1295. continue;
  1296. }
  1297. float d = cv::pointPolygonTest( contours[max_length_idx], cv::Point2f(x,r), true );
  1298. if(d<0){
  1299. inner_max_radius.push_back(0.0);
  1300. nnindex.push_back(-1);
  1301. xs.push_back(xi);
  1302. continue;
  1303. }
  1304. inner_max_radius.push_back(d);
  1305. nnindex.push_back(-1);
  1306. xs.push_back(xi);
  1307. }
  1308. //find max radius point
  1309. vector<float>::iterator max_it = max_element(inner_max_radius.begin(),inner_max_radius.end());
  1310. int max_idx = max_it - inner_max_radius.begin();
  1311. //基于flann的方法
  1312. //max_radius = sqrt(inner_max_radius[max_idx]);
  1313. max_radius = inner_max_radius[max_idx];
  1314. //offset
  1315. double r_offset = cut_point_offset_ratio * max_radius;
  1316. double dy = r_offset * (1.0 / sqrt(1.0 + beta1 * beta1));
  1317. double x_offset = beta0 + beta1 * (max_idx + dy);
  1318. fork_cent.x = (int)(x_offset+0.5);
  1319. fork_cent.y =(int)(max_idx + dy + 0.5);
  1320. ///////////////////////////////////////////////
  1321. //test CForkDetectOptimal 2022-2-17
  1322. /*CForkDetectOptimal fdo(100,20);
  1323. vector<float>opt_max;
  1324. int opt_max_idx = 0;
  1325. double optm=0;
  1326. for(size_t r =0; r<xs.size();++r){
  1327. if(abs((int)r-max_idx)>50){
  1328. opt_max.push_back(0.0);
  1329. continue;
  1330. }
  1331. else{
  1332. if(xs[r]<0){
  1333. opt_max.push_back(0.0);
  1334. continue;
  1335. }
  1336. Point cent_pt = Point2f(xs[r],r);
  1337. double rt = fdo.center_pt_index(bin_img,cent_pt,-60.0);
  1338. opt_max.push_back((float)rt);
  1339. if(rt>optm){
  1340. optm=rt;
  1341. opt_max_idx=r;
  1342. }
  1343. }
  1344. }*/
  1345. ///////////////////////////////////////////////
  1346. ///////////////////////////////////////////////
  1347. //test
  1348. if(image_show){
  1349. cv::Mat edge;
  1350. edge = cv::Mat::zeros(roi_img.rows, roi_img.cols, CV_8UC1);
  1351. cv::drawContours(edge, contours, -1, cv::Scalar(255, 255, 0), 1);
  1352. for(int r=0;r<xs.size();++r){
  1353. int c = xs[r];
  1354. if(c<0){continue;}
  1355. if(inner_max_radius[r]<=0){continue;}
  1356. /*
  1357. //基于flann的方法
  1358. if(nnindex[r]<0){continue;}
  1359. line(edge,Point(c,r),contours_serial[nnindex[r]],Scalar(128,0,0));
  1360. */
  1361. int rad = int(inner_max_radius[r]);
  1362. cv::line(edge, cv::Point(c-rad,r), cv::Point(c+rad,r), cv::Scalar(80,0,0));
  1363. }
  1364. cv::Point fork_cent_origin;
  1365. fork_cent_origin.x = xs[max_idx];
  1366. fork_cent_origin.y =max_idx;
  1367. cv::circle(edge,fork_cent_origin,3, cv::Scalar(200,0,0));
  1368. cv::circle(edge,fork_cent_origin,(int)(max_radius), cv::Scalar(200,0,0));
  1369. //circle(edge,Point(xs[opt_max_idx],opt_max_idx),5,Scalar(200,0,0));
  1370. cv::circle(edge,fork_cent,3, cv::Scalar(128,0,0));
  1371. cv::circle(edge,fork_cent,(int)(max_radius), cv::Scalar(128,0,0));
  1372. imshow_wait("rs_bin_roi_radius_circle", edge);
  1373. }
  1374. ///////////////////////////////////////////////
  1375. };
  1376. //分叉位置指标 yfork_validity_position()
  1377. // max_y---hist中最大点的y-index,
  1378. // end_y ----hist中茎最底端点的y-index
  1379. // fork_y --- 预期分叉点y-index
  1380. // max_y > fork_y > end_y
  1381. double yfork_validity_position(int max_y, int end_y, int fork_y){
  1382. //确定hist最大点位置向下到end_y点最优分叉点位置系数
  1383. double validity = 0.0;
  1384. double opt_pos = 0.85;
  1385. if(fork_y<end_y || fork_y>max_y){return validity;}
  1386. double opt_y = (max_y-end_y+1)*opt_pos + end_y;
  1387. if(fork_y<=end_y || fork_y>=max_y){return validity;}
  1388. if(fork_y>end_y && fork_y<opt_y){
  1389. validity =(double)( (fork_y-end_y+1) / (opt_y-end_y+1));
  1390. }
  1391. else{
  1392. validity =(double)( (max_y -fork_y+1) / (max_y -opt_y+1));
  1393. }
  1394. return validity;
  1395. }
  1396. //茎粗比指标 yfork_validity_stemdiaratio()
  1397. // stem_dia---茎粗,
  1398. // dia_y ----hist中y-index对应的茎粗
  1399. double yfork_validity_stemdiaratio(int stem_dia, int dia_y,double opt_dia_ratio){
  1400. //确定hist最大点位置向下到end_y点最优分叉点位置系数
  1401. double validity = 0.0;
  1402. //double opt_pos = 1.2;
  1403. double opt_dia = stem_dia*opt_dia_ratio;
  1404. if(dia_y<opt_dia){return dia_y/opt_dia;}
  1405. else{
  1406. return opt_dia/dia_y;
  1407. }
  1408. }
  1409. //计算上下窗口茎粗变化率
  1410. void yfork_validity_stemdiachange(
  1411. const std::vector<int>& hist,
  1412. int stem_dia_min,
  1413. std::vector<double>& var_ratio
  1414. )
  1415. {
  1416. var_ratio.clear();
  1417. int smooth_width = 5;
  1418. double mean_pre=0.0;
  1419. double mean_nxt=0.0;
  1420. int low_y,up_y;
  1421. low_y=up_y=-1;
  1422. for(int i=0;i<hist.size();++i){
  1423. if(hist[i]>=stem_dia_min){
  1424. if( low_y<0){low_y=i;}
  1425. if(i>up_y){up_y = i;}
  1426. }
  1427. }
  1428. for(int i=0;i<hist.size();++i){
  1429. if(i-smooth_width<0 || i+smooth_width>=hist.size()){
  1430. var_ratio.push_back(0.0);
  1431. continue;
  1432. }
  1433. if(i<low_y+smooth_width || i>up_y-smooth_width){
  1434. var_ratio.push_back(0.0);
  1435. continue;
  1436. }
  1437. mean_pre = mean_nxt=0.0;
  1438. //low sum
  1439. for(int di = -smooth_width;di<0;++di){
  1440. mean_pre += hist[i+di];
  1441. }
  1442. //up sum
  1443. for(int di = 0;di<smooth_width;++di){
  1444. mean_nxt += hist[i+di];
  1445. }
  1446. if(mean_pre<1.0e-5){
  1447. var_ratio.push_back(0.0);
  1448. continue;
  1449. }
  1450. else{
  1451. double ratio = mean_nxt / mean_pre;
  1452. if(ratio>3.0){ratio=3.0;}
  1453. var_ratio.push_back(ratio);
  1454. }
  1455. }
  1456. }
  1457. void imshow_wait(
  1458. const char* winname,
  1459. cv::Mat&img,
  1460. int waittype/*=-1*/
  1461. )
  1462. {
  1463. cv::namedWindow(winname, cv::WINDOW_NORMAL);
  1464. cv::imshow(winname, img);
  1465. cv::waitKey(waittype);
  1466. };
  1467. int get_stem_fork_right(
  1468. cv::Mat&stem_img,
  1469. int stem_fork_y,
  1470. int stem_x0,
  1471. int stem_x1,
  1472. int detect_window
  1473. )
  1474. {
  1475. /*
  1476. 通过茎分叉坐标找到右侧茎分叉点坐标
  1477. stem_img, 二值图像,茎部分的局部图像
  1478. stem_fork_y, 检测到的茎分叉的y坐标
  1479. stem_x0, 茎左侧边缘(通过hist检测,针对stem_fork_y不一定精确)
  1480. stem_x1, 茎右侧边缘(通过hist检测,针对stem_fork_y不一定精确)
  1481. */
  1482. // 注释掉 2021-11-3 开始
  1483. /*double min_dist = 1.0;
  1484. int min_x1 = -1;
  1485. for(size_t i=stem_x0; i<stem_x1+2*detect_window; ++i){
  1486. double left_cnt = 0.0;
  1487. double right_cnt = 0.0;
  1488. for(size_t j=i-detect_window+1; j<i+1; ++j){
  1489. if (stem_img.at<unsigned char>(stem_fork_y,j)>0){
  1490. left_cnt+=1;
  1491. }
  1492. }
  1493. for(size_t j=i+1; j<i+detect_window+1; ++j){
  1494. if( stem_img.at<unsigned char>(stem_fork_y,j)==0){
  1495. right_cnt+=1;
  1496. }
  1497. }
  1498. if( left_cnt==0 || right_cnt ==0){continue;}
  1499. double dist = 1.0 - min(left_cnt/right_cnt, right_cnt/left_cnt);
  1500. if (dist < min_dist){
  1501. min_dist = dist;
  1502. min_x1 = i;
  1503. }
  1504. }
  1505. return min_x1;
  1506. */
  1507. // 注释掉 2021-11-3 结束
  1508. size_t left_min = stem_x0-4*detect_window;
  1509. size_t right_max = stem_x1+4*detect_window;
  1510. if(left_min<0){left_min = 0;}
  1511. if(right_max>stem_img.cols){right_max = stem_img.cols;}
  1512. int max_len = -1;
  1513. int max_idx = -1;
  1514. int start_x=left_min;
  1515. for(size_t i=left_min; i<right_max; ++i){
  1516. if(i==left_min){
  1517. if(stem_img.at<unsigned char>(stem_fork_y,i)>0){
  1518. start_x =i;
  1519. }
  1520. continue;
  1521. }
  1522. if (stem_img.at<unsigned char>(stem_fork_y,i-1)==0 &&
  1523. stem_img.at<unsigned char>(stem_fork_y,i)>0){
  1524. start_x =i;
  1525. continue;
  1526. }
  1527. if ((stem_img.at<unsigned char>(stem_fork_y,i-1)>0 && stem_img.at<unsigned char>(stem_fork_y,i)==0) ||
  1528. (stem_img.at<unsigned char>(stem_fork_y,i)>0 && i==right_max-1)){
  1529. if(((int)i-start_x) > max_len){
  1530. max_len = i-start_x;
  1531. max_idx = i;
  1532. }
  1533. }
  1534. }
  1535. if(max_idx<0){max_idx = stem_x1;}
  1536. return max_idx;
  1537. };
  1538. int get_stem_fork_left(
  1539. cv::Mat&stem_img,
  1540. int stem_fork_y,
  1541. int stem_x0,
  1542. int stem_x1,
  1543. int detect_window
  1544. )
  1545. {
  1546. /*
  1547. 通过茎分叉坐标找到右侧茎分叉点坐标
  1548. stem_img, 二值图像,茎部分的局部图像
  1549. stem_fork_y, 检测到的茎分叉的y坐标
  1550. stem_x0, 茎左侧边缘(通过hist检测,针对stem_fork_y不一定精确)
  1551. stem_x1, 茎右侧边缘(通过hist检测,针对stem_fork_y不一定精确)
  1552. */
  1553. size_t left_min = stem_x0-8*detect_window;
  1554. size_t right_max = stem_x1+8*detect_window;
  1555. if(left_min<0){left_min = 0;}
  1556. if(right_max>stem_img.cols-1){right_max=stem_img.cols;}
  1557. int max_len = -1;
  1558. int max_idx = -1;
  1559. int start_x=left_min;
  1560. for(size_t i=left_min; i<right_max; ++i){
  1561. if(i==left_min){
  1562. if(stem_img.at<unsigned char>(stem_fork_y,i)>0){
  1563. start_x =i;
  1564. }
  1565. continue;
  1566. }
  1567. if (stem_img.at<unsigned char>(stem_fork_y,i-1)==0 &&
  1568. stem_img.at<unsigned char>(stem_fork_y,i)>0)
  1569. {
  1570. start_x =i;
  1571. continue;
  1572. }
  1573. if ((stem_img.at<unsigned char>(stem_fork_y,i-1)>0 && stem_img.at<unsigned char>(stem_fork_y,i)==0) ||
  1574. (stem_img.at<unsigned char>(stem_fork_y,i)>0 && i==right_max-1)){
  1575. if(((int)i-start_x) > max_len){
  1576. max_len = i-start_x;
  1577. max_idx = start_x;
  1578. }
  1579. }
  1580. }
  1581. if(max_idx<0){max_idx = stem_x0;}
  1582. return max_idx;
  1583. }
  1584. void get_stem_fork_xs(
  1585. cv::Mat&stem_img,
  1586. int stem_fork_y,
  1587. int stem_x0,
  1588. int stem_x1,
  1589. int x0,
  1590. int x1,
  1591. int & fork_x0,
  1592. int & fork_x1
  1593. )
  1594. {
  1595. size_t left_min = x0;
  1596. size_t right_max = x1;
  1597. if(left_min<0){left_min = 0;}
  1598. if(right_max>=stem_img.cols){right_max = stem_img.cols-1;}
  1599. int max_len = -1;
  1600. int max_idx = -1;
  1601. int max_idx_right = -1;
  1602. int start_x=left_min;
  1603. for(size_t i=left_min; i<right_max; ++i){
  1604. if ( i==0 &&
  1605. stem_img.at<unsigned char>(stem_fork_y,i)>0)
  1606. {
  1607. start_x =i;
  1608. continue;
  1609. }
  1610. if (stem_img.at<unsigned char>(stem_fork_y,i)==0 &&
  1611. stem_img.at<unsigned char>(stem_fork_y,i+1)>0)
  1612. {
  1613. start_x =i;
  1614. }
  1615. if ((stem_img.at<unsigned char>(stem_fork_y,i)>0 && stem_img.at<unsigned char>(stem_fork_y,i+1)==0) ||
  1616. (stem_img.at<unsigned char>(stem_fork_y,i)>0 && i==right_max-1)){
  1617. if(((int)i-start_x) > max_len)
  1618. {
  1619. max_len = i-start_x;
  1620. max_idx = start_x;
  1621. max_idx_right = i;
  1622. }
  1623. }
  1624. }
  1625. if(max_idx<0){max_idx = stem_x0;}
  1626. fork_x0 = max_idx;
  1627. if(max_idx_right<0){max_idx_right = stem_x1;}
  1628. fork_x1 = max_idx_right;
  1629. }
  1630. // 根据边缘上的一点,爬取下一点
  1631. void get_next_pt(
  1632. cv::Mat& edge_img,
  1633. gcv_point<int>&start_pt,
  1634. gcv_point<int>&pre_pt,
  1635. gcv_point<int>&nxt_pt,//output
  1636. bool is_up //true 向上爬取, false 向下爬取
  1637. )
  1638. {
  1639. nxt_pt.x=-1;
  1640. nxt_pt.y=-1;
  1641. int dy_range[3] = {-1,0,1};
  1642. int dx_range[3] = {1,0,-1};
  1643. if (!is_up){
  1644. dy_range[0] = 1;
  1645. dy_range[2] = -1;
  1646. }
  1647. for (int dyi =0; dyi<3;++dyi){
  1648. int dy = dy_range[dyi];
  1649. bool break_x = false;
  1650. for( int dxi=0;dxi<3;++dxi){
  1651. int dx = dx_range[dxi];
  1652. int x = start_pt.x+dx;
  1653. int y = start_pt.y+dy;
  1654. if (dx==0 && dy == 0){continue;}
  1655. if (x==pre_pt.x && y==pre_pt.y){continue;}
  1656. if (edge_img.at<unsigned char>(y,x)>0){
  1657. nxt_pt.x = x;
  1658. nxt_pt.y = y;
  1659. break_x = true;
  1660. break;
  1661. }
  1662. }
  1663. if (break_x){
  1664. break;
  1665. }
  1666. }
  1667. }
  1668. // qua_fitting
  1669. // input: xx ---- sorted x values, ascending
  1670. // yy ----- y values
  1671. //
  1672. // return: oa --- optimal angle, which is the x coordinates of center line of quadratic fitting line
  1673. double qua_fitting(vector<double>&xx, vector<double>&yy)
  1674. {
  1675. double oa = 0.0;
  1676. // fit quadratic function with opencv
  1677. cv::Mat A = cv::Mat::zeros(cv::Size(3,xx.size()), CV_64FC1);
  1678. for(int i=0; i<xx.size();++i){
  1679. A.at<double>(i,0) = 1;
  1680. A.at<double>(i,1) = xx[i];
  1681. A.at<double>(i,2) = xx[i] * xx[i];
  1682. }
  1683. cv::Mat b = cv::Mat::zeros(cv::Size(1,yy.size()), CV_64FC1);
  1684. for(int i = 0; i<yy.size(); ++i){
  1685. b.at<double>(i,0) = yy[i];
  1686. }
  1687. cv::Mat c, d;
  1688. c = A.t() * A;
  1689. d = A.t() * b;
  1690. cv::Mat result = cv::Mat::zeros(cv::Size(1,3),CV_64FC1);
  1691. cv::solve(c,d,result);
  1692. double a0 = result.at<double>(0, 0);
  1693. double a1 = result.at<double>(1, 0);
  1694. double a2 = result.at<double>(2, 0);
  1695. if(fabs(a2)<1.0e-6){
  1696. throw_msg(string("Error: a2 = 0 in solver"));
  1697. }
  1698. oa = -a1 / a2 / 2;
  1699. if(oa >180.0){oa -= 180.0;}
  1700. return oa;
  1701. }
  1702. };