From d796b45cd8a8db71423c009380faa096120f8fb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Candice=20Bent=C3=A9jac?= Date: Mon, 27 Mar 2023 15:25:59 +0200 Subject: [PATCH 01/12] [dataio] Use 4-spaces indentation for all the module's files This commit only modifies the syntax of the dataio module and does not change its content in any way. The hpp and cpp files have been clang- formatted to respect the 4-spaces indentation that can be found in other modules (in the "keyframe" module, primarily), and some functions' prototypes have been re-aligned accordingly. --- src/aliceVision/dataio/FeedProvider.cpp | 142 +++--- src/aliceVision/dataio/FeedProvider.hpp | 197 ++++---- src/aliceVision/dataio/IFeed.cpp | 55 +-- src/aliceVision/dataio/IFeed.hpp | 104 ++--- src/aliceVision/dataio/ImageFeed.cpp | 590 ++++++++++++------------ src/aliceVision/dataio/ImageFeed.hpp | 202 ++++---- src/aliceVision/dataio/VideoFeed.cpp | 373 ++++++++------- src/aliceVision/dataio/VideoFeed.hpp | 220 +++++---- 8 files changed, 927 insertions(+), 956 deletions(-) diff --git a/src/aliceVision/dataio/FeedProvider.cpp b/src/aliceVision/dataio/FeedProvider.cpp index 73c5b16059..d0aaeda46a 100644 --- a/src/aliceVision/dataio/FeedProvider.cpp +++ b/src/aliceVision/dataio/FeedProvider.cpp @@ -19,116 +19,112 @@ #include #include -namespace aliceVision{ -namespace dataio{ +namespace aliceVision +{ +namespace dataio +{ -FeedProvider::FeedProvider(const std::string &feedPath, const std::string &calibPath) -: _isVideo(false), _isLiveFeed(false) +FeedProvider::FeedProvider(const std::string& feedPath, const std::string& calibPath) + : _isVideo(false) + , _isLiveFeed(false) { - namespace bf = boost::filesystem; - if(feedPath.empty()) - { - throw std::invalid_argument("Empty filepath."); - } - if(bf::is_regular_file(bf::path(feedPath))) - { - // Image or video file - const std::string extension = bf::path(feedPath).extension().string(); - if(ImageFeed::isSupported(extension)) + namespace bf = boost::filesystem; + if(feedPath.empty()) { - _feeder.reset(new ImageFeed(feedPath, calibPath)); + throw std::invalid_argument("Empty filepath."); } - else + if(bf::is_regular_file(bf::path(feedPath))) { + // Image or video file + const std::string extension = bf::path(feedPath).extension().string(); + if(ImageFeed::isSupported(extension)) + { + _feeder.reset(new ImageFeed(feedPath, calibPath)); + } + else + { #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OPENCV) - if(VideoFeed::isSupported(extension)) - { - - // let's try it with a video - _feeder.reset(new VideoFeed(feedPath, calibPath)); - _isVideo = true; - } - else - { - throw std::invalid_argument("Unsupported file format: " + feedPath); - } + if(VideoFeed::isSupported(extension)) + { + // let's try it with a video + _feeder.reset(new VideoFeed(feedPath, calibPath)); + _isVideo = true; + } + else + { + throw std::invalid_argument("Unsupported file format: " + feedPath); + } #else - throw std::invalid_argument("Unsupported mode! If you intended to use a video" - " please add OpenCV support"); + throw std::invalid_argument("Unsupported mode! If you intended to use a video" + " please add OpenCV support"); #endif + } + } + // parent_path() returns "/foo/bar/" when input path equals to "/foo/bar/" + // if the user just gives the relative path as "bar", throws invalid argument exception. + else if(bf::is_directory(bf::path(feedPath)) || bf::is_directory(bf::path(feedPath).parent_path())) + { + // Folder or sequence of images + _feeder.reset(new ImageFeed(feedPath, calibPath)); } - } - // parent_path() returns "/foo/bar/" when input path equals to "/foo/bar/" - // if the user just gives the relative path as "bar", throws invalid argument exception. - else if(bf::is_directory(bf::path(feedPath)) || bf::is_directory(bf::path(feedPath).parent_path())) - { - // Folder or sequence of images - _feeder.reset(new ImageFeed(feedPath, calibPath)); - } #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OPENCV) - else if(isdigit(feedPath[0])) - { - // let's try it with a video - const int deviceNumber = std::stoi(feedPath); - _feeder.reset(new VideoFeed(deviceNumber, calibPath)); - _isVideo = true; - _isLiveFeed = true; - } + else if(isdigit(feedPath[0])) + { + // let's try it with a video + const int deviceNumber = std::stoi(feedPath); + _feeder.reset(new VideoFeed(deviceNumber, calibPath)); + _isVideo = true; + _isLiveFeed = true; + } #endif - else - { - throw std::invalid_argument(std::string("Input filepath not supported: ") + feedPath); - } + else + { + throw std::invalid_argument(std::string("Input filepath not supported: ") + feedPath); + } } -bool FeedProvider::readImage(image::Image &imageRGB, - camera::PinholeRadialK3 &camIntrinsics, - std::string &mediaPath, - bool &hasIntrinsics) +bool FeedProvider::readImage(image::Image& imageRGB, camera::PinholeRadialK3& camIntrinsics, + std::string& mediaPath, bool& hasIntrinsics) { - return(_feeder->readImage(imageRGB, camIntrinsics, mediaPath, hasIntrinsics)); + return (_feeder->readImage(imageRGB, camIntrinsics, mediaPath, hasIntrinsics)); } -bool FeedProvider::readImage(image::Image &imageGray, - camera::PinholeRadialK3 &camIntrinsics, - std::string &mediaPath, - bool &hasIntrinsics) +bool FeedProvider::readImage(image::Image& imageGray, camera::PinholeRadialK3& camIntrinsics, + std::string& mediaPath, bool& hasIntrinsics) { - return(_feeder->readImage(imageGray, camIntrinsics, mediaPath, hasIntrinsics)); + return (_feeder->readImage(imageGray, camIntrinsics, mediaPath, hasIntrinsics)); } -bool FeedProvider::readImage(image::Image &imageGray, - camera::PinholeRadialK3 &camIntrinsics, - std::string &mediaPath, - bool &hasIntrinsics) +bool FeedProvider::readImage(image::Image& imageGray, camera::PinholeRadialK3& camIntrinsics, + std::string& mediaPath, bool& hasIntrinsics) { - return(_feeder->readImage(imageGray, camIntrinsics, mediaPath, hasIntrinsics)); + return (_feeder->readImage(imageGray, camIntrinsics, mediaPath, hasIntrinsics)); } std::size_t FeedProvider::nbFrames() const { - if(_isLiveFeed) - return std::numeric_limits::infinity(); + if(_isLiveFeed) + return std::numeric_limits::infinity(); - return _feeder->nbFrames(); + return _feeder->nbFrames(); } bool FeedProvider::goToFrame(const unsigned int frame) { - return _feeder->goToFrame(frame); + return _feeder->goToFrame(frame); } bool FeedProvider::goToNextFrame() { - return _feeder->goToNextFrame(); + return _feeder->goToNextFrame(); } bool FeedProvider::isInit() const { - return(_feeder->isInit()); + return (_feeder->isInit()); } -FeedProvider::~FeedProvider( ) { } +FeedProvider::~FeedProvider() {} -}//namespace dataio -}//namespace aliceVision +} // namespace dataio +} // namespace aliceVision diff --git a/src/aliceVision/dataio/FeedProvider.hpp b/src/aliceVision/dataio/FeedProvider.hpp index 1d2cb13108..b007bece09 100644 --- a/src/aliceVision/dataio/FeedProvider.hpp +++ b/src/aliceVision/dataio/FeedProvider.hpp @@ -11,113 +11,106 @@ #include #include -namespace aliceVision{ -namespace dataio{ +namespace aliceVision +{ +namespace dataio +{ class FeedProvider { public: - - FeedProvider(const std::string &feedPath, const std::string &calibPath = ""); - - /** - * @brief Provide a new RGB image from the feed. - * - * @param[out] imageRGB The new image from the feed. - * @param[out] camIntrinsics The associated camera intrinsics. - * @param[out] mediaPath The original media path, for a video is the path to the - * file, for an image sequence is the path to the single image. - * @param[out] hasIntrinsics True if \p camIntrinsics is valid, otherwise there - * is no intrinsics associated to \p imageRGB. - * @return True if there is a new image, false otherwise. - */ - bool readImage(image::Image &imageRGB, - camera::PinholeRadialK3 &camIntrinsics, - std::string &mediaPath, - bool &hasIntrinsics); - - /** - * @brief Provide a new float grayscale image from the feed. - * - * @param[out] imageGray The new image from the feed. - * @param[out] camIntrinsics The associated camera intrinsics. - * @param[out] mediaPath The original media path, for a video is the path to the - * file, for an image sequence is the path to the single image. - * @param[out] hasIntrinsics True if \p camIntrinsics is valid, otherwise there - * is no intrinsics associated to \p imageGray. - * @return True if there is a new image, false otherwise. - */ - bool readImage(image::Image &imageGray, - camera::PinholeRadialK3 &camIntrinsics, - std::string &mediaPath, - bool &hasIntrinsics); - - /** - * @brief Provide a new grayscale image from the feed. - * - * @param[out] imageGray The new image from the feed. - * @param[out] camIntrinsics The associated camera intrinsics. - * @param[out] mediaPath The original media path, for a video is the path to the - * file, for an image sequence is the path to the single image. - * @param[out] hasIntrinsics True if \p camIntrinsics is valid, otherwise there - * is no intrinsics associated to \p imageGray. - * @return True if there is a new image, false otherwise. - */ - bool readImage(image::Image &imageGray, - camera::PinholeRadialK3 &camIntrinsics, - std::string &mediaPath, - bool &hasIntrinsics); - - /** - * @brief It returns the number of frames contained of the video. It return infinity - * if the feed is a live stream. - * @return the number of frames of the video or infinity if it is a live stream. - */ - std::size_t nbFrames() const; - - /** - * @brief It retrieve the given frame number. In case - * of live feeds, it just give the next available frame. - * @return true if successful. - */ - bool goToFrame(const unsigned int frame); - - /** - * @brief It acquires the next available frame. - * @return true if successful. - */ - bool goToNextFrame(); - - /** - * @brief Return true if the feed is correctly initialized. - * - * @return True if the feed is correctly initialized. - */ - bool isInit() const; - - /** - * @brief Return true if the feed is a video. - * - * @return True if the feed is a video. - */ - bool isVideo() const {return _isVideo; } - - /** - * @brief Return true if the feed is a live stream (e.g. a webcam). - * - * @return True if the feed is correctly initialized. - */ - bool isLiveFeed() const {return _isLiveFeed; } - - virtual ~FeedProvider(); + FeedProvider(const std::string& feedPath, const std::string& calibPath = ""); + + /** + * @brief Provide a new RGB image from the feed. + * + * @param[out] imageRGB The new image from the feed. + * @param[out] camIntrinsics The associated camera intrinsics. + * @param[out] mediaPath The original media path, for a video is the path to the + * file, for an image sequence is the path to the single image. + * @param[out] hasIntrinsics True if \p camIntrinsics is valid, otherwise there + * is no intrinsics associated to \p imageRGB. + * @return True if there is a new image, false otherwise. + */ + bool readImage(image::Image& imageRGB, camera::PinholeRadialK3& camIntrinsics, + std::string& mediaPath, bool& hasIntrinsics); + + /** + * @brief Provide a new float grayscale image from the feed. + * + * @param[out] imageGray The new image from the feed. + * @param[out] camIntrinsics The associated camera intrinsics. + * @param[out] mediaPath The original media path, for a video is the path to the + * file, for an image sequence is the path to the single image. + * @param[out] hasIntrinsics True if \p camIntrinsics is valid, otherwise there + * is no intrinsics associated to \p imageGray. + * @return True if there is a new image, false otherwise. + */ + bool readImage(image::Image& imageGray, camera::PinholeRadialK3& camIntrinsics, std::string& mediaPath, + bool& hasIntrinsics); + + /** + * @brief Provide a new grayscale image from the feed. + * + * @param[out] imageGray The new image from the feed. + * @param[out] camIntrinsics The associated camera intrinsics. + * @param[out] mediaPath The original media path, for a video is the path to the + * file, for an image sequence is the path to the single image. + * @param[out] hasIntrinsics True if \p camIntrinsics is valid, otherwise there + * is no intrinsics associated to \p imageGray. + * @return True if there is a new image, false otherwise. + */ + bool readImage(image::Image& imageGray, camera::PinholeRadialK3& camIntrinsics, + std::string& mediaPath, bool& hasIntrinsics); + + /** + * @brief It returns the number of frames contained of the video. It return infinity + * if the feed is a live stream. + * @return the number of frames of the video or infinity if it is a live stream. + */ + std::size_t nbFrames() const; + + /** + * @brief It retrieve the given frame number. In case + * of live feeds, it just give the next available frame. + * @return true if successful. + */ + bool goToFrame(const unsigned int frame); + + /** + * @brief It acquires the next available frame. + * @return true if successful. + */ + bool goToNextFrame(); + + /** + * @brief Return true if the feed is correctly initialized. + * + * @return True if the feed is correctly initialized. + */ + bool isInit() const; + + /** + * @brief Return true if the feed is a video. + * + * @return True if the feed is a video. + */ + bool isVideo() const { return _isVideo; } + + /** + * @brief Return true if the feed is a live stream (e.g. a webcam). + * + * @return True if the feed is correctly initialized. + */ + bool isLiveFeed() const { return _isLiveFeed; } + + virtual ~FeedProvider(); private: - std::unique_ptr _feeder; - bool _isVideo; - bool _isLiveFeed; - + std::unique_ptr _feeder; + bool _isVideo; + bool _isLiveFeed; }; -}//namespace dataio -}//namespace aliceVision - +} // namespace dataio +} // namespace aliceVision diff --git a/src/aliceVision/dataio/IFeed.cpp b/src/aliceVision/dataio/IFeed.cpp index 117aef9245..f7ac55efdb 100644 --- a/src/aliceVision/dataio/IFeed.cpp +++ b/src/aliceVision/dataio/IFeed.cpp @@ -11,8 +11,10 @@ #include #include -namespace aliceVision{ -namespace dataio{ +namespace aliceVision +{ +namespace dataio +{ // the structure of the file is // int #image width @@ -23,31 +25,30 @@ namespace dataio{ // double #k0 // double #k1 // double #k2 -void readCalibrationFromFile(const std::string &filename, camera::PinholeRadialK3 &camIntrinsics) +void readCalibrationFromFile(const std::string& filename, camera::PinholeRadialK3& camIntrinsics) { - std::ifstream fs(filename, std::ios::in); - if(!fs.is_open()) - { - ALICEVISION_LOG_WARNING("Unable to open the calibration file " << filename); - throw std::invalid_argument("Unable to open the calibration file "+filename); - } - int width = 0; - int height = 0; - const size_t numParam = 6; - std::vector params(numParam, 0); - - fs >> width; - fs >> height; - for(size_t i = 0; i < numParam; ++i) - { - fs >> params[i]; - } - camIntrinsics = camera::PinholeRadialK3(width, height, - params[0], params[1], params[2], - params[3], params[4], params[5]); - - fs.close(); + std::ifstream fs(filename, std::ios::in); + if(!fs.is_open()) + { + ALICEVISION_LOG_WARNING("Unable to open the calibration file " << filename); + throw std::invalid_argument("Unable to open the calibration file " + filename); + } + int width = 0; + int height = 0; + const size_t numParam = 6; + std::vector params(numParam, 0); + + fs >> width; + fs >> height; + for(size_t i = 0; i < numParam; ++i) + { + fs >> params[i]; + } + camIntrinsics = + camera::PinholeRadialK3(width, height, params[0], params[1], params[2], params[3], params[4], params[5]); + + fs.close(); } -}//namespace dataio -}//namespace aliceVision +} // namespace dataio +} // namespace aliceVision diff --git a/src/aliceVision/dataio/IFeed.hpp b/src/aliceVision/dataio/IFeed.hpp index 68cee71740..b1ecec0591 100644 --- a/src/aliceVision/dataio/IFeed.hpp +++ b/src/aliceVision/dataio/IFeed.hpp @@ -10,70 +10,65 @@ #include #include -namespace aliceVision{ -namespace dataio{ +namespace aliceVision +{ +namespace dataio +{ class IFeed { public: - IFeed() { }; - - /** - * @brief Return true if the feed is correctly initialized. - * @return True if the feed is correctly initialized. - */ - virtual bool isInit() const = 0; + IFeed(){}; /** - * @brief Provide a new RGB image from the feed - * @param[out] imageRGB The new RGB image from the feed. - * @param[out] camIntrinsics The associated camera intrinsics. - * @param[out] mediaPath The original media path. - * @param[out] hasIntrinsics True if \p camIntrinsics is valid, otherwise there - * is no intrinsics associated to \p imageRGB. - * @return True if there is a new image, false otherwise. - */ - virtual bool readImage(image::Image &imageRGB, - camera::PinholeRadialK3 &camIntrinsics, - std::string &mediaPath, - bool &hasIntrinsics) = 0; + * @brief Return true if the feed is correctly initialized. + * @return True if the feed is correctly initialized. + */ + virtual bool isInit() const = 0; - /** - * @brief Provide a new float grayscale image from the feed - * @param[out] imageGray The new image from the feed. - * @param[out] camIntrinsics The associated camera intrinsics. - * @param[out] mediaPath The original media path. - * @param[out] hasIntrinsics True if \p camIntrinsics is valid, otherwise there - * is no intrinsics associated to \p imageGray. - * @return True if there is a new image, false otherwise. - */ - virtual bool readImage(image::Image &imageGray, - camera::PinholeRadialK3 &camIntrinsics, - std::string &mediaPath, - bool &hasIntrinsics) = 0; + /** + * @brief Provide a new RGB image from the feed + * @param[out] imageRGB The new RGB image from the feed. + * @param[out] camIntrinsics The associated camera intrinsics. + * @param[out] mediaPath The original media path. + * @param[out] hasIntrinsics True if \p camIntrinsics is valid, otherwise there + * is no intrinsics associated to \p imageRGB. + * @return True if there is a new image, false otherwise. + */ + virtual bool readImage(image::Image& imageRGB, camera::PinholeRadialK3& camIntrinsics, + std::string& mediaPath, bool& hasIntrinsics) = 0; - /** - * @brief Provide a new grayscale image from the feed - * @param[out] imageGray The new image from the feed. - * @param[out] camIntrinsics The associated camera intrinsics. - * @param[out] mediaPath The original media path. - * @param[out] hasIntrinsics True if \p camIntrinsics is valid, otherwise there - * is no intrinsics associated to \p imageGray. - * @return True if there is a new image, false otherwise. - */ - virtual bool readImage(image::Image &imageGray, - camera::PinholeRadialK3 &camIntrinsics, - std::string &mediaPath, - bool &hasIntrinsics) = 0; + /** + * @brief Provide a new float grayscale image from the feed + * @param[out] imageGray The new image from the feed. + * @param[out] camIntrinsics The associated camera intrinsics. + * @param[out] mediaPath The original media path. + * @param[out] hasIntrinsics True if \p camIntrinsics is valid, otherwise there + * is no intrinsics associated to \p imageGray. + * @return True if there is a new image, false otherwise. + */ + virtual bool readImage(image::Image& imageGray, camera::PinholeRadialK3& camIntrinsics, + std::string& mediaPath, bool& hasIntrinsics) = 0; - virtual std::size_t nbFrames() const = 0; + /** + * @brief Provide a new grayscale image from the feed + * @param[out] imageGray The new image from the feed. + * @param[out] camIntrinsics The associated camera intrinsics. + * @param[out] mediaPath The original media path. + * @param[out] hasIntrinsics True if \p camIntrinsics is valid, otherwise there + * is no intrinsics associated to \p imageGray. + * @return True if there is a new image, false otherwise. + */ + virtual bool readImage(image::Image& imageGray, camera::PinholeRadialK3& camIntrinsics, + std::string& mediaPath, bool& hasIntrinsics) = 0; - virtual bool goToFrame(const unsigned int frame) = 0; + virtual std::size_t nbFrames() const = 0; - virtual bool goToNextFrame() = 0; + virtual bool goToFrame(const unsigned int frame) = 0; - virtual ~IFeed( ) {} + virtual bool goToNextFrame() = 0; + virtual ~IFeed() {} }; //@todo to be move somewhere else more appropriated @@ -82,8 +77,7 @@ class IFeed * @param[in] filename The file containing the calibration parameters. * @param[out] camIntrinsics The loaded parameters. */ -void readCalibrationFromFile(const std::string &filename, camera::PinholeRadialK3 &camIntrinsics); - -}//namespace dataio -}//namespace aliceVision +void readCalibrationFromFile(const std::string& filename, camera::PinholeRadialK3& camIntrinsics); +} // namespace dataio +} // namespace aliceVision diff --git a/src/aliceVision/dataio/ImageFeed.cpp b/src/aliceVision/dataio/ImageFeed.cpp index c3dab7c24e..213ac09766 100644 --- a/src/aliceVision/dataio/ImageFeed.cpp +++ b/src/aliceVision/dataio/ImageFeed.cpp @@ -11,7 +11,7 @@ #include #include -#include +#include #include #include @@ -22,387 +22,385 @@ #include #include -namespace aliceVision{ -namespace dataio{ +namespace aliceVision +{ +namespace dataio +{ class ImageFeed::FeederImpl { public: - - FeederImpl() : _isInit(false) {} - - FeederImpl(const std::string& imagePath, const std::string& calibPath); - - template - bool readImage(image::Image &image, - camera::PinholeRadialK3 &camIntrinsics, - std::string &imageName, - bool &hasIntrinsics) - { - if(!_isInit) + FeederImpl() + : _isInit(false) { - ALICEVISION_LOG_WARNING("Image feed is not initialized "); - return false; } - // dealing with SFM mode - if(_sfmMode) - { - return feedWithJson(image, camIntrinsics, imageName, hasIntrinsics); - } - else - { - if(_images.empty()) - return false; - if(_currentImageIndex >= _images.size()) - return false; + FeederImpl(const std::string& imagePath, const std::string& calibPath); - if(_withCalibration) - { - // get the calibration - camIntrinsics = _camIntrinsics; - hasIntrinsics = true; - } - else - { - hasIntrinsics = false; - } - imageName = _images[_currentImageIndex]; - - ALICEVISION_LOG_DEBUG(imageName); - - image::readImage(imageName, image, image::EImageColorSpace::NO_CONVERSION); - return true; + template + bool readImage(image::Image& image, camera::PinholeRadialK3& camIntrinsics, std::string& imageName, + bool& hasIntrinsics) + { + if(!_isInit) + { + ALICEVISION_LOG_WARNING("Image feed is not initialized "); + return false; + } + + // dealing with SFM mode + if(_sfmMode) + { + return feedWithJson(image, camIntrinsics, imageName, hasIntrinsics); + } + else + { + if(_images.empty()) + return false; + if(_currentImageIndex >= _images.size()) + return false; + + if(_withCalibration) + { + // get the calibration + camIntrinsics = _camIntrinsics; + hasIntrinsics = true; + } + else + { + hasIntrinsics = false; + } + imageName = _images[_currentImageIndex]; + + ALICEVISION_LOG_DEBUG(imageName); + + image::readImage(imageName, image, image::EImageColorSpace::NO_CONVERSION); + return true; + } + return true; } - return true; - } - std::size_t nbFrames() const; + std::size_t nbFrames() const; - bool goToFrame(const unsigned int frame); + bool goToFrame(const unsigned int frame); - bool goToNextFrame(); + bool goToNextFrame(); - bool isInit() const {return _isInit;} + bool isInit() const { return _isInit; } private: - - template - bool feedWithJson(image::Image &image, - camera::PinholeRadialK3 &camIntrinsics, - std::string &imageName, - bool &hasIntrinsics) - { - // if there are no more images to process - if(_viewIterator == _sfmdata.getViews().end()) + template + bool feedWithJson(image::Image& image, camera::PinholeRadialK3& camIntrinsics, std::string& imageName, + bool& hasIntrinsics) { - return false; + // if there are no more images to process + if(_viewIterator == _sfmdata.getViews().end()) + { + return false; + } + + namespace bf = boost::filesystem; + + // get the image + const sfmData::View* view = _viewIterator->second.get(); + imageName = view->getImagePath(); + image::readImage(imageName, image, image::EImageColorSpace::NO_CONVERSION); + + // get the associated Intrinsics + if((view->getIntrinsicId() == UndefinedIndexT) || (!_sfmdata.getIntrinsics().count(view->getIntrinsicId()))) + { + ALICEVISION_LOG_DEBUG("Image " << imageName << " does not have associated intrinsics"); + hasIntrinsics = false; + } + else + { + const camera::IntrinsicBase* cam = _sfmdata.getIntrinsics().at(view->getIntrinsicId()).get(); + if(cam->getType() != camera::EINTRINSIC::PINHOLE_CAMERA_RADIAL3) + { + ALICEVISION_LOG_WARNING("Only PinholeRadialK3 is supported"); + hasIntrinsics = false; + } + else + { + const camera::PinholeRadialK3* intrinsics = dynamic_cast(cam); + + // simply copy values + camIntrinsics = *intrinsics; + hasIntrinsics = true; + } + } + ++_viewIterator; + return true; } - namespace bf = boost::filesystem; - - // get the image - const sfmData::View *view = _viewIterator->second.get(); - imageName = view->getImagePath(); - image::readImage(imageName, image, image::EImageColorSpace::NO_CONVERSION); - - // get the associated Intrinsics - if((view->getIntrinsicId() == UndefinedIndexT) || (!_sfmdata.getIntrinsics().count(view->getIntrinsicId()))) - { - ALICEVISION_LOG_DEBUG("Image "<< imageName << " does not have associated intrinsics"); - hasIntrinsics = false; - } - else - { - const camera::IntrinsicBase * cam = _sfmdata.getIntrinsics().at(view->getIntrinsicId()).get(); - if(cam->getType() != camera::EINTRINSIC::PINHOLE_CAMERA_RADIAL3) - { - ALICEVISION_LOG_WARNING("Only PinholeRadialK3 is supported"); - hasIntrinsics = false; - } - else - { - const camera::PinholeRadialK3 * intrinsics = dynamic_cast(cam); - - // simply copy values - camIntrinsics = *intrinsics; - hasIntrinsics = true; - } - } - ++_viewIterator; - return true; - } - private: - bool _isInit; - bool _withCalibration; - // It contains the images to be fed - std::vector _images; - camera::PinholeRadialK3 _camIntrinsics; - - bool _sfmMode = false; - sfmData::SfMData _sfmdata; - sfmData::Views::const_iterator _viewIterator; - unsigned int _currentImageIndex = 0; + bool _isInit; + bool _withCalibration; + // It contains the images to be fed + std::vector _images; + camera::PinholeRadialK3 _camIntrinsics; + + bool _sfmMode = false; + sfmData::SfMData _sfmdata; + sfmData::Views::const_iterator _viewIterator; + unsigned int _currentImageIndex = 0; }; ImageFeed::FeederImpl::FeederImpl(const std::string& imagePath, const std::string& calibPath) -: _isInit(false) -, _withCalibration(false) + : _isInit(false) + , _withCalibration(false) { - namespace bf = boost::filesystem; -// ALICEVISION_LOG_DEBUG(imagePath); - // if it is a json, calibPath is neglected - if(bf::is_regular_file(imagePath)) - { - const std::string ext = bf::path(imagePath).extension().string(); - // if it is a sfmdata.json - if(ext == ".json") - { - // load the json - _isInit = sfmDataIO::Load(_sfmdata, imagePath, sfmDataIO::ESfMData(sfmDataIO::ESfMData::VIEWS | sfmDataIO::ESfMData::INTRINSICS)); - _viewIterator = _sfmdata.getViews().begin(); - _sfmMode = true; - } - // if it is an image file - else if(image::isSupported(ext) && !image::isVideoExtension(ext)) - { - _images.push_back(imagePath); - _withCalibration = !calibPath.empty(); - _sfmMode = false; - _isInit = true; - } - // if it is an image file - else if(ext == ".txt") - { - // we expect a simple txt file with a list of path to images relative to the - // location of the txt file itself - std::fstream fs(imagePath, std::ios::in); - std::string line; - // parse each line of the text file - while(getline(fs, line)) - { - // compose the file name as the base path of the inputPath and - // the filename just read - const std::string filename = (bf::path(imagePath).parent_path() / line).string(); - _images.push_back(filename); - } - // Close file - fs.close(); - _withCalibration = !calibPath.empty(); - _sfmMode = false; - _isInit = true; - } - else + namespace bf = boost::filesystem; + // ALICEVISION_LOG_DEBUG(imagePath); + // if it is a json, calibPath is neglected + if(bf::is_regular_file(imagePath)) { - // no other file format are supported - throw std::invalid_argument("File or mode not yet implemented"); + const std::string ext = bf::path(imagePath).extension().string(); + // if it is a sfmdata.json + if(ext == ".json") + { + // load the json + _isInit = sfmDataIO::Load( + _sfmdata, imagePath, sfmDataIO::ESfMData(sfmDataIO::ESfMData::VIEWS | sfmDataIO::ESfMData::INTRINSICS)); + _viewIterator = _sfmdata.getViews().begin(); + _sfmMode = true; + } + // if it is an image file + else if(image::isSupported(ext) && !image::isVideoExtension(ext)) + { + _images.push_back(imagePath); + _withCalibration = !calibPath.empty(); + _sfmMode = false; + _isInit = true; + } + // if it is an image file + else if(ext == ".txt") + { + // we expect a simple txt file with a list of path to images relative to the + // location of the txt file itself + std::fstream fs(imagePath, std::ios::in); + std::string line; + // parse each line of the text file + while(getline(fs, line)) + { + // compose the file name as the base path of the inputPath and + // the filename just read + const std::string filename = (bf::path(imagePath).parent_path() / line).string(); + _images.push_back(filename); + } + // Close file + fs.close(); + _withCalibration = !calibPath.empty(); + _sfmMode = false; + _isInit = true; + } + else + { + // no other file format are supported + throw std::invalid_argument("File or mode not yet implemented"); + } } - } - else if(bf::is_directory(imagePath) || bf::is_directory(bf::path(imagePath).parent_path())) - { - std::string folder = imagePath; - // Recover the pattern : img.@.png (for example) - std::string filePattern; - std::regex re; - if(!bf::is_directory(imagePath)) + else if(bf::is_directory(imagePath) || bf::is_directory(bf::path(imagePath).parent_path())) { - filePattern = bf::path(imagePath).filename().string(); - folder = bf::path(imagePath).parent_path().string(); - ALICEVISION_LOG_DEBUG("filePattern: " << filePattern); - std::string regexStr = filePattern; - re = utils::filterToRegex(regexStr); + std::string folder = imagePath; + // Recover the pattern : img.@.png (for example) + std::string filePattern; + std::regex re; + if(!bf::is_directory(imagePath)) + { + filePattern = bf::path(imagePath).filename().string(); + folder = bf::path(imagePath).parent_path().string(); + ALICEVISION_LOG_DEBUG("filePattern: " << filePattern); + std::string regexStr = filePattern; + re = utils::filterToRegex(regexStr); + } + else + { + ALICEVISION_LOG_DEBUG("folder without expression: " << imagePath); + } + ALICEVISION_LOG_DEBUG("directory feedImage"); + // if it is a directory, list all the images and add them to the list + bf::directory_iterator iterator(folder); + // since some OS will provide the files in a random order, first store them + // in a priority queue and then fill the _image queue with the alphabetical + // order from the priority queue + std::priority_queue, std::greater> tmpSorter; + for(; iterator != bf::directory_iterator(); ++iterator) + { + // get the extension of the current file to check whether it is an image + const std::string ext = iterator->path().extension().string(); + if(image::isSupported(ext) && !image::isVideoExtension(ext)) + { + const std::string filepath = iterator->path().string(); + const std::string filename = iterator->path().filename().string(); + // If we have a filePattern (a sequence of images), we have to match the regex. + if(filePattern.empty() || std::regex_match(filename, re)) + tmpSorter.push(filepath); + } + else + { + ALICEVISION_LOG_WARNING("Unsupported file extension " << ext << " for " << iterator->path().string() + << "."); + } + } + // put all the retrieve files inside the queue + while(!tmpSorter.empty()) + { + _images.push_back(tmpSorter.top()); + tmpSorter.pop(); + } + + _withCalibration = !calibPath.empty(); + _sfmMode = false; + _isInit = true; } else { - ALICEVISION_LOG_DEBUG("folder without expression: " << imagePath); - } - ALICEVISION_LOG_DEBUG("directory feedImage"); - // if it is a directory, list all the images and add them to the list - bf::directory_iterator iterator(folder); - // since some OS will provide the files in a random order, first store them - // in a priority queue and then fill the _image queue with the alphabetical - // order from the priority queue - std::priority_queue, - std::greater > tmpSorter; - for(; iterator != bf::directory_iterator(); ++iterator) - { - // get the extension of the current file to check whether it is an image - const std::string ext = iterator->path().extension().string(); - if(image::isSupported(ext) && !image::isVideoExtension(ext)) - { - const std::string filepath = iterator->path().string(); - const std::string filename = iterator->path().filename().string(); - // If we have a filePattern (a sequence of images), we have to match the regex. - if(filePattern.empty() || std::regex_match(filename, re)) - tmpSorter.push(filepath); - } - else - { - ALICEVISION_LOG_WARNING("Unsupported file extension " << ext << " for " << iterator->path().string() << "."); - } + throw std::invalid_argument("File or mode not yet implemented"); } - // put all the retrieve files inside the queue - while(!tmpSorter.empty()) + + // last thing: if _withCalibration is true it means that it is not a json and + // a path to a calibration file has been passed + // then load the calibration + if(_withCalibration) { - _images.push_back(tmpSorter.top()); - tmpSorter.pop(); + // load the calibration from calibPath + readCalibrationFromFile(calibPath, _camIntrinsics); } - - _withCalibration = !calibPath.empty(); - _sfmMode = false; - _isInit = true; - } - else - { - throw std::invalid_argument("File or mode not yet implemented"); - } - - // last thing: if _withCalibration is true it means that it is not a json and - // a path to a calibration file has been passed - // then load the calibration - if(_withCalibration) - { - // load the calibration from calibPath - readCalibrationFromFile(calibPath, _camIntrinsics); - } } std::size_t ImageFeed::FeederImpl::nbFrames() const { - if(!_isInit) - return 0; + if(!_isInit) + return 0; - if(_sfmMode) - return _sfmdata.getViews().size(); + if(_sfmMode) + return _sfmdata.getViews().size(); - return _images.size(); + return _images.size(); } bool ImageFeed::FeederImpl::goToFrame(const unsigned int frame) { - if(!_isInit) - { - _currentImageIndex = frame; - ALICEVISION_LOG_WARNING("Image feed is not initialized"); - return false; - } - - // Reconstruction mode - if(_sfmMode) - { - if(frame >= _sfmdata.getViews().size()) + if(!_isInit) { - _viewIterator = _sfmdata.getViews().end(); - ALICEVISION_LOG_WARNING("The current frame is out of the range."); - return false; + _currentImageIndex = frame; + ALICEVISION_LOG_WARNING("Image feed is not initialized"); + return false; } - _viewIterator = _sfmdata.getViews().begin(); - std::advance(_viewIterator, frame); - } - else - { - _currentImageIndex = frame; - // Image list mode - if(frame >= _images.size()) + // Reconstruction mode + if(_sfmMode) + { + if(frame >= _sfmdata.getViews().size()) + { + _viewIterator = _sfmdata.getViews().end(); + ALICEVISION_LOG_WARNING("The current frame is out of the range."); + return false; + } + + _viewIterator = _sfmdata.getViews().begin(); + std::advance(_viewIterator, frame); + } + else { - ALICEVISION_LOG_WARNING("The current frame is out of the range."); - return false; + _currentImageIndex = frame; + // Image list mode + if(frame >= _images.size()) + { + ALICEVISION_LOG_WARNING("The current frame is out of the range."); + return false; + } + ALICEVISION_LOG_DEBUG("frame " << frame); } - ALICEVISION_LOG_DEBUG("frame " << frame); - } - return true; + return true; } bool ImageFeed::FeederImpl::goToNextFrame() { - if(_sfmMode) - { - if(_viewIterator == _sfmdata.getViews().end()) - return false; - ++_viewIterator; - if(_viewIterator == _sfmdata.getViews().end()) - return false; - } - else - { - ++_currentImageIndex; - ALICEVISION_LOG_DEBUG("next frame " << _currentImageIndex); - if(_currentImageIndex >= _images.size()) - return false; - } - return true; + if(_sfmMode) + { + if(_viewIterator == _sfmdata.getViews().end()) + return false; + ++_viewIterator; + if(_viewIterator == _sfmdata.getViews().end()) + return false; + } + else + { + ++_currentImageIndex; + ALICEVISION_LOG_DEBUG("next frame " << _currentImageIndex); + if(_currentImageIndex >= _images.size()) + return false; + } + return true; } /*******************************************************************************/ /* ImageFeed */ /*******************************************************************************/ -ImageFeed::ImageFeed() : _imageFeed(new FeederImpl()) { } +ImageFeed::ImageFeed() + : _imageFeed(new FeederImpl()) +{ +} ImageFeed::ImageFeed(const std::string& imagePath, const std::string& calibPath) - : _imageFeed( new FeederImpl(imagePath, calibPath) ) { } + : _imageFeed(new FeederImpl(imagePath, calibPath)) +{ +} -bool ImageFeed::readImage(image::Image &imageRGB, - camera::PinholeRadialK3 &camIntrinsics, - std::string &mediaPath, - bool &hasIntrinsics) +bool ImageFeed::readImage(image::Image& imageRGB, camera::PinholeRadialK3& camIntrinsics, + std::string& mediaPath, bool& hasIntrinsics) { - return(_imageFeed->readImage(imageRGB, camIntrinsics, mediaPath, hasIntrinsics)); + return (_imageFeed->readImage(imageRGB, camIntrinsics, mediaPath, hasIntrinsics)); } -bool ImageFeed::readImage(image::Image &imageGray, - camera::PinholeRadialK3 &camIntrinsics, - std::string &mediaPath, - bool &hasIntrinsics) +bool ImageFeed::readImage(image::Image& imageGray, camera::PinholeRadialK3& camIntrinsics, + std::string& mediaPath, bool& hasIntrinsics) { - return(_imageFeed->readImage(imageGray, camIntrinsics, mediaPath, hasIntrinsics)); + return (_imageFeed->readImage(imageGray, camIntrinsics, mediaPath, hasIntrinsics)); } -bool ImageFeed::readImage(image::Image &imageGray, - camera::PinholeRadialK3 &camIntrinsics, - std::string &mediaPath, - bool &hasIntrinsics) +bool ImageFeed::readImage(image::Image& imageGray, camera::PinholeRadialK3& camIntrinsics, + std::string& mediaPath, bool& hasIntrinsics) { - return(_imageFeed->readImage(imageGray, camIntrinsics, mediaPath, hasIntrinsics)); + return (_imageFeed->readImage(imageGray, camIntrinsics, mediaPath, hasIntrinsics)); } std::size_t ImageFeed::nbFrames() const { - return _imageFeed->nbFrames(); + return _imageFeed->nbFrames(); } bool ImageFeed::goToFrame(const unsigned int frame) { - return _imageFeed->goToFrame(frame); + return _imageFeed->goToFrame(frame); } bool ImageFeed::goToNextFrame() { - return _imageFeed->goToNextFrame(); + return _imageFeed->goToNextFrame(); } bool ImageFeed::isInit() const { - return(_imageFeed->isInit()); + return (_imageFeed->isInit()); } -bool ImageFeed::isSupported(const std::string &extension) +bool ImageFeed::isSupported(const std::string& extension) { - std::string ext = boost::to_lower_copy(extension); - if(ext == ".json" || ext == ".txt") - { - return true; - } - else - { - return (image::isSupported(ext) && !image::isVideoExtension(ext)); - } + std::string ext = boost::to_lower_copy(extension); + if(ext == ".json" || ext == ".txt") + { + return true; + } + else + { + return (image::isSupported(ext) && !image::isVideoExtension(ext)); + } } -ImageFeed::~ImageFeed() { } +ImageFeed::~ImageFeed() {} -}//namespace dataio -}//namespace aliceVision +} // namespace dataio +} // namespace aliceVision diff --git a/src/aliceVision/dataio/ImageFeed.hpp b/src/aliceVision/dataio/ImageFeed.hpp index 166ac3d869..ba820e4319 100644 --- a/src/aliceVision/dataio/ImageFeed.hpp +++ b/src/aliceVision/dataio/ImageFeed.hpp @@ -11,115 +11,109 @@ #include #include -namespace aliceVision{ -namespace dataio{ +namespace aliceVision +{ +namespace dataio +{ class ImageFeed : public IFeed { public: - /** - * @brief Empty constructor - */ - ImageFeed(); - - /** - * @brief Set up an image based feed from a choice of different sources: - * 1) a directory containing images - * 2) a json file containing a sfm data reconstruction (in that case \p calibPath is ignored) - * 3) a txt file containing a list of images to use - * 4) a regex for an image sequence - * - * @param[in] imagePath The source of images, it could be a file (json, txt) or a directory. - * @param[in] calibPath The source for the camera intrinsics common to each image. - * The format for the file is - * int #image width - * int #image height - * double #focal - * double #ppx principal point x-coord - * double #ppy principal point y-coord - * double #k0 - * double #k1 - * double #k2 - * - * @see readCalibrationFromFile() - */ - ImageFeed(const std::string& imagePath, const std::string& calibPath); - - /** - * @brief Provide a new RGB image from the feed - * - * @param[out] imageRGB The new RGB image from the feed. - * @param[out] camIntrinsics The associated camera intrinsics. - * @param[out] mediaPath The path to the current image. - * @param[out] hasIntrinsics True if \p camIntrinsics is valid, otherwise there - * is no intrinsics associated to \p imageRGB. - * @return True if there is a new image, false otherwise. - */ - bool readImage(image::Image &imageRGB, - camera::PinholeRadialK3 &camIntrinsics, - std::string &mediaPath, - bool &hasIntrinsics); - - /** - * @brief Provide a new float grayscale image from the feed - * - * @param[out] imageGray The new image from the feed. - * @param[out] camIntrinsics The associated camera intrinsics. - * @param[out] mediaPath The path to the current image. - * @param[out] hasIntrinsics True if \p camIntrinsics is valid, otherwise there - * is no intrinsics associated to \p imageGray. - * @return True if there is a new image, false otherwise. - */ - bool readImage(image::Image &imageGray, - camera::PinholeRadialK3 &camIntrinsics, - std::string &mediaPath, - bool &hasIntrinsics); - - /** - * @brief Provide a new grayscale image from the feed - * - * @param[out] imageGray The new image from the feed. - * @param[out] camIntrinsics The associated camera intrinsics. - * @param[out] mediaPath The path to the current image. - * @param[out] hasIntrinsics True if \p camIntrinsics is valid, otherwise there - * is no intrinsics associated to \p imageGray. - * @return True if there is a new image, false otherwise. - */ - bool readImage(image::Image &imageGray, - camera::PinholeRadialK3 &camIntrinsics, - std::string &mediaPath, - bool &hasIntrinsics); - - std::size_t nbFrames() const; - - bool goToFrame(const unsigned int frame); - - bool goToNextFrame(); - - /** - * @brief Return true if the feed is correctly initialized. - * - * @return True if the feed is correctly initialized. - */ - bool isInit() const; - - virtual ~ImageFeed( ); - - /** - * @brief For a given extension, return true if that file can be used as input - * for the feed. ImageFeed supports .json, .txt, and the most common image files. - * - * @param extension The file extension to check in ".ext" format (case insensitive) - * @return True if the file is supported. - */ - static bool isSupported(const std::string &extension); + /** + * @brief Empty constructor + */ + ImageFeed(); + + /** + * @brief Set up an image based feed from a choice of different sources: + * 1) a directory containing images + * 2) a json file containing a sfm data reconstruction (in that case \p calibPath is ignored) + * 3) a txt file containing a list of images to use + * 4) a regex for an image sequence + * + * @param[in] imagePath The source of images, it could be a file (json, txt) or a directory. + * @param[in] calibPath The source for the camera intrinsics common to each image. + * The format for the file is + * int #image width + * int #image height + * double #focal + * double #ppx principal point x-coord + * double #ppy principal point y-coord + * double #k0 + * double #k1 + * double #k2 + * + * @see readCalibrationFromFile() + */ + ImageFeed(const std::string& imagePath, const std::string& calibPath); + + /** + * @brief Provide a new RGB image from the feed + * + * @param[out] imageRGB The new RGB image from the feed. + * @param[out] camIntrinsics The associated camera intrinsics. + * @param[out] mediaPath The path to the current image. + * @param[out] hasIntrinsics True if \p camIntrinsics is valid, otherwise there + * is no intrinsics associated to \p imageRGB. + * @return True if there is a new image, false otherwise. + */ + bool readImage(image::Image& imageRGB, camera::PinholeRadialK3& camIntrinsics, + std::string& mediaPath, bool& hasIntrinsics); + + /** + * @brief Provide a new float grayscale image from the feed + * + * @param[out] imageGray The new image from the feed. + * @param[out] camIntrinsics The associated camera intrinsics. + * @param[out] mediaPath The path to the current image. + * @param[out] hasIntrinsics True if \p camIntrinsics is valid, otherwise there + * is no intrinsics associated to \p imageGray. + * @return True if there is a new image, false otherwise. + */ + bool readImage(image::Image& imageGray, camera::PinholeRadialK3& camIntrinsics, std::string& mediaPath, + bool& hasIntrinsics); + + /** + * @brief Provide a new grayscale image from the feed + * + * @param[out] imageGray The new image from the feed. + * @param[out] camIntrinsics The associated camera intrinsics. + * @param[out] mediaPath The path to the current image. + * @param[out] hasIntrinsics True if \p camIntrinsics is valid, otherwise there + * is no intrinsics associated to \p imageGray. + * @return True if there is a new image, false otherwise. + */ + bool readImage(image::Image& imageGray, camera::PinholeRadialK3& camIntrinsics, + std::string& mediaPath, bool& hasIntrinsics); + + std::size_t nbFrames() const; + + bool goToFrame(const unsigned int frame); + + bool goToNextFrame(); + + /** + * @brief Return true if the feed is correctly initialized. + * + * @return True if the feed is correctly initialized. + */ + bool isInit() const; + + virtual ~ImageFeed(); + + /** + * @brief For a given extension, return true if that file can be used as input + * for the feed. ImageFeed supports .json, .txt, and the most common image files. + * + * @param extension The file extension to check in ".ext" format (case insensitive) + * @return True if the file is supported. + */ + static bool isSupported(const std::string& extension); private: - class FeederImpl; - std::unique_ptr _imageFeed; + class FeederImpl; + std::unique_ptr _imageFeed; }; -}//namespace dataio -}//namespace aliceVision - - +} // namespace dataio +} // namespace aliceVision diff --git a/src/aliceVision/dataio/VideoFeed.cpp b/src/aliceVision/dataio/VideoFeed.cpp index 3362b3dce4..8c53daafe3 100644 --- a/src/aliceVision/dataio/VideoFeed.cpp +++ b/src/aliceVision/dataio/VideoFeed.cpp @@ -18,280 +18,279 @@ #include #include -namespace aliceVision{ -namespace dataio{ +namespace aliceVision +{ +namespace dataio +{ class VideoFeed::FeederImpl { public: - FeederImpl() : _isInit(false) { } + FeederImpl() + : _isInit(false) + { + } - FeederImpl(const std::string &videoPath, const std::string &calibPath); + FeederImpl(const std::string& videoPath, const std::string& calibPath); - FeederImpl(int videoDevice, const std::string &calibPath); + FeederImpl(int videoDevice, const std::string& calibPath); - bool isInit() const {return _isInit;} + bool isInit() const { return _isInit; } - bool readImage(image::Image &imageRGB, - camera::PinholeRadialK3 &camIntrinsics, - std::string &mediaPath, - bool &hasIntrinsics); + bool readImage(image::Image& imageRGB, camera::PinholeRadialK3& camIntrinsics, + std::string& mediaPath, bool& hasIntrinsics); - bool readImage(image::Image &imageGray, - camera::PinholeRadialK3 &camIntrinsics, - std::string &mediaPath, - bool &hasIntrinsics); + bool readImage(image::Image& imageGray, camera::PinholeRadialK3& camIntrinsics, std::string& mediaPath, + bool& hasIntrinsics); - bool readImage(image::Image &imageGray, - camera::PinholeRadialK3 &camIntrinsics, - std::string &mediaPath, - bool &hasIntrinsics); + bool readImage(image::Image& imageGray, camera::PinholeRadialK3& camIntrinsics, + std::string& mediaPath, bool& hasIntrinsics); - bool goToFrame(const unsigned int frame); + bool goToFrame(const unsigned int frame); - bool goToNextFrame(); + bool goToNextFrame(); - std::size_t nbFrames() const; + std::size_t nbFrames() const; private: - bool _isInit; - bool _isLive; - bool _withIntrinsics; - std::string _videoPath; - cv::VideoCapture _videoCapture; - camera::PinholeRadialK3 _camIntrinsics; + bool _isInit; + bool _isLive; + bool _withIntrinsics; + std::string _videoPath; + cv::VideoCapture _videoCapture; + camera::PinholeRadialK3 _camIntrinsics; }; - -VideoFeed::FeederImpl::FeederImpl(const std::string &videoPath, const std::string &calibPath) -: _isInit(false), _isLive(false), _withIntrinsics(false), _videoPath(videoPath) +VideoFeed::FeederImpl::FeederImpl(const std::string& videoPath, const std::string& calibPath) + : _isInit(false) + , _isLive(false) + , _withIntrinsics(false) + , _videoPath(videoPath) { // load the video - _videoCapture.open(videoPath); - if (!_videoCapture.isOpened()) - { - ALICEVISION_LOG_WARNING("Unable to open the video : " << videoPath); - throw std::invalid_argument("Unable to open the video : "+videoPath); - } - // Grab frame 0, so we can call readImage. - goToFrame(0); - - // load the calibration path - _withIntrinsics = !calibPath.empty(); - if(_withIntrinsics) - readCalibrationFromFile(calibPath, _camIntrinsics); - - _isInit = true; + _videoCapture.open(videoPath); + if(!_videoCapture.isOpened()) + { + ALICEVISION_LOG_WARNING("Unable to open the video : " << videoPath); + throw std::invalid_argument("Unable to open the video : " + videoPath); + } + // Grab frame 0, so we can call readImage. + goToFrame(0); + + // load the calibration path + _withIntrinsics = !calibPath.empty(); + if(_withIntrinsics) + readCalibrationFromFile(calibPath, _camIntrinsics); + + _isInit = true; } -VideoFeed::FeederImpl::FeederImpl(int videoDevice, const std::string &calibPath) -: _isInit(false), _isLive(true), _withIntrinsics(false), _videoPath(std::to_string(videoDevice)) +VideoFeed::FeederImpl::FeederImpl(int videoDevice, const std::string& calibPath) + : _isInit(false) + , _isLive(true) + , _withIntrinsics(false) + , _videoPath(std::to_string(videoDevice)) { // load the video - _videoCapture.open(videoDevice); - if (!_videoCapture.isOpened()) - { - ALICEVISION_LOG_WARNING("Unable to open the video : " << _videoPath); - throw std::invalid_argument("Unable to open the video : "+_videoPath); - } + _videoCapture.open(videoDevice); + if(!_videoCapture.isOpened()) + { + ALICEVISION_LOG_WARNING("Unable to open the video : " << _videoPath); + throw std::invalid_argument("Unable to open the video : " + _videoPath); + } - goToNextFrame(); + goToNextFrame(); - // load the calibration path - _withIntrinsics = !calibPath.empty(); - if(_withIntrinsics) - readCalibrationFromFile(calibPath, _camIntrinsics); + // load the calibration path + _withIntrinsics = !calibPath.empty(); + if(_withIntrinsics) + readCalibrationFromFile(calibPath, _camIntrinsics); - _isInit = true; + _isInit = true; } -bool VideoFeed::FeederImpl::readImage(image::Image &imageRGB, - camera::PinholeRadialK3 &camIntrinsics, - std::string &mediaPath, - bool &hasIntrinsics) +bool VideoFeed::FeederImpl::readImage(image::Image& imageRGB, camera::PinholeRadialK3& camIntrinsics, + std::string& mediaPath, bool& hasIntrinsics) { - cv::Mat frame; - const bool grabStatus = _videoCapture.retrieve(frame); + cv::Mat frame; + const bool grabStatus = _videoCapture.retrieve(frame); - if(!grabStatus || !frame.data) - { - return false; - } + if(!grabStatus || !frame.data) + { + return false; + } + + if(frame.channels() == 3) + { + cv::Mat color; + resize(frame, color, cv::Size(frame.cols, frame.rows)); + + cv::cvtColor(frame, color, cv::COLOR_BGR2RGB); + imageRGB.resize(color.cols, color.rows); + + unsigned char* pixelPtr = (unsigned char*)color.data; + for(int i = 0; i < color.rows; i++) + { + for(int j = 0; j < color.cols; j++) + { + const size_t index = i * color.cols * 3 + j * 3; + imageRGB(i, j) = image::RGBColor(pixelPtr[index], pixelPtr[index + 1], pixelPtr[index + 2]); + } + } + } + else + { + ALICEVISION_LOG_WARNING("Error can't read RGB frame " << _videoPath); + throw std::invalid_argument("Error can't read RGB frame " + _videoPath); + } - if(frame.channels() == 3) - { - cv::Mat color; - resize(frame, color, cv::Size(frame.cols, frame.rows)); + hasIntrinsics = _withIntrinsics; + if(_withIntrinsics) + camIntrinsics = _camIntrinsics; - cv::cvtColor(frame, color, cv::COLOR_BGR2RGB); - imageRGB.resize(color.cols, color.rows); + mediaPath = _videoPath; + return true; +} - unsigned char* pixelPtr = (unsigned char*)color.data; - for(int i = 0; i < color.rows; i++) +bool VideoFeed::FeederImpl::readImage(image::Image& imageGray, camera::PinholeRadialK3& camIntrinsics, + std::string& mediaPath, bool& hasIntrinsics) +{ + image::Image imageGrayUChar; + if(FeederImpl::readImage(imageGrayUChar, camIntrinsics, mediaPath, hasIntrinsics)) { - for(int j = 0; j < color.cols; j++) - { - const size_t index = i*color.cols*3 + j*3; - imageRGB(i,j) = image::RGBColor(pixelPtr[index], pixelPtr[index + 1], pixelPtr[index + 2]); - } + imageGray = (imageGrayUChar.GetMat().cast() / 255.f); + return true; } - } - else - { - ALICEVISION_LOG_WARNING("Error can't read RGB frame " << _videoPath); - throw std::invalid_argument("Error can't read RGB frame " + _videoPath); - } - - hasIntrinsics = _withIntrinsics; - if(_withIntrinsics) - camIntrinsics = _camIntrinsics; - - mediaPath = _videoPath; - return true; + return false; } -bool VideoFeed::FeederImpl::readImage(image::Image &imageGray, - camera::PinholeRadialK3 &camIntrinsics, - std::string &mediaPath, - bool &hasIntrinsics) +bool VideoFeed::FeederImpl::readImage(image::Image& imageGray, camera::PinholeRadialK3& camIntrinsics, + std::string& mediaPath, bool& hasIntrinsics) { - image::Image imageGrayUChar; - if(FeederImpl::readImage(imageGrayUChar, camIntrinsics, mediaPath, hasIntrinsics)) - { - imageGray = (imageGrayUChar.GetMat().cast() / 255.f); - return true; - } - return false; -} + cv::Mat frame; + const bool grabStatus = _videoCapture.retrieve(frame); + if(!grabStatus || !frame.data) + { + return false; + } -bool VideoFeed::FeederImpl::readImage(image::Image &imageGray, - camera::PinholeRadialK3 &camIntrinsics, - std::string &mediaPath, - bool &hasIntrinsics) -{ - cv::Mat frame; - const bool grabStatus = _videoCapture.retrieve(frame); + if(frame.channels() == 3) + { + // convert to gray + cv::Mat grey; + cv::cvtColor(frame, grey, cv::COLOR_BGR2GRAY); + imageGray.resize(grey.cols, grey.rows); + cv::cv2eigen(grey, imageGray); + // ALICEVISION_LOG_DEBUG(grey.channels() << " " << grey.rows << " " << grey.cols); + // ALICEVISION_LOG_DEBUG(imageGray.Depth() << " " << imageGray.Height() << " " << imageGray.Width()); + } + else + { + cv::cv2eigen(frame, imageGray); + } - if(!grabStatus || !frame.data) - { - return false; - } - - if(frame.channels() == 3) - { - // convert to gray - cv::Mat grey; - cv::cvtColor(frame, grey, cv::COLOR_BGR2GRAY); - imageGray.resize(grey.cols, grey.rows); - cv::cv2eigen(grey, imageGray); -// ALICEVISION_LOG_DEBUG(grey.channels() << " " << grey.rows << " " << grey.cols); -// ALICEVISION_LOG_DEBUG(imageGray.Depth() << " " << imageGray.Height() << " " << imageGray.Width()); - } - else - { - cv::cv2eigen(frame, imageGray); - } - - hasIntrinsics = _withIntrinsics; - if(_withIntrinsics) - camIntrinsics = _camIntrinsics; - - mediaPath = _videoPath; - return true; + hasIntrinsics = _withIntrinsics; + if(_withIntrinsics) + camIntrinsics = _camIntrinsics; + + mediaPath = _videoPath; + return true; } std::size_t VideoFeed::FeederImpl::nbFrames() const { - if (!_videoCapture.isOpened()) - { - ALICEVISION_LOG_WARNING("The video file could not be opened."); - return 0; - } - return _videoCapture.get(cv::CAP_PROP_FRAME_COUNT); + if(!_videoCapture.isOpened()) + { + ALICEVISION_LOG_WARNING("The video file could not be opened."); + return 0; + } + return _videoCapture.get(cv::CAP_PROP_FRAME_COUNT); } bool VideoFeed::FeederImpl::goToFrame(const unsigned int frame) { - if (!_videoCapture.isOpened()) - { - ALICEVISION_LOG_WARNING("We cannot open the video file."); - return false; - } + if(!_videoCapture.isOpened()) + { + ALICEVISION_LOG_WARNING("We cannot open the video file."); + return false; + } - if (_isLive) - return goToNextFrame(); + if(_isLive) + return goToNextFrame(); - _videoCapture.set(cv::CAP_PROP_POS_FRAMES, frame); - return _videoCapture.grab(); + _videoCapture.set(cv::CAP_PROP_POS_FRAMES, frame); + return _videoCapture.grab(); } bool VideoFeed::FeederImpl::goToNextFrame() { - return _videoCapture.grab(); + return _videoCapture.grab(); } /*******************************************************************************/ /* VideoFeed */ /*******************************************************************************/ -VideoFeed::VideoFeed() : _feeder(new FeederImpl()) { } +VideoFeed::VideoFeed() + : _feeder(new FeederImpl()) +{ +} -VideoFeed::VideoFeed(const std::string &videoPath, const std::string &calibPath) - : _feeder(new FeederImpl(videoPath, calibPath)) -{ } +VideoFeed::VideoFeed(const std::string& videoPath, const std::string& calibPath) + : _feeder(new FeederImpl(videoPath, calibPath)) +{ +} -VideoFeed::VideoFeed(int videoDevice, const std::string &calibPath) - : _feeder(new FeederImpl(videoDevice, calibPath)) -{ } +VideoFeed::VideoFeed(int videoDevice, const std::string& calibPath) + : _feeder(new FeederImpl(videoDevice, calibPath)) +{ +} -bool VideoFeed::readImage(image::Image &imageRGB, - camera::PinholeRadialK3 &camIntrinsics, - std::string &mediaPath, - bool &hasIntrinsics) +bool VideoFeed::readImage(image::Image& imageRGB, camera::PinholeRadialK3& camIntrinsics, + std::string& mediaPath, bool& hasIntrinsics) { - return(_feeder->readImage(imageRGB, camIntrinsics, mediaPath, hasIntrinsics)); + return (_feeder->readImage(imageRGB, camIntrinsics, mediaPath, hasIntrinsics)); } -bool VideoFeed::readImage(image::Image &imageGray, - camera::PinholeRadialK3 &camIntrinsics, - std::string &mediaPath, - bool &hasIntrinsics) +bool VideoFeed::readImage(image::Image& imageGray, camera::PinholeRadialK3& camIntrinsics, + std::string& mediaPath, bool& hasIntrinsics) { - return(_feeder->readImage(imageGray, camIntrinsics, mediaPath, hasIntrinsics)); + return (_feeder->readImage(imageGray, camIntrinsics, mediaPath, hasIntrinsics)); } -bool VideoFeed::readImage(image::Image &imageGray, - camera::PinholeRadialK3 &camIntrinsics, - std::string &mediaPath, - bool &hasIntrinsics) +bool VideoFeed::readImage(image::Image& imageGray, camera::PinholeRadialK3& camIntrinsics, + std::string& mediaPath, bool& hasIntrinsics) { - return(_feeder->readImage(imageGray, camIntrinsics, mediaPath, hasIntrinsics)); + return (_feeder->readImage(imageGray, camIntrinsics, mediaPath, hasIntrinsics)); } std::size_t VideoFeed::nbFrames() const { - return _feeder->nbFrames(); + return _feeder->nbFrames(); } bool VideoFeed::goToFrame(const unsigned int frame) { - return _feeder->goToFrame(frame); + return _feeder->goToFrame(frame); } bool VideoFeed::goToNextFrame() { - return _feeder->goToNextFrame(); + return _feeder->goToNextFrame(); } -bool VideoFeed::isInit() const {return(_feeder->isInit()); } +bool VideoFeed::isInit() const +{ + return (_feeder->isInit()); +} -bool VideoFeed::isSupported(const std::string &extension) +bool VideoFeed::isSupported(const std::string& extension) { - return image::isVideoExtension(extension); + return image::isVideoExtension(extension); } -VideoFeed::~VideoFeed() { } +VideoFeed::~VideoFeed() {} -}//namespace dataio -}//namespace aliceVision +} // namespace dataio +} // namespace aliceVision diff --git a/src/aliceVision/dataio/VideoFeed.hpp b/src/aliceVision/dataio/VideoFeed.hpp index a715000908..a1779edbe5 100644 --- a/src/aliceVision/dataio/VideoFeed.hpp +++ b/src/aliceVision/dataio/VideoFeed.hpp @@ -11,123 +11,119 @@ #include #include -namespace aliceVision{ -namespace dataio{ +namespace aliceVision +{ +namespace dataio +{ class VideoFeed : public IFeed { public: - VideoFeed(); - - /** - * @brief Set up an image feed from a video - * - * @param[in] videoPath The video source. - * @param[in] calibPath The source for the camera intrinsics. - * The format for the file is - * int #image width - * int #image height - * double #focal - * double #ppx principal point x-coord - * double #ppy principal point y-coord - * double #k0 - * double #k1 - * double #k2 - * - * @see readCalibrationFromFile() - */ - VideoFeed(const std::string &videoPath, const std::string &calibPath); - - /** - * @brief Set up an image feed from a video - * - * @param[in] videoDevice The device id from which capture the live feed. - * @param[in] calibPath The source for the camera intrinsics. - * The format for the file is - * int #image width - * int #image height - * double #focal - * double #ppx principal point x-coord - * double #ppy principal point y-coord - * double #k0 - * double #k1 - * double #k2 - * - * @see readCalibrationFromFile() - */ - VideoFeed(int videoDevice, const std::string &calibPath); - - /** - * @brief Provide a new RGB image from the feed - * - * @param[out] imageRGB The new RGB image from the feed. - * @param[out] camIntrinsics The associated camera intrinsics. - * @param[out] mediaPath The original video path. - * @param[out] hasIntrinsics True if \p camIntrinsics is valid, otherwise there - * is no intrinsics associated to \p imageRGB. - * @return True if there is a new image, false otherwise. - */ - bool readImage(image::Image &imageRGB, - camera::PinholeRadialK3 &camIntrinsics, - std::string &mediaPath, - bool &hasIntrinsics); - - /** - * @brief Provide a new float grayscale image from the feed - * - * @param[out] imageGray The new image from the feed. - * @param[out] camIntrinsics The associated camera intrinsics. - * @param[out] mediaPath The original video path. - * @param[out] hasIntrinsics True if \p camIntrinsics is valid, otherwise there - * is no intrinsics associated to \p imageGray. - * @return True if there is a new image, false otherwise. - */ - bool readImage(image::Image &imageGray, - camera::PinholeRadialK3 &camIntrinsics, - std::string &mediaPath, - bool &hasIntrinsics); - - /** - * @brief Provide a new grayscale image from the feed - * - * @param[out] imageGray The new image from the feed. - * @param[out] camIntrinsics The associated camera intrinsics. - * @param[out] mediaPath The original video path. - * @param[out] hasIntrinsics True if \p camIntrinsics is valid, otherwise there - * is no intrinsics associated to \p imageGray. - * @return True if there is a new image, false otherwise. - */ - bool readImage(image::Image &imageGray, - camera::PinholeRadialK3 &camIntrinsics, - std::string &mediaPath, - bool &hasIntrinsics); - - std::size_t nbFrames() const; - - bool goToFrame(const unsigned int frame); - - bool goToNextFrame(); - - /** - * @brief Return true if the feed is correctly initialized. - * - * @return True if the feed is correctly initialized. - */ - bool isInit() const; - - virtual ~VideoFeed(); - -/** - * @brief For a given extension, return true if that file can be used as input video for the feed. - * @param extension The file extension to check in ".ext" format (case insensitive). - * @return True if the file is supported. - */ -static bool isSupported(const std::string &extension); + VideoFeed(); + + /** + * @brief Set up an image feed from a video + * + * @param[in] videoPath The video source. + * @param[in] calibPath The source for the camera intrinsics. + * The format for the file is + * int #image width + * int #image height + * double #focal + * double #ppx principal point x-coord + * double #ppy principal point y-coord + * double #k0 + * double #k1 + * double #k2 + * + * @see readCalibrationFromFile() + */ + VideoFeed(const std::string& videoPath, const std::string& calibPath); + + /** + * @brief Set up an image feed from a video + * + * @param[in] videoDevice The device id from which capture the live feed. + * @param[in] calibPath The source for the camera intrinsics. + * The format for the file is + * int #image width + * int #image height + * double #focal + * double #ppx principal point x-coord + * double #ppy principal point y-coord + * double #k0 + * double #k1 + * double #k2 + * + * @see readCalibrationFromFile() + */ + VideoFeed(int videoDevice, const std::string& calibPath); + + /** + * @brief Provide a new RGB image from the feed + * + * @param[out] imageRGB The new RGB image from the feed. + * @param[out] camIntrinsics The associated camera intrinsics. + * @param[out] mediaPath The original video path. + * @param[out] hasIntrinsics True if \p camIntrinsics is valid, otherwise there + * is no intrinsics associated to \p imageRGB. + * @return True if there is a new image, false otherwise. + */ + bool readImage(image::Image& imageRGB, camera::PinholeRadialK3& camIntrinsics, + std::string& mediaPath, bool& hasIntrinsics); + + /** + * @brief Provide a new float grayscale image from the feed + * + * @param[out] imageGray The new image from the feed. + * @param[out] camIntrinsics The associated camera intrinsics. + * @param[out] mediaPath The original video path. + * @param[out] hasIntrinsics True if \p camIntrinsics is valid, otherwise there + * is no intrinsics associated to \p imageGray. + * @return True if there is a new image, false otherwise. + */ + bool readImage(image::Image& imageGray, camera::PinholeRadialK3& camIntrinsics, std::string& mediaPath, + bool& hasIntrinsics); + + /** + * @brief Provide a new grayscale image from the feed + * + * @param[out] imageGray The new image from the feed. + * @param[out] camIntrinsics The associated camera intrinsics. + * @param[out] mediaPath The original video path. + * @param[out] hasIntrinsics True if \p camIntrinsics is valid, otherwise there + * is no intrinsics associated to \p imageGray. + * @return True if there is a new image, false otherwise. + */ + bool readImage(image::Image& imageGray, camera::PinholeRadialK3& camIntrinsics, + std::string& mediaPath, bool& hasIntrinsics); + + std::size_t nbFrames() const; + + bool goToFrame(const unsigned int frame); + + bool goToNextFrame(); + + /** + * @brief Return true if the feed is correctly initialized. + * + * @return True if the feed is correctly initialized. + */ + bool isInit() const; + + virtual ~VideoFeed(); + + /** + * @brief For a given extension, return true if that file can be used as input video for the feed. + * @param extension The file extension to check in ".ext" format (case insensitive). + * @return True if the file is supported. + */ + static bool isSupported(const std::string& extension); private: - class FeederImpl; - std::unique_ptr _feeder; + class FeederImpl; + std::unique_ptr _feeder; }; -}//namespace dataio -}//namespace aliceVision +} // namespace dataio +} // namespace aliceVision From c0122c6834de777050e4ba2070d3dc9860dfda42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Candice=20Bent=C3=A9jac?= Date: Mon, 3 Apr 2023 11:42:50 +0200 Subject: [PATCH 02/12] [dataio] Remove the JSON support from the ImageFeed The ImageFeed accepted as an input: - a text file containing a list of images' paths - input images - a directory - a JSON file (SfMData) The support for the JSON file is removed from the ImageFeed. It will later be added to a specific feeder. --- src/aliceVision/dataio/ImageFeed.cpp | 157 +++++---------------------- src/aliceVision/dataio/ImageFeed.hpp | 5 +- 2 files changed, 30 insertions(+), 132 deletions(-) diff --git a/src/aliceVision/dataio/ImageFeed.cpp b/src/aliceVision/dataio/ImageFeed.cpp index 213ac09766..11a2545c0f 100644 --- a/src/aliceVision/dataio/ImageFeed.cpp +++ b/src/aliceVision/dataio/ImageFeed.cpp @@ -5,8 +5,6 @@ // You can obtain one at https://mozilla.org/MPL/2.0/. #include "ImageFeed.hpp" -#include -#include #include #include @@ -47,35 +45,26 @@ class ImageFeed::FeederImpl return false; } - // dealing with SFM mode - if(_sfmMode) + if(_images.empty()) + return false; + if(_currentImageIndex >= _images.size()) + return false; + + if(_withCalibration) { - return feedWithJson(image, camIntrinsics, imageName, hasIntrinsics); + // get the calibration + camIntrinsics = _camIntrinsics; + hasIntrinsics = true; } else { - if(_images.empty()) - return false; - if(_currentImageIndex >= _images.size()) - return false; - - if(_withCalibration) - { - // get the calibration - camIntrinsics = _camIntrinsics; - hasIntrinsics = true; - } - else - { - hasIntrinsics = false; - } - imageName = _images[_currentImageIndex]; + hasIntrinsics = false; + } - ALICEVISION_LOG_DEBUG(imageName); + imageName = _images[_currentImageIndex]; + ALICEVISION_LOG_DEBUG(imageName); + image::readImage(imageName, image, image::EImageColorSpace::NO_CONVERSION); - image::readImage(imageName, image, image::EImageColorSpace::NO_CONVERSION); - return true; - } return true; } @@ -87,51 +76,6 @@ class ImageFeed::FeederImpl bool isInit() const { return _isInit; } -private: - template - bool feedWithJson(image::Image& image, camera::PinholeRadialK3& camIntrinsics, std::string& imageName, - bool& hasIntrinsics) - { - // if there are no more images to process - if(_viewIterator == _sfmdata.getViews().end()) - { - return false; - } - - namespace bf = boost::filesystem; - - // get the image - const sfmData::View* view = _viewIterator->second.get(); - imageName = view->getImagePath(); - image::readImage(imageName, image, image::EImageColorSpace::NO_CONVERSION); - - // get the associated Intrinsics - if((view->getIntrinsicId() == UndefinedIndexT) || (!_sfmdata.getIntrinsics().count(view->getIntrinsicId()))) - { - ALICEVISION_LOG_DEBUG("Image " << imageName << " does not have associated intrinsics"); - hasIntrinsics = false; - } - else - { - const camera::IntrinsicBase* cam = _sfmdata.getIntrinsics().at(view->getIntrinsicId()).get(); - if(cam->getType() != camera::EINTRINSIC::PINHOLE_CAMERA_RADIAL3) - { - ALICEVISION_LOG_WARNING("Only PinholeRadialK3 is supported"); - hasIntrinsics = false; - } - else - { - const camera::PinholeRadialK3* intrinsics = dynamic_cast(cam); - - // simply copy values - camIntrinsics = *intrinsics; - hasIntrinsics = true; - } - } - ++_viewIterator; - return true; - } - private: bool _isInit; bool _withCalibration; @@ -139,9 +83,6 @@ class ImageFeed::FeederImpl std::vector _images; camera::PinholeRadialK3 _camIntrinsics; - bool _sfmMode = false; - sfmData::SfMData _sfmdata; - sfmData::Views::const_iterator _viewIterator; unsigned int _currentImageIndex = 0; }; @@ -155,21 +96,11 @@ ImageFeed::FeederImpl::FeederImpl(const std::string& imagePath, const std::strin if(bf::is_regular_file(imagePath)) { const std::string ext = bf::path(imagePath).extension().string(); - // if it is a sfmdata.json - if(ext == ".json") - { - // load the json - _isInit = sfmDataIO::Load( - _sfmdata, imagePath, sfmDataIO::ESfMData(sfmDataIO::ESfMData::VIEWS | sfmDataIO::ESfMData::INTRINSICS)); - _viewIterator = _sfmdata.getViews().begin(); - _sfmMode = true; - } // if it is an image file - else if(image::isSupported(ext) && !image::isVideoExtension(ext)) + if(image::isSupported(ext) && !image::isVideoExtension(ext)) { _images.push_back(imagePath); _withCalibration = !calibPath.empty(); - _sfmMode = false; _isInit = true; } // if it is an image file @@ -190,7 +121,6 @@ ImageFeed::FeederImpl::FeederImpl(const std::string& imagePath, const std::strin // Close file fs.close(); _withCalibration = !calibPath.empty(); - _sfmMode = false; _isInit = true; } else @@ -250,7 +180,6 @@ ImageFeed::FeederImpl::FeederImpl(const std::string& imagePath, const std::strin } _withCalibration = !calibPath.empty(); - _sfmMode = false; _isInit = true; } else @@ -258,8 +187,7 @@ ImageFeed::FeederImpl::FeederImpl(const std::string& imagePath, const std::strin throw std::invalid_argument("File or mode not yet implemented"); } - // last thing: if _withCalibration is true it means that it is not a json and - // a path to a calibration file has been passed + // last thing: if _withCalibration is true it means that a path to a calibration file has been passed // then load the calibration if(_withCalibration) { @@ -273,9 +201,6 @@ std::size_t ImageFeed::FeederImpl::nbFrames() const if(!_isInit) return 0; - if(_sfmMode) - return _sfmdata.getViews().size(); - return _images.size(); } @@ -288,50 +213,24 @@ bool ImageFeed::FeederImpl::goToFrame(const unsigned int frame) return false; } - // Reconstruction mode - if(_sfmMode) - { - if(frame >= _sfmdata.getViews().size()) - { - _viewIterator = _sfmdata.getViews().end(); - ALICEVISION_LOG_WARNING("The current frame is out of the range."); - return false; - } - - _viewIterator = _sfmdata.getViews().begin(); - std::advance(_viewIterator, frame); - } - else + _currentImageIndex = frame; + // Image list mode + if(frame >= _images.size()) { - _currentImageIndex = frame; - // Image list mode - if(frame >= _images.size()) - { - ALICEVISION_LOG_WARNING("The current frame is out of the range."); - return false; - } - ALICEVISION_LOG_DEBUG("frame " << frame); + ALICEVISION_LOG_WARNING("The current frame is out of the range."); + return false; } + ALICEVISION_LOG_DEBUG("frame " << frame); return true; } bool ImageFeed::FeederImpl::goToNextFrame() { - if(_sfmMode) - { - if(_viewIterator == _sfmdata.getViews().end()) - return false; - ++_viewIterator; - if(_viewIterator == _sfmdata.getViews().end()) - return false; - } - else - { - ++_currentImageIndex; - ALICEVISION_LOG_DEBUG("next frame " << _currentImageIndex); - if(_currentImageIndex >= _images.size()) - return false; - } + ++_currentImageIndex; + ALICEVISION_LOG_DEBUG("next frame " << _currentImageIndex); + if(_currentImageIndex >= _images.size()) + return false; + return true; } @@ -390,7 +289,7 @@ bool ImageFeed::isInit() const bool ImageFeed::isSupported(const std::string& extension) { std::string ext = boost::to_lower_copy(extension); - if(ext == ".json" || ext == ".txt") + if(ext == ".txt") { return true; } diff --git a/src/aliceVision/dataio/ImageFeed.hpp b/src/aliceVision/dataio/ImageFeed.hpp index ba820e4319..20c9040574 100644 --- a/src/aliceVision/dataio/ImageFeed.hpp +++ b/src/aliceVision/dataio/ImageFeed.hpp @@ -27,11 +27,10 @@ class ImageFeed : public IFeed /** * @brief Set up an image based feed from a choice of different sources: * 1) a directory containing images - * 2) a json file containing a sfm data reconstruction (in that case \p calibPath is ignored) * 3) a txt file containing a list of images to use * 4) a regex for an image sequence * - * @param[in] imagePath The source of images, it could be a file (json, txt) or a directory. + * @param[in] imagePath The source of images, it could be a txt file or a directory. * @param[in] calibPath The source for the camera intrinsics common to each image. * The format for the file is * int #image width @@ -103,7 +102,7 @@ class ImageFeed : public IFeed /** * @brief For a given extension, return true if that file can be used as input - * for the feed. ImageFeed supports .json, .txt, and the most common image files. + * for the feed. ImageFeed supports .txt and the most common image files. * * @param extension The file extension to check in ".ext" format (case insensitive) * @return True if the file is supported. From 4f8ef5c89add479a1a087285b111167162931ea6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Candice=20Bent=C3=A9jac?= Date: Mon, 3 Apr 2023 17:40:13 +0200 Subject: [PATCH 03/12] [dataio] Add support for SfMData with an SfMDataFeed The SfMDataFeed supports SfMData files, which can be .sfm, .json or .abc. The provided input file is parsed, and the views are ordered according to their frame ID. --- src/aliceVision/dataio/CMakeLists.txt | 2 + src/aliceVision/dataio/FeedProvider.cpp | 9 +- src/aliceVision/dataio/FeedProvider.hpp | 8 + src/aliceVision/dataio/SfMDataFeed.cpp | 187 ++++++++++++++++++++++++ src/aliceVision/dataio/SfMDataFeed.hpp | 118 +++++++++++++++ 5 files changed, 323 insertions(+), 1 deletion(-) create mode 100644 src/aliceVision/dataio/SfMDataFeed.cpp create mode 100644 src/aliceVision/dataio/SfMDataFeed.hpp diff --git a/src/aliceVision/dataio/CMakeLists.txt b/src/aliceVision/dataio/CMakeLists.txt index 75c3352673..ab24fb6e3d 100644 --- a/src/aliceVision/dataio/CMakeLists.txt +++ b/src/aliceVision/dataio/CMakeLists.txt @@ -3,6 +3,7 @@ set(dataio_files_headers FeedProvider.hpp IFeed.hpp ImageFeed.hpp + SfMDataFeed.hpp ) # Sources @@ -10,6 +11,7 @@ set(dataio_files_sources FeedProvider.cpp IFeed.cpp ImageFeed.cpp + SfMDataFeed.cpp ) if(ALICEVISION_HAVE_OPENCV) diff --git a/src/aliceVision/dataio/FeedProvider.cpp b/src/aliceVision/dataio/FeedProvider.cpp index d0aaeda46a..2752eb69fe 100644 --- a/src/aliceVision/dataio/FeedProvider.cpp +++ b/src/aliceVision/dataio/FeedProvider.cpp @@ -7,6 +7,7 @@ #include "FeedProvider.hpp" #include #include "ImageFeed.hpp" +#include "SfMDataFeed.hpp" #if ALICEVISION_IS_DEFINED(ALICEVISION_HAVE_OPENCV) #include "VideoFeed.hpp" #endif @@ -27,6 +28,7 @@ namespace dataio FeedProvider::FeedProvider(const std::string& feedPath, const std::string& calibPath) : _isVideo(false) , _isLiveFeed(false) + , _isSfmData(false) { namespace bf = boost::filesystem; if(feedPath.empty()) @@ -37,7 +39,12 @@ FeedProvider::FeedProvider(const std::string& feedPath, const std::string& calib { // Image or video file const std::string extension = bf::path(feedPath).extension().string(); - if(ImageFeed::isSupported(extension)) + if(SfMDataFeed::isSupported(extension)) + { + _feeder.reset(new SfMDataFeed(feedPath, calibPath)); + _isSfmData = true; + } + else if(ImageFeed::isSupported(extension)) { _feeder.reset(new ImageFeed(feedPath, calibPath)); } diff --git a/src/aliceVision/dataio/FeedProvider.hpp b/src/aliceVision/dataio/FeedProvider.hpp index b007bece09..14c31acc06 100644 --- a/src/aliceVision/dataio/FeedProvider.hpp +++ b/src/aliceVision/dataio/FeedProvider.hpp @@ -104,12 +104,20 @@ class FeedProvider */ bool isLiveFeed() const { return _isLiveFeed; } + /** + * @brief Return true if the feed is an SfMData one. + * + * @return True if the feed is an SfMData feed, false otherwise. + */ + bool isSfMData() const { return _isSfmData; } + virtual ~FeedProvider(); private: std::unique_ptr _feeder; bool _isVideo; bool _isLiveFeed; + bool _isSfmData; }; } // namespace dataio diff --git a/src/aliceVision/dataio/SfMDataFeed.cpp b/src/aliceVision/dataio/SfMDataFeed.cpp new file mode 100644 index 0000000000..0d1909f6b1 --- /dev/null +++ b/src/aliceVision/dataio/SfMDataFeed.cpp @@ -0,0 +1,187 @@ +// This file is part of the AliceVision project. +// Copyright (c) 2023 AliceVision contributors. +// This Source Code Form is subject to the terms of the Mozilla Public License, +// v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +#include "SfMDataFeed.hpp" +#include +#include +#include + +#include + +#include +#include +#include + +namespace aliceVision +{ +namespace dataio +{ + +class SfMDataFeed::FeederImpl +{ +public: + FeederImpl() + : _isInit(false) + { + } + + FeederImpl(const std::string& imagePath, const std::string& calibPath); + + template + bool readImage(image::Image& image, camera::PinholeRadialK3& camIntrinsics, std::string& imageName, + bool& hasIntrinsics) + { + if(!_isInit) + { + ALICEVISION_LOG_WARNING("SfMData feed is not initialized "); + return false; + } + + if(_currentImageIndex >= _views.size()) + { + ALICEVISION_LOG_WARNING("No more images to process"); + return false; + } + + // Get the image path + const sfmData::View* view = _views.at(_currentImageIndex); + imageName = view->getImagePath(); + image::readImage(imageName, image, image::EImageColorSpace::NO_CONVERSION); + + return true; + } + + std::size_t nbFrames() const; + + bool goToFrame(const unsigned int frame); + + bool goToNextFrame(); + + bool isInit() const { return _isInit; } + +private: + bool _isInit; + + sfmData::SfMData _sfmData; + std::vector _views; + unsigned int _currentImageIndex = 0; +}; + +SfMDataFeed::FeederImpl::FeederImpl(const std::string& imagePath, const std::string& calibPath) + : _isInit(false) +{ + _isInit = sfmDataIO::Load(_sfmData, imagePath, + sfmDataIO::ESfMData(sfmDataIO::ESfMData::VIEWS | sfmDataIO::ESfMData::INTRINSICS)); + + // Order the views according to the frame ID + for(auto it = _sfmData.getViews().begin(); it != _sfmData.getViews().end(); ++it) + { + _views.push_back(it->second.get()); + } + std::sort(_views.begin(), _views.end(), + [](const sfmData::View* v1, const sfmData::View* v2) { return (v1->getFrameId() < v2->getFrameId()); }); +} + +std::size_t SfMDataFeed::FeederImpl::nbFrames() const +{ + if(!_isInit) + return 0; + + return _views.size(); +} + +bool SfMDataFeed::FeederImpl::goToFrame(const unsigned int frame) +{ + _currentImageIndex = frame; + if(!_isInit) + { + ALICEVISION_LOG_WARNING("SfmData feed is not initialized"); + return false; + } + + if(frame >= _views.size()) + { + ALICEVISION_LOG_WARNING("The current frame is out of the range."); + return false; + } + + return true; +} + +bool SfMDataFeed::FeederImpl::goToNextFrame() +{ + ++_currentImageIndex; + ALICEVISION_LOG_DEBUG("next frame " << _currentImageIndex); + if(_currentImageIndex >= _views.size()) + return false; + + return true; + + return true; +} + +/*******************************************************************************/ +/* SfMDataFeed */ +/*******************************************************************************/ + +SfMDataFeed::SfMDataFeed() + : _sfmDataFeed(new FeederImpl()) +{ +} + +SfMDataFeed::SfMDataFeed(const std::string& imagePath, const std::string& calibPath) + : _sfmDataFeed(new FeederImpl(imagePath, calibPath)) +{ +} + +bool SfMDataFeed::readImage(image::Image& imageRGB, camera::PinholeRadialK3& camIntrinsics, + std::string& mediaPath, bool& hasIntrinsics) +{ + return (_sfmDataFeed->readImage(imageRGB, camIntrinsics, mediaPath, hasIntrinsics)); +} + +bool SfMDataFeed::readImage(image::Image& imageGray, camera::PinholeRadialK3& camIntrinsics, + std::string& mediaPath, bool& hasIntrinsics) +{ + return (_sfmDataFeed->readImage(imageGray, camIntrinsics, mediaPath, hasIntrinsics)); +} + +bool SfMDataFeed::readImage(image::Image& imageGray, camera::PinholeRadialK3& camIntrinsics, + std::string& mediaPath, bool& hasIntrinsics) +{ + return (_sfmDataFeed->readImage(imageGray, camIntrinsics, mediaPath, hasIntrinsics)); +} + +std::size_t SfMDataFeed::nbFrames() const +{ + return _sfmDataFeed->nbFrames(); +} + +bool SfMDataFeed::goToFrame(const unsigned int frame) +{ + return _sfmDataFeed->goToFrame(frame); +} + +bool SfMDataFeed::goToNextFrame() +{ + return _sfmDataFeed->goToNextFrame(); +} + +bool SfMDataFeed::isInit() const +{ + return (_sfmDataFeed->isInit()); +} + +bool SfMDataFeed::isSupported(const std::string& extension) +{ + std::string ext = boost::to_lower_copy(extension); + return (ext == ".sfm" || ext == ".abc" || ext == ".json"); +} + +SfMDataFeed::~SfMDataFeed() {} + +} // namespace dataio +} // namespace aliceVision diff --git a/src/aliceVision/dataio/SfMDataFeed.hpp b/src/aliceVision/dataio/SfMDataFeed.hpp new file mode 100644 index 0000000000..89bca2a554 --- /dev/null +++ b/src/aliceVision/dataio/SfMDataFeed.hpp @@ -0,0 +1,118 @@ +// This file is part of the AliceVision project. +// Copyright (c) 2023 AliceVision contributors. +// This Source Code Form is subject to the terms of the Mozilla Public License, +// v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +#pragma once + +#include "IFeed.hpp" + +#include +#include + +namespace aliceVision +{ +namespace dataio +{ + +class SfMDataFeed : public IFeed +{ +public: + /** + * @brief Empty constructor + */ + SfMDataFeed(); + + /** + * @brief Set up an SfMData-based feed, which can be: + * 1) an ".sfm" file + * 2) a ".json" file + * 3) an ".abc" file + * + * @param[in] imagePath The source of images, it could be a file (json, txt) or a directory. + * @param[in] calibPath The source for the camera intrinsics common to each image. + * The format for the file is + * int #image width + * int #image height + * double #focal + * double #ppx principal point x-coord + * double #ppy principal point y-coord + * double #k0 + * double #k1 + * double #k2 + * + * @see readCalibrationFromFile() + */ + SfMDataFeed(const std::string& imagePath, const std::string& calibPath); + + /** + * @brief Provide a new RGB image from the feed + * + * @param[out] imageRGB The new RGB image from the feed. + * @param[out] camIntrinsics The associated camera intrinsics. + * @param[out] mediaPath The path to the current image. + * @param[out] hasIntrinsics True if \p camIntrinsics is valid, otherwise there + * is no intrinsics associated to \p imageRGB. + * @return True if there is a new image, false otherwise. + */ + bool readImage(image::Image& imageRGB, camera::PinholeRadialK3& camIntrinsics, + std::string& mediaPath, bool& hasIntrinsics); + + /** + * @brief Provide a new float grayscale image from the feed + * + * @param[out] imageGray The new image from the feed. + * @param[out] camIntrinsics The associated camera intrinsics. + * @param[out] mediaPath The path to the current image. + * @param[out] hasIntrinsics True if \p camIntrinsics is valid, otherwise there + * is no intrinsics associated to \p imageGray. + * @return True if there is a new image, false otherwise. + */ + bool readImage(image::Image& imageGray, camera::PinholeRadialK3& camIntrinsics, std::string& mediaPath, + bool& hasIntrinsics); + + /** + * @brief Provide a new grayscale image from the feed + * + * @param[out] imageGray The new image from the feed. + * @param[out] camIntrinsics The associated camera intrinsics. + * @param[out] mediaPath The path to the current image. + * @param[out] hasIntrinsics True if \p camIntrinsics is valid, otherwise there + * is no intrinsics associated to \p imageGray. + * @return True if there is a new image, false otherwise. + */ + bool readImage(image::Image& imageGray, camera::PinholeRadialK3& camIntrinsics, + std::string& mediaPath, bool& hasIntrinsics); + + std::size_t nbFrames() const; + + bool goToFrame(const unsigned int frame); + + bool goToNextFrame(); + + /** + * @brief Return true if the feed is correctly initialized. + * + * @return True if the feed is correctly initialized. + */ + bool isInit() const; + + virtual ~SfMDataFeed(); + + /** + * @brief For a given extension, return true if that file can be used as input + * for the feed. SfMDataFeed supports .sfm, .json, and .abc files. + * + * @param extension The file extension to check in ".ext" format (case insensitive) + * @return True if the file is supported. + */ + static bool isSupported(const std::string& extension); + +private: + class FeederImpl; + std::unique_ptr _sfmDataFeed; +}; + +} // namespace dataio +} // namespace aliceVision From 4d85b5d484f01ee3f22e63017c1847f56cb864a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Candice=20Bent=C3=A9jac?= Date: Tue, 4 Apr 2023 15:45:06 +0200 Subject: [PATCH 04/12] KeyframeSelection: Add SfMData files as outputs The keyframe selection now supports an SfMData file as the input, and outputs two SfMData files: - one that contains all the selected keyframes - one that contains all the frames that were not selected as keyframes --- src/aliceVision/keyframe/CMakeLists.txt | 2 ++ src/aliceVision/keyframe/KeyframeSelector.cpp | 6 +++++- src/aliceVision/keyframe/KeyframeSelector.hpp | 15 ++++++++++++--- src/aliceVision/keyframe/README.md | 4 +++- src/software/utils/main_keyframeSelection.cpp | 19 +++++++++++++------ 5 files changed, 35 insertions(+), 11 deletions(-) diff --git a/src/aliceVision/keyframe/CMakeLists.txt b/src/aliceVision/keyframe/CMakeLists.txt index 8d148a8b1f..20a9e33830 100644 --- a/src/aliceVision/keyframe/CMakeLists.txt +++ b/src/aliceVision/keyframe/CMakeLists.txt @@ -15,6 +15,8 @@ alicevision_add_library(aliceVision_keyframe OpenImageIO::OpenImageIO PRIVATE_LINKS aliceVision_sensorDB + aliceVision_sfmData + aliceVision_sfmDataIO aliceVision_system Boost::filesystem ) diff --git a/src/aliceVision/keyframe/KeyframeSelector.cpp b/src/aliceVision/keyframe/KeyframeSelector.cpp index bbe06d11bc..ff659e9333 100644 --- a/src/aliceVision/keyframe/KeyframeSelector.cpp +++ b/src/aliceVision/keyframe/KeyframeSelector.cpp @@ -65,10 +65,14 @@ double findMedian(const std::vector& vec) KeyframeSelector::KeyframeSelector(const std::vector& mediaPaths, const std::string& sensorDbPath, - const std::string& outputFolder) + const std::string& outputFolder, + const std::string& outputSfmKeyframes, + const std::string& outputSfmFrames) : _mediaPaths(mediaPaths) , _sensorDbPath(sensorDbPath) , _outputFolder(outputFolder) + , _outputSfmKeyframesPath(outputSfmKeyframes) + , _outputSfmFramesPath(outputSfmFrames) { // Check that a least one media file path has been provided if (mediaPaths.empty()) { diff --git a/src/aliceVision/keyframe/KeyframeSelector.hpp b/src/aliceVision/keyframe/KeyframeSelector.hpp index 9ce9060ba4..d34fcec228 100644 --- a/src/aliceVision/keyframe/KeyframeSelector.hpp +++ b/src/aliceVision/keyframe/KeyframeSelector.hpp @@ -19,6 +19,7 @@ #include #include #include + namespace aliceVision { namespace image { @@ -36,13 +37,17 @@ class KeyframeSelector public: /** * @brief KeyframeSelector constructor - * @param[in] mediaPath video file path or image sequence directory + * @param[in] mediaPath video file path, image sequence directory or SfMData file * @param[in] sensorDbPath camera sensor width database path * @param[in] outputFolder output keyframes directory + * @param[in] outputSfmKeyframes output SfMData file containing the keyframes + * @param[in] outputSfmFrames output SfMData file containing all the non-selected frames */ KeyframeSelector(const std::vector& mediaPaths, - const std::string& sensorDbPath, - const std::string& outputFolder); + const std::string& sensorDbPath, + const std::string& outputFolder, + const std::string& outputSfmKeyframes, + const std::string& outputSfmFrames); /** * @brief KeyframeSelector copy constructor - NO COPY @@ -246,6 +251,10 @@ class KeyframeSelector std::string _sensorDbPath; /// Output folder for keyframes std::string _outputFolder; + /// Path of the output SfMData with keyframes + std::string _outputSfmKeyframesPath; + /// Path of the output SfMData with non-selected frames + std::string _outputSfmFramesPath; // Parameters common to both the regular and smart methods /// Maximum number of output frames (0 = no limit) diff --git a/src/aliceVision/keyframe/README.md b/src/aliceVision/keyframe/README.md index e9bc2cce8a..bd108b90b6 100644 --- a/src/aliceVision/keyframe/README.md +++ b/src/aliceVision/keyframe/README.md @@ -97,7 +97,9 @@ Debug options specific to the smart selection method are available: ```cpp KeyframeSelector(const std::vector& mediaPaths, const std::string& sensorDbPath, - const std::string& outputFolder); + const std::string& outputFolder, + const std::string& outputSfmKeyframes, + const std::string& outputSfmFrames); ``` - Selection with regular method ```cpp diff --git a/src/software/utils/main_keyframeSelection.cpp b/src/software/utils/main_keyframeSelection.cpp index 446f0c5ae9..c238eb599e 100644 --- a/src/software/utils/main_keyframeSelection.cpp +++ b/src/software/utils/main_keyframeSelection.cpp @@ -31,12 +31,14 @@ const std::string supportedExtensions = "exr, jpg, png"; int aliceVision_main(int argc, char** argv) { // Command-line parameters - std::vector mediaPaths; // media file path list + std::vector inputPaths; // media or SfMData file path list std::vector brands; // media brand list std::vector models; // media model list std::vector mmFocals; // media focal (mm) list std::string sensorDbPath; // camera sensor width database std::string outputFolder; // output folder for keyframes + std::string outputSfMDataKeyframes; // output SfMData file containing the selected keyframes + std::string outputSfMDataFrames; // output SfMData file containing the rejected frames // Algorithm variables bool useSmartSelection = true; // enable the smart selection instead of the regular one @@ -65,12 +67,16 @@ int aliceVision_main(int argc, char** argv) po::options_description inputParams("Required parameters"); inputParams.add_options() - ("mediaPaths", po::value>(&mediaPaths)->required()->multitoken(), + ("inputPaths", po::value>(&inputPaths)->required()->multitoken(), "Input video files or image sequence directories.") ("sensorDbPath", po::value(&sensorDbPath)->required(), "Camera sensor width database path.") ("outputFolder", po::value(&outputFolder)->required(), - "Output folder in which the selected keyframes are written."); + "Output folder in which the selected keyframes are written.") + ("outputSfMDataKeyframes", po::value(&outputSfMDataKeyframes)->required(), + "Output SfMData file containing all the selected keyframes.") + ("outputSfMDataFrames", po::value(&outputSfMDataFrames)->required(), + "Output SfMData file containing all the rejected frames."); po::options_description metadataParams("Metadata parameters"); metadataParams.add_options() @@ -157,7 +163,7 @@ int aliceVision_main(int argc, char** argv) return EXIT_FAILURE; } - const std::size_t nbCameras = mediaPaths.size(); + const std::size_t nbCameras = inputPaths.size(); // Check output folder and update to its absolute path { @@ -202,7 +208,7 @@ int aliceVision_main(int argc, char** argv) ALICEVISION_LOG_INFO("Camera rig of " << nbCameras << " cameras."); for (std::size_t i = 0; i < nbCameras; ++i) { - ALICEVISION_LOG_INFO("Camera: " << mediaPaths.at(i) << std::endl + ALICEVISION_LOG_INFO("Camera: " << inputPaths.at(i) << std::endl << "\t - brand: " << brands.at(i) << std::endl << "\t - model: " << models.at(i) << std::endl << "\t - focal (mm): " << mmFocals.at(i) << std::endl); @@ -210,7 +216,8 @@ int aliceVision_main(int argc, char** argv) } // Initialize KeyframeSelector - keyframe::KeyframeSelector selector(mediaPaths, sensorDbPath, outputFolder); + keyframe::KeyframeSelector selector(inputPaths, sensorDbPath, outputFolder, outputSfMDataKeyframes, + outputSfMDataFrames); // Set frame-related algorithm parameters selector.setMinFrameStep(minFrameStep); From dfccee3db4c3ee4bfcbc2515c796ba699f4d5ffd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Candice=20Bent=C3=A9jac?= Date: Tue, 4 Apr 2023 16:05:59 +0200 Subject: [PATCH 05/12] [keyframe] Fill the "selectedFrames" vector for the regular method The "selectedFrames" vector is filled with 0s and 1s depending on whether the frame with the ID corresponding to the position within the vector has been selected as a keyframe or not. It was filled with the smart method, but not with the regular one. As it will be needed later on and is not costly, it should also be filled for the regular selection method. --- src/aliceVision/keyframe/KeyframeSelector.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/aliceVision/keyframe/KeyframeSelector.cpp b/src/aliceVision/keyframe/KeyframeSelector.cpp index ff659e9333..df16abd4aa 100644 --- a/src/aliceVision/keyframe/KeyframeSelector.cpp +++ b/src/aliceVision/keyframe/KeyframeSelector.cpp @@ -86,6 +86,7 @@ KeyframeSelector::KeyframeSelector(const std::vector& mediaPaths, void KeyframeSelector::processRegular() { _selectedKeyframes.clear(); + _selectedFrames.clear(); std::size_t nbFrames = std::numeric_limits::max(); std::vector> feeds; @@ -111,6 +112,10 @@ void KeyframeSelector::processRegular() ALICEVISION_THROW(std::invalid_argument, "One or multiple medias can't be found or empty!"); } + // All frames are unselected so far + _selectedFrames.resize(nbFrames); + std::fill(_selectedFrames.begin(), _selectedFrames.end(), '0'); + unsigned int step = _minFrameStep; if (_maxFrameStep > 0) { // By default, if _maxFrameStep is set, set the step to be right between _minFrameStep and _maxFrameStep @@ -135,6 +140,7 @@ void KeyframeSelector::processRegular() for (unsigned int id = 0; id < nbFrames; id += step) { ALICEVISION_LOG_INFO("Selecting frame with ID " << id); _selectedKeyframes.push_back(id); + _selectedFrames.at(id) = '1'; if (_maxOutFrames > 0 && _selectedKeyframes.size() >= _maxOutFrames) break; } From 4de58a30dfcd0aebba695ec2eb5a8051457811c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Candice=20Bent=C3=A9jac?= Date: Wed, 5 Apr 2023 12:51:51 +0200 Subject: [PATCH 06/12] [keyframe] Write SfMData files as outputs if the input is an SfMData file This commit adds a new method that writes two output SfMData files (one containing the keyframes, the other containing all the frames that were not selected) when the input file is an SfMData file. Once all the selected keyframes have been written on disk, the input SfMData file is loaded (outside of the feed) and its views and intrinsics are extracted to be written in their corresponding output SfMData file. No information written is these files is built from scratch. Rigs are currently not supported and therefore ignored when writing the output files. If the input is a video or a sequence of images, no SfMData file is written for now. --- src/aliceVision/keyframe/CMakeLists.txt | 4 +- src/aliceVision/keyframe/KeyframeSelector.cpp | 62 ++++++++++++++++++- src/aliceVision/keyframe/KeyframeSelector.hpp | 20 +++++- 3 files changed, 80 insertions(+), 6 deletions(-) diff --git a/src/aliceVision/keyframe/CMakeLists.txt b/src/aliceVision/keyframe/CMakeLists.txt index 20a9e33830..3915919feb 100644 --- a/src/aliceVision/keyframe/CMakeLists.txt +++ b/src/aliceVision/keyframe/CMakeLists.txt @@ -12,11 +12,11 @@ alicevision_add_library(aliceVision_keyframe SOURCES ${keyframe_files_headers} ${keyframe_files_sources} PUBLIC_LINKS aliceVision_dataio + aliceVision_sfmData + aliceVision_sfmDataIO OpenImageIO::OpenImageIO PRIVATE_LINKS aliceVision_sensorDB - aliceVision_sfmData - aliceVision_sfmDataIO aliceVision_system Boost::filesystem ) diff --git a/src/aliceVision/keyframe/KeyframeSelector.cpp b/src/aliceVision/keyframe/KeyframeSelector.cpp index df16abd4aa..b3905d6c86 100644 --- a/src/aliceVision/keyframe/KeyframeSelector.cpp +++ b/src/aliceVision/keyframe/KeyframeSelector.cpp @@ -5,7 +5,6 @@ // You can obtain one at https://mozilla.org/MPL/2.0/. #include "KeyframeSelector.hpp" -#include #include #include @@ -155,6 +154,8 @@ void KeyframeSelector::processSmart(const float pxDisplacement, const std::size_ { _selectedKeyframes.clear(); _selectedFrames.clear(); + _outputSfmKeyframes.clear(); + _outputSfmFrames.clear(); // Step 0: compute all the scores computeScores(rescaledWidthSharpness, rescaledWidthFlow, sharpnessWindowSize, flowCellSize, skipSharpnessComputation); @@ -434,7 +435,7 @@ bool KeyframeSelector::writeSelection(const std::vector& brands, const std::vector& mmFocals, const bool renameKeyframes, const std::string& outputExtension, - const image::EStorageDataType storageDataType) const + const image::EStorageDataType storageDataType) { image::Image image; camera::PinholeRadialK3 queryIntrinsics; @@ -524,6 +525,9 @@ bool KeyframeSelector::writeSelection(const std::vector& brands, image::writeImage(filepath, image, options, metadata); ALICEVISION_LOG_DEBUG("Wrote selected keyframe " << pos); } + + if (!writeSfMData(path, feed.isSfMData())) + ALICEVISION_LOG_ERROR("Failed to write the output SfMData files."); } return true; @@ -804,5 +808,59 @@ double KeyframeSelector::estimateFlow(const cv::Ptr& ptrFl return findMedian(motionByCell); } +bool KeyframeSelector::writeSfMData(const std::string& mediaPath, const bool isSfmInput) +{ + if (!isSfmInput) { + ALICEVISION_LOG_INFO("The input was not an SfMData file: no output SfMData file will be written."); + return true; + } + + sfmData::SfMData inputSfm; + std::vector> views; + if (!sfmDataIO::Load(inputSfm, mediaPath, sfmDataIO::ESfMData::ALL)) { + ALICEVISION_THROW_ERROR("Could not open input SfMData file " << mediaPath << "."); + } + + auto& keyframesViews = _outputSfmKeyframes.getViews(); + auto& framesViews = _outputSfmFrames.getViews(); + + auto& keyframesIntrinsics = _outputSfmKeyframes.getIntrinsics(); + auto& framesIntrinsics = _outputSfmFrames.getIntrinsics(); + + // Sort input SfMData file according to the frame IDs for the views to be correctly ordered + for (auto it = inputSfm.getViews().begin(); it != inputSfm.getViews().end(); ++it) { + views.push_back(it->second); + } + std::sort(views.begin(), views.end(), + [](std::shared_ptr v1, std::shared_ptr v2) + { return (v1->getFrameId() < v2->getFrameId()); }); + + IndexT viewId; + IndexT intrinsicId; + for (int i = 0; i < views.size(); ++i) { + viewId = views[i]->getViewId(); + intrinsicId = views[i]->getIntrinsicId(); + if (_selectedFrames[i] == '1') { + keyframesViews[viewId] = views[i]; + keyframesIntrinsics[intrinsicId] = inputSfm.getIntrinsics()[intrinsicId]; + } else { + framesViews[viewId] = views[i]; + framesIntrinsics[intrinsicId] = inputSfm.getIntrinsics()[intrinsicId]; + } + } + + if (!sfmDataIO::Save(_outputSfmKeyframes, _outputSfmKeyframesPath, sfmDataIO::ESfMData::ALL)) { + ALICEVISION_LOG_ERROR("The output SfMData file '" << _outputSfmKeyframesPath << "' could not be written."); + return false; + } + + if (!sfmDataIO::Save(_outputSfmFrames, _outputSfmFramesPath, sfmDataIO::ESfMData::ALL)) { + ALICEVISION_LOG_ERROR("The output SfMData file '" << _outputSfmFramesPath << "' could not be written."); + return false; + } + + return true; +} + } // namespace keyframe } // namespace aliceVision diff --git a/src/aliceVision/keyframe/KeyframeSelector.hpp b/src/aliceVision/keyframe/KeyframeSelector.hpp index d34fcec228..6cf6b4fb9c 100644 --- a/src/aliceVision/keyframe/KeyframeSelector.hpp +++ b/src/aliceVision/keyframe/KeyframeSelector.hpp @@ -8,6 +8,8 @@ #include #include +#include +#include #include #include @@ -122,8 +124,9 @@ class KeyframeSelector * @return true if all the selected keyframes were successfully written, false otherwise */ bool writeSelection(const std::vector& brands, const std::vector& models, - const std::vector& mmFocals, const bool renameKeyframes, const std::string& outputExtension, - const image::EStorageDataType storageDataType = image::EStorageDataType::Undefined) const; + const std::vector& mmFocals, const bool renameKeyframes, + const std::string& outputExtension, + const image::EStorageDataType storageDataType = image::EStorageDataType::Undefined); /** * @brief Export the computed sharpness and optical flow scores to a CSV file @@ -242,6 +245,14 @@ class KeyframeSelector double estimateFlow(const cv::Ptr& ptrFlow, const cv::Mat& grayscaleImage, const cv::Mat& previousGrayscaleImage, const std::size_t cellSize); + /** + * @brief Write the output SfMData files with the selected and non-selected keyframes information. + * @param[in] mediaPath input video file path, image sequence directory or SfMData file + * @param[in] isSfmInput true if the input file is an SfMData file, false otherwise + * @return true if the output SfMData files were written as expected, false otherwise + */ + bool writeSfMData(const std::string& mediaPath, const bool isSfmInput); + /// Selected keyframes IDs std::vector _selectedKeyframes; @@ -277,6 +288,11 @@ class KeyframeSelector /// Vector containing 1s for frames that have been selected, 0 for those which have not std::vector _selectedFrames; + /// Output SfMData containing the keyframes + sfmData::SfMData _outputSfmKeyframes; + /// Output SfMData containing the non-selected frames + sfmData::SfMData _outputSfmFrames; + /// Size of the frame (afer rescale, if any is applied) unsigned int _frameWidth = 0; unsigned int _frameHeight = 0; From 0be352bcfdeba98a9660d8fea0f11f12e78388d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Candice=20Bent=C3=A9jac?= Date: Thu, 6 Apr 2023 11:20:33 +0200 Subject: [PATCH 07/12] [dataio] SfMDataFeed: Order with the views' intrinsics' serial number Prior to this commit, views were ordered based solely on their frame ID. If the inpput SfMData only contains one sequence, this works perfectly fine. However, in case of a rig or several sequences dropped one after the other, there might be some identical frame IDs which would affect the entire sorting order: the sequences would be mixed together instead of being sorted back-to-back. For example, if there are two sequences with identical frame IDs, the sorting order will be "seq1#id0, seq2#id0, seq1#id1, seq2#id1, seq1#id2, seq2#id2" instead of "seq1#id0, seq1#id1, seq1#id2, seq2#id0, seq2#id1, seq2#id2". This commit sorts views with different intrinsics' serial numbers separately based on their frame IDs, and then concatenates all the sorted views together. --- src/aliceVision/dataio/SfMDataFeed.cpp | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/aliceVision/dataio/SfMDataFeed.cpp b/src/aliceVision/dataio/SfMDataFeed.cpp index 0d1909f6b1..81534247c0 100644 --- a/src/aliceVision/dataio/SfMDataFeed.cpp +++ b/src/aliceVision/dataio/SfMDataFeed.cpp @@ -76,13 +76,25 @@ SfMDataFeed::FeederImpl::FeederImpl(const std::string& imagePath, const std::str _isInit = sfmDataIO::Load(_sfmData, imagePath, sfmDataIO::ESfMData(sfmDataIO::ESfMData::VIEWS | sfmDataIO::ESfMData::INTRINSICS)); - // Order the views according to the frame ID + // Order the views according to the frame ID and the intrinsics serial number + std::map> viewSequences; + + // Separate the views depending on their intrinsics' serial number + auto& intrinsics = _sfmData.getIntrinsics(); for(auto it = _sfmData.getViews().begin(); it != _sfmData.getViews().end(); ++it) { - _views.push_back(it->second.get()); + auto view = it->second.get(); + auto serialNumber = intrinsics[view->getIntrinsicId()]->serialNumber(); + viewSequences[serialNumber].push_back(view); + } + + // Sort the views with the same intrinsics together based on their frame ID and add them to the final global vector + for(auto& view : viewSequences) + { + std::sort(view.second.begin(), view.second.end(), + [](const sfmData::View* v1, const sfmData::View* v2) { return v1->getFrameId() < v2->getFrameId(); }); + _views.insert(_views.end(), view.second.begin(), view.second.end()); } - std::sort(_views.begin(), _views.end(), - [](const sfmData::View* v1, const sfmData::View* v2) { return (v1->getFrameId() < v2->getFrameId()); }); } std::size_t SfMDataFeed::FeederImpl::nbFrames() const From ba34c77630fb98b9b767305600f440430b7d18e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Candice=20Bent=C3=A9jac?= Date: Thu, 6 Apr 2023 15:11:47 +0200 Subject: [PATCH 08/12] [keyframe] Parse the sensor database in the constructor if the path exists The database, if available, will later be used to try to determine the intrinsics of input videos or image sequences. --- src/aliceVision/keyframe/KeyframeSelector.cpp | 5 +++++ src/aliceVision/keyframe/KeyframeSelector.hpp | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/src/aliceVision/keyframe/KeyframeSelector.cpp b/src/aliceVision/keyframe/KeyframeSelector.cpp index b3905d6c86..fc74a0a88d 100644 --- a/src/aliceVision/keyframe/KeyframeSelector.cpp +++ b/src/aliceVision/keyframe/KeyframeSelector.cpp @@ -80,6 +80,11 @@ KeyframeSelector::KeyframeSelector(const std::vector& mediaPaths, scoresMap["Sharpness"] = &_sharpnessScores; scoresMap["OpticalFlow"] = &_flowScores; + + // Parse the sensor database if the path is not empty + if (!_sensorDbPath.empty() && sensorDB::parseDatabase(_sensorDbPath, _sensorDatabase)) { + _parsedSensorDb = true; + } } void KeyframeSelector::processRegular() diff --git a/src/aliceVision/keyframe/KeyframeSelector.hpp b/src/aliceVision/keyframe/KeyframeSelector.hpp index 6cf6b4fb9c..947842a3a9 100644 --- a/src/aliceVision/keyframe/KeyframeSelector.hpp +++ b/src/aliceVision/keyframe/KeyframeSelector.hpp @@ -8,6 +8,7 @@ #include #include +#include #include #include @@ -297,6 +298,10 @@ class KeyframeSelector unsigned int _frameWidth = 0; unsigned int _frameHeight = 0; + /// Parsed sensor database + std::vector _sensorDatabase; + bool _parsedSensorDb = false; + /// Map score vectors with names for export std::map*> scoresMap; }; From 00bd0ee58ea2ca67aaf37fc287a1f6acb7f74802 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Candice=20Bent=C3=A9jac?= Date: Thu, 6 Apr 2023 17:14:15 +0200 Subject: [PATCH 09/12] [keyframe] Order views depending on their ID and intrinsics' serial number The ordering of the views when writing the output SfMData files should be identical to the one used in the SfMDataFeed. Otherwise, the views written in the output files will not reflect the keyframes that were saved on disk. --- src/aliceVision/keyframe/KeyframeSelector.cpp | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/aliceVision/keyframe/KeyframeSelector.cpp b/src/aliceVision/keyframe/KeyframeSelector.cpp index fc74a0a88d..83faf3327a 100644 --- a/src/aliceVision/keyframe/KeyframeSelector.cpp +++ b/src/aliceVision/keyframe/KeyframeSelector.cpp @@ -832,13 +832,23 @@ bool KeyframeSelector::writeSfMData(const std::string& mediaPath, const bool isS auto& keyframesIntrinsics = _outputSfmKeyframes.getIntrinsics(); auto& framesIntrinsics = _outputSfmFrames.getIntrinsics(); - // Sort input SfMData file according to the frame IDs for the views to be correctly ordered - for (auto it = inputSfm.getViews().begin(); it != inputSfm.getViews().end(); ++it) { - views.push_back(it->second); + // Order the views according to the frame ID and the intrinsics serial number + std::map>> viewSequences; + auto& intrinsics = inputSfm.getIntrinsics(); + for(auto it = inputSfm.getViews().begin(); it != inputSfm.getViews().end(); ++it) { + auto view = it->second; + auto serialNumber = intrinsics[view->getIntrinsicId()]->serialNumber(); + viewSequences[serialNumber].push_back(view); + } + + // Sort the views with the same intrinsics together based on their frame ID and add them to the final global vector + for(auto& view : viewSequences) { + std::sort(view.second.begin(), view.second.end(), + [](std::shared_ptr v1, std::shared_ptr v2) { + return v1->getFrameId() < v2->getFrameId(); + }); + views.insert(views.end(), view.second.begin(), view.second.end()); } - std::sort(views.begin(), views.end(), - [](std::shared_ptr v1, std::shared_ptr v2) - { return (v1->getFrameId() < v2->getFrameId()); }); IndexT viewId; IndexT intrinsicId; From aed8e974e9aee40ce5b227db60031c560fd91392 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Candice=20Bent=C3=A9jac?= Date: Fri, 7 Apr 2023 17:20:58 +0200 Subject: [PATCH 10/12] [keyframe] Output SfMData files independently from the input's type SfMData files are written as outputs for all types of inputs, with the following specifications: - for an input SfMData file, both the keyframes and frames SfMData files will be written; rigs are not supported - for an input sequence (or several) of images, both the keyframes and frames SfMData files will be written; rigs are supported and will be written in the output SfMData files - for an input video, only the keyframes SfMData file will be written, no frames SfMData file will be output; rigs are supported and will be written in the keyframes SfMData files --- src/aliceVision/keyframe/KeyframeSelector.cpp | 237 ++++++++++++++++-- src/aliceVision/keyframe/KeyframeSelector.hpp | 60 ++++- 2 files changed, 276 insertions(+), 21 deletions(-) diff --git a/src/aliceVision/keyframe/KeyframeSelector.cpp b/src/aliceVision/keyframe/KeyframeSelector.cpp index 83faf3327a..4cc901de8a 100644 --- a/src/aliceVision/keyframe/KeyframeSelector.cpp +++ b/src/aliceVision/keyframe/KeyframeSelector.cpp @@ -5,6 +5,7 @@ // You can obtain one at https://mozilla.org/MPL/2.0/. #include "KeyframeSelector.hpp" +#include #include #include @@ -91,6 +92,9 @@ void KeyframeSelector::processRegular() { _selectedKeyframes.clear(); _selectedFrames.clear(); + _keyframesPaths.clear(); + _outputSfmKeyframes.clear(); + _outputSfmFrames.clear(); std::size_t nbFrames = std::numeric_limits::max(); std::vector> feeds; @@ -159,6 +163,7 @@ void KeyframeSelector::processSmart(const float pxDisplacement, const std::size_ { _selectedKeyframes.clear(); _selectedFrames.clear(); + _keyframesPaths.clear(); _outputSfmKeyframes.clear(); _outputSfmFrames.clear(); @@ -529,9 +534,11 @@ bool KeyframeSelector::writeSelection(const std::vector& brands, image::writeImage(filepath, image, options, metadata); ALICEVISION_LOG_DEBUG("Wrote selected keyframe " << pos); + + _keyframesPaths[id].push_back(filepath); } - if (!writeSfMData(path, feed.isSfMData())) + if (!writeSfMData(path, feed, brands, models, mmFocals)) ALICEVISION_LOG_ERROR("Failed to write the output SfMData files."); } @@ -718,7 +725,7 @@ cv::Mat KeyframeSelector::readImage(dataio::FeedProvider &feed, std::size_t widt cv::Mat cvRescaled; if (cvGrayscale.cols > width && width > 0) { cv::resize(cvGrayscale, cvRescaled, - cv::Size(width,double(cvGrayscale.rows) * double(width) / double(cvGrayscale.cols))); + cv::Size(width, double(cvGrayscale.rows) * double(width) / double(cvGrayscale.cols))); } return cvRescaled; @@ -813,25 +820,59 @@ double KeyframeSelector::estimateFlow(const cv::Ptr& ptrFl return findMedian(motionByCell); } -bool KeyframeSelector::writeSfMData(const std::string& mediaPath, const bool isSfmInput) +bool KeyframeSelector::writeSfMData(const std::string& mediaPath, dataio::FeedProvider &feed, + const std::vector& brands, const std::vector& models, + const std::vector& mmFocals) { - if (!isSfmInput) { - ALICEVISION_LOG_INFO("The input was not an SfMData file: no output SfMData file will be written."); - return true; + bool filledOutputs = false; + + if (!feed.isSfMData()) { + filledOutputs = writeSfMDataFromSequences(mediaPath, feed, brands, models, mmFocals); + } else { + filledOutputs = writeSfMDataFromSfMData(mediaPath); } - sfmData::SfMData inputSfm; - std::vector> views; - if (!sfmDataIO::Load(inputSfm, mediaPath, sfmDataIO::ESfMData::ALL)) { - ALICEVISION_THROW_ERROR("Could not open input SfMData file " << mediaPath << "."); + if (!filledOutputs) { + ALICEVISION_LOG_ERROR("Error while filling the output SfMData files."); + return false; + } + + if (!sfmDataIO::Save(_outputSfmKeyframes, _outputSfmKeyframesPath, sfmDataIO::ESfMData::ALL)) { + ALICEVISION_LOG_ERROR("The output SfMData file '" << _outputSfmKeyframesPath << "' could not be written."); + return false; + } + + if (!feed.isVideo()) { + if (!sfmDataIO::Save(_outputSfmFrames, _outputSfmFramesPath, sfmDataIO::ESfMData::ALL)) { + ALICEVISION_LOG_ERROR("The output SfMData file '" << _outputSfmFramesPath << "' could not be written."); + return false; + } + } else { + ALICEVISION_LOG_DEBUG("The input feed is a video. The SfMData file containing the unselected frames will not" + " be written."); } + return true; +} + +bool KeyframeSelector::writeSfMDataFromSfMData(const std::string& mediaPath) +{ auto& keyframesViews = _outputSfmKeyframes.getViews(); auto& framesViews = _outputSfmFrames.getViews(); auto& keyframesIntrinsics = _outputSfmKeyframes.getIntrinsics(); auto& framesIntrinsics = _outputSfmFrames.getIntrinsics(); + IndexT viewId; + IndexT intrinsicId; + + sfmData::SfMData inputSfm; + std::vector> views; + if (!sfmDataIO::Load(inputSfm, mediaPath, sfmDataIO::ESfMData::ALL)) { + ALICEVISION_LOG_ERROR("Could not open input SfMData file " << mediaPath << "."); + return false; + } + // Order the views according to the frame ID and the intrinsics serial number std::map>> viewSequences; auto& intrinsics = inputSfm.getIntrinsics(); @@ -850,8 +891,6 @@ bool KeyframeSelector::writeSfMData(const std::string& mediaPath, const bool isS views.insert(views.end(), view.second.begin(), view.second.end()); } - IndexT viewId; - IndexT intrinsicId; for (int i = 0; i < views.size(); ++i) { viewId = views[i]->getViewId(); intrinsicId = views[i]->getIntrinsicId(); @@ -864,18 +903,180 @@ bool KeyframeSelector::writeSfMData(const std::string& mediaPath, const bool isS } } - if (!sfmDataIO::Save(_outputSfmKeyframes, _outputSfmKeyframesPath, sfmDataIO::ESfMData::ALL)) { - ALICEVISION_LOG_ERROR("The output SfMData file '" << _outputSfmKeyframesPath << "' could not be written."); - return false; + return true; +} + +bool KeyframeSelector::writeSfMDataFromSequences(const std::string& mediaPath, dataio::FeedProvider &feed, + const std::vector& brands, + const std::vector& models, + const std::vector& mmFocals) +{ + static std::size_t mediaIndex = 0; + static IndexT intrinsicId = 0; + + auto& keyframesViews = _outputSfmKeyframes.getViews(); + auto& framesViews = _outputSfmFrames.getViews(); + + auto& keyframesIntrinsics = _outputSfmKeyframes.getIntrinsics(); + auto& framesIntrinsics = _outputSfmFrames.getIntrinsics(); + + auto& keyframesRigs = _outputSfmKeyframes.getRigs(); + auto& framesRigs = _outputSfmFrames.getRigs(); + + const IndexT rigId = 0; // 0 by convention + IndexT viewId = 0; // Will be used to distinguish frames coming from videos + IndexT previousFrameId = UndefinedIndexT; + + feed.goToFrame(0); + + // Feed provider variables + image::Image image; + camera::PinholeRadialK3 queryIntrinsics; + bool hasIntrinsics = false; + std::string currentImgName; + + std::size_t selectedKeyframesCounter = 0; // Used to find the path of the written images when the feed is video + std::shared_ptr previousIntrinsic = nullptr; + + // Create rig structure if it is needed and does not already exist + // A rig structure is needed when there is more than one input path + if (_mediaPaths.size() > 1 && keyframesRigs.size() == 0 && framesRigs.size() == 0) { + sfmData::Rig rig(_mediaPaths.size()); + keyframesRigs[rigId] = rig; + framesRigs[rigId] = rig; } - if (!sfmDataIO::Save(_outputSfmFrames, _outputSfmFramesPath, sfmDataIO::ESfMData::ALL)) { - ALICEVISION_LOG_ERROR("The output SfMData file '" << _outputSfmFramesPath << "' could not be written."); - return false; + for (std::size_t i = 0; i < feed.nbFrames(); ++i) { + // Need to read the image to get its size and path + if (!feed.readImage(image, queryIntrinsics, currentImgName, hasIntrinsics)) { + ALICEVISION_LOG_ERROR("Error reading image"); + return false; + } + + // Create the view + auto view = createView(currentImgName, intrinsicId, previousFrameId, image.Width(), image.Height()); + previousFrameId = view->getFrameId(); + + // If there is a rig, the view's rig and sub-pose IDs need to be set once it has been completed + if (keyframesRigs.size() > 0 && framesRigs.size() > 0) { + view->setRigAndSubPoseId(rigId, mediaIndex); + } + + // Prepare settings for the intrinsics + double focalLength = view->getMetadataFocalLength(); + if (focalLength == -1 && mmFocals[mediaIndex] != 0) + focalLength = mmFocals[mediaIndex]; + std::string make = view->getMetadataMake(); + if (make.empty() && !brands[mediaIndex].empty()) + make = brands[mediaIndex]; + std::string model = view->getMetadataModel(); + if (model.empty() && !models[mediaIndex].empty()) + model = models[mediaIndex]; + + const double imageRatio = static_cast(image.Width()) / static_cast(image.Height()); + double sensorWidth = -1.0; + sensorDB::Datasheet datasheet; + + if (_parsedSensorDb && !make.empty() && !model.empty() && + sensorDB::getInfo(make, model, _sensorDatabase, datasheet)) { + sensorWidth = datasheet._sensorWidth; + } + + // Create the intrinsic for the view + auto intrinsic = createIntrinsic(*view, focalLength == -1.0 ? 0 : focalLength, sensorWidth, mediaIndex, + imageRatio); + + // Update intrinsics ID if this is a new one + if (previousIntrinsic != nullptr && *previousIntrinsic != *intrinsic) + view->setIntrinsicId(++intrinsicId); + + // Fill the SfMData files + if (_selectedFrames[i] == '1') { + if (feed.isVideo()) { + // If the feed is a video, all views will have the same view ID by default, this needs to be fixed + view->setViewId(view->getViewId() + viewId++); + view->setPoseId(view->getViewId()); + // The path for the view will be the video's; it needs to be replaced with the corresponding keyframe's + view->setImagePath(_keyframesPaths[mediaIndex][selectedKeyframesCounter++]); + } + keyframesViews[view->getViewId()] = view; + keyframesIntrinsics[intrinsicId] = intrinsic; + } else { + // No rejected frames if the feed is a video one, as they are not written on disk + if (!feed.isVideo()) { + framesViews[view->getViewId()] = view; + framesIntrinsics[intrinsicId] = intrinsic; + } + } + + previousIntrinsic = intrinsic; + feed.goToNextFrame(); } + ++mediaIndex; + ++intrinsicId; + return true; } +std::shared_ptr KeyframeSelector::createView(const std::string& imagePath, IndexT intrinsicId, + IndexT previousFrameId, std::size_t imageWidth, std::size_t imageHeight) +{ + // Create the View object: most attributes are set with default values and will be updated later on + auto view = std::make_shared( + imagePath, // filepath + UndefinedIndexT, // view ID + intrinsicId, // intrinsics ID + UndefinedIndexT, // pose ID + imageWidth, // image width + imageHeight, // image height + UndefinedIndexT, // rig ID + UndefinedIndexT, // sub-pose ID + std::map() // metadata + ); + + // Complete the View attributes + sfmDataIO::EViewIdMethod viewIdMethod = sfmDataIO::EViewIdMethod::METADATA; + std::string viewIdRegex = ".*?(\\d+)"; + sfmDataIO::updateIncompleteView(*(view.get()), viewIdMethod, viewIdRegex); + + // Set the frame ID + IndexT frameId; + std::string prefix; + std::string suffix; + // Use the filename to determine the frame ID (if available) + if (sfmDataIO::extractNumberFromFileStem(fs::path(view->getImagePath()).stem().string(), frameId, prefix, suffix)) { + view->setFrameId(frameId); + } + // Otherwise, set it fully manually + if (view->getFrameId() == 1 && previousFrameId != UndefinedIndexT) { + view->setFrameId(previousFrameId + 1); + } + + return view; +} + +std::shared_ptr KeyframeSelector::createIntrinsic(const sfmData::View& view, const double focalLength, + const double sensorWidth, const double imageRatio, const std::size_t mediaIndex) +{ + auto intrinsic = sfmDataIO::getViewIntrinsic(view, focalLength, sensorWidth); + if (imageRatio > 1.0 && sensorWidth > -1.0) { + intrinsic->setSensorWidth(sensorWidth); + intrinsic->setSensorHeight(sensorWidth / imageRatio); + } else if (imageRatio <= 1.0 && sensorWidth > -1.0) { + intrinsic->setSensorWidth(sensorWidth); + intrinsic->setSensorHeight(sensorWidth * imageRatio); + } else { + // Set default values for the sensor width and sensor height + intrinsic->setSensorWidth(36.0); + intrinsic->setSensorHeight(24.0); + } + + if (intrinsic->serialNumber().empty()) // Likely to happen with video feeds + intrinsic->setSerialNumber(fs::path(view.getImagePath()).parent_path().string() + std::to_string(mediaIndex)); + + return intrinsic; +} + } // namespace keyframe } // namespace aliceVision diff --git a/src/aliceVision/keyframe/KeyframeSelector.hpp b/src/aliceVision/keyframe/KeyframeSelector.hpp index 947842a3a9..5ffdc59722 100644 --- a/src/aliceVision/keyframe/KeyframeSelector.hpp +++ b/src/aliceVision/keyframe/KeyframeSelector.hpp @@ -247,12 +247,63 @@ class KeyframeSelector const cv::Mat& previousGrayscaleImage, const std::size_t cellSize); /** - * @brief Write the output SfMData files with the selected and non-selected keyframes information. + * @brief Write the output SfMData files with the selected and non-selected keyframes information * @param[in] mediaPath input video file path, image sequence directory or SfMData file - * @param[in] isSfmInput true if the input file is an SfMData file, false otherwise + * @param[in] feed the feed provider + * @param[in] brands brand name for each camera + * @param[in] models model name for each camera + * @param[in] mmFocals focal in millimeters for each camera * @return true if the output SfMData files were written as expected, false otherwise */ - bool writeSfMData(const std::string& mediaPath, const bool isSfmInput); + bool writeSfMData(const std::string& mediaPath, dataio::FeedProvider &feed, const std::vector& brands, + const std::vector& models, const std::vector& mmFocals); + + /** + * @brief Copy the relevant information from the input SfMData file and fill the output SfMData files that will + * contain the selected and non-selected keyframes information (rigs are not supported) + * @param[in] mediaPath input SfMData file + * @return true if the output SfMData files have successfully been filled, false otherwise + */ + bool writeSfMDataFromSfMData(const std::string& mediaPath); + + /** + * @brief Fill the output SfMData files with the information about the selected keyframes and the non-selected + * frames (rigs are supported, but non-selected frames will not be written in the corresponding output + * SfMData file if the input is a video) + * @param mediaPath input video file path or image sequence directory + * @param feed the feed provider + * @param brands brand name for each camera + * @param models model name for each camera + * @param mmFocals focal in millimiters for each camera + * @return true if the output SfMData files have successfully been filled, false otherwise + */ + bool writeSfMDataFromSequences(const std::string& mediaPath, dataio::FeedProvider &feed, + const std::vector& brands, const std::vector& models, + const std::vector& mmFocals); + + /** + * @brief Create a View object for the SfMData files + * @param imagePath the path of the image corresponding to the view to create + * @param intrinsicId the intrinsic ID for the view to create + * @param previousFrameId the frame ID of the last created view + * @param imageWidth the width of the image corresponding to the view to create + * @param imageHeight the height of the image corresponding to the view to create + * @return a shared pointer to the created View + */ + std::shared_ptr createView(const std::string& imagePath, IndexT intrinsicId, IndexT previousFrameId, + std::size_t imageWidth, std::size_t imageHeight); + + /** + * @brief Create an Intrinsic object associated to a specific View + * @param view the View that the intrinsic will be associated to + * @param focalLength the focal length in millimiter (if 0, default value will be used) + * @param sensorWidth the sensor width + * @param imageRatio the width over height ratio for the View's image + * @param mediaIndex the media index + * @return a shared pointer to the created Intrinsic + */ + std::shared_ptr createIntrinsic(const sfmData::View& view, const double focalLength, const double sensorWidth, + const double imageRatio, std::size_t mediaIndex); /// Selected keyframes IDs std::vector _selectedKeyframes; @@ -302,6 +353,9 @@ class KeyframeSelector std::vector _sensorDatabase; bool _parsedSensorDb = false; + /// Map media path index with names of the output images (used when the input medias are videos) + std::map> _keyframesPaths; + /// Map score vectors with names for export std::map*> scoresMap; }; From d44dea164b3a71f311f529fb119f5355b47d6c82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Candice=20Bent=C3=A9jac?= Date: Fri, 7 Apr 2023 17:15:42 +0200 Subject: [PATCH 11/12] [keyframe] Update the README with information regarding SfMData files --- src/aliceVision/keyframe/README.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/aliceVision/keyframe/README.md b/src/aliceVision/keyframe/README.md index bd108b90b6..ededaa69af 100644 --- a/src/aliceVision/keyframe/README.md +++ b/src/aliceVision/keyframe/README.md @@ -2,9 +2,9 @@ This module provides several methods to perform a keyframe selection. -The goal of the keyframe selection is to extract, from an input video or an input sequence of images, keyframes. +The goal of the keyframe selection is to extract, from an input video, an input sequence of images or an SfMData file, keyframes. Two methods are currently supported: -- a **regular** selection method, which selects keyframes regularly across the input video / sequence according to a set of parameters; +- a **regular** selection method, which selects keyframes regularly across the input video / sequence / SfMData according to a set of parameters; - a **smart** selection method, which analyses the sharpness and motion of all the frames to select those which are deemed the most relevant (a frame is considered relevant if it contains significant motion in comparison to the last selected keyframe while being as sharp as possible). The selected keyframes can be written as JPG, PNG or EXR images, and the storage data type can be specified when the EXR file extension is selected. @@ -13,8 +13,15 @@ The keyframe selection module supports the following inputs: - a path to a video file (e.g. "/path/to/video.mp4") - a path to a folder containing images (e.g. "/path/to/folder/") - a path to a folder containing images with a regular expression (e.g. "/path/to/folder/*.exr") +- a path to an SfMData file (e.g. "/path/to/sfmData.sfm") -Camera rigs are also supported. +Camera rigs are also supported for all the inputs except the SfMData file. + +In addition to writing the selected keyframes on disk, two SfMData files are written: +- one that contains all the selected keyframes +- one that contains all the frames that were not selected as keyframes + +_N.B: If the input is a video file, the SfMData file which contains the rejected frames will not be written at all, since none of these frames is available on disk. As the selected keyframes will be written at the end of the selection, the SfMData file containing the keyframes **will** be written._ ## Regular selection method From 73fb09f79c487fde4e44ed0e2e0f470e0b6bbd30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Candice=20Bent=C3=A9jac?= Date: Fri, 7 Apr 2023 17:19:21 +0200 Subject: [PATCH 12/12] [utils] KeyframeSelection: Update software version Following the addition of the SfMData files as outputs in the command line, the version needs to be updated. --- src/software/utils/main_keyframeSelection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/software/utils/main_keyframeSelection.cpp b/src/software/utils/main_keyframeSelection.cpp index c238eb599e..8a632da739 100644 --- a/src/software/utils/main_keyframeSelection.cpp +++ b/src/software/utils/main_keyframeSelection.cpp @@ -18,7 +18,7 @@ // These constants define the current software version. // They must be updated when the command line is changed. -#define ALICEVISION_SOFTWARE_VERSION_MAJOR 3 +#define ALICEVISION_SOFTWARE_VERSION_MAJOR 4 #define ALICEVISION_SOFTWARE_VERSION_MINOR 0 using namespace aliceVision;