bounds.cpp 7.39 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
// Copyright (c) 2012-2017 VideoStitch SAS
// Copyright (c) 2018 stitchEm

#include "bounds.hpp"

#include "gpu/memcpy.hpp"
#include "gpu/core1/boundsKernel.hpp"
#include "core1/imageMapping.hpp"

#include "libvideostitch/stereoRigDef.hpp"

#include <algorithm>

namespace VideoStitch {
namespace Core {

/**
 * Find the min and max set pixels of a buffer. Returns false if there are no set pixels.
 */
bool findMinMaxSetPixels(const uint32_t* buffer, uint32_t mask, int bufSize, int* min, int* max) {
  *min = -1;
  *max = -1;
  int i;
  for (i = 0; i < bufSize; ++i) {
    if (buffer[i] & mask) {
      *min = i;
      break;
    }
  }
  if (*min == -1) {
    return false;
  }
  for (; i < bufSize; ++i) {
    if (buffer[i] & mask) {
      *max = i;
    }
  }
  return true;
}

void findMostNonSetPixels(const int min, const int croppedWidth, const uint32_t mask, const uint32_t* buffer,
                          int& mostNonSetPixels, int& mostNonSetPixelsStart) {
  // Here it's a bit more complicated since the panorama wraps. The problem can be reduced down to the following graph
  // problem:
  //  - Each contiguous set of set pixels is represented by a red vertex (contiguity is computed in the wrapping domain,
  //  i.e. the first and last pixel are contiguous).
  //  - Each contiguous set of non-set pixels is represented by a black vertex.
  //  - To vertices are connected if the two correspond sets touch each other.
  //  - Each vertex is attributed a integer weight equal to the number of pixels in its set.
  // Obviously, this graph is either a single vertex, or a cycle of alternating black and red vertices (even number of
  // vertices). In addition, the sum of all vertex weights is the width of the pano.
  //  - If there are at least two vertices, finding the best interval is the same as finding the black vertex that can
  //  be removed such that it minimizes the sum of the weights of all other vertices.
  //    Since the sum of weights is constant, this is actually the same as finding the black vertex with the largest
  //    weight, or finding the largest contiguous (wrapping) set of non-set pixels.
  //  - If there is only one vertex, then the interval is either empty (black) or the whole thing (red).

  // We have at least one set pixel, else we would already have returned.
  // Because the problem cycles, there is a translation symmetry, so we can choose to have the first pixel be the first
  // set pixel.
#define COL(i) ((min + (i)) % (int)croppedWidth)
  assert(buffer[COL(0)] & mask);
  // Find the largest contiguous set of non-set pixels.
  mostNonSetPixels = 0;
  mostNonSetPixelsStart = -1;
  for (int i = 0; i < croppedWidth;) {
    // Skip all set pixels.
    while (i < croppedWidth && (buffer[COL(i)] & mask)) {
      ++i;
    }
    // Count all non-set pixels.
    int nonSetPixels = 0;
    const int start = COL(i);
    while (i < croppedWidth && !(buffer[COL(i)] & mask)) {
      ++nonSetPixels;
      ++i;
    }
    if (nonSetPixels > mostNonSetPixels) {
      mostNonSetPixels = nonSetPixels;
      mostNonSetPixelsStart = start;
    }
  }
#undef COL
}

Status computeHBounds(TextureTarget t, int64_t croppedWidth, int64_t croppedHeight,
                      std::map<readerid_t, VideoStitch::Core::ImageMapping*>& imageMappings,
                      const StereoRigDefinition* rigDef, Eye eye, GPU::Buffer<const uint32_t> panoDevOut,
                      GPU::HostBuffer<uint32_t> tmpHostBuffer, GPU::Buffer<uint32_t> tmpDevBuffer, GPU::Stream stream,
                      bool canWrap) {
  FAIL_RETURN(vertOr(croppedWidth, croppedHeight, panoDevOut, tmpDevBuffer, stream));

  GPU::HostBuffer<uint32_t> rowBuffer = tmpHostBuffer;
  FAIL_CAUSE(GPU::memcpyAsync(rowBuffer, tmpDevBuffer.as_const(), size_t(croppedWidth * 4), stream), Origin::Stitcher,
             ErrType::SetupFailure, "Could not compute horizontal bounds");
  FAIL_CAUSE(stream.synchronize(), Origin::Stitcher, ErrType::SetupFailure, "Could not compute horizontal bounds");

  // find min/max for each image
  for (auto mapping : imageMappings) {
    int min = -1, max = -1;
    uint32_t mask = 1 << mapping.first;
    // The problem here is to find an interval of minimum size that covers all set pixels for an image.
    // First find the first and the last set pixels.
    if (!findMinMaxSetPixels(rowBuffer, mask, (int)croppedWidth, &min, &max)) {
      // This can happen if there are no image pixels in the panorama (e.g. the image is behind us for rectilinear
      // panos). In this case we don't set bounds, and the mapper will remain empty.
      continue;
    }
    if (rigDef && rigDef->getGeometry() == StereoRigDefinition::Polygonal) {
      std::vector<int> inputs = (eye == LeftEye ? rigDef->getLeftInputs() : rigDef->getRightInputs());
      if (std::find(inputs.begin(), inputs.end(), (int)mapping.first) == inputs.end()) {
        continue;
      }
    }

    if (!canWrap || t != EQUIRECTANGULAR) {
      // Here it's simple: the lower bound is the first set pixel and the upper bound is the last set pixel.
      // Note that even if the image does not wrap through the antipode, it is still possible that there are two
      // separate continuous sets of pixels.
      mapping.second->setHBounds(t, min, max, croppedWidth);
    } else {
      int mostNonSetPixels = 0;
      int mostNonSetPixelsStart = -1;
      findMostNonSetPixels(min, int(croppedWidth), mask, rowBuffer, mostNonSetPixels, mostNonSetPixelsStart);
      if (mostNonSetPixels == 0) {
        // All set: full image.
        mapping.second->setHBounds(t, 0, (int)croppedWidth - 1, croppedWidth);
      } else {
        mapping.second->setHBounds(t, (mostNonSetPixelsStart + mostNonSetPixels) % (int)croppedWidth,
                                   mostNonSetPixelsStart - 1, croppedWidth);
      }
    }
  }
  return Status::OK();
}

Status computeVBounds(TextureTarget t, int64_t croppedWidth, int64_t croppedHeight,
                      std::map<readerid_t, VideoStitch::Core::ImageMapping*>& imageMappings,
                      GPU::Buffer<const uint32_t> panoDevOut, GPU::HostBuffer<uint32_t> tmpHostBuffer,
                      GPU::Buffer<uint32_t> tmpDevBuffer, GPU::Stream stream) {
  FAIL_RETURN(horizOr(croppedWidth, croppedHeight, panoDevOut, tmpDevBuffer, stream));

  GPU::HostBuffer<uint32_t> colBuffer = tmpHostBuffer;
  FAIL_CAUSE(GPU::memcpyAsync(colBuffer, tmpDevBuffer.as_const(), size_t(croppedHeight * 4), stream), Origin::Stitcher,
             ErrType::SetupFailure, "Could not compute vertical bounds")
  FAIL_CAUSE(stream.synchronize(), Origin::Stitcher, ErrType::SetupFailure, "Could not compute vertical bounds");

  // find min/max for each image
  for (auto mapping : imageMappings) {
    if (mapping.second->getOutputRect(t).horizontallyEmpty()) {  // skip images that don't contribute pixels.
      continue;
    }
    uint32_t mask = 1 << mapping.first;
    int min = -1, max = -1;
    // The problem here is to find an interval of minimum size that covers all set pixels for an image.
    // First find the first and the last set pixels.
    if (!findMinMaxSetPixels(colBuffer, mask, (int)croppedHeight, &min, &max)) {
      return {
          Origin::Stitcher, ErrType::SetupFailure,
          "Could not compute vertical bounds. No pixels should have been caught by the horizontal bounds computation."};
    }
    assert(min < croppedHeight);
    assert(max < croppedHeight);
    mapping.second->setVBounds(t, min, max);
  }
  return Status::OK();
}

}  // namespace Core
}  // namespace VideoStitch