diff --git a/src/aliceVision/image/io.cpp b/src/aliceVision/image/io.cpp index 6418dace00..2c1e3dae80 100644 --- a/src/aliceVision/image/io.cpp +++ b/src/aliceVision/image/io.cpp @@ -419,7 +419,14 @@ oiio::ParamValueList readImageMetadata(const std::string& path, int& width, int& oiio::ImageSpec readImageSpec(const std::string& path) { - std::unique_ptr in(oiio::ImageInput::open(path)); + oiio::ImageSpec configSpec; +#if OIIO_VERSION >= (10000 * 2 + 100 * 4 + 12) // OIIO_VERSION >= 2.4.12 + // To disable the application of the orientation, we need the PR https://github.com/OpenImageIO/oiio/pull/3669, + // so we can disable the auto orientation and keep the metadata. + configSpec.attribute("raw:user_flip", 0); // disable auto rotation of the image buffer but keep exif metadata orientation valid +#endif + + std::unique_ptr in(oiio::ImageInput::open(path, &configSpec)); oiio::ImageSpec spec = in->spec(); if(!in) @@ -551,6 +558,12 @@ void readImage(const std::string& path, // libRAW configuration // See https://openimageio.readthedocs.io/en/master/builtinplugins.html#raw-digital-camera-files +#if OIIO_VERSION >= (10000 * 2 + 100 * 4 + 12) // OIIO_VERSION >= 2.4.12 + // To disable the application of the orientation, we need the PR https://github.com/OpenImageIO/oiio/pull/3669, + // so we can disable the auto orientation and keep the metadata. + configSpec.attribute("raw:user_flip", 0); // disable auto rotation of the image buffer but keep exif metadata orientation valid +#endif + if (imageReadOptions.rawColorInterpretation == ERawColorInterpretation::None) { if (imageReadOptions.workingColorSpace != EImageColorSpace::NO_CONVERSION) @@ -648,13 +661,30 @@ void readImage(const std::string& path, if (inBuf.spec().nchannels == 2) ALICEVISION_THROW_ERROR("Load of 2 channels is not supported. Image file: '" + path + "'."); + oiio::ParamValueList imgMetadata = readImageMetadata(path); + + if (isRawImage) + { + // Check orientation metadata. If image is mirrored, mirror it back and update orientation metadata + int orientation = imgMetadata.get_int("orientation", -1); + + if (orientation == 2 || orientation == 4 || orientation == 5 || orientation == 7) + { + // horizontal mirroring + oiio::ImageBuf inBufMirrored = oiio::ImageBufAlgo::flop(inBuf); + inBuf = inBufMirrored; + + orientation += (orientation == 2 || orientation == 4) ? -1 : 1; + } + } + // Apply DCP profile if (!imageReadOptions.colorProfileFileName.empty() && imageReadOptions.rawColorInterpretation == ERawColorInterpretation::DcpLinearProcessing) { image::DCPProfile dcpProfile(imageReadOptions.colorProfileFileName); - oiio::ParamValueList imgMetadata = readImageMetadata(path); + //oiio::ParamValueList imgMetadata = readImageMetadata(path); std::string cam_mul = ""; if (!imgMetadata.getattribute("raw:cam_mul", cam_mul)) { diff --git a/src/software/pipeline/main_panoramaInit.cpp b/src/software/pipeline/main_panoramaInit.cpp index f2be8e5d62..3576cb39a8 100644 --- a/src/software/pipeline/main_panoramaInit.cpp +++ b/src/software/pipeline/main_panoramaInit.cpp @@ -45,15 +45,6 @@ namespace po = boost::program_options; namespace fs = boost::filesystem; namespace pt = boost::property_tree; -struct Contact -{ - int rank; - std::string path; - int width; - int height; - sfmData::EEXIFOrientation orientation; -}; - /** * A simple class for gaussian pyramid */ @@ -665,16 +656,93 @@ class CircleDetector size_t _minimal_size; }; -void resample(image::Image& output, const image::Image& input) +/** + * @brief Utility function for resizing an image. + */ +void resample(image::Image& output, + const image::Image& input) { const oiio::ImageBuf inBuf(oiio::ImageSpec(input.Width(), input.Height(), 3, oiio::TypeDesc::FLOAT), const_cast(input.data())); + oiio::ImageBuf outBuf(oiio::ImageSpec(output.Width(), output.Height(), 3, oiio::TypeDesc::FLOAT), (image::RGBfColor*)output.data()); oiio::ImageBufAlgo::resample(outBuf, inBuf, false); } +/** + * @brief Utility function for rotating an image given its orientation metadata. + */ +void applyOrientation(image::Image& output, + const image::Image& input, + sfmData::EEXIFOrientation orientation) +{ + const oiio::ImageBuf inBuf(oiio::ImageSpec(input.Width(), input.Height(), 3, oiio::TypeDesc::FLOAT), + const_cast(input.data())); + + oiio::ImageBuf outBuf(oiio::ImageSpec(output.Width(), output.Height(), 3, oiio::TypeDesc::FLOAT), + (image::RGBfColor*)output.data()); + + switch (orientation) + { + case sfmData::EEXIFOrientation::UPSIDEDOWN: + oiio::ImageBufAlgo::rotate180(outBuf, inBuf); + break; + case sfmData::EEXIFOrientation::LEFT: + oiio::ImageBufAlgo::rotate90(outBuf, inBuf); + break; + case sfmData::EEXIFOrientation::RIGHT: + oiio::ImageBufAlgo::rotate270(outBuf, inBuf); + break; + default: + outBuf.copy(inBuf); + break; + } +} + +/** + * @brief Utility struct for contact sheet elements. + */ +struct Contact +{ + int rank; + std::string path; + int width; + int height; + sfmData::EEXIFOrientation orientation; +}; + +/** + * @brief Width of contact sheet element, taking into account orientation metadata. + */ +int orientedWidth(const Contact& contact) +{ + switch (contact.orientation) + { + case sfmData::EEXIFOrientation::LEFT: + case sfmData::EEXIFOrientation::RIGHT: + return contact.height; + default: + return contact.width; + } +} + +/** + * @brief Height of contact sheet element, taking into account orientation metadata. + */ +int orientedHeight(const Contact& contact) +{ + switch (contact.orientation) + { + case sfmData::EEXIFOrientation::LEFT: + case sfmData::EEXIFOrientation::RIGHT: + return contact.width; + default: + return contact.height; + } +} + bool buildContactSheetImage(image::Image& output, const std::map>& contactSheetInfo, int contactSheetItemMaxSize) { @@ -686,8 +754,8 @@ bool buildContactSheetImage(image::Image& output, { for(const auto& item : rowpair.second) { - maxdim = std::max(maxdim, item.second.width); - maxdim = std::max(maxdim, item.second.height); + maxdim = std::max(maxdim, orientedWidth(item.second)); + maxdim = std::max(maxdim, orientedHeight(item.second)); } } double ratioResize = double(contactSheetItemMaxSize) / double(maxdim); @@ -702,8 +770,8 @@ bool buildContactSheetImage(image::Image& output, for(const auto& item : rowpair.second) { - int resizedHeight = int(ratioResize * double(item.second.height)); - int resizedWidth = int(ratioResize * double(item.second.width)); + int resizedHeight = int(ratioResize * double(orientedHeight(item.second))); + int resizedWidth = int(ratioResize * double(orientedWidth(item.second))); rowHeight = std::max(rowHeight, resizedHeight); rowWidth += resizedWidth + space; @@ -729,8 +797,8 @@ bool buildContactSheetImage(image::Image& output, for(const auto& item : rowpair.second) { - int resizedHeight = int(ratioResize * double(item.second.height)); - int resizedWidth = int(ratioResize * double(item.second.width)); + int resizedHeight = int(ratioResize * double(orientedHeight(item.second))); + int resizedWidth = int(ratioResize * double(orientedWidth(item.second))); rowHeight = std::max(rowHeight, resizedHeight); rowWidth += resizedWidth + space; @@ -742,15 +810,20 @@ bool buildContactSheetImage(image::Image& output, int posX = space; for(const auto& item : rowpair.second) { - int resizedHeight = int(ratioResize * double(item.second.height)); - int resizedWidth = int(ratioResize * double(item.second.width)); + int rawResizedHeight = int(ratioResize * double(item.second.height)); + int rawResizedWidth = int(ratioResize * double(item.second.width)); + + int resizedHeight = int(ratioResize * double(orientedHeight(item.second))); + int resizedWidth = int(ratioResize * double(orientedWidth(item.second))); image::Image input; + image::Image rawThumbnail(rawResizedWidth, rawResizedHeight); image::Image thumbnail(resizedWidth, resizedHeight); image::readImage(item.second.path, input, image::EImageColorSpace::SRGB); - resample(thumbnail, input); + resample(rawThumbnail, input); + applyOrientation(thumbnail, rawThumbnail, item.second.orientation); rowOutput.block(0, posX, resizedHeight, resizedWidth) = thumbnail; posX += resizedWidth + space; @@ -1154,9 +1227,32 @@ int main(int argc, char* argv[]) for(const auto& item_rotation : rotations) { IndexT viewIdx = namesWithRank[index].second; - if(item_rotation.second.trace() != 0) + const sfmData::View& v = sfmData.getView(viewIdx); + + sfmData::EEXIFOrientation orientation = v.getMetadataOrientation(); + double orientationAngle = 0.; + switch (orientation) + { + case sfmData::EEXIFOrientation::UPSIDEDOWN: + orientationAngle = boost::math::constants::pi(); + break; + case sfmData::EEXIFOrientation::LEFT: + orientationAngle = boost::math::constants::pi() * .5; + break; + case sfmData::EEXIFOrientation::RIGHT: + orientationAngle = boost::math::constants::pi() * -.5; + break; + default: + break; + } + + const Eigen::AngleAxis Morientation(orientationAngle, Eigen::Vector3d::UnitZ()); + + const Eigen::Matrix3d viewRotation = Morientation.toRotationMatrix().transpose() * item_rotation.second; + + if(viewRotation.trace() != 0) { - sfmData::CameraPose pose(geometry::Pose3(item_rotation.second, Eigen::Vector3d::Zero())); + sfmData::CameraPose pose(geometry::Pose3(viewRotation, Eigen::Vector3d::Zero())); sfmData.setAbsolutePose(viewIdx, pose); } ++index;