tea_sorter.cpp 38 KB

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