tea_sorter.cpp 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554
  1. #include <opencv.hpp>
  2. #include <math.h>
  3. #include <io.h>
  4. #include "tea_sorter.h"
  5. #include "utils.h"
  6. using namespace cv;
  7. namespace graft_cv{
  8. CTeaSort::CTeaSort(
  9. ConfigParam& cp,
  10. img_type dtpye,
  11. CGcvLogger*pLog)
  12. :
  13. m_cp(cp),
  14. m_dtype(dtpye),
  15. m_pLogger(pLog),
  16. m_ppImgSaver(0),
  17. m_pImginfoRaw(0),
  18. m_pImginfoDetected(0)
  19. {
  20. m_drop_detector = RetinaDrop(m_pLogger, 0.5, 0.5);
  21. }
  22. CTeaSort::~CTeaSort()
  23. {
  24. clear_imginfo();
  25. }
  26. int CTeaSort::detect(
  27. ImgInfo*imginfo,
  28. PositionInfo& posinfo,
  29. const char* fn
  30. )
  31. {
  32. //1 model status
  33. if (!m_drop_detector.IsModelLoaded()) {
  34. m_pLogger->ERRORINFO(
  35. string("tea detect model NOT loaded"));
  36. return 1;
  37. }
  38. //2 update recognize threshold
  39. if (m_dtype == img_type::tea_grab) {
  40. m_drop_detector.SetThreshold(m_cp.object_threshold_grab, m_cp.nms_threshold_grab);
  41. }
  42. else {
  43. m_drop_detector.SetThreshold(m_cp.object_threshold_cut, m_cp.nms_threshold_cut);
  44. }
  45. //3 load data
  46. load_data(imginfo, fn);
  47. if (m_cp.image_show) {
  48. cv::destroyAllWindows();
  49. imshow_wait("input_img", m_raw_img);
  50. }
  51. //4 generate_detect_windows(vector<Rect>&boxes)
  52. vector<Rect> drop_regions;
  53. int region_cnt = generate_detect_windows(drop_regions);
  54. if (region_cnt == 0) {
  55. stringstream buff_;
  56. buff_ << m_imgId << m_dtype_str << "tea detect image regions' size == 0";
  57. m_pLogger->ERRORINFO(buff_.str());
  58. return 1;
  59. }
  60. else {
  61. stringstream bufftmp;
  62. bufftmp << m_imgId << m_dtype_str << "tea detect image regions' size = "<<region_cnt;
  63. m_pLogger->INFO(bufftmp.str());
  64. }
  65. if (m_cp.image_show) {
  66. cv::Mat rects_img = m_raw_img.clone();
  67. int step_c = int(255 / (float)region_cnt);
  68. int step_cc = step_c / 2;
  69. int step_ccc = step_cc / 2;
  70. int cnt = 0;
  71. for (auto&r : drop_regions) {
  72. cv::rectangle(rects_img, r, cv::Scalar(step_cc*cnt, step_c*cnt, step_ccc*cnt), 3);
  73. cnt += 1;
  74. }
  75. imshow_wait("regions_img", rects_img);
  76. }
  77. //5 detect
  78. vector<Bbox> droplets_raw;
  79. int dn = detect_impl(m_raw_img, drop_regions, droplets_raw);
  80. if (dn < 2 && m_dtype == img_type::tea_grab) {
  81. //up-down flip
  82. cv::Mat flip_img;
  83. cv::flip(m_raw_img, flip_img, 0);
  84. if (m_cp.image_show) {
  85. imshow_wait("flip_img", flip_img);
  86. }
  87. vector<Bbox> droplets_flip;
  88. int dn_flip = detect_impl(flip_img, drop_regions, droplets_flip);
  89. for (auto&b: droplets_flip) {
  90. int y2 = flip_img.rows - b.y1;
  91. int y1 = flip_img.rows - b.y2;
  92. b.y1 = y1;
  93. b.y2 = y2;
  94. for (int i = 0; i < 5; ++i) {
  95. b.ppoint[2 * i + 1] = flip_img.rows - b.ppoint[2 * i + 1];
  96. }
  97. }
  98. if (dn_flip > 0) {
  99. droplets_raw.insert(
  100. droplets_raw.end(),
  101. droplets_flip.begin(),
  102. droplets_flip.end());
  103. }
  104. }
  105. /*for (auto rect : drop_regions) {
  106. Mat roi = m_raw_img(rect);
  107. vector<Bbox> head_droplets = m_drop_detector.RunModel(roi, m_pLogger);
  108. if (m_pLogger) {
  109. stringstream buff_;
  110. buff_ << m_imgId << m_dtype_str << "-------crop_rect["<< rect.x<<","<<rect.y<<","<<rect.width
  111. <<","<<rect.height<<"],"
  112. <<" roi image detect over. tea number is " << head_droplets.size();
  113. m_pLogger->INFO(buff_.str());
  114. }
  115. for (Bbox& b : head_droplets) {
  116. b.x1 += rect.x;
  117. b.x2 += rect.x;
  118. b.y1 += rect.y;
  119. b.y2 += rect.y;
  120. for (int i = 0; i < 5; ++i) {
  121. b.ppoint[2 * i] += rect.x;
  122. b.ppoint[2 * i + 1] += rect.y;
  123. }
  124. }
  125. if (head_droplets.size()) {
  126. droplets_raw.insert(
  127. droplets_raw.end(),
  128. head_droplets.begin(),
  129. head_droplets.end());
  130. }
  131. }*/
  132. if (m_pLogger) {
  133. stringstream buff_;
  134. buff_ << m_imgId<<m_dtype_str << "image detect over. tea number is " << droplets_raw.size();
  135. m_pLogger->INFO(buff_.str());
  136. }
  137. //6 nms, width(height) filt and area calculation
  138. vector<Bbox> droplets;
  139. vector<int> keep;
  140. nms_bbox(droplets_raw, m_drop_detector.GetNmsThreshold(), keep);
  141. //nms keep and area filter
  142. double min_area_th = m_cp.min_area_ratio_grab;
  143. double max_area_th = m_cp.max_area_ratio_grab;
  144. if (m_dtype == img_type::tea_cut) {
  145. min_area_th = m_cp.min_area_ratio_cut;
  146. max_area_th = m_cp.max_area_ratio_cut;
  147. }
  148. for (int i : keep) {
  149. Bbox&tbox = droplets_raw[i];
  150. double area_ratio = static_cast<double>(tbox.y2 - tbox.y1) * static_cast<double>(tbox.x2 - tbox.x1);
  151. area_ratio = fabs(area_ratio);
  152. area_ratio /= static_cast<double>(m_raw_img.rows);
  153. area_ratio /= static_cast<double>(m_raw_img.cols);
  154. tbox.area = area_ratio;
  155. if (area_ratio < min_area_th || area_ratio > max_area_th) { continue; }
  156. //检查box边界是否在图像内,如果没有,修改之
  157. if (tbox.x1 < 0) { tbox.x1 = 0; }
  158. if (tbox.y1 < 0) { tbox.y1 = 0; }
  159. if (tbox.x2 >= m_raw_img.cols) { tbox.x2 = m_raw_img.cols - 1; }
  160. if (tbox.y2 >= m_raw_img.rows) { tbox.y2 = m_raw_img.rows - 1; }
  161. droplets.push_back(tbox);
  162. }
  163. if (m_pLogger) {
  164. stringstream buff_;
  165. buff_ << m_imgId << m_dtype_str << "after nms, keep tea number is " << droplets.size();
  166. for (auto&tbox : droplets) {
  167. buff_ << "\nscore:" << tbox.score << ", area_ratio:" << tbox.area << ", left_top:(" << tbox.x1 << "," << tbox.y1 << "), bottom_rigt:(" << tbox.x2 << "," << tbox.y2 << ")";
  168. }
  169. m_pLogger->INFO(buff_.str());
  170. }
  171. int valid_cnt = 0;
  172. if (m_dtype == img_type::tea_grab) {
  173. //grab
  174. double pre_cx, pre_cy;
  175. double min_dist_grab = m_cp.min_distance_grab;
  176. pre_cx = -min_dist_grab;
  177. pre_cy = -min_dist_grab;
  178. for (int i = 0; i < droplets.size(); ++i) {
  179. if (valid_cnt > 1) { break; }
  180. Bbox&b = droplets.at(i);
  181. double cx = 0.5*(b.x1 + b.x2);
  182. double cy = 0.5*(b.y1 + b.y2);
  183. double dist = sqrt((cx - pre_cx)*(cx - pre_cx) + (cy - pre_cy)*(cy - pre_cy));
  184. if (dist < min_dist_grab) {
  185. continue;
  186. }
  187. double grab_x, grab_y;
  188. double angle = calculate_angle(b,/* true, */grab_x, grab_y);
  189. //grab point
  190. if (valid_cnt == 0) {
  191. posinfo.tea_grab_x1 = grab_x;
  192. posinfo.tea_grab_y1 = grab_y;
  193. posinfo.tea_grab_angle1 = angle;
  194. }
  195. else {
  196. posinfo.tea_grab_x2 = grab_x;
  197. posinfo.tea_grab_y2 = grab_y;
  198. posinfo.tea_grab_angle2 = angle;
  199. }
  200. b.operate_point[0] = grab_x;
  201. b.operate_point[1] = grab_y;
  202. b.operate_angle = angle;
  203. b.status = 1;
  204. pre_cx = cx;
  205. pre_cy = cy;
  206. valid_cnt += 1;
  207. }
  208. }
  209. else {
  210. //cut
  211. for (int i = 0; i < droplets.size();++i) {
  212. if (i > 1) { break; }
  213. Bbox&b = droplets.at(i);
  214. double grab_x, grab_y;
  215. double angle = calculate_angle(b,/* true,*/ grab_x, grab_y);
  216. valid_cnt += 1;
  217. if (i == 0) {
  218. // 切割点是3、4的中间的点
  219. posinfo.tea_cut_x1 = grab_x;
  220. posinfo.tea_cut_y1 = grab_y;
  221. posinfo.tea_cut_angle1 = angle;
  222. }
  223. else {
  224. // 切割点是3、4的中间的点
  225. posinfo.tea_cut_x2 = grab_x;
  226. posinfo.tea_cut_y2 = grab_y;
  227. posinfo.tea_cut_angle2 = angle;
  228. }
  229. b.operate_point[0] = grab_x;
  230. b.operate_point[1] = grab_y;
  231. b.operate_angle = angle;
  232. b.status = 1; // selected
  233. }
  234. }
  235. //6 draw
  236. if (m_cp.image_return) {
  237. this->clear_imginfo();
  238. cv::Mat img_rst = m_raw_img.clone();
  239. for (auto& b : droplets) {
  240. //rectangle
  241. cv::Rect r = cv::Rect(cv::Point2i(b.x1, b.y1), cv::Point2i(b.x2, b.y2));
  242. if (b.status > 0) {
  243. cv::rectangle(img_rst, r, cv::Scalar(0, 0, 255),2);
  244. }
  245. else {
  246. cv::rectangle(img_rst, r, cv::Scalar(0, 255, 0),2);
  247. }
  248. //score
  249. char name[256];
  250. cv::Scalar color(120, 120, 0);//bgr
  251. sprintf_s(name, "%.2f", b.score);
  252. cv::putText(img_rst, name,
  253. cv::Point(b.x1, b.y1),
  254. cv::FONT_HERSHEY_COMPLEX, 0.7, color, 2);
  255. //points
  256. cv::circle(img_rst, cv::Point(int(b.ppoint[0]), int(b.ppoint[1])), 4, cv::Scalar(255, 0, 255), -1, 3, 0);
  257. cv::circle(img_rst, cv::Point(int(b.ppoint[2]), int(b.ppoint[3])), 4, cv::Scalar(0, 255, 255), -1, 3, 0);
  258. cv::circle(img_rst, cv::Point(int(b.ppoint[4]), int(b.ppoint[5])), 4, cv::Scalar(255, 0, 0), -1, 3, 0);
  259. cv::circle(img_rst, cv::Point(int(b.ppoint[6]), int(b.ppoint[7])), 4, cv::Scalar(0, 255, 0), -1, 3, 0);
  260. cv::circle(img_rst, cv::Point(int(b.ppoint[8]), int(b.ppoint[9])), 4, cv::Scalar(0, 0, 255), -1, 3, 0);
  261. //grab points
  262. if (m_dtype == img_type::tea_grab) {
  263. if (b.status == 1) {
  264. double grab_x, grab_y, grab_angle;
  265. grab_x = b.operate_point[0];
  266. grab_y = b.operate_point[1];
  267. grab_angle = b.operate_angle;
  268. //bool need_precise = b.status == 1;
  269. //double grab_angle = calculate_angle(b, /*need_precise,*/ grab_x, grab_y);
  270. //cv::circle(img_rst, cv::Point(int(grab_x), int(grab_y)), 4, cv::Scalar(0, 215, 255), -1, 3, 0);
  271. //lines, p4-p5, p5-grab
  272. cv::line(img_rst,
  273. cv::Point(int(b.ppoint[6]), int(b.ppoint[7])),
  274. cv::Point(int(b.ppoint[8]), int(b.ppoint[9])),
  275. cv::Scalar(0, 215, 255), 2);
  276. cv::line(img_rst,
  277. cv::Point(int(b.ppoint[8]), int(b.ppoint[9])),
  278. cv::Point(int(grab_x), int(grab_y)),
  279. cv::Scalar(0, 215, 255), 2);
  280. //line x
  281. int radius = 20;
  282. int cx = int(grab_x);
  283. int cy = int(grab_y);
  284. cv::line(img_rst, cv::Point(cx - radius, cy - radius), cv::Point(cx + radius, cy + radius), cv::Scalar(0, 215, 255), 2);
  285. cv::line(img_rst, cv::Point(cx - radius, cy + radius), cv::Point(cx + radius, cy - radius), cv::Scalar(0, 215, 255), 2);
  286. //grab point angle
  287. int radius_dir = m_cp.offset_grab / 2;
  288. grab_angle *= (CV_PI / 180.0);
  289. double dx = radius_dir * sin(grab_angle);
  290. double dy = radius_dir * cos(grab_angle);
  291. int dir_x = int(grab_x + dx);
  292. int dir_y = int(grab_y + dy);
  293. cv::line(img_rst, cv::Point(cx, cy), cv::Point(dir_x, dir_y), cv::Scalar(20, 255, 20), 2);
  294. }
  295. }
  296. //cut points
  297. if (m_dtype == img_type::tea_cut) {
  298. //lines, p3-p4
  299. cv::line(img_rst,
  300. cv::Point(int(b.ppoint[4]), int(b.ppoint[5])),
  301. cv::Point(int(b.ppoint[6]), int(b.ppoint[7])),
  302. cv::Scalar(0, 215, 255), 2);
  303. //line x
  304. int cx = int(b.operate_point[0]);
  305. int cy = int(b.operate_point[1]);
  306. int radius = 20;
  307. cv::line(img_rst, cv::Point(cx - radius, cy - radius), cv::Point(cx + radius, cy + radius), cv::Scalar(0, 215, 255),2);
  308. cv::line(img_rst, cv::Point(cx - radius, cy + radius), cv::Point(cx + radius, cy - radius), cv::Scalar(0, 215, 255),2);
  309. }
  310. }
  311. if (m_cp.image_show) {
  312. imshow_wait("result_img", img_rst);
  313. }
  314. m_pImginfoRaw = mat2imginfo(m_raw_img);
  315. m_pImginfoDetected = mat2imginfo(img_rst);
  316. posinfo.pp_images[0] = m_pImginfoRaw;
  317. posinfo.pp_images[1] = m_pImginfoDetected;
  318. if (m_ppImgSaver && *m_ppImgSaver) {
  319. (*m_ppImgSaver)->saveImage(img_rst, m_imgId + "_rst_0");
  320. }
  321. }
  322. //拍照无苗, 返回识别结果-1
  323. if (valid_cnt != 2) { return -1; }
  324. return 0;
  325. }
  326. int CTeaSort::detect_impl(
  327. cv::Mat& raw_img, //input, image
  328. vector<Rect>&drop_regions, //input, detect regions
  329. vector<Bbox> &droplets_raw //output, detect result
  330. )
  331. {
  332. //return number of detect result
  333. droplets_raw.clear();
  334. for (auto rect : drop_regions) {
  335. Mat roi = raw_img(rect);
  336. vector<Bbox> head_droplets = m_drop_detector.RunModel(roi, m_pLogger);
  337. if (m_pLogger) {
  338. stringstream buff_;
  339. buff_ << m_imgId << m_dtype_str << "-------crop_rect[" << rect.x << "," << rect.y << "," << rect.width
  340. << "," << rect.height << "],"
  341. << " roi image detect over. tea number is " << head_droplets.size();
  342. m_pLogger->INFO(buff_.str());
  343. }
  344. for (Bbox& b : head_droplets) {
  345. b.x1 += rect.x;
  346. b.x2 += rect.x;
  347. b.y1 += rect.y;
  348. b.y2 += rect.y;
  349. for (int i = 0; i < 5; ++i) {
  350. b.ppoint[2 * i] += rect.x;
  351. b.ppoint[2 * i + 1] += rect.y;
  352. }
  353. }
  354. if (head_droplets.size()) {
  355. droplets_raw.insert(
  356. droplets_raw.end(),
  357. head_droplets.begin(),
  358. head_droplets.end());
  359. }
  360. }
  361. return droplets_raw.size();
  362. }
  363. double CTeaSort::calculate_angle(
  364. Bbox&b, //input
  365. //bool need_precise_angle,//input
  366. double& grab_x, //output
  367. double& grab_y //output
  368. )
  369. {
  370. grab_x = grab_y = 0.0;
  371. double angle = 0.0;
  372. float x3,y3,x4,y4,x5,y5;
  373. x3 = b.ppoint[4];
  374. y3 = b.ppoint[5];
  375. x4 = b.ppoint[6];
  376. y4 = b.ppoint[7];
  377. x5 = b.ppoint[8];
  378. y5 = b.ppoint[9];
  379. if (m_dtype == img_type::tea_grab) {
  380. angle = atan2(x5 - x3, y5 - y3);
  381. calculate_stem_grab_position_opt(b, grab_x, grab_y, angle);
  382. //计算抓取点
  383. if (grab_x < 0 && grab_y < 0) {
  384. double pr = (double)m_cp.offset_grab;
  385. double dx = pr * sin(angle);
  386. double dy = pr * cos(angle);
  387. grab_x = x5 + dx;
  388. grab_y = y5 + dy;
  389. }
  390. }
  391. else {
  392. //tea cut, calculate line of p3 ans p4
  393. angle = atan2(x3 - x4, y3 - y4);
  394. calculate_stem_cut_position_opt(b, grab_x, grab_y, angle);
  395. }
  396. angle *= (180.0 / 3.1415926);
  397. return angle;
  398. }
  399. int CTeaSort::load_data(
  400. ImgInfo*imginfo,
  401. const char* fn/* = 0*/)
  402. {
  403. //数据加载功能实现,并生成imageid,保存原始数据到文件
  404. int rst = 0;
  405. //generate image id
  406. if (m_dtype == img_type::tea_grab) {
  407. m_imgId = getImgId(img_type::tea_grab);
  408. m_dtype_str = string(" tea_grab ");
  409. }
  410. else {
  411. m_imgId = getImgId(img_type::tea_cut);
  412. m_dtype_str = string(" tea_cut ");
  413. }
  414. if (imginfo) {
  415. if (m_pLogger) {
  416. stringstream buff;
  417. buff << m_imgId << m_dtype_str << "image, width=" << imginfo->width
  418. << "\theight=" << imginfo->height;
  419. m_pLogger->INFO(buff.str());
  420. }
  421. if (!isvalid(imginfo) || (imginfo->channel!=1 && imginfo->channel!=3)) {
  422. if (m_pLogger) {
  423. m_pLogger->ERRORINFO(m_imgId + m_dtype_str + "input image invalid.");
  424. }
  425. throw_msg(m_imgId + " invalid input image");
  426. }
  427. if (imginfo->channel == 1) {
  428. cv::Mat tmp_img = imginfo2mat(imginfo);
  429. vector<Mat> channels;
  430. for (size_t i = 0; i < 3; ++i) { channels.push_back(tmp_img); }
  431. cv::merge(channels, m_raw_img);
  432. }
  433. else {
  434. m_raw_img = imginfo2mat(imginfo);
  435. }
  436. }
  437. else {
  438. cv::Mat img = imread(fn, cv::IMREAD_COLOR);
  439. if (img.empty()) {
  440. if (m_pLogger) {
  441. m_pLogger->ERRORINFO(m_imgId + m_dtype_str + "input image invalid:" + string(fn));
  442. }
  443. throw_msg(m_imgId + m_dtype_str + "invalid input image: " + string(fn));
  444. }
  445. if (m_pLogger) {
  446. stringstream buff;
  447. buff << m_imgId << m_dtype_str << "image, width=" << img.cols
  448. << "\theight=" << img.rows;
  449. m_pLogger->INFO(buff.str());
  450. }
  451. m_raw_img = img.clone();
  452. }
  453. //image saver
  454. if (m_ppImgSaver && *m_ppImgSaver) {
  455. (*m_ppImgSaver)->saveImage(m_raw_img, m_imgId);
  456. }
  457. return rst;
  458. }
  459. int CTeaSort::load_model()
  460. {
  461. bool b = false;
  462. if (!m_drop_detector.IsModelLoaded()) {
  463. if (m_dtype == img_type::tea_grab) {
  464. b = m_drop_detector.LoadModel(m_cp.model_path_grab);
  465. }
  466. else {
  467. b = m_drop_detector.LoadModel(m_cp.model_path_cut);
  468. }
  469. }
  470. else {
  471. b = true;
  472. }
  473. return b ? 0 : 1;
  474. }
  475. void CTeaSort::clear_imginfo() {
  476. if (m_pImginfoDetected) {
  477. imginfo_release(&m_pImginfoDetected);
  478. m_pImginfoDetected = 0;
  479. }
  480. if (m_pImginfoRaw) {
  481. imginfo_release(&m_pImginfoRaw);
  482. m_pImginfoRaw = 0;
  483. }
  484. }
  485. int CTeaSort::generate_detect_windows(vector<Rect>&boxes)
  486. {
  487. boxes.clear();
  488. int grid_row = m_cp.grid_row_cut;
  489. int grid_col = m_cp.grid_col_cut;
  490. int grid_padding = m_cp.grid_padding_cut;
  491. if (m_dtype == img_type::tea_grab) {
  492. grid_row = m_cp.grid_row_grab;
  493. grid_col = m_cp.grid_col_grab;
  494. grid_padding = m_cp.grid_padding_grab;
  495. }
  496. if (grid_row < 1) { grid_row = 1; }
  497. if (grid_col < 1) { grid_col = 1; }
  498. if (grid_padding < 0) { grid_padding = 0; }
  499. int block_height = int(m_raw_img.rows / (float)grid_row + 0.5);
  500. int block_width = int(m_raw_img.cols / (float)grid_col + 0.5);
  501. for (int r = 0; r < grid_row; ++r) {
  502. for (int c = 0; c < grid_col; ++c) {
  503. int x0 = c*block_width - grid_padding;
  504. int y0 = r*block_height - grid_padding;
  505. int x1 = (c+1)*block_width + grid_padding;
  506. int y1 = (r+1)*block_height + grid_padding;
  507. if (x0 < 0) { x0 = 0; }
  508. if (y0 < 0) { y0 = 0; }
  509. if (x1 > m_raw_img.cols) { x1 = m_raw_img.cols; }
  510. if (y1 > m_raw_img.rows) { y1 = m_raw_img.rows; }
  511. Rect r(x0, y0, x1-x0, y1-y0);
  512. boxes.push_back(r);
  513. }
  514. }
  515. return boxes.size();
  516. }
  517. void CTeaSort::calculate_stem_grab_position(
  518. Bbox&b,
  519. double& grab_x, //output
  520. double& grab_y, //output
  521. double& grab_angle //output
  522. )
  523. {
  524. grab_x = grab_y = -1.0;
  525. //crop image
  526. int padding = 2 * m_cp.offset_grab;
  527. int y3 = int(b.ppoint[5]);
  528. int y5 = int(b.ppoint[9]);
  529. cv::Point p3(int(b.ppoint[4] - b.x1), int(b.ppoint[5] - b.y1));
  530. cv::Point p4(int(b.ppoint[6] - b.x1), int(b.ppoint[7] - b.y1));
  531. cv::Point p5(int(b.ppoint[8] - b.x1), int(b.ppoint[9] - b.y1));
  532. cv::Mat crop_img;
  533. if (y5 > y3) {
  534. // Y position
  535. int ymax = b.y2 + padding;
  536. if (ymax > m_raw_img.rows) {
  537. ymax = m_raw_img.rows;
  538. }
  539. crop_img = m_raw_img(cv::Range(b.y1, ymax), cv::Range(b.x1, b.x2)).clone();
  540. }
  541. else {
  542. // ^ position
  543. if (b.y1 - padding < 0) {
  544. padding = b.y1;
  545. }
  546. p5.y = int(b.ppoint[9] - b.y1 + padding);
  547. p4.y = int(b.ppoint[7] - b.y1 + padding);
  548. p3.y = int(b.ppoint[5] - b.y1 + padding);
  549. crop_img = m_raw_img(cv::Range(b.y1 - padding, b.y2), cv::Range(b.x1, b.x2)).clone();
  550. }
  551. if (m_cp.image_show) {
  552. cv::Mat crop_img_tmp = crop_img.clone();
  553. cv::circle(crop_img_tmp, p3, 4, cv::Scalar(255, 0, 0), -1, 3, 0);
  554. cv::circle(crop_img_tmp, p4, 4, cv::Scalar(0, 255, 0), -1, 3, 0);
  555. cv::circle(crop_img_tmp, p5, 4, cv::Scalar(0, 0, 255), -1, 3, 0);
  556. imshow_wait("cropped box", crop_img_tmp);
  557. }
  558. //to gray
  559. cv::Mat gray_img;
  560. if (crop_img.channels() == 1) { gray_img = crop_img; }
  561. else {
  562. cv::cvtColor(crop_img, gray_img, cv::COLOR_BGR2GRAY);
  563. }
  564. //binary
  565. cv::Mat bin_img;
  566. double th = cv::threshold(gray_img, bin_img, 255, 255, cv::THRESH_OTSU);
  567. cv::bitwise_not(bin_img, bin_img);
  568. if (m_cp.image_show) {
  569. imshow_wait("cropped binary img", bin_img);
  570. }
  571. // skeletonize() or medial_axis()
  572. cv::Mat ske_img;
  573. thinning(bin_img, ske_img);
  574. /*if (m_cp.image_show) {
  575. imshow_wait("skeleton img", ske_img);
  576. }*/
  577. //遍历所有点,找到距离等于指定距离的点的位置, 以及距离p5最近的骨架上的点
  578. std::vector<cv::Point> candidate_pts;
  579. cv::Point p5_nearst;
  580. double dist_th = 5;
  581. double dist_min = 1.0e6;
  582. for (int r = 0; r < ske_img.rows; ++r) {
  583. for (int c = 0; c < ske_img.cols; ++c) {
  584. if (ske_img.at<unsigned char>(r, c) == 0) { continue; }
  585. double dist = std::powf((p5.x - c), 2) + std::powf((p5.y - r),2);
  586. dist = std::sqrtf(dist);
  587. if (dist < dist_min) {
  588. dist_min = dist;
  589. p5_nearst.x = c;
  590. p5_nearst.y = r;
  591. }
  592. if (std::fabs(dist - m_cp.offset_grab) < dist_th) {
  593. candidate_pts.push_back(cv::Point(c, r));
  594. }
  595. }
  596. }
  597. //按与参考角度的差,找到有效的候选点集合
  598. std::vector<cv::Point> valid_candidate_pts;
  599. double ref_angle = atan2(p5.x - p3.x, p5.y - p3.y);
  600. cv::Point p_min_angle(-1,-1);
  601. double min_angle = CV_PI;
  602. for (auto&p : candidate_pts) {
  603. double angle_to_p3 = atan2(p.x - p3.x, p.y - p3.y);
  604. //计算夹角
  605. double fabs_angle = intersection_angle(ref_angle, angle_to_p3);
  606. /*if (ref_angle > 0.5 * CV_PI) {
  607. if (angle_to_p3 < 0) {
  608. angle_to_p3 += 2 * CV_PI;
  609. }
  610. fabs_angle = std::fabs(angle_to_p3 - ref_angle);
  611. }
  612. else {
  613. if (ref_angle < -0.5 * CV_PI) {
  614. if (angle_to_p3 > 0) {
  615. angle_to_p3 -= 2 * CV_PI;
  616. }
  617. fabs_angle = std::fabs(angle_to_p3 - ref_angle);
  618. }
  619. else {
  620. fabs_angle = std::fabs(angle_to_p3 - ref_angle);
  621. }
  622. }*/
  623. if (fabs_angle > CV_PI / 4.0) { continue; }
  624. if (fabs_angle < min_angle) {
  625. min_angle = fabs_angle;
  626. p_min_angle.x = p.x;
  627. p_min_angle.y = p.y;
  628. }
  629. valid_candidate_pts.push_back(p);
  630. }
  631. if (p_min_angle.x>0 && p_min_angle.y>0) {
  632. grab_x = p_min_angle.x;
  633. grab_y = p_min_angle.y;
  634. }
  635. if (m_cp.image_show) {
  636. cv::Mat ske_img_tmp = ske_img.clone();
  637. for (auto&p : valid_candidate_pts) {
  638. ske_img_tmp.at<unsigned char>(p) = 100;
  639. }
  640. cv::circle(ske_img_tmp, p5, 4, cv::Scalar(255, 0, 255), 1, 3, 0);
  641. if (grab_x > 0 && grab_y > 0) {
  642. cv::circle(ske_img_tmp, cv::Point(int(grab_x), int(grab_y)), 4, cv::Scalar(156, 0, 255), 1, 3, 0);
  643. }
  644. imshow_wait("skeleton img label", ske_img_tmp);
  645. }
  646. //计算grab点的抓取角度
  647. if (p_min_angle.x > 0 && p_min_angle.y > 0) {
  648. grab_angle = get_grab_position(ske_img, p_min_angle, ref_angle);
  649. }
  650. //重新得到grab_x,grab_y的坐标
  651. if (grab_x > 0 && grab_y > 0) {
  652. int real_padding_y = p5.y - int(b.ppoint[9] - b.y1);
  653. grab_y -= real_padding_y;
  654. grab_y += b.y1;
  655. grab_x += b.x1;
  656. }
  657. }
  658. /**
  659. * Code for thinning a binary image using Zhang-Suen algorithm.
  660. *
  661. * Author: Nash (nash [at] opencv-code [dot] com)
  662. * Website: http://opencv-code.com
  663. */
  664. /**
  665. * Perform one thinning iteration.
  666. * Normally you wouldn't call this function directly from your code.
  667. *
  668. * Parameters:
  669. * im Binary image with range = [0,1]
  670. * iter 0=even, 1=odd
  671. */
  672. void CTeaSort::thinningIteration(cv::Mat& img, int iter)
  673. {
  674. CV_Assert(img.channels() == 1);
  675. CV_Assert(img.depth() != sizeof(uchar));
  676. CV_Assert(img.rows > 3 && img.cols > 3);
  677. cv::Mat marker = cv::Mat::zeros(img.size(), CV_8UC1);
  678. int nRows = img.rows;
  679. int nCols = img.cols;
  680. if (img.isContinuous()) {
  681. nCols *= nRows;
  682. nRows = 1;
  683. }
  684. int x, y;
  685. uchar *pAbove;
  686. uchar *pCurr;
  687. uchar *pBelow;
  688. uchar *nw, *no, *ne; // north (pAbove)
  689. uchar *we, *me, *ea;
  690. uchar *sw, *so, *se; // south (pBelow)
  691. uchar *pDst;
  692. // initialize row pointers
  693. pAbove = NULL;
  694. pCurr = img.ptr<uchar>(0);
  695. pBelow = img.ptr<uchar>(1);
  696. for (y = 1; y < img.rows - 1; ++y) {
  697. // shift the rows up by one
  698. pAbove = pCurr;
  699. pCurr = pBelow;
  700. pBelow = img.ptr<uchar>(y + 1);
  701. pDst = marker.ptr<uchar>(y);
  702. // initialize col pointers
  703. no = &(pAbove[0]);
  704. ne = &(pAbove[1]);
  705. me = &(pCurr[0]);
  706. ea = &(pCurr[1]);
  707. so = &(pBelow[0]);
  708. se = &(pBelow[1]);
  709. for (x = 1; x < img.cols - 1; ++x) {
  710. // shift col pointers left by one (scan left to right)
  711. nw = no;
  712. no = ne;
  713. ne = &(pAbove[x + 1]);
  714. we = me;
  715. me = ea;
  716. ea = &(pCurr[x + 1]);
  717. sw = so;
  718. so = se;
  719. se = &(pBelow[x + 1]);
  720. int A = (*no == 0 && *ne == 1) + (*ne == 0 && *ea == 1) +
  721. (*ea == 0 && *se == 1) + (*se == 0 && *so == 1) +
  722. (*so == 0 && *sw == 1) + (*sw == 0 && *we == 1) +
  723. (*we == 0 && *nw == 1) + (*nw == 0 && *no == 1);
  724. int B = *no + *ne + *ea + *se + *so + *sw + *we + *nw;
  725. int m1 = iter == 0 ? (*no * *ea * *so) : (*no * *ea * *we);
  726. int m2 = iter == 0 ? (*ea * *so * *we) : (*no * *so * *we);
  727. if (A == 1 && (B >= 2 && B <= 6) && m1 == 0 && m2 == 0)
  728. pDst[x] = 1;
  729. }
  730. }
  731. img &= ~marker;
  732. }
  733. /**
  734. * Function for thinning the given binary image
  735. *
  736. * Parameters:
  737. * src The source image, binary with range = [0,255]
  738. * dst The destination image
  739. */
  740. void CTeaSort::thinning(const cv::Mat& src, cv::Mat& dst)
  741. {
  742. dst = src.clone();
  743. dst /= 255; // convert to binary image
  744. cv::Mat prev = cv::Mat::zeros(dst.size(), CV_8UC1);
  745. cv::Mat diff;
  746. do {
  747. thinningIteration(dst, 0);
  748. thinningIteration(dst, 1);
  749. cv::absdiff(dst, prev, diff);
  750. dst.copyTo(prev);
  751. } while (cv::countNonZero(diff) > 0);
  752. dst *= 255;
  753. }
  754. /**
  755. 计算 [-pi,pi]间的两个角间的夹角
  756. */
  757. double CTeaSort::intersection_angle(
  758. double ref_angle,
  759. double angle_to_p3
  760. )
  761. {
  762. //计算夹角
  763. double fabs_angle = 0;
  764. if (ref_angle > 0.5 * CV_PI) {
  765. if (angle_to_p3 < 0) {
  766. angle_to_p3 += 2 * CV_PI;
  767. }
  768. fabs_angle = std::fabs(angle_to_p3 - ref_angle);
  769. }
  770. else {
  771. if (ref_angle < -0.5 * CV_PI) {
  772. if (angle_to_p3 > 0) {
  773. angle_to_p3 -= 2 * CV_PI;
  774. }
  775. fabs_angle = std::fabs(angle_to_p3 - ref_angle);
  776. }
  777. else {
  778. fabs_angle = std::fabs(angle_to_p3 - ref_angle);
  779. }
  780. }
  781. return fabs_angle;
  782. }
  783. /**
  784. *
  785. */
  786. double CTeaSort::get_grab_position(
  787. const cv::Mat& skele_img,
  788. cv::Point&vertex,
  789. double ref_angle
  790. )
  791. {
  792. double grab_point_angle = CV_2PI;
  793. cv::Point pt0, pt1, pt2, pt3;
  794. double radius = static_cast<double>(m_cp.offset_grab) * 0.5;
  795. calc_bottom_vertex(vertex, ref_angle, CV_PI / 8.0, radius, pt0, pt1);
  796. calc_bottom_vertex(vertex, ref_angle+CV_PI, CV_PI / 8.0, radius, pt2, pt3);
  797. std::vector<cv::Point> triangle_region;
  798. triangle_region.push_back(pt0);
  799. triangle_region.push_back(pt1);
  800. triangle_region.push_back(pt2);
  801. triangle_region.push_back(pt3);
  802. //构建多边形,然后判别骨架图中在多边形内的骨架像素
  803. std::vector<cv::Point> curve_pts;
  804. cv::Mat roi_img = skele_img.clone();
  805. for(int r=0;r<roi_img.rows;++r){
  806. for(int c=0;c<roi_img.cols;++c){
  807. if(roi_img.at<unsigned char>(r,c)==0){continue;}
  808. double d = cv::pointPolygonTest(triangle_region,cv::Point2f(c,r),false);
  809. // d 1-内部点, 0-边缘点 -1-外部点
  810. if(d<=0){
  811. roi_img.at<unsigned char>(r,c)==0;
  812. }
  813. else{
  814. curve_pts.push_back(cv::Point(c,r));
  815. }
  816. }
  817. }
  818. //根据curve_pts进行曲线拟合,得到茎的曲线
  819. //cv::Mat curve_model;
  820. //poly_fit_cv(curve_pts, 1, curve_model);
  821. cv::Vec4f line_model;//[vx,vy, x0,y0], vx,vy---方向的归一化向量,x0,y0---直线上任意一点
  822. line_fit(curve_pts, line_model);
  823. //double k = curve_model.at<double>(1, 0);
  824. //double y_angle = 0.5 * CV_PI - atan(k); // y_angle in range [0, pi]
  825. double y_angle = atan2(line_model[0], line_model[1]);// y_angle in range [-pi, pi]
  826. double fabs_angle = intersection_angle(ref_angle, y_angle);
  827. double y_angle_inv = atan2(-line_model[0], -line_model[1]);; //y_angle_inv in range [-pi, pi]
  828. double fabs_angle_inv = intersection_angle(ref_angle, y_angle_inv);
  829. grab_point_angle = y_angle;
  830. if (fabs_angle_inv < fabs_angle) {
  831. grab_point_angle = y_angle_inv;
  832. }
  833. //可视化
  834. if (m_cp.image_show) {
  835. cv::Mat ske_img_tmp = skele_img.clone();
  836. for (auto&p : curve_pts) {
  837. ske_img_tmp.at<unsigned char>(p) = 100;
  838. }
  839. cv::circle(ske_img_tmp, vertex, 4, cv::Scalar(156, 0, 255), 1, 3, 0);
  840. cv::circle(ske_img_tmp, pt0, 4, cv::Scalar(156, 0, 255), 1, 3, 0);
  841. cv::circle(ske_img_tmp, pt1, 4, cv::Scalar(156, 0, 255), 1, 3, 0);
  842. cv::circle(ske_img_tmp, pt2, 4, cv::Scalar(156, 0, 255), 1, 3, 0);
  843. cv::circle(ske_img_tmp, pt3, 4, cv::Scalar(156, 0, 255), 1, 3, 0);
  844. cv::line(ske_img_tmp, pt0, pt1, cv::Scalar(255, 215, 255), 2);
  845. cv::line(ske_img_tmp, pt0, pt3, cv::Scalar(255, 215, 255), 2);
  846. cv::line(ske_img_tmp, pt1, pt2, cv::Scalar(255, 215, 255), 2);
  847. cv::line(ske_img_tmp, pt2, pt3, cv::Scalar(255, 215, 255), 2);
  848. double dcx = radius * sin(grab_point_angle);
  849. double dcy = radius * cos(grab_point_angle);
  850. cv::Point dir_o;
  851. cv::Point dir_p;
  852. dir_o.x = vertex.x + 10;
  853. dir_o.y = vertex.y;
  854. dir_p.x = int(vertex.x + 10 + dcx);
  855. dir_p.y = int(vertex.y + dcy);
  856. cv::line(ske_img_tmp, dir_o, dir_p, cv::Scalar(255, 215, 255), 2);
  857. imshow_wait("grab angle", ske_img_tmp);
  858. }
  859. return grab_point_angle;
  860. }
  861. /**
  862. * calc_bottom_vertex
  863. * 找到三角形两个底角点
  864. */
  865. void CTeaSort::calc_bottom_vertex(
  866. cv::Point&vertex, //input
  867. double ref_angle, //input, rad
  868. double delta_angle, //input, rad
  869. double radius, //input
  870. cv::Point&bpt0, //output
  871. cv::Point&bpt1 //output
  872. )
  873. {
  874. //double delta_angle = CV_PI / 8.0; // 22.5 degree
  875. //double radius = static_cast<double>(m_cp.offset_grab) * 1.5;
  876. double angle = ref_angle - delta_angle;
  877. int x = static_cast<int>(radius * sin(angle) + 0.5) + vertex.x;
  878. int y = static_cast<int>(radius * cos(angle) + 0.5) + vertex.y;
  879. bpt0.x = x;
  880. bpt0.y = y;
  881. angle = ref_angle + delta_angle;
  882. x = static_cast<int>(radius * sin(angle) + 0.5) + vertex.x;
  883. y = static_cast<int>(radius * cos(angle) + 0.5) + vertex.y;
  884. bpt1.x = x;
  885. bpt1.y = y;
  886. }
  887. //cv::Mat CTeaSort::poly_fit(
  888. // std::vector<cv::Point2f>& chain,
  889. // int n
  890. //)
  891. //{
  892. // //https://blog.csdn.net/jpc20144055069/article/details/103232641
  893. // cv::Mat y(chain.size(), 1, CV_32F, cv::Scalar::all(0));
  894. // cv::Mat phy(chain.size(), n, CV_32F, cv::Scalar::all(0));
  895. // for(int i=0;i<phy.rows;++i){
  896. // float* pr = phy.ptr<float>(i);
  897. // for(int j=0; j<phy.cols;++j){
  898. // pr[j] = pow(chain[i].x,j);
  899. // }
  900. // y.at<float>(i) = chain[i].y;
  901. // }
  902. //
  903. // cv::Mat phy_t = phy.t();
  904. // cv::Mat phyMULphy_t = phy.t() * phy;
  905. // cv::Mat phyMphyInv = phyMULphy_t.inv();
  906. // cv::Mat a = phyMphyInv * phy_t;
  907. // a = a*y;
  908. // return a;
  909. //}
  910. void CTeaSort::line_fit(std::vector<cv::Point>& key_point, cv::Vec4f& lines)
  911. {
  912. std::vector<cv::Point2f> pts;
  913. for (auto&p : key_point) {
  914. pts.push_back(cv::Point2f(p.x, p.y));
  915. }
  916. double param = 0;
  917. double reps = 0.01;
  918. double aeps = 0.01;
  919. //cv::Vec4f lines;//[vx,vy, x0,y0], vx,vy---方向的归一化向量,x0,y0---直线上任意一点
  920. cv::fitLine(pts, lines, DIST_L1, param, reps, aeps);
  921. }
  922. //bool CTeaSort::poly_fit_cv(
  923. //std::vector<cv::Point>& key_point,
  924. //int n,
  925. //cv::Mat& A
  926. //)
  927. //{
  928. // //https://blog.csdn.net/KYJL888/article/details/103073956
  929. // int N = key_point.size();
  930. //
  931. // //构造矩阵X
  932. // cv::Mat X = cv::Mat::zeros(n+1, n+1, CV_64FC1);
  933. // for(int i=0;i<n+1; ++i){
  934. // for(int j=0;j<n+1;++j){
  935. // for(int k=0;k<N;++k){
  936. // X.at<double>(i,j) = X.at<double>(i,j) +
  937. // std::pow(key_point[k].x, i+j);
  938. // }
  939. // }
  940. // }
  941. //
  942. // //构造矩阵Y
  943. // cv::Mat Y = cv::Mat::zeros(n+1, 1, CV_64FC1);
  944. // for(int i=0;i<n+1;++i){
  945. // for(int k=0;k<N;++k){
  946. // Y.at<double>(i,0) = Y.at<double>(i,0) +
  947. // std::pow(key_point[k].x, i) + key_point[k].y;
  948. // }
  949. // }
  950. //
  951. // A = cv::Mat::zeros(n+1, 1, CV_64FC1);
  952. // cv::solve(X,Y,A,cv::DECOMP_LU);
  953. // return true;
  954. //}
  955. //double CTeaSort::calc_fit_y(
  956. //double x, //input
  957. //cv::Mat& A //input
  958. //)
  959. //{
  960. // //double y = A.at<double>(0,0) + A.at<double>(1,0) * x +
  961. // // A.at<double>(2,0) * std::pow(x,2) + A.at<double>(3,0) * std::pow(x,3);
  962. // //return y;
  963. //
  964. // double y = 0.0;
  965. // for(int i=0; i<A.rows;++i){
  966. // y += A.at<double>(i,0) * std::pow(x,i);
  967. // }
  968. // return y;
  969. //}
  970. //}
  971. //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  972. // calculate_stem_grab_position_opt()替代calculate_stem_grab_position函数
  973. // 1)采用局部thinning方法提高效率
  974. // 2) 重新用局部线性拟合的方向替代ref_angle(原始是p5和p3点连线与y正方向的夹角)
  975. void CTeaSort::calculate_stem_grab_position_opt(
  976. Bbox&b_original,
  977. double& grab_x, //output
  978. double& grab_y, //output
  979. double& grab_angle //input-output
  980. )
  981. {
  982. Bbox b(b_original);
  983. int padding_border = m_cp.offset_grab;
  984. b.x1 -= padding_border;
  985. b.x1 = b.x1 < 0 ? 0 : b.x1;
  986. b.y1 -= padding_border;
  987. b.y1 = b.y1 < 0 ? 0 : b.y1;
  988. b.x2 += padding_border;
  989. b.x2 = b.x2 < m_raw_img.cols ? b.x2 : m_raw_img.cols - 1;
  990. b.y2 += padding_border;
  991. b.y2 = b.y2 < m_raw_img.rows ? b.y2 : m_raw_img.rows - 1;
  992. grab_x = grab_y = -1.0;
  993. //crop image
  994. int padding = 0;
  995. int y3 = int(b.ppoint[5]);
  996. int y5 = int(b.ppoint[9]);
  997. cv::Point p3(int(b.ppoint[4] - b.x1), int(b.ppoint[5] - b.y1));
  998. cv::Point p4(int(b.ppoint[6] - b.x1), int(b.ppoint[7] - b.y1));
  999. cv::Point p5(int(b.ppoint[8] - b.x1), int(b.ppoint[9] - b.y1));
  1000. cv::Mat crop_img;
  1001. if (y5 > y3) {
  1002. // Y position
  1003. int ymax = b.y2 + padding;
  1004. if (ymax > m_raw_img.rows) {
  1005. ymax = m_raw_img.rows;
  1006. }
  1007. crop_img = m_raw_img(cv::Range(b.y1, ymax), cv::Range(b.x1, b.x2)).clone();
  1008. }
  1009. else {
  1010. // ^ position
  1011. if (b.y1 - padding < 0) {
  1012. padding = b.y1;
  1013. }
  1014. p5.y = int(b.ppoint[9] - b.y1 + padding);
  1015. p4.y = int(b.ppoint[7] - b.y1 + padding);
  1016. p3.y = int(b.ppoint[5] - b.y1 + padding);
  1017. crop_img = m_raw_img(cv::Range(b.y1 - padding, b.y2), cv::Range(b.x1, b.x2)).clone();
  1018. }
  1019. if (m_cp.image_show) {
  1020. cv::Mat crop_img_tmp = crop_img.clone();
  1021. cv::circle(crop_img_tmp, p3, 4, cv::Scalar(255, 0, 0), -1, 3, 0);
  1022. cv::circle(crop_img_tmp, p4, 4, cv::Scalar(0, 255, 0), -1, 3, 0);
  1023. cv::circle(crop_img_tmp, p5, 4, cv::Scalar(0, 0, 255), -1, 3, 0);
  1024. imshow_wait("cropped box", crop_img_tmp);
  1025. }
  1026. //to gray
  1027. cv::Mat gray_img;
  1028. if (crop_img.channels() == 1) { gray_img = crop_img; }
  1029. else {
  1030. cv::cvtColor(crop_img, gray_img, cv::COLOR_BGR2GRAY);
  1031. }
  1032. //binary
  1033. cv::Mat bin_img;
  1034. double th = cv::threshold(gray_img, bin_img, 255, 255, cv::THRESH_OTSU);
  1035. cv::bitwise_not(bin_img, bin_img);
  1036. if (m_cp.image_show) {
  1037. imshow_wait("cropped binary img", bin_img);
  1038. }
  1039. //contours image
  1040. /*vector<vector<cv::Point>> contours;
  1041. vector<cv::Vec4i> hierarchy;
  1042. contours.clear();
  1043. hierarchy.clear();
  1044. findContours(bin_img, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE);
  1045. cv::Mat con_img = cv::Mat::zeros(bin_img.size(), CV_8UC1);
  1046. for (auto&c : contours) {
  1047. for (auto&p : c) {
  1048. con_img.at<unsigned char>(p.y, p.x) = 255;
  1049. }
  1050. }
  1051. if (m_cp.image_show) {
  1052. imshow_wait("findContours", con_img);
  1053. }*/
  1054. // skeletonize() or medial_axis()
  1055. cv::Mat ske_img;
  1056. thinning(bin_img, ske_img);
  1057. /*if (m_cp.image_show) {
  1058. imshow_wait("skeleton img", ske_img);
  1059. }*/
  1060. //在grab_angle的指导下找到最优方向,截图,进行局部thinning
  1061. double ref_angle_init = grab_angle;
  1062. double delta_angle = CV_PI / 24.0;
  1063. double radius = static_cast<double>(m_cp.offset_grab);
  1064. cv::Point pt0, pt1, pt2, pt3;
  1065. double step_angle = CV_PI / 36.0; // 5 degree
  1066. int max_pixels = 0;
  1067. cv::Point pt0_opt, pt1_opt, pt2_opt, pt3_opt, center_opt;
  1068. int minx_opt, maxx_opt, miny_opt, maxy_opt;
  1069. double target_angle_opt;
  1070. for (int i = -8; i <= 8; ++i) { //-30 degree ---- 30 degree
  1071. //在指定方向的矩形框内,找到内部点最多的方向,作为主方向
  1072. double target_angle = ref_angle_init + i*step_angle;
  1073. cv::Point center_pt;
  1074. center_pt.x = p5.x + static_cast<int>(radius * sin(target_angle));
  1075. center_pt.y = p5.y + static_cast<int>(radius * cos(target_angle));
  1076. calc_bottom_vertex(center_pt, target_angle, delta_angle, radius, pt0, pt1);
  1077. calc_bottom_vertex(center_pt, target_angle + CV_PI, delta_angle, radius, pt2, pt3);
  1078. std::vector<cv::Point> triangle_region;
  1079. triangle_region.push_back(pt0);
  1080. triangle_region.push_back(pt1);
  1081. triangle_region.push_back(pt2);
  1082. triangle_region.push_back(pt3);
  1083. //外接4边形
  1084. int minx, maxx, miny, maxy;
  1085. minx = maxx = pt0.x;
  1086. miny = maxy = pt0.y;
  1087. for (auto& pt : triangle_region) {
  1088. minx = minx > pt.x ? pt.x : minx;
  1089. maxx = maxx > pt.x ? maxx : pt.x;
  1090. miny = miny > pt.y ? pt.y : miny;
  1091. maxy = maxy > pt.y ? maxy : pt.y;
  1092. }
  1093. //counting
  1094. int pixel_num = 0;
  1095. for (int r = miny; r <= maxy; ++r) {
  1096. if (r < 0) { continue; }
  1097. if (r >= ske_img.rows) { continue; }
  1098. for (int c = minx; c <= maxx; ++c) {
  1099. if (c < 0) { continue; }
  1100. if (c >= ske_img.cols) { continue; }
  1101. if (ske_img.at<unsigned char>(r, c) == 0) { continue; }
  1102. double d = cv::pointPolygonTest(triangle_region, cv::Point2f(c, r), false);
  1103. // d 1-内部点, 0-边缘点 -1-外部点
  1104. if (d >= 0) {
  1105. pixel_num++;
  1106. }
  1107. }
  1108. }
  1109. if (pixel_num > max_pixels) {
  1110. max_pixels = pixel_num;
  1111. pt0_opt = pt0;
  1112. pt1_opt = pt1;
  1113. pt2_opt = pt2;
  1114. pt3_opt = pt3;
  1115. center_opt = center_pt;
  1116. minx_opt = minx;
  1117. maxx_opt = maxx;
  1118. miny_opt = miny;
  1119. maxy_opt = maxy;
  1120. target_angle_opt = target_angle;
  1121. }
  1122. /*if (m_cp.image_show) {
  1123. cv::Mat bin_tmp = bin_img.clone();
  1124. cv::circle(bin_tmp, p5, 4, cv::Scalar(156, 0, 255), 1, 3, 0);
  1125. cv::circle(bin_tmp, pt0, 4, cv::Scalar(156, 0, 255), 1, 3, 0);
  1126. cv::circle(bin_tmp, pt1, 4, cv::Scalar(156, 0, 255), 1, 3, 0);
  1127. cv::circle(bin_tmp, pt2, 4, cv::Scalar(156, 0, 255), 1, 3, 0);
  1128. cv::circle(bin_tmp, pt3, 4, cv::Scalar(156, 0, 255), 1, 3, 0);
  1129. cv::line(bin_tmp, pt0, pt1, cv::Scalar(180, 215, 255), 2);
  1130. cv::line(bin_tmp, pt0, pt3, cv::Scalar(180, 215, 255), 2);
  1131. cv::line(bin_tmp, pt1, pt2, cv::Scalar(180, 215, 255), 2);
  1132. cv::line(bin_tmp, pt2, pt3, cv::Scalar(180, 215, 255), 2);
  1133. imshow_wait("binary img box", bin_tmp);
  1134. }*/
  1135. }
  1136. //opt box process
  1137. if (m_cp.image_show) {
  1138. cv::Mat bin_tmp = ske_img.clone();
  1139. cv::circle(bin_tmp, p5, 4, cv::Scalar(156, 0, 255), 1, 3, 0);
  1140. cv::circle(bin_tmp, pt0_opt, 4, cv::Scalar(156, 0, 255), 1, 3, 0);
  1141. cv::circle(bin_tmp, pt1_opt, 4, cv::Scalar(156, 0, 255), 1, 3, 0);
  1142. cv::circle(bin_tmp, pt2_opt, 4, cv::Scalar(156, 0, 255), 1, 3, 0);
  1143. cv::circle(bin_tmp, pt3_opt, 4, cv::Scalar(156, 0, 255), 1, 3, 0);
  1144. cv::line(bin_tmp, pt0_opt, pt1_opt, cv::Scalar(180, 215, 255), 2);
  1145. cv::line(bin_tmp, pt0_opt, pt3_opt, cv::Scalar(180, 215, 255), 2);
  1146. cv::line(bin_tmp, pt1_opt, pt2_opt, cv::Scalar(180, 215, 255), 2);
  1147. cv::line(bin_tmp, pt2_opt, pt3_opt, cv::Scalar(180, 215, 255), 2);
  1148. imshow_wait("binary img box opt", bin_tmp);
  1149. }
  1150. // skeletonize() or medial_axis()
  1151. /*cv::Mat ske_img;
  1152. cv::Mat roi_bin_img = cv::Mat::zeros(bin_img.size(), CV_8UC1);
  1153. for (int r = miny_opt; r <= maxy_opt; ++r) {
  1154. if (r < 0) { continue; }
  1155. if (r >= bin_img.rows) { continue; }
  1156. for (int c = minx_opt; c <= maxx_opt; ++c) {
  1157. if (c < 0) { continue; }
  1158. if (c >= bin_img.cols) { continue; }
  1159. if (bin_img.at<unsigned char>(r, c) == 0) { continue; }
  1160. roi_bin_img.at<unsigned char>(r, c) = bin_img.at<unsigned char>(r, c);
  1161. }
  1162. }
  1163. thinning(roi_bin_img, ske_img);
  1164. if (m_cp.image_show) {
  1165. imshow_wait("skeleton img", ske_img);
  1166. }*/
  1167. //通过区域内的骨架点计算ref_angle
  1168. std::vector<cv::Point> triangle_region;
  1169. triangle_region.push_back(pt0_opt);
  1170. triangle_region.push_back(pt1_opt);
  1171. triangle_region.push_back(pt2_opt);
  1172. triangle_region.push_back(pt3_opt);
  1173. std::vector<cv::Point> in_region_pts;
  1174. for (int r = miny_opt; r <= maxy_opt; ++r) {
  1175. if (r < 0) { continue; }
  1176. if (r >= ske_img.rows) { continue; }
  1177. for (int c = minx_opt; c <= maxx_opt; ++c) {
  1178. if (c < 0) { continue; }
  1179. if (c >= ske_img.cols) { continue; }
  1180. if (ske_img.at<unsigned char>(r, c) == 0) { continue; }
  1181. double d = cv::pointPolygonTest(triangle_region, cv::Point2f(c, r), false);
  1182. // d 1-内部点, 0-边缘点 -1-外部点
  1183. if (d > 0) {
  1184. in_region_pts.push_back(cv::Point(c, r));
  1185. }
  1186. }
  1187. }
  1188. //计算ref_angle
  1189. cv::Vec4f line_model;//[vx,vy, x0,y0], vx,vy---方向的归一化向量,x0,y0---直线上任意一点
  1190. line_fit(in_region_pts, line_model);
  1191. double y_angle = atan2(line_model[0], line_model[1]);// y_angle in range [-pi, pi]
  1192. double fabs_angle = intersection_angle(target_angle_opt, y_angle);
  1193. double y_angle_inv = atan2(-line_model[0], -line_model[1]);; //y_angle_inv in range [-pi, pi]
  1194. double fabs_angle_inv = intersection_angle(target_angle_opt, y_angle_inv);
  1195. double ref_angle = y_angle;
  1196. if (fabs_angle_inv < fabs_angle) {
  1197. ref_angle = y_angle_inv;
  1198. }
  1199. //可视化
  1200. /*if (m_cp.image_show) {
  1201. cv::Mat ske_img_tmp = ske_img.clone();
  1202. for (auto&p : in_region_pts) {
  1203. ske_img_tmp.at<unsigned char>(p) = 100;
  1204. }
  1205. double dcx = radius * sin(ref_angle);
  1206. double dcy = radius * cos(ref_angle);
  1207. cv::Point dir_o;
  1208. cv::Point dir_p;
  1209. dir_o.x = center_opt.x + 10;
  1210. dir_o.y = center_opt.y;
  1211. dir_p.x = int(center_opt.x + 10 + dcx);
  1212. dir_p.y = int(center_opt.y + dcy);
  1213. cv::line(ske_img_tmp, dir_o, dir_p, cv::Scalar(255, 215, 255), 2);
  1214. imshow_wait("ref angle", ske_img_tmp);
  1215. }*/
  1216. //遍历所有点,找到距离等于指定距离的点的位置, 以及距离p5最近的骨架上的点
  1217. std::vector<cv::Point> candidate_pts;
  1218. cv::Point p5_nearst;
  1219. double dist_th = 5;
  1220. double dist_min = 1.0e6;
  1221. for (auto& pt : in_region_pts) {
  1222. int c = pt.x;
  1223. int r = pt.y;
  1224. double dist = std::powf((p5.x - c), 2) + std::powf((p5.y - r), 2);
  1225. dist = std::sqrtf(dist);
  1226. if (dist < dist_min) {
  1227. dist_min = dist;
  1228. p5_nearst.x = c;
  1229. p5_nearst.y = r;
  1230. }
  1231. if (std::fabs(dist - m_cp.offset_grab) < dist_th) {
  1232. candidate_pts.push_back(cv::Point(c, r));
  1233. }
  1234. }
  1235. //按与参考角度的差,找到有效的候选点集合
  1236. std::vector<cv::Point> valid_candidate_pts;
  1237. cv::Point p_min_angle(-1, -1);
  1238. double min_angle = CV_PI;
  1239. for (auto&p : candidate_pts) {
  1240. double angle_to_p3 = atan2(p.x - p3.x, p.y - p3.y);
  1241. //计算夹角
  1242. double fabs_angle = intersection_angle(ref_angle, angle_to_p3);
  1243. if (fabs_angle > CV_PI / 4.0) { continue; }
  1244. if (fabs_angle < min_angle) {
  1245. min_angle = fabs_angle;
  1246. p_min_angle.x = p.x;
  1247. p_min_angle.y = p.y;
  1248. }
  1249. valid_candidate_pts.push_back(p);
  1250. }
  1251. if (p_min_angle.x>0 && p_min_angle.y>0) {
  1252. grab_x = p_min_angle.x;
  1253. grab_y = p_min_angle.y;
  1254. }
  1255. if (m_cp.image_show) {
  1256. cv::Mat ske_img_tmp = ske_img.clone();
  1257. for (auto&p : valid_candidate_pts) {
  1258. ske_img_tmp.at<unsigned char>(p) = 100;
  1259. }
  1260. cv::circle(ske_img_tmp, p5, 4, cv::Scalar(255, 0, 255), 1, 3, 0);
  1261. if (grab_x > 0 && grab_y > 0) {
  1262. cv::circle(ske_img_tmp, cv::Point(int(grab_x), int(grab_y)), 4, cv::Scalar(156, 0, 255), 1, 3, 0);
  1263. }
  1264. imshow_wait("skeleton img label", ske_img_tmp);
  1265. }
  1266. //计算grab点的抓取角度
  1267. if (p_min_angle.x > 0 && p_min_angle.y > 0) {
  1268. grab_angle = get_grab_position(ske_img, p_min_angle, ref_angle);
  1269. }
  1270. //重新得到grab_x,grab_y的坐标
  1271. if (grab_x > 0 && grab_y > 0) {
  1272. int real_padding_y = p5.y - int(b.ppoint[9] - b.y1);
  1273. grab_y -= real_padding_y;
  1274. grab_y += b.y1;
  1275. grab_x += b.x1;
  1276. }
  1277. }
  1278. void CTeaSort::calculate_stem_cut_position_opt(
  1279. Bbox&b,
  1280. double& grab_x, //output
  1281. double& grab_y, //output
  1282. double& grab_angle //input-output
  1283. )
  1284. {
  1285. int padding = 40;
  1286. grab_x = grab_y = -1.0;
  1287. //crop image
  1288. cv::Point p3o(int(b.ppoint[4]), int(b.ppoint[5]));
  1289. cv::Point p4o(int(b.ppoint[6]), int(b.ppoint[7]));
  1290. int x1, y1, x2, y2;
  1291. x1 = min(p3o.x, p4o.x);
  1292. y1 = min(p3o.y, p4o.y);
  1293. x2 = max(p3o.x, p4o.x);
  1294. y2 = max(p3o.y, p4o.y);
  1295. x1 -= padding;
  1296. x1 = x1 < 0 ? 0 : x1;
  1297. y1 -= padding;
  1298. y1 = y1 < 0 ? 0 : y1;
  1299. x2 += padding;
  1300. x2 = x2 < m_raw_img.cols ?x2 : m_raw_img.cols - 1;
  1301. y2 += padding;
  1302. y2 = y2 < m_raw_img.rows ? y2 : m_raw_img.rows - 1;
  1303. cv::Point p3(int(b.ppoint[4] - x1), int(b.ppoint[5] - y1));
  1304. cv::Point p4(int(b.ppoint[6] - x1), int(b.ppoint[7] - y1));
  1305. cv::Mat crop_img;
  1306. crop_img = m_raw_img(cv::Range(y1, y2), cv::Range(x1, x2)).clone();
  1307. if (m_cp.image_show) {
  1308. cv::Mat crop_img_tmp = crop_img.clone();
  1309. cv::circle(crop_img_tmp, p3, 4, cv::Scalar(255, 0, 0), -1, 3, 0);
  1310. cv::circle(crop_img_tmp, p4, 4, cv::Scalar(0, 255, 0), -1, 3, 0);
  1311. imshow_wait("cropped box", crop_img_tmp);
  1312. }
  1313. //to gray
  1314. cv::Mat gray_img;
  1315. if (crop_img.channels() == 1) { gray_img = crop_img; }
  1316. else {
  1317. cv::cvtColor(crop_img, gray_img, cv::COLOR_BGR2GRAY);
  1318. }
  1319. //binary
  1320. cv::Mat bin_img;
  1321. double th = cv::threshold(gray_img, bin_img, 255, 255, cv::THRESH_OTSU);
  1322. cv::bitwise_not(bin_img, bin_img);
  1323. if (m_cp.image_show) {
  1324. imshow_wait("cropped binary img", bin_img);
  1325. }
  1326. // skeletonize() or medial_axis()
  1327. cv::Mat ske_img;
  1328. thinning(bin_img, ske_img);
  1329. if (m_cp.image_show) {
  1330. imshow_wait("skeleton img", ske_img);
  1331. }
  1332. cv::Point2f center_pt;
  1333. center_pt.x = 0.5*(p3.x + p4.x);
  1334. center_pt.y = 0.5*(p3.y + p4.y);
  1335. int min_x, min_y;
  1336. double min_loss = 1.0e6;
  1337. double ref_angle = grab_angle + CV_PI / 2.0;
  1338. if (ref_angle > CV_PI) {
  1339. ref_angle = ref_angle - 2 * CV_PI;
  1340. }
  1341. for (int r = 0; r < ske_img.rows; ++r) {
  1342. for (int c = 0; c < ske_img.cols; ++c) {
  1343. if (ske_img.at<unsigned char>(r, c) == 0) { continue; }
  1344. double target_angle = atan2(double(c- center_pt.x), double(r - center_pt.y));
  1345. double dangle = intersection_angle(ref_angle, target_angle);
  1346. if (dangle > CV_PI / 36.0) { continue; }
  1347. double dist = std::powf((center_pt.x - c), 2) + std::powf((center_pt.y - r), 2);
  1348. dist = std::sqrtf(dist);
  1349. double loss = dist;
  1350. // d 1-内部点, 0-边缘点 -1-外部点
  1351. if (loss < min_loss) {
  1352. min_loss = loss;
  1353. min_x = c;
  1354. min_y = r;
  1355. }
  1356. }
  1357. }
  1358. //另一个方向
  1359. ref_angle = grab_angle - CV_PI / 2.0;
  1360. if (ref_angle < -CV_PI) {
  1361. ref_angle = ref_angle + 2 * CV_PI;
  1362. }
  1363. for (int r = 0; r < ske_img.rows; ++r) {
  1364. for (int c = 0; c < ske_img.cols; ++c) {
  1365. if (ske_img.at<unsigned char>(r, c) == 0) { continue; }
  1366. double target_angle = atan2(double(c - center_pt.x), double(r - center_pt.y));
  1367. double dangle = intersection_angle(ref_angle, target_angle);
  1368. if (dangle > CV_PI / 36.0) { continue; }
  1369. double dist = std::powf((center_pt.x - c), 2) + std::powf((center_pt.y - r), 2);
  1370. dist = std::sqrtf(dist);
  1371. double loss = dist;
  1372. // d 1-内部点, 0-边缘点 -1-外部点
  1373. if (loss < min_loss) {
  1374. min_loss = loss;
  1375. min_x = c;
  1376. min_y = r;
  1377. }
  1378. }
  1379. }
  1380. grab_x = min_x;
  1381. grab_y = min_y;
  1382. grab_x += x1;
  1383. grab_y += y1;
  1384. }
  1385. }