AliceVision
Photogrammetric Computer Vision Framework
graphcut.hpp
1 // This file is part of the AliceVision project.
2 // Copyright (c) 2020 AliceVision contributors.
3 // This Source Code Form is subject to the terms of the Mozilla Public License,
4 // v. 2.0. If a copy of the MPL was not distributed with this file,
5 // You can obtain one at https://mozilla.org/MPL/2.0/.
6 
7 #pragma once
8 
9 #include <boost/graph/adjacency_list.hpp>
10 #include <boost/graph/boykov_kolmogorov_max_flow.hpp>
11 
12 #include <aliceVision/image/Image.hpp>
13 
14 #include "distance.hpp"
15 #include "boundingBox.hpp"
16 #include "imageOps.hpp"
17 #include "seams.hpp"
18 
19 namespace aliceVision {
20 
27 {
28  public:
29  using NodeType = int;
30  using ValueType = float;
31 
32  using Traits = boost::adjacency_list_traits<boost::vecS, // OutEdgeListS
33  boost::vecS, // VertexListS
34  boost::directedS,
35  boost::vecS // EdgeListS
36  >;
37  using edge_descriptor = typename Traits::edge_descriptor;
38  using vertex_descriptor = typename Traits::vertex_descriptor;
39  using vertex_size_type = typename Traits::vertices_size_type;
40  struct Edge
41  {
42  ValueType capacity{};
43  ValueType residual{};
44  edge_descriptor reverse;
45  };
46  using Graph = boost::adjacency_list<boost::vecS, // OutEdgeListS
47  boost::vecS, // VertexListS
48  boost::directedS,
49  boost::no_property, // VertexProperty
50  Edge, // EdgeProperty
51  boost::no_property, // GraphProperty
52  boost::vecS // EdgeListS
53  >;
54  using VertexIterator = typename boost::graph_traits<Graph>::vertex_iterator;
55 
56  public:
57  explicit MaxFlow_AdjList(size_t numNodes)
58  : _graph(numNodes + 2),
59  _S(NodeType(numNodes)),
60  _T(NodeType(numNodes + 1))
61  {
62  VertexIterator vi, vi_end;
63  for (boost::tie(vi, vi_end) = vertices(_graph); vi != vi_end; ++vi)
64  {
65  _graph.m_vertices[*vi].m_out_edges.reserve(9);
66  }
67  _graph.m_vertices[numNodes].m_out_edges.reserve(numNodes);
68  _graph.m_vertices[numNodes + 1].m_out_edges.reserve(numNodes);
69  }
70 
71  inline void addNodeToSource(NodeType n, ValueType source)
72  {
73  assert(source >= 0);
74 
75  edge_descriptor edge(boost::add_edge(_S, n, _graph).first);
76  edge_descriptor reverseEdge(boost::add_edge(n, _S, _graph).first);
77 
78  _graph[edge].capacity = source;
79  _graph[edge].reverse = reverseEdge;
80  _graph[reverseEdge].reverse = edge;
81  _graph[reverseEdge].capacity = source;
82  }
83 
84  inline void addNodeToSink(NodeType n, ValueType sink)
85  {
86  assert(sink >= 0);
87 
88  edge_descriptor edge(boost::add_edge(_T, n, _graph).first);
89  edge_descriptor reverseEdge(boost::add_edge(n, _T, _graph).first);
90 
91  _graph[edge].capacity = sink;
92  _graph[edge].reverse = reverseEdge;
93  _graph[reverseEdge].reverse = edge;
94  _graph[reverseEdge].capacity = sink;
95  }
96 
97  inline void addEdge(NodeType n1, NodeType n2, ValueType capacity, ValueType reverseCapacity)
98  {
99  assert(capacity >= 0 && reverseCapacity >= 0);
100 
101  edge_descriptor edge(boost::add_edge(n1, n2, _graph).first);
102  edge_descriptor reverseEdge(boost::add_edge(n2, n1, _graph).first);
103  _graph[edge].capacity = capacity;
104  _graph[edge].reverse = reverseEdge;
105 
106  _graph[reverseEdge].capacity = reverseCapacity;
107  _graph[reverseEdge].reverse = edge;
108  }
109 
110  void printStats() const;
111  void printColorStats() const;
112 
113  inline ValueType compute()
114  {
115  vertex_size_type nbVertices(boost::num_vertices(_graph));
116  _color.resize(nbVertices, boost::white_color);
117  std::vector<edge_descriptor> pred(nbVertices);
118  std::vector<vertex_size_type> dist(nbVertices);
119 
120  ValueType v = boost::boykov_kolmogorov_max_flow(_graph,
121  boost::get(&Edge::capacity, _graph),
122  boost::get(&Edge::residual, _graph),
123  boost::get(&Edge::reverse, _graph),
124  &pred[0],
125  &_color[0],
126  &dist[0],
127  boost::get(boost::vertex_index, _graph),
128  _S,
129  _T);
130 
131  return v;
132  }
133 
135  inline bool isSource(NodeType n) const { return (_color[n] == boost::black_color); }
137  inline bool isTarget(NodeType n) const { return (_color[n] == boost::white_color); }
138 
139  protected:
140  Graph _graph;
141  std::vector<boost::default_color_type> _color;
142  const NodeType _S; //< emptyness
143  const NodeType _T; //< fullness
144 };
145 
146 bool computeSeamsMap(image::Image<unsigned char>& seams, const image::Image<IndexT>& labels);
147 
149 {
150  public:
151  struct InputData
152  {
153  IndexT id;
154  BoundingBox rect;
157  };
158 
159  using IndexedColor = std::pair<IndexT, image::RGBfColor>;
160  using PixelInfo = std::vector<IndexedColor>;
161 
162  public:
163  GraphcutSeams(size_t outputWidth, size_t outputHeight)
164  : _outputWidth(outputWidth),
165  _outputHeight(outputHeight),
166  _maximal_distance_change(outputWidth + outputHeight),
167  _labels(outputWidth, outputHeight, true, UndefinedIndexT)
168  {}
169 
170  virtual ~GraphcutSeams() = default;
171 
172  std::pair<IndexT, image::RGBfColor> findIndex(const PixelInfo& pix, IndexT id)
173  {
174  for (int i = 0; i < pix.size(); i++)
175  {
176  if (pix[i].first == id)
177  {
178  return pix[i];
179  }
180  }
181 
182  return std::make_pair(UndefinedIndexT, image::RGBfColor(0.0f));
183  }
184 
185  bool existIndex(const PixelInfo& pix, IndexT id)
186  {
187  for (int i = 0; i < pix.size(); i++)
188  {
189  if (pix[i].first == id)
190  {
191  return true;
192  }
193  }
194 
195  return false;
196  }
197 
198  bool setOriginalLabels(const image::Image<IndexT>& existing_labels)
199  {
200  _labels = existing_labels;
201 
202  return true;
203  }
204 
205  bool append(const image::Image<image::RGBfColor>& input,
206  const image::Image<unsigned char>& inputMask,
207  IndexT currentIndex,
208  size_t offset_x,
209  size_t offset_y)
210  {
211  if (inputMask.width() != input.width())
212  {
213  return false;
214  }
215 
216  if (inputMask.height() != input.height())
217  {
218  return false;
219  }
220 
221  InputData data;
222 
223  data.id = currentIndex;
224  data.color = input;
225  data.mask = inputMask;
226  data.rect.width = input.width();
227  data.rect.height = input.height();
228  data.rect.left = offset_x;
229  data.rect.top = offset_y;
230 
231  _inputs[currentIndex] = data;
232 
233  return true;
234  }
235 
236  void setMaximalDistance(int dist) { _maximal_distance_change = dist; }
237 
238  bool createInputOverlappingObservations(image::Image<PixelInfo>& graphCutInput, const BoundingBox& interestBbox)
239  {
240  for (auto& otherInput : _inputs)
241  {
242  BoundingBox otherBbox = otherInput.second.rect;
243  BoundingBox otherBboxLoop = otherInput.second.rect;
244  otherBboxLoop.left = otherBbox.left - _outputWidth;
245  BoundingBox otherBboxLoopRight = otherInput.second.rect;
246  otherBboxLoopRight.left = otherBbox.left + _outputWidth;
247 
248  BoundingBox otherInputBbox = otherBbox;
249  otherInputBbox.left = 0;
250  otherInputBbox.top = 0;
251 
252  BoundingBox intersection = interestBbox.intersectionWith(otherBbox);
253  BoundingBox intersectionLoop = interestBbox.intersectionWith(otherBboxLoop);
254  BoundingBox intersectionLoopRight = interestBbox.intersectionWith(otherBboxLoopRight);
255  if (intersection.isEmpty() && intersectionLoop.isEmpty() && intersectionLoopRight.isEmpty())
256  {
257  continue;
258  }
259 
260  image::Image<image::RGBfColor> otherColor(otherInputBbox.width, otherInputBbox.height);
261  otherColor.block(otherInputBbox.top, otherInputBbox.left, otherInputBbox.height, otherInputBbox.width) =
262  otherInput.second.color.block(otherInputBbox.top, otherInputBbox.left, otherInputBbox.height, otherInputBbox.width);
263 
264  image::Image<unsigned char> otherMask(otherInputBbox.width, otherInputBbox.height);
265  otherMask.block(otherInputBbox.top, otherInputBbox.left, otherInputBbox.height, otherInputBbox.width) =
266  otherInput.second.mask.block(otherInputBbox.top, otherInputBbox.left, otherInputBbox.height, otherInputBbox.width);
267 
268  if (!intersection.isEmpty())
269  {
270  BoundingBox interestOther = intersection;
271  interestOther.left -= otherBbox.left;
272  interestOther.top -= otherBbox.top;
273 
274  BoundingBox interestThis = intersection;
275  interestThis.left -= interestBbox.left;
276  interestThis.top -= interestBbox.top;
277 
278  for (int y = 0; y < intersection.height; y++)
279  {
280  int y_other = interestOther.top + y;
281  int y_current = interestThis.top + y;
282 
283  for (int x = 0; x < intersection.width; x++)
284  {
285  int x_other = interestOther.left + x;
286  int x_current = interestThis.left + x;
287 
288  if (!otherMask(y_other, x_other))
289  {
290  continue;
291  }
292 
293  PixelInfo& pix = graphCutInput(y_current, x_current);
294  pix.push_back(std::make_pair(otherInput.first, otherColor(y_other, x_other)));
295  }
296  }
297  }
298 
299  if (!intersectionLoop.isEmpty())
300  {
301  BoundingBox interestOther = intersectionLoop;
302  interestOther.left -= otherBboxLoop.left;
303  interestOther.top -= otherBboxLoop.top;
304 
305  BoundingBox interestThis = intersectionLoop;
306  interestThis.left -= interestBbox.left;
307  interestThis.top -= interestBbox.top;
308 
309  for (int y = 0; y < intersectionLoop.height; y++)
310  {
311  int y_other = interestOther.top + y;
312  int y_current = interestThis.top + y;
313 
314  for (int x = 0; x < intersectionLoop.width; x++)
315  {
316  int x_other = interestOther.left + x;
317  int x_current = interestThis.left + x;
318 
319  if (!otherMask(y_other, x_other))
320  {
321  continue;
322  }
323 
324  PixelInfo& pix = graphCutInput(y_current, x_current);
325  pix.push_back(std::make_pair(otherInput.first, otherColor(y_other, x_other)));
326  }
327  }
328  }
329 
330  if (!intersectionLoopRight.isEmpty())
331  {
332  BoundingBox interestOther = intersectionLoopRight;
333  interestOther.left -= otherBboxLoopRight.left;
334  interestOther.top -= otherBboxLoopRight.top;
335 
336  BoundingBox interestThis = intersectionLoopRight;
337  interestThis.left -= interestBbox.left;
338  interestThis.top -= interestBbox.top;
339 
340  for (int y = 0; y < intersectionLoopRight.height; y++)
341  {
342  int y_other = interestOther.top + y;
343  int y_current = interestThis.top + y;
344 
345  for (int x = 0; x < intersectionLoopRight.width; x++)
346  {
347  int x_other = interestOther.left + x;
348  int x_current = interestThis.left + x;
349 
350  if (!otherMask(y_other, x_other))
351  {
352  continue;
353  }
354 
355  PixelInfo& pix = graphCutInput(y_current, x_current);
356  pix.push_back(std::make_pair(otherInput.first, otherColor(y_other, x_other)));
357  }
358  }
359  }
360  }
361 
362  return true;
363  }
364 
365  bool fixUpscaling(image::Image<IndexT>& labels, const image::Image<PixelInfo>& graphCutInput)
366  {
367  // Because of upscaling, some labels may be incorrect
368  // Some pixels may be affected to labels they don't see.
369  for (int y = 0; y < graphCutInput.height(); y++)
370  {
371  for (int x = 0; x < graphCutInput.width(); x++)
372  {
373  IndexT label = labels(y, x);
374 
375  if (label == UndefinedIndexT)
376  {
377  continue;
378  }
379 
380  // If there is no input for this pixel, we can't do anything
381  const PixelInfo& pix = graphCutInput(y, x);
382 
383  // Check if the input associated to this label is seen by this pixel
384  auto it = existIndex(pix, label);
385  if (it)
386  {
387  continue;
388  }
389 
390  // Look for another label in the neighborhood which is seen by this pixel
391  bool modified = false;
392  bool hadUndefined = false;
393  for (int l = -1; l <= 1; l++)
394  {
395  int ny = y + l;
396  if (ny < 0 || ny >= labels.height())
397  {
398  continue;
399  }
400 
401  for (int c = -1; c <= 1; c++)
402  {
403  int nx = x + c;
404  if (nx < 0 || nx >= labels.width())
405  {
406  continue;
407  }
408 
409  IndexT otherLabel = labels(ny, nx);
410  if (otherLabel == label)
411  {
412  continue;
413  }
414 
415  if (otherLabel == UndefinedIndexT)
416  {
417  hadUndefined = true;
418  continue;
419  }
420 
421  // Check that this other label is seen by our pixel
422  const PixelInfo& pixOther = graphCutInput(ny, nx);
423  auto itOther = existIndex(pixOther, otherLabel);
424  if (!itOther)
425  {
426  continue;
427  }
428 
429  auto it = existIndex(pix, otherLabel);
430  if (it)
431  {
432  labels(y, x) = otherLabel;
433  modified = true;
434  }
435  }
436  }
437 
438  if (!modified)
439  {
440  labels(y, x) = UndefinedIndexT;
441  }
442  }
443  }
444 
445  return true;
446  }
447 
448  bool computeInputDistanceMap(image::Image<int>& distanceMap, const image::Image<IndexT>& localLabels, IndexT inputId)
449  {
450  image::Image<IndexT> binarizedWorld(localLabels.width(), localLabels.height());
451 
452  for (int y = 0; y < localLabels.height(); y++)
453  {
454  for (int x = 0; x < localLabels.width(); x++)
455  {
456  IndexT label = localLabels(y, x);
457 
458  if (label == inputId)
459  {
460  binarizedWorld(y, x) = 1;
461  }
462  else
463  {
464  binarizedWorld(y, x) = 0;
465  }
466  }
467  }
468 
469  image::Image<unsigned char> seams(localLabels.width(), localLabels.height());
470  if (!computeSeamsMap(seams, binarizedWorld))
471  {
472  return false;
473  }
474 
475  if (!computeDistanceMap(distanceMap, seams))
476  {
477  return false;
478  }
479 
480  return true;
481  }
482 
483  bool processInput(double& newCost, InputData& input)
484  {
485  // Get bounding box of input in panorama
486  // Dilate to have some pixels outside of the input
487  BoundingBox localBbox = input.rect.dilate(3);
488  localBbox.clampLeft();
489  localBbox.clampTop();
490  localBbox.clampBottom(_labels.height() - 1);
491 
492  // Output must keep a margin also
493  BoundingBox outputBbox = input.rect;
494  outputBbox.left = input.rect.left - localBbox.left;
495  outputBbox.top = input.rect.top - localBbox.top;
496 
497  // Extract labels for the ROI
498  image::Image<IndexT> localLabels(localBbox.width, localBbox.height);
499  if (!loopyImageExtract(localLabels, _labels, localBbox))
500  {
501  return false;
502  }
503 
504  // Build the input
505  image::Image<PixelInfo> graphCutInput(localBbox.width, localBbox.height, true);
506  if (!createInputOverlappingObservations(graphCutInput, localBbox))
507  {
508  return false;
509  }
510 
511  // Fix upscaling induced bad labeling
512  if (!fixUpscaling(localLabels, graphCutInput))
513  {
514  return false;
515  }
516 
517  // Backup update for upscaling
518  BoundingBox inputBb = localBbox;
519  inputBb.left = 0;
520  inputBb.top = 0;
521  if (!loopyImageAssign(_labels, localLabels, localBbox, inputBb))
522  {
523  return false;
524  }
525 
526  // Compute distance map to borders of the input seams
527  image::Image<int> distanceMap(localLabels.width(), localLabels.height());
528  if (!computeInputDistanceMap(distanceMap, localLabels, input.id))
529  {
530  return false;
531  }
532 
533  // Remove pixels too far from seams
534  for (int i = 0; i < graphCutInput.height(); i++)
535  {
536  for (int j = 0; j < graphCutInput.width(); j++)
537  {
538  float d2 = float(distanceMap(i, j));
539  float d = sqrt(d2);
540 
541  if (d > _maximal_distance_change + 1.0f)
542  {
543  graphCutInput(i, j).clear();
544  }
545  }
546  }
547 
548  double oldCost = cost(localLabels, graphCutInput, input.id);
549  if (!alphaExpansion(localLabels, distanceMap, graphCutInput, input.id))
550  {
551  return false;
552  }
553  newCost = cost(localLabels, graphCutInput, input.id);
554 
555  if (newCost < oldCost)
556  {
557  BoundingBox inputBb = localBbox;
558  inputBb.left = 0;
559  inputBb.top = 0;
560 
561  if (!loopyImageAssign(_labels, localLabels, localBbox, inputBb))
562  {
563  return false;
564  }
565  }
566  else
567  {
568  newCost = oldCost;
569  }
570 
571  return true;
572  }
573 
574  bool process()
575  {
576  std::map<IndexT, double> costs;
577  for (auto& info : _inputs)
578  {
579  costs[info.first] = std::numeric_limits<double>::max();
580  }
581 
582  for (int i = 0; i < 10; i++)
583  {
584  ALICEVISION_LOG_INFO("GraphCut processing iteration #" << i);
585 
586  // For each possible label, try to extends its domination on the label's world
587  bool hasChange = false;
588 
589  int pos = 0;
590  for (auto& info : _inputs)
591  {
592  double cost;
593  if (!processInput(cost, info.second))
594  {
595  return false;
596  }
597 
598  if (costs[info.first] != cost)
599  {
600  costs[info.first] = cost;
601  hasChange = true;
602  }
603  }
604 
605  if (!hasChange)
606  {
607  break;
608  }
609  }
610 
611  return true;
612  }
613 
614  double cost(const image::Image<IndexT> localLabels, const image::Image<PixelInfo>& input, IndexT currentLabel)
615  {
616  double cost = 0.0;
617 
618  for (int y = 0; y < input.height() - 1; y++)
619  {
620  for (int x = 0; x < input.width() - 1; x++)
621  {
622  int xp = x + 1;
623  int yp = y + 1;
624 
625  IndexT label = localLabels(y, x);
626  IndexT labelx = localLabels(y, xp);
627  IndexT labely = localLabels(yp, x);
628 
629  if (label == UndefinedIndexT)
630  continue;
631  if (labelx == UndefinedIndexT)
632  continue;
633  if (labely == UndefinedIndexT)
634  continue;
635 
636  image::RGBfColor CColorLC;
637  image::RGBfColor CColorLX;
638  image::RGBfColor CColorLY;
639  image::RGBfColor XColorLC;
640  image::RGBfColor XColorLX;
641  image::RGBfColor YColorLC;
642  image::RGBfColor YColorLY;
643  bool hasYLC = false;
644  bool hasYLY = false;
645  bool hasXLC = false;
646  bool hasXLX = false;
647  bool hasCLC = false;
648  bool hasCLX = false;
649  bool hasCLY = false;
650 
651  auto it = findIndex(input(y, x), label);
652  if (it.first != UndefinedIndexT)
653  {
654  hasCLC = true;
655  CColorLC = it.second;
656  }
657 
658  it = findIndex(input(y, x), labelx);
659  if (it.first != UndefinedIndexT)
660  {
661  hasCLX = true;
662  CColorLX = it.second;
663  }
664 
665  it = findIndex(input(y, x), labely);
666  if (it.first != UndefinedIndexT)
667  {
668  hasCLY = true;
669  CColorLY = it.second;
670  }
671 
672  it = findIndex(input(y, xp), label);
673  if (it.first != UndefinedIndexT)
674  {
675  hasXLC = true;
676  XColorLC = it.second;
677  }
678 
679  it = findIndex(input(y, xp), labelx);
680  if (it.first != UndefinedIndexT)
681  {
682  hasXLX = true;
683  XColorLX = it.second;
684  }
685 
686  it = findIndex(input(yp, x), label);
687  if (it.first != UndefinedIndexT)
688  {
689  hasYLC = true;
690  YColorLC = it.second;
691  }
692 
693  it = findIndex(input(yp, x), labely);
694  if (it.first != UndefinedIndexT)
695  {
696  hasYLY = true;
697  YColorLY = it.second;
698  }
699 
700  if (!hasCLC || !hasXLX || !hasYLY)
701  {
702  continue;
703  }
704 
705  if (!hasCLX)
706  {
707  CColorLX = CColorLC;
708  }
709 
710  if (!hasCLY)
711  {
712  CColorLY = CColorLC;
713  }
714 
715  if (!hasXLC)
716  {
717  XColorLC = XColorLX;
718  }
719 
720  if (!hasYLC)
721  {
722  YColorLC = YColorLY;
723  }
724 
725  double c1 = (CColorLC - CColorLX).norm();
726  double c2 = (CColorLC - CColorLY).norm();
727  double c3 = (XColorLC - XColorLX).norm();
728  double c4 = (YColorLC - YColorLY).norm();
729 
730  c1 = std::min(2.0, c1);
731  c2 = std::min(2.0, c2);
732  c3 = std::min(2.0, c3);
733  c4 = std::min(2.0, c4);
734 
735  cost += c1 + c2 + c3 + c4;
736  }
737  }
738 
739  return cost;
740  }
741 
742  bool alphaExpansion(image::Image<IndexT>& labels, const image::Image<int>& distanceMap, const image::Image<PixelInfo>& input, IndexT currentLabel)
743  {
744  image::Image<unsigned char> mask(labels.width(), labels.height(), true, 0);
745  image::Image<int> ids(labels.width(), labels.height(), true, -1);
746  image::Image<image::RGBfColor> color_label(labels.width(), labels.height(), true, image::RGBfColor(0.0f, 0.0f, 0.0f));
747  image::Image<image::RGBfColor> color_other(labels.width(), labels.height(), true, image::RGBfColor(0.0f, 0.0f, 0.0f));
748 
749  for (int y = 0; y < labels.height(); y++)
750  {
751  for (int x = 0; x < labels.width(); x++)
752  {
753  IndexT label = labels(y, x);
754 
755  float dist = sqrt(float(distanceMap(y, x)));
756 
757  image::RGBfColor currentColor;
758  image::RGBfColor otherColor;
759 
760  auto it = findIndex(input(y, x), currentLabel);
761  if (it.first != UndefinedIndexT)
762  {
763  currentColor = it.second;
764  mask(y, x) = 1;
765  }
766 
767  if (label != currentLabel)
768  {
769  auto it = findIndex(input(y, x), label);
770  if (it.first != UndefinedIndexT)
771  {
772  otherColor = it.second;
773 
774  if (dist > _maximal_distance_change)
775  {
776  mask(y, x) = 2;
777  }
778  else
779  {
780  mask(y, x) |= 2;
781  }
782  }
783  }
784 
785  // If the pixel may be a new kingdom for alpha
786  if (mask(y, x) == 1)
787  {
788  color_label(y, x) = currentColor;
789  color_other(y, x) = currentColor;
790  }
791  else if (mask(y, x) == 2)
792  {
793  color_label(y, x) = otherColor;
794  color_other(y, x) = otherColor;
795  }
796  else if (mask(y, x) == 3)
797  {
798  color_label(y, x) = currentColor;
799  color_other(y, x) = otherColor;
800  }
801  }
802  }
803 
804  // The rectangle is a grid.
805  // However we want to ignore a lot of pixel.
806  // Let's create an index per valid pixels for graph cut reference
807  int count = 0;
808  for (int y = 0; y < labels.height(); y++)
809  {
810  for (int x = 0; x < labels.width(); x++)
811  {
812  if (mask(y, x) == 0)
813  {
814  continue;
815  }
816 
817  ids(y, x) = count;
818  count++;
819  }
820  }
821 
822  // Create graph
823  MaxFlow_AdjList gc(count);
824  size_t countValid = 0;
825 
826  for (int y = 0; y < labels.height(); y++)
827  {
828  for (int x = 0; x < labels.width(); x++)
829  {
830  // If this pixel is not valid, ignore
831  if (mask(y, x) == 0)
832  {
833  continue;
834  }
835 
836  // Get this pixel ID
837  int node_id = ids(y, x);
838 
839  int ym1 = std::max(y - 1, 0);
840  int xm1 = std::max(x - 1, 0);
841  int yp1 = std::min(y + 1, labels.height() - 1);
842  int xp1 = std::min(x + 1, labels.width() - 1);
843 
844  if (mask(y, x) == 1)
845  {
846  // Only add nodes close to borders
847  if (mask(ym1, xm1) == 1 && mask(ym1, x) == 1 && mask(ym1, xp1) == 1 && mask(y, xm1) == 1 && mask(y, xp1) == 1 &&
848  mask(yp1, xm1) == 1 && mask(yp1, x) == 1 && mask(yp1, xp1) == 1)
849  {
850  continue;
851  }
852 
853  // This pixel is only seen by alpha.
854  // Enforce its domination by stating that removing this pixel
855  // from alpha territory is infinitely costly (impossible).
856  gc.addNodeToSource(node_id, 100000);
857  }
858  else if (mask(y, x) == 2)
859  {
860  // Only add nodes close to borders
861  if (mask(ym1, xm1) == 2 && mask(ym1, x) == 2 && mask(ym1, xp1) == 2 && mask(y, xm1) == 2 && mask(y, xp1) == 2 &&
862  mask(yp1, xm1) == 2 && mask(yp1, x) == 2 && mask(yp1, xp1) == 2)
863  {
864  continue;
865  }
866 
867  // This pixel is only seen by an ennemy.
868  // Enforce its domination by stating that removing this pixel
869  // from ennemy territory is infinitely costly (impossible).
870  gc.addNodeToSink(node_id, 100000);
871  }
872  else if (mask(y, x) == 3)
873  {
874  // This pixel is seen by both alpha and enemies but is owned by ennemy.
875  // Make sure that changing node owner will have no direct cost.
876  // Connect it to both alpha and ennemy for the moment
877  // (Graph cut will not allow a pixel to have both owners at the end).
878  gc.addNodeToSource(node_id, 0);
879  gc.addNodeToSink(node_id, 0);
880  countValid++;
881  }
882  }
883  }
884 
885  if (countValid == 0)
886  {
887  // We have no possibility for territory expansion
888  // let's exit
889  return true;
890  }
891 
892  // Loop over alpha bounding box.
893  // Let's define the transition cost.
894  // When two neighbor pixels have different labels, there is a seam (border) cost.
895  // Graph cut will try to make sure the territory will have a minimal border cost
896 
897  for (int y = 0; y < labels.height(); y++)
898  {
899  for (int x = 0; x < labels.width(); x++)
900  {
901  if (mask(y, x) == 0)
902  {
903  continue;
904  }
905 
906  int node_id = ids(y, x);
907 
908  // Make sure it is possible to estimate this horizontal border
909  if (y < mask.height() - 1)
910  {
911  // Make sure the other pixel is owned by someone
912  if (mask(y + 1, x))
913  {
914  int other_node_id = ids(y + 1, x);
915  float w = 1000;
916 
917  if (((mask(y, x) & 1) && (mask(y + 1, x) & 2)) || ((mask(y, x) & 2) && (mask(y + 1, x) & 1)))
918  {
919  float d1 = (color_label(y, x) - color_other(y, x)).norm();
920  float d2 = (color_label(y + 1, x) - color_other(y + 1, x)).norm();
921 
922  d1 = std::min(2.0f, d1);
923  d2 = std::min(2.0f, d2);
924 
925  w = (d1 + d2) * 100.0 + 1.0;
926  }
927 
928  gc.addEdge(node_id, other_node_id, w, w);
929  }
930  }
931 
932  if (x < mask.width() - 1)
933  {
934  if (mask(y, x + 1))
935  {
936  int other_node_id = ids(y, x + 1);
937  float w = 1000;
938 
939  if (((mask(y, x) & 1) && (mask(y, x + 1) & 2)) || ((mask(y, x) & 2) && (mask(y, x + 1) & 1)))
940  {
941  float d1 = (color_label(y, x) - color_other(y, x)).norm();
942  float d2 = (color_label(y, x + 1) - color_other(y, x + 1)).norm();
943 
944  d1 = std::min(2.0f, d1);
945  d2 = std::min(2.0f, d2);
946 
947  w = (d1 + d2) * 100.0 + 1.0;
948  }
949 
950  gc.addEdge(node_id, other_node_id, w, w);
951  }
952  }
953  }
954  }
955 
956  gc.compute();
957 
958  int changeCount = 0;
959  for (int y = 0; y < labels.height(); y++)
960  {
961  for (int x = 0; x < labels.width(); x++)
962  {
963  IndexT label = labels(y, x);
964  int id = ids(y, x);
965 
966  if (gc.isSource(id))
967  {
968  if (label != currentLabel)
969  {
970  changeCount++;
971  }
972 
973  labels(y, x) = currentLabel;
974  }
975  }
976  }
977 
978  return true;
979  }
980 
981  image::Image<IndexT>& getLabels() { return _labels; }
982 
983  private:
984  std::map<IndexT, InputData> _inputs;
985 
986  int _outputWidth;
987  int _outputHeight;
988  size_t _maximal_distance_change;
989  image::Image<IndexT> _labels;
990 };
991 
992 } // namespace aliceVision
aliceVision::MaxFlow_AdjList::Edge
Definition: graphcut.hpp:40
aliceVision::BoundingBox
Definition: boundingBox.hpp:17
aliceVision::MaxFlow_AdjList::isSource
bool isSource(NodeType n) const
is empty
Definition: graphcut.hpp:135
aliceVision::GraphcutSeams::InputData
Definition: graphcut.hpp:151
aliceVision
Definition: checkerDetector.cpp:32
aliceVision::image::Image
Definition: ImageDescriber_AKAZE_OCV.hpp:21
aliceVision::MaxFlow_AdjList::isTarget
bool isTarget(NodeType n) const
is full
Definition: graphcut.hpp:137
aliceVision::image::Image::width
int width() const
Retrieve the width of the image.
Definition: Image.hpp:110
aliceVision::GraphcutSeams
Definition: graphcut.hpp:148
aliceVision::image::Image::height
int height() const
Retrieve the height of the image.
Definition: Image.hpp:116
aliceVision::MaxFlow_AdjList
Maxflow computation based on a standard Adjacency List graph representation.
Definition: graphcut.hpp:26
aliceVision::computeDistanceMap
bool computeDistanceMap(image::Image< int > &distance, const image::Image< unsigned char > &mask)
Code adapted from VFLib: https://github.com/vinniefalco/VFLib (Licence MIT)
Definition: distance.cpp:53