source/util/Camera.cpp (247 lines of code) (raw):

/** * Copyright 2004-present Facebook. All Rights Reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. */ #include "source/util/Camera.h" #include <unsupported/Eigen/Polynomials> #include <folly/Format.h> namespace fb360_dep { const Camera::Real Camera::kNearInfinity = 1e4; Camera::Camera(const Type type, const Vector2& res, const Vector2& focal) : type(type), resolution(res), focal(focal) { position.setZero(); rotation.setIdentity(); principal = resolution / 2; setDefaultDistortion(); setDefaultFov(); } Camera::Camera(const folly::dynamic& json) { CHECK_GE(json["version"].asDouble(), 1.0); id = json["id"].getString(); type = deserializeType(json["type"]); position = deserializeVector<3>(json["origin"]); setRotation( deserializeVector<3>(json["forward"]), deserializeVector<3>(json["up"]), deserializeVector<3>(json["right"])); resolution = deserializeVector<2>(json["resolution"]); if (json.count("principal")) { principal = deserializeVector<2>(json["principal"]); } else { principal = resolution / 2; } if (json.count("distortion")) { const folly::dynamic& entry = json["distortion"]; CHECK_LE(entry.size(), getDistortion().size()) << "bad distortion " << entry; Distortion distortion; for (int i = 0; i < distortion.size(); ++i) { distortion[i] = i < int(entry.size()) ? entry[i].asDouble() : 0; } setDistortion(distortion); } else { setDefaultDistortion(); } if (json.count("fov")) { setFov(json["fov"].asDouble()); } else { setDefaultFov(); } focal = deserializeVector<2>(json["focal"]); if (json.count("group")) { group = json["group"].getString(); } } void Camera::setRotation(const Vector3& forward, const Vector3& up, const Vector3& right) { CHECK_LT(right.cross(up).dot(forward), 0) << "rotation must be right-handed"; rotation.row(2) = -forward; // +z is back rotation.row(1) = up; // +y is up rotation.row(0) = right; // +x is right // re-unitarize const Camera::Real tol = 0.001; CHECK(rotation.isUnitary(tol)) << rotation << " is not close to unitary"; Eigen::AngleAxis<Camera::Real> aa(rotation); rotation = aa.toRotationMatrix(); } void Camera::setRotation(const Vector3& forward, const Vector3& up) { setRotation(forward, up, forward.cross(up)); } void Camera::setRotation(const Vector3& angleAxis) { // convert angle * axis to rotation matrix Real angle = angleAxis.norm(); Vector3 axis = angleAxis / angle; if (angle == 0) { axis = Vector3::UnitX(); } rotation = Eigen::AngleAxis<Real>(angle, axis).toRotationMatrix(); } Camera::Vector3 Camera::getRotation() const { // convert rotation matrix to angle * axis Eigen::AngleAxis<Real> angleAxis(rotation); if (angleAxis.angle() > M_PI) { angleAxis.angle() = 2 * M_PI - angleAxis.angle(); angleAxis.axis() = -angleAxis.axis(); } return angleAxis.angle() * angleAxis.axis(); } void Camera::setDefaultDistortion() { distortion_.setZero(); distortionMax_ = INFINITY; } void Camera::setDistortion(const Distortion& distortion) { // ignore trailing zeros Eigen::Index count = distortion.size(); while (distortion[count - 1] == 0) { if (--count == 0) { return setDefaultDistortion(); } } // distortion polynomial is x + d[0] * x^3 + d[1] * x^5 ... // derivative is: 1 + d[0] * 3 x^2 + d[1] * 5 x^4 ... // using y = x^2: 1 + d[0] * 3 y + d[1] * 5 y^2 ... VLOG(2) << "Solving for camera distortions..."; // HACK: Prevents seg fault in solver Eigen::Matrix<Real, Eigen::Dynamic, 1> derivative(count + 1); derivative[0] = 1; for (int i = 0; i < count; ++i) { derivative[i + 1] = distortion[i] * (2 * i + 3); } // find real roots in derivative Eigen::PolynomialSolver<Real, Eigen::Dynamic> solver; solver.compute(derivative); std::vector<Real> roots; solver.realRoots(roots); // find smallest root greater than zero Real y = INFINITY; for (const Real& root : roots) { if (0 < root && root < y) { y = root; } } distortion_ = distortion; distortionMax_ = sqrt(y); } #ifndef SUPPRESS_RIG_IO folly::dynamic Camera::serialize() const { // clang-format off folly::dynamic result = folly::dynamic::object("version", 1)("type", serializeType(type))( "origin", serializeVector(position))("forward", serializeVector(forward()))( "up", serializeVector(up()))("right", serializeVector(right()))( "resolution", serializeVector(resolution))("focal", serializeVector(focal))("id", id); // clang-format on if (principal != resolution / 2) { result["principal"] = serializeVector(principal); } if (!getDistortion().isZero()) { result["distortion"] = serializeVector(getDistortion()); } if (!isDefaultFov()) { result["fov"] = getFov(); } if (!group.empty()) { result["group"] = group; } return result; } #endif // SUPPRESS_RIG_IO void Camera::setScalarFocal(const Real& scalar) { focal = {scalar, -scalar}; } Camera::Real Camera::getScalarFocal() const { CHECK_EQ(focal.x(), -focal.y()) << "pixels are not square"; return focal.x(); } Camera::Real Camera::getDefaultCosFov(Camera::Type type) { switch (type) { case Camera::Type::RECTILINEAR: case Camera::Type::ORTHOGRAPHIC: return 0; // hemisphere default: return -1; // sphere } } void Camera::setDefaultFov() { cosFov = getDefaultCosFov(type); } void Camera::setFov(const Real& fov) { cosFov = std::cos(fov); CHECK(cosFov >= getDefaultCosFov(type)); } Camera::Real Camera::getFov() const { return std::acos(cosFov); } bool Camera::isDefaultFov() const { return cosFov == getDefaultCosFov(type); } Camera Camera::rescale(const Vector2& newResolution) const { Camera result = *this; result.principal.array() *= newResolution.array() / result.resolution.array(); result.focal.array() *= newResolution.array() / result.resolution.array(); result.resolution = newResolution; return result; } void Camera::normalize() { principal = principal.cwiseQuotient(resolution); focal = focal.cwiseQuotient(resolution); resolution = Vector2::Ones(); } bool Camera::isNormalized() const { return resolution == Vector2::Ones(); } // WARNING: modifies input cameras void Camera::normalizeRig(Camera::Rig& rig) { for (Camera& cam : rig) { if (!cam.isNormalized()) { cam.normalize(); } } } Camera::Rig Camera::loadRig(const filesystem::path& filename) { std::string json; folly::readFile(filename.string().c_str(), json); CHECK(!json.empty()) << "could not read JSON file: " << filename; return loadRigFromJsonString(json); } Camera::Rig Camera::loadRigFromJsonString(const std::string& json) { folly::dynamic dynamic = folly::parseJson(json); std::vector<Camera> cameras; for (const auto& camera : dynamic["cameras"]) { cameras.emplace_back(camera); } return cameras; } void Camera::perturbCameras( std::vector<Camera>& cameras, const double posAmount, const double rotAmount, const double principalAmount, const double focalAmount) { for (auto& camera : cameras) { if (&camera != &cameras[0]) { perturb(camera.position, posAmount); auto rotation = camera.getRotation(); perturb(rotation, rotAmount); camera.setRotation(rotation); } perturb(camera.principal, principalAmount); if (focalAmount != 0) { Camera::Real scalarFocal = camera.getScalarFocal(); perturbScalar(scalarFocal, focalAmount); camera.setScalarFocal(scalarFocal); } } } const Camera& Camera::findCameraById(const std::string id, const Camera::Rig& rig) { for (const Camera& camera : rig) { if (camera.id == id) { return camera; } } LOG(FATAL) << folly::sformat("Camera id {} not found", id); } #ifndef SUPPRESS_RIG_IO void Camera::saveRig( const std::string& filename, const std::vector<Camera>& cameras, const std::vector<std::string>& comments, const int doubleNumDigits) { folly::dynamic dynamic = folly::dynamic::object("cameras", folly::dynamic::array()); for (const auto& camera : cameras) { dynamic["cameras"].push_back(camera.serialize()); } if (!comments.empty()) { dynamic["comments"] = folly::dynamic::array(comments.begin(), comments.end()); } folly::json::serialization_opts opts; opts.sort_keys = true; opts.pretty_formatting = true; if (doubleNumDigits > 0) { opts.double_mode = double_conversion::DoubleToStringConverter::FIXED; opts.double_num_digits = doubleNumDigits; } folly::writeFile(folly::json::serialize(dynamic, opts), filename.c_str()); } #endif // SUPPRESS_RIG_IO } // namespace fb360_dep