tea_sorter.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799
  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. droplets.push_back(tbox);
  157. }
  158. if (m_pLogger) {
  159. stringstream buff_;
  160. buff_ << m_imgId << m_dtype_str << "after nms, keep tea number is " << droplets.size();
  161. for (auto&tbox : droplets) {
  162. buff_ << "\nscore:" << tbox.score << ", area_ratio:" << tbox.area << ", left_top:(" << tbox.x1 << "," << tbox.y1 << "), bottom_rigt:(" << tbox.x2 << "," << tbox.y2 << ")";
  163. }
  164. m_pLogger->INFO(buff_.str());
  165. }
  166. int valid_cnt = 0;
  167. if (m_dtype == img_type::tea_grab) {
  168. //grab
  169. double pre_cx, pre_cy;
  170. double min_dist_grab = m_cp.min_distance_grab;
  171. pre_cx = -min_dist_grab;
  172. pre_cy = -min_dist_grab;
  173. for (int i = 0; i < droplets.size(); ++i) {
  174. if (valid_cnt > 1) { break; }
  175. Bbox&b = droplets.at(i);
  176. double cx = 0.5*(b.x1 + b.x2);
  177. double cy = 0.5*(b.y1 + b.y2);
  178. double dist = sqrt((cx - pre_cx)*(cx - pre_cx) + (cy - pre_cy)*(cy - pre_cy));
  179. if (dist < min_dist_grab) {
  180. continue;
  181. }
  182. double grab_x, grab_y;
  183. double angle = calculate_angle(b, grab_x, grab_y);
  184. //grab point
  185. if (valid_cnt == 0) {
  186. posinfo.tea_grab_x1 = grab_x;
  187. posinfo.tea_grab_y1 = grab_y;
  188. posinfo.tea_grab_angle1 = angle;
  189. }
  190. else {
  191. posinfo.tea_grab_x2 = grab_x;
  192. posinfo.tea_grab_y2 = grab_y;
  193. posinfo.tea_grab_angle2 = angle;
  194. }
  195. pre_cx = cx;
  196. pre_cy = cy;
  197. b.status = 1;
  198. valid_cnt += 1;
  199. }
  200. }
  201. else {
  202. //cut
  203. for (int i = 0; i < droplets.size();++i) {
  204. if (i > 1) { break; }
  205. Bbox&b = droplets.at(i);
  206. b.status = 1; // selected
  207. double grab_x, grab_y;
  208. double angle = calculate_angle(b, grab_x, grab_y);
  209. valid_cnt += 1;
  210. if (i == 0) {
  211. // 切割点是3、4的中间的点
  212. posinfo.tea_cut_x1 = 0.5 * (b.ppoint[4] + b.ppoint[6]);
  213. posinfo.tea_cut_y1 = 0.5 * (b.ppoint[5] + b.ppoint[7]);
  214. posinfo.tea_cut_angle1 = angle;
  215. }
  216. else {
  217. // 切割点是3、4的中间的点
  218. posinfo.tea_cut_x2 = 0.5 * (b.ppoint[4] + b.ppoint[6]);
  219. posinfo.tea_cut_y2 = 0.5 * (b.ppoint[5] + b.ppoint[7]);
  220. posinfo.tea_cut_angle2 = angle;
  221. }
  222. }
  223. }
  224. //6 draw
  225. if (m_cp.image_return) {
  226. this->clear_imginfo();
  227. cv::Mat img_rst = m_raw_img.clone();
  228. for (auto& b : droplets) {
  229. //rectangle
  230. cv::Rect r = cv::Rect(cv::Point2i(b.x1, b.y1), cv::Point2i(b.x2, b.y2));
  231. if (b.status > 0) {
  232. cv::rectangle(img_rst, r, cv::Scalar(0, 0, 255),2);
  233. }
  234. else {
  235. cv::rectangle(img_rst, r, cv::Scalar(0, 255, 0),2);
  236. }
  237. //score
  238. char name[256];
  239. cv::Scalar color(120, 120, 0);//bgr
  240. sprintf_s(name, "%.2f", b.score);
  241. cv::putText(img_rst, name,
  242. cv::Point(b.x1, b.y1),
  243. cv::FONT_HERSHEY_COMPLEX, 0.7, color, 2);
  244. //points
  245. cv::circle(img_rst, cv::Point(int(b.ppoint[0]), int(b.ppoint[1])), 4, cv::Scalar(255, 0, 255), -1, 3, 0);
  246. cv::circle(img_rst, cv::Point(int(b.ppoint[2]), int(b.ppoint[3])), 4, cv::Scalar(0, 255, 255), -1, 3, 0);
  247. cv::circle(img_rst, cv::Point(int(b.ppoint[4]), int(b.ppoint[5])), 4, cv::Scalar(255, 0, 0), -1, 3, 0);
  248. cv::circle(img_rst, cv::Point(int(b.ppoint[6]), int(b.ppoint[7])), 4, cv::Scalar(0, 255, 0), -1, 3, 0);
  249. cv::circle(img_rst, cv::Point(int(b.ppoint[8]), int(b.ppoint[9])), 4, cv::Scalar(0, 0, 255), -1, 3, 0);
  250. //grab points
  251. if (m_dtype == img_type::tea_grab) {
  252. double grab_x, grab_y;
  253. calculate_angle(b, grab_x, grab_y);
  254. //cv::circle(img_rst, cv::Point(int(grab_x), int(grab_y)), 4, cv::Scalar(0, 215, 255), -1, 3, 0);
  255. //lines, p4-p5, p5-grab
  256. cv::line(img_rst,
  257. cv::Point(int(b.ppoint[6]), int(b.ppoint[7])),
  258. cv::Point(int(b.ppoint[8]), int(b.ppoint[9])),
  259. cv::Scalar(0, 215, 255), 2);
  260. cv::line(img_rst,
  261. cv::Point(int(b.ppoint[8]), int(b.ppoint[9])),
  262. cv::Point(int(grab_x), int(grab_y)),
  263. cv::Scalar(0, 215, 255), 2);
  264. //line x
  265. int radius = 20;
  266. int cx = int(grab_x);
  267. int cy = int(grab_y);
  268. cv::line(img_rst, cv::Point(cx - radius, cy - radius), cv::Point(cx + radius, cy + radius), cv::Scalar(0, 215, 255), 2);
  269. cv::line(img_rst, cv::Point(cx - radius, cy + radius), cv::Point(cx + radius, cy - radius), cv::Scalar(0, 215, 255), 2);
  270. }
  271. //cut points
  272. if (m_dtype == img_type::tea_cut) {
  273. //lines, p3-p4
  274. cv::line(img_rst,
  275. cv::Point(int(b.ppoint[4]), int(b.ppoint[5])),
  276. cv::Point(int(b.ppoint[6]), int(b.ppoint[7])),
  277. cv::Scalar(0, 215, 255), 2);
  278. //line x
  279. int cx = int(0.5 * (b.ppoint[4] + b.ppoint[6]));
  280. int cy = int(0.5 * (b.ppoint[5] + b.ppoint[7]));
  281. int radius = 20;
  282. cv::line(img_rst, cv::Point(cx - radius, cy - radius), cv::Point(cx + radius, cy + radius), cv::Scalar(0, 215, 255),2);
  283. cv::line(img_rst, cv::Point(cx - radius, cy + radius), cv::Point(cx + radius, cy - radius), cv::Scalar(0, 215, 255),2);
  284. }
  285. }
  286. if (m_cp.image_show) {
  287. imshow_wait("result_img", img_rst);
  288. }
  289. m_pImginfoRaw = mat2imginfo(m_raw_img);
  290. m_pImginfoDetected = mat2imginfo(img_rst);
  291. posinfo.pp_images[0] = m_pImginfoRaw;
  292. posinfo.pp_images[1] = m_pImginfoDetected;
  293. if (m_ppImgSaver && *m_ppImgSaver) {
  294. (*m_ppImgSaver)->saveImage(img_rst, m_imgId + "_rst_0");
  295. }
  296. }
  297. //拍照无苗, 返回识别结果-1
  298. if (valid_cnt == 0) { return -1; }
  299. return 0;
  300. }
  301. int CTeaSort::detect_impl(
  302. cv::Mat& raw_img, //input, image
  303. vector<Rect>&drop_regions, //input, detect regions
  304. vector<Bbox> &droplets_raw //output, detect result
  305. )
  306. {
  307. //return number of detect result
  308. droplets_raw.clear();
  309. for (auto rect : drop_regions) {
  310. Mat roi = raw_img(rect);
  311. vector<Bbox> head_droplets = m_drop_detector.RunModel(roi, m_pLogger);
  312. if (m_pLogger) {
  313. stringstream buff_;
  314. buff_ << m_imgId << m_dtype_str << "-------crop_rect[" << rect.x << "," << rect.y << "," << rect.width
  315. << "," << rect.height << "],"
  316. << " roi image detect over. tea number is " << head_droplets.size();
  317. m_pLogger->INFO(buff_.str());
  318. }
  319. for (Bbox& b : head_droplets) {
  320. b.x1 += rect.x;
  321. b.x2 += rect.x;
  322. b.y1 += rect.y;
  323. b.y2 += rect.y;
  324. for (int i = 0; i < 5; ++i) {
  325. b.ppoint[2 * i] += rect.x;
  326. b.ppoint[2 * i + 1] += rect.y;
  327. }
  328. }
  329. if (head_droplets.size()) {
  330. droplets_raw.insert(
  331. droplets_raw.end(),
  332. head_droplets.begin(),
  333. head_droplets.end());
  334. }
  335. }
  336. return droplets_raw.size();
  337. }
  338. double CTeaSort::calculate_angle(
  339. Bbox&b, //input
  340. double& grab_x, //output
  341. double& grab_y //output
  342. )
  343. {
  344. grab_x = grab_y = 0.0;
  345. double angle = 0.0;
  346. float x3,y3,x4,y4,x5,y5;
  347. x3 = b.ppoint[4];
  348. y3 = b.ppoint[5];
  349. x4 = b.ppoint[6];
  350. y4 = b.ppoint[7];
  351. x5 = b.ppoint[8];
  352. y5 = b.ppoint[9];
  353. if (m_dtype == img_type::tea_grab) {
  354. calculate_stem_grab_position(b, grab_x, grab_y);
  355. //calculate line of p4 ans p5
  356. double r45 = sqrt((x4 - x5)*(x4 - x5) + (y4 - y5)*(y4 - y5));
  357. if (r45 < 15.0) {
  358. angle = atan2(x5 - x3, y5 - y3);
  359. }
  360. else {
  361. angle = atan2(x5 - x4, y5 - y4);
  362. }
  363. //计算抓取点
  364. if (grab_x < 0 && grab_y < 0) {
  365. double pr = (double)m_cp.offset_grab;
  366. double dx = pr * sin(angle);
  367. double dy = pr * cos(angle);
  368. grab_x = x5 + dx;
  369. grab_y = y5 + dy;
  370. }
  371. }
  372. else {
  373. //tea cut, calculate line of p3 ans p4
  374. angle = atan2(x3 - x4, y3 - y4);
  375. }
  376. angle *= (180.0 / 3.1415926);
  377. return angle;
  378. }
  379. int CTeaSort::load_data(
  380. ImgInfo*imginfo,
  381. const char* fn/* = 0*/)
  382. {
  383. //数据加载功能实现,并生成imageid,保存原始数据到文件
  384. int rst = 0;
  385. //generate image id
  386. if (m_dtype == img_type::tea_grab) {
  387. m_imgId = getImgId(img_type::tea_grab);
  388. m_dtype_str = string(" tea_grab ");
  389. }
  390. else {
  391. m_imgId = getImgId(img_type::tea_cut);
  392. m_dtype_str = string(" tea_cut ");
  393. }
  394. if (imginfo) {
  395. if (m_pLogger) {
  396. stringstream buff;
  397. buff << m_imgId << m_dtype_str << "image, width=" << imginfo->width
  398. << "\theight=" << imginfo->height;
  399. m_pLogger->INFO(buff.str());
  400. }
  401. if (!isvalid(imginfo) || (imginfo->channel!=1 && imginfo->channel!=3)) {
  402. if (m_pLogger) {
  403. m_pLogger->ERRORINFO(m_imgId + m_dtype_str + "input image invalid.");
  404. }
  405. throw_msg(m_imgId + " invalid input image");
  406. }
  407. if (imginfo->channel == 1) {
  408. cv::Mat tmp_img = imginfo2mat(imginfo);
  409. vector<Mat> channels;
  410. for (size_t i = 0; i < 3; ++i) { channels.push_back(tmp_img); }
  411. cv::merge(channels, m_raw_img);
  412. }
  413. else {
  414. m_raw_img = imginfo2mat(imginfo);
  415. }
  416. }
  417. else {
  418. cv::Mat img = imread(fn, cv::IMREAD_COLOR);
  419. if (img.empty()) {
  420. if (m_pLogger) {
  421. m_pLogger->ERRORINFO(m_imgId + m_dtype_str + "input image invalid:" + string(fn));
  422. }
  423. throw_msg(m_imgId + m_dtype_str + "invalid input image: " + string(fn));
  424. }
  425. if (m_pLogger) {
  426. stringstream buff;
  427. buff << m_imgId << m_dtype_str << "image, width=" << img.cols
  428. << "\theight=" << img.rows;
  429. m_pLogger->INFO(buff.str());
  430. }
  431. m_raw_img = img.clone();
  432. }
  433. //image saver
  434. if (m_ppImgSaver && *m_ppImgSaver) {
  435. (*m_ppImgSaver)->saveImage(m_raw_img, m_imgId);
  436. }
  437. return rst;
  438. }
  439. int CTeaSort::load_model()
  440. {
  441. bool b = false;
  442. if (!m_drop_detector.IsModelLoaded()) {
  443. if (m_dtype == img_type::tea_grab) {
  444. b = m_drop_detector.LoadModel(m_cp.model_path_grab);
  445. }
  446. else {
  447. b = m_drop_detector.LoadModel(m_cp.model_path_cut);
  448. }
  449. }
  450. else {
  451. b = true;
  452. }
  453. return b ? 0 : 1;
  454. }
  455. void CTeaSort::clear_imginfo() {
  456. if (m_pImginfoDetected) {
  457. imginfo_release(&m_pImginfoDetected);
  458. m_pImginfoDetected = 0;
  459. }
  460. if (m_pImginfoRaw) {
  461. imginfo_release(&m_pImginfoRaw);
  462. m_pImginfoRaw = 0;
  463. }
  464. }
  465. int CTeaSort::generate_detect_windows(vector<Rect>&boxes)
  466. {
  467. boxes.clear();
  468. int grid_row = m_cp.grid_row_cut;
  469. int grid_col = m_cp.grid_col_cut;
  470. int grid_padding = m_cp.grid_padding_cut;
  471. if (m_dtype == img_type::tea_grab) {
  472. grid_row = m_cp.grid_row_grab;
  473. grid_col = m_cp.grid_col_grab;
  474. grid_padding = m_cp.grid_padding_grab;
  475. }
  476. if (grid_row < 1) { grid_row = 1; }
  477. if (grid_col < 1) { grid_col = 1; }
  478. if (grid_padding < 0) { grid_padding = 0; }
  479. int block_height = int(m_raw_img.rows / (float)grid_row + 0.5);
  480. int block_width = int(m_raw_img.cols / (float)grid_col + 0.5);
  481. for (int r = 0; r < grid_row; ++r) {
  482. for (int c = 0; c < grid_col; ++c) {
  483. int x0 = c*block_width - grid_padding;
  484. int y0 = r*block_height - grid_padding;
  485. int x1 = (c+1)*block_width + grid_padding;
  486. int y1 = (r+1)*block_height + grid_padding;
  487. if (x0 < 0) { x0 = 0; }
  488. if (y0 < 0) { y0 = 0; }
  489. if (x1 > m_raw_img.cols) { x1 = m_raw_img.cols; }
  490. if (y1 > m_raw_img.rows) { y1 = m_raw_img.rows; }
  491. Rect r(x0, y0, x1-x0, y1-y0);
  492. boxes.push_back(r);
  493. }
  494. }
  495. return boxes.size();
  496. }
  497. void CTeaSort::calculate_stem_grab_position(
  498. Bbox&b,
  499. double& grab_x, //output
  500. double& grab_y //output
  501. )
  502. {
  503. grab_x = grab_y = -1.0;
  504. //crop image
  505. int padding = 2 * m_cp.offset_grab;
  506. int y3 = int(b.ppoint[5]);
  507. int y5 = int(b.ppoint[9]);
  508. cv::Point p3(int(b.ppoint[4] - b.x1), int(b.ppoint[5] - b.y1));
  509. cv::Point p5(int(b.ppoint[8] - b.x1), int(b.ppoint[9] - b.y1));
  510. cv::Mat crop_img;
  511. if (y5 > y3) {
  512. // Y position
  513. crop_img = m_raw_img(cv::Range(b.y1, b.y2 + padding), cv::Range(b.x1, b.x2)).clone();
  514. }
  515. else {
  516. // ^ position
  517. if (b.y1 - padding < 0) {
  518. padding = b.y1;
  519. }
  520. p5.y = int(b.ppoint[9] - b.y1 + padding);
  521. p3.y = int(b.ppoint[5] - b.y1 + padding);
  522. crop_img = m_raw_img(cv::Range(b.y1 - padding, b.y2), cv::Range(b.x1, b.x2)).clone();
  523. }
  524. if (m_cp.image_show) {
  525. cv::Mat crop_img_tmp = crop_img.clone();
  526. cv::circle(crop_img_tmp, p5, 4, cv::Scalar(255, 0, 255), -1, 3, 0);
  527. imshow_wait("cropped box", crop_img_tmp);
  528. }
  529. //to gray
  530. cv::Mat gray_img;
  531. if (crop_img.channels() == 1) { gray_img = crop_img; }
  532. else {
  533. cv::cvtColor(crop_img, gray_img, cv::COLOR_BGR2GRAY);
  534. }
  535. //binary
  536. cv::Mat bin_img;
  537. double th = cv::threshold(gray_img, bin_img, 255, 255, cv::THRESH_OTSU);
  538. cv::bitwise_not(bin_img, bin_img);
  539. if (m_cp.image_show) {
  540. imshow_wait("cropped binary img", bin_img);
  541. }
  542. // skeletonize() or medial_axis()
  543. cv::Mat ske_img;
  544. thinning(bin_img, ske_img);
  545. /*if (m_cp.image_show) {
  546. imshow_wait("skeleton img", ske_img);
  547. }*/
  548. //遍历所有点,找到距离等于指定距离的点的位置
  549. std::vector<cv::Point> candidate_pts;
  550. double dist_th = 5;
  551. for (int r = 0; r < ske_img.rows; ++r) {
  552. for (int c = 0; c < ske_img.cols; ++c) {
  553. if (ske_img.at<unsigned char>(r, c) == 0) { continue; }
  554. double dist = std::powf((p5.x - c), 2) + std::powf((p5.y - r),2);
  555. dist = std::sqrtf(dist);
  556. if (std::fabs(dist - m_cp.offset_grab) < dist_th) {
  557. candidate_pts.push_back(cv::Point(c, r));
  558. }
  559. }
  560. }
  561. //按与参考角度的差,找到有效的候选点集合
  562. std::vector<cv::Point> valid_candidate_pts;
  563. double ref_angle = atan2(p5.x - p3.x, p5.y - p3.y);
  564. for (auto&p : candidate_pts) {
  565. double angle_to_p3 = atan2(p.x - p3.x, p.y - p3.y);
  566. //计算夹角
  567. double fabs_angle = 0;
  568. if (ref_angle > 0.5 * CV_PI) {
  569. if (angle_to_p3 < 0) {
  570. angle_to_p3 += 2 * CV_PI;
  571. }
  572. fabs_angle = std::fabs(angle_to_p3 - ref_angle);
  573. }
  574. else {
  575. if (ref_angle < -0.5 * CV_PI) {
  576. if (angle_to_p3 > 0) {
  577. angle_to_p3 -= 2 * CV_PI;
  578. }
  579. fabs_angle = std::fabs(angle_to_p3 - ref_angle);
  580. }
  581. else {
  582. fabs_angle = std::fabs(angle_to_p3 - ref_angle);
  583. }
  584. }
  585. if (fabs_angle > CV_PI / 4.0) { continue; }
  586. valid_candidate_pts.push_back(p);
  587. }
  588. // 找到离重心最近的点作为抓取点
  589. if (valid_candidate_pts.size() > 0) {
  590. cv::Point2f p_mu(0,0);
  591. for (auto&p : valid_candidate_pts) {
  592. p_mu.x += p.x;
  593. p_mu.y += p.y;
  594. }
  595. p_mu.x /= (float)(valid_candidate_pts.size());
  596. p_mu.y /= (float)(valid_candidate_pts.size());
  597. double min_dist = 1.0e8;
  598. for (auto&p : valid_candidate_pts) {
  599. double dist = std::powf((p.x - p_mu.x), 2) + std::powf((p.y - p_mu.y), 2);
  600. dist = std::sqrtf(dist);
  601. if (dist < min_dist) {
  602. min_dist = dist;
  603. grab_x = p.x;
  604. grab_y = p.y;
  605. }
  606. }
  607. }
  608. if (m_cp.image_show) {
  609. cv::Mat ske_img_tmp = ske_img.clone();
  610. for (auto&p : valid_candidate_pts) {
  611. ske_img_tmp.at<unsigned char>(p) = 100;
  612. }
  613. cv::circle(ske_img_tmp, p5, 4, cv::Scalar(255, 0, 255), 1, 3, 0);
  614. if (grab_x > 0 && grab_y > 0) {
  615. cv::circle(ske_img_tmp, cv::Point(int(grab_x), int(grab_y)), 4, cv::Scalar(156, 0, 255), 1, 3, 0);
  616. }
  617. imshow_wait("skeleton img label", ske_img_tmp);
  618. }
  619. //重新得到grab_x,grab_y的坐标
  620. if (grab_x > 0 && grab_y > 0) {
  621. int real_padding_y = p5.y - int(b.ppoint[9] - b.y1);
  622. grab_y -= real_padding_y;
  623. grab_y += b.y1;
  624. grab_x += b.x1;
  625. }
  626. }
  627. /**
  628. * Code for thinning a binary image using Zhang-Suen algorithm.
  629. *
  630. * Author: Nash (nash [at] opencv-code [dot] com)
  631. * Website: http://opencv-code.com
  632. */
  633. /**
  634. * Perform one thinning iteration.
  635. * Normally you wouldn't call this function directly from your code.
  636. *
  637. * Parameters:
  638. * im Binary image with range = [0,1]
  639. * iter 0=even, 1=odd
  640. */
  641. void CTeaSort::thinningIteration(cv::Mat& img, int iter)
  642. {
  643. CV_Assert(img.channels() == 1);
  644. CV_Assert(img.depth() != sizeof(uchar));
  645. CV_Assert(img.rows > 3 && img.cols > 3);
  646. cv::Mat marker = cv::Mat::zeros(img.size(), CV_8UC1);
  647. int nRows = img.rows;
  648. int nCols = img.cols;
  649. if (img.isContinuous()) {
  650. nCols *= nRows;
  651. nRows = 1;
  652. }
  653. int x, y;
  654. uchar *pAbove;
  655. uchar *pCurr;
  656. uchar *pBelow;
  657. uchar *nw, *no, *ne; // north (pAbove)
  658. uchar *we, *me, *ea;
  659. uchar *sw, *so, *se; // south (pBelow)
  660. uchar *pDst;
  661. // initialize row pointers
  662. pAbove = NULL;
  663. pCurr = img.ptr<uchar>(0);
  664. pBelow = img.ptr<uchar>(1);
  665. for (y = 1; y < img.rows - 1; ++y) {
  666. // shift the rows up by one
  667. pAbove = pCurr;
  668. pCurr = pBelow;
  669. pBelow = img.ptr<uchar>(y + 1);
  670. pDst = marker.ptr<uchar>(y);
  671. // initialize col pointers
  672. no = &(pAbove[0]);
  673. ne = &(pAbove[1]);
  674. me = &(pCurr[0]);
  675. ea = &(pCurr[1]);
  676. so = &(pBelow[0]);
  677. se = &(pBelow[1]);
  678. for (x = 1; x < img.cols - 1; ++x) {
  679. // shift col pointers left by one (scan left to right)
  680. nw = no;
  681. no = ne;
  682. ne = &(pAbove[x + 1]);
  683. we = me;
  684. me = ea;
  685. ea = &(pCurr[x + 1]);
  686. sw = so;
  687. so = se;
  688. se = &(pBelow[x + 1]);
  689. int A = (*no == 0 && *ne == 1) + (*ne == 0 && *ea == 1) +
  690. (*ea == 0 && *se == 1) + (*se == 0 && *so == 1) +
  691. (*so == 0 && *sw == 1) + (*sw == 0 && *we == 1) +
  692. (*we == 0 && *nw == 1) + (*nw == 0 && *no == 1);
  693. int B = *no + *ne + *ea + *se + *so + *sw + *we + *nw;
  694. int m1 = iter == 0 ? (*no * *ea * *so) : (*no * *ea * *we);
  695. int m2 = iter == 0 ? (*ea * *so * *we) : (*no * *so * *we);
  696. if (A == 1 && (B >= 2 && B <= 6) && m1 == 0 && m2 == 0)
  697. pDst[x] = 1;
  698. }
  699. }
  700. img &= ~marker;
  701. }
  702. /**
  703. * Function for thinning the given binary image
  704. *
  705. * Parameters:
  706. * src The source image, binary with range = [0,255]
  707. * dst The destination image
  708. */
  709. void CTeaSort::thinning(const cv::Mat& src, cv::Mat& dst)
  710. {
  711. dst = src.clone();
  712. dst /= 255; // convert to binary image
  713. cv::Mat prev = cv::Mat::zeros(dst.size(), CV_8UC1);
  714. cv::Mat diff;
  715. do {
  716. thinningIteration(dst, 0);
  717. thinningIteration(dst, 1);
  718. cv::absdiff(dst, prev, diff);
  719. dst.copyTo(prev);
  720. } while (cv::countNonZero(diff) > 0);
  721. dst *= 255;
  722. }
  723. }