new multipage image decoder api - ImageCollection

pull/22128/head
Berke 2 years ago committed by ocpalo
parent 139c443770
commit 062cee2933
  1. 45
      modules/imgcodecs/include/opencv2/imgcodecs.hpp
  2. 298
      modules/imgcodecs/src/loadsave.cpp
  3. 177
      modules/imgcodecs/test/test_read_write.cpp

@ -332,6 +332,51 @@ CV_EXPORTS_W bool haveImageReader( const String& filename );
*/
CV_EXPORTS_W bool haveImageWriter( const String& filename );
/** @brief To read Multi Page images on demand
The ImageCollection class provides iterator API to read multi page images on demand. Create iterator
to the collection of the images and iterate over the collection. Decode the necessary page with operator*.
The performance of page decoding is O(1) if collection is increment sequentially. If the user wants to access random page,
then the time Complexity is O(n) because the collection has to be reinitialized every time in order to go to the correct page.
However, the intermediate pages are not decoded during the process, so typically it's quite fast.
This is required because multipage codecs does not support going backwards.
After decoding the one page, it is stored inside the collection cache. Hence, trying to get Mat object from already decoded page is O(1).
If you need memory, you can use .releaseCache() method to release cached index.
The space complexity is O(n) if all pages are decoded into memory. The user is able to decode and release images on demand.
*/
class CV_EXPORTS ImageCollection {
public:
struct CV_EXPORTS iterator {
iterator(ImageCollection* col);
iterator(ImageCollection* col, int end);
Mat& operator*();
Mat* operator->();
iterator& operator++();
iterator operator++(int);
friend bool operator== (const iterator& a, const iterator& b) { return a.m_curr == b.m_curr; };
friend bool operator!= (const iterator& a, const iterator& b) { return a.m_curr != b.m_curr; };
private:
ImageCollection* m_pCollection;
int m_curr;
};
ImageCollection();
ImageCollection(const String& filename, int flags);
void init(const String& img, int flags);
size_t size() const;
const Mat& at(int index);
const Mat& operator[](int index);
void releaseCache(int index);
iterator begin();
iterator end();
class Impl;
Ptr<Impl> getImpl();
protected:
Ptr<Impl> pImpl;
};
//! @} imgcodecs

@ -54,6 +54,8 @@
#include <cerrno>
#include <opencv2/core/utils/logger.hpp>
#include <opencv2/core/utils/configuration.private.hpp>
#include <opencv2/imgcodecs.hpp>
/****************************************************************************************\
@ -658,57 +660,14 @@ bool imreadmulti(const String& filename, std::vector<Mat>& mats, int start, int
static
size_t imcount_(const String& filename, int flags)
{
/// Search for the relevant decoder to handle the imagery
ImageDecoder decoder;
#ifdef HAVE_GDAL
if (flags != IMREAD_UNCHANGED && (flags & IMREAD_LOAD_GDAL) == IMREAD_LOAD_GDAL) {
decoder = GdalDecoder().newDecoder();
}
else {
#else
CV_UNUSED(flags);
#endif
decoder = findDecoder(filename);
#ifdef HAVE_GDAL
}
#endif
/// if no decoder was found, return nothing.
if (!decoder) {
return 0;
}
/// set the filename in the driver
decoder->setSource(filename);
// read the header to make sure it succeeds
try
{
// read the header to make sure it succeeds
if (!decoder->readHeader())
return 0;
}
catch (const cv::Exception& e)
{
std::cerr << "imcount_('" << filename << "'): can't read header: " << e.what() << std::endl << std::flush;
return 0;
}
catch (...)
{
std::cerr << "imcount_('" << filename << "'): can't read header: unknown exception" << std::endl << std::flush;
try{
ImageCollection collection(filename, flags);
return collection.size();
} catch(cv::Exception const& e) {
// Reading header or finding decoder for the filename is failed
return 0;
}
size_t result = 1;
while (decoder->nextPage())
{
++result;
}
return result;
return 0;
}
size_t imcount(const String& filename, int flags)
@ -1032,6 +991,247 @@ bool haveImageWriter( const String& filename )
return !encoder.empty();
}
class ImageCollection::Impl {
public:
Impl() = default;
Impl(const std::string& filename, int flags);
void init(String const& filename, int flags);
size_t size() const;
Mat& at(int index);
Mat& operator[](int index);
void releaseCache(int index);
ImageCollection::iterator begin(ImageCollection* ptr);
ImageCollection::iterator end(ImageCollection* ptr);
Mat read();
int width() const;
int height() const;
bool readHeader();
Mat readData();
bool advance();
int currentIndex() const;
void reset();
private:
String m_filename;
int m_flags{};
std::size_t m_size{};
int m_width{};
int m_height{};
int m_current{};
std::vector<cv::Mat> m_pages;
ImageDecoder m_decoder;
};
ImageCollection::Impl::Impl(std::string const& filename, int flags) {
this->init(filename, flags);
}
void ImageCollection::Impl::init(String const& filename, int flags) {
m_filename = filename;
m_flags = flags;
#ifdef HAVE_GDAL
if (m_flags != IMREAD_UNCHANGED && (m_flags & IMREAD_LOAD_GDAL) == IMREAD_LOAD_GDAL) {
m_decoder = GdalDecoder().newDecoder();
}
else {
#endif
m_decoder = findDecoder(filename);
#ifdef HAVE_GDAL
}
#endif
CV_Assert(m_decoder);
m_decoder->setSource(filename);
CV_Assert(m_decoder->readHeader());
// count the pages of the image collection
size_t count = 1;
while(m_decoder->nextPage()) count++;
m_size = count;
m_pages.resize(m_size);
// Reinitialize the decoder because we advanced to the last page while counting the pages of the image
#ifdef HAVE_GDAL
if (m_flags != IMREAD_UNCHANGED && (m_flags & IMREAD_LOAD_GDAL) == IMREAD_LOAD_GDAL) {
m_decoder = GdalDecoder().newDecoder();
}
else {
#endif
m_decoder = findDecoder(m_filename);
#ifdef HAVE_GDAL
}
#endif
m_decoder->setSource(m_filename);
m_decoder->readHeader();
}
size_t ImageCollection::Impl::size() const { return m_size; }
Mat ImageCollection::Impl::read() {
auto result = this->readHeader();
if(!result) {
return {};
}
return this->readData();
}
int ImageCollection::Impl::width() const {
return m_width;
}
int ImageCollection::Impl::height() const {
return m_height;
}
bool ImageCollection::Impl::readHeader() {
bool status = m_decoder->readHeader();
m_width = m_decoder->width();
m_height = m_decoder->height();
return status;
}
// readHeader must be called before calling this method
Mat ImageCollection::Impl::readData() {
int type = m_decoder->type();
if ((m_flags & IMREAD_LOAD_GDAL) != IMREAD_LOAD_GDAL && m_flags != IMREAD_UNCHANGED) {
if ((m_flags & IMREAD_ANYDEPTH) == 0)
type = CV_MAKETYPE(CV_8U, CV_MAT_CN(type));
if ((m_flags & IMREAD_COLOR) != 0 ||
((m_flags & IMREAD_ANYCOLOR) != 0 && CV_MAT_CN(type) > 1))
type = CV_MAKETYPE(CV_MAT_DEPTH(type), 3);
else
type = CV_MAKETYPE(CV_MAT_DEPTH(type), 1);
}
// established the required input image size
Size size = validateInputImageSize(Size(m_width, m_height));
Mat mat(size.height, size.width, type);
bool success = false;
try {
if (m_decoder->readData(mat))
success = true;
}
catch (const cv::Exception &e) {
std::cerr << "ImageCollection class: can't read data: " << e.what() << std::endl << std::flush;
}
catch (...) {
std::cerr << "ImageCollection class:: can't read data: unknown exception" << std::endl << std::flush;
}
if (!success)
return cv::Mat();
if ((m_flags & IMREAD_IGNORE_ORIENTATION) == 0 && m_flags != IMREAD_UNCHANGED) {
ApplyExifOrientation(m_decoder->getExifTag(ORIENTATION), mat);
}
return mat;
}
bool ImageCollection::Impl::advance() { ++m_current; return m_decoder->nextPage(); }
int ImageCollection::Impl::currentIndex() const { return m_current; }
ImageCollection::iterator ImageCollection::Impl::begin(ImageCollection* ptr) { return ImageCollection::iterator(ptr); }
ImageCollection::iterator ImageCollection::Impl::end(ImageCollection* ptr) { return ImageCollection::iterator(ptr, this->size()); }
void ImageCollection::Impl::reset() {
m_current = 0;
#ifdef HAVE_GDAL
if (m_flags != IMREAD_UNCHANGED && (m_flags & IMREAD_LOAD_GDAL) == IMREAD_LOAD_GDAL) {
m_decoder = GdalDecoder().newDecoder();
}
else {
#endif
m_decoder = findDecoder(m_filename);
#ifdef HAVE_GDAL
}
#endif
m_decoder->setSource(m_filename);
m_decoder->readHeader();
}
Mat& ImageCollection::Impl::at(int index) {
CV_Assert(index >= 0 && size_t(index) < m_size);
return operator[](index);
}
Mat& ImageCollection::Impl::operator[](int index) {
if(m_pages.at(index).empty()) {
// We can't go backward in multi images. If the page is not in vector yet,
// go back to first page and advance until the desired page and read it into memory
if(m_current != index) {
reset();
for(int i = 0; i != index && advance(); ++i) {}
}
m_pages[index] = read();
}
return m_pages[index];
}
void ImageCollection::Impl::releaseCache(int index) {
CV_Assert(index >= 0 && size_t(index) < m_size);
m_pages[index].release();
}
/* ImageCollection API*/
ImageCollection::ImageCollection() : pImpl(new Impl()) {}
ImageCollection::ImageCollection(const std::string& filename, int flags) : pImpl(new Impl(filename, flags)) {}
void ImageCollection::init(const String& img, int flags) { pImpl->init(img, flags); }
size_t ImageCollection::size() const { return pImpl->size(); }
const Mat& ImageCollection::at(int index) { return pImpl->at(index); }
const Mat& ImageCollection::operator[](int index) { return pImpl->operator[](index); }
void ImageCollection::releaseCache(int index) { pImpl->releaseCache(index); }
Ptr<ImageCollection::Impl> ImageCollection::getImpl() { return pImpl; }
/* Iterator API */
ImageCollection::iterator ImageCollection::begin() { return pImpl->begin(this); }
ImageCollection::iterator ImageCollection::end() { return pImpl->end(this); }
ImageCollection::iterator::iterator(ImageCollection* col) : m_pCollection(col), m_curr(0) {}
ImageCollection::iterator::iterator(ImageCollection* col, int end) : m_pCollection(col), m_curr(end) {}
Mat& ImageCollection::iterator::operator*() {
CV_Assert(m_pCollection);
return m_pCollection->getImpl()->operator[](m_curr);
}
Mat* ImageCollection::iterator::operator->() {
CV_Assert(m_pCollection);
return &m_pCollection->getImpl()->operator[](m_curr);
}
ImageCollection::iterator& ImageCollection::iterator::operator++() {
if(m_pCollection->pImpl->currentIndex() == m_curr) {
m_pCollection->pImpl->advance();
}
m_curr++;
return *this;
}
ImageCollection::iterator ImageCollection::iterator::operator++(int) {
iterator tmp = *this;
++(*this);
return tmp;
}
}
/* End of file. */

@ -303,4 +303,181 @@ TEST(Imgcodecs_Image, write_umat)
EXPECT_EQ(0, remove(dst_name.c_str()));
}
TEST(Imgcodecs_Image, multipage_collection_size)
{
const string root = cvtest::TS::ptr()->get_data_path();
const string filename = root + "readwrite/multipage.tif";
ImageCollection collection(filename, IMREAD_ANYCOLOR);
EXPECT_EQ((std::size_t)6, collection.size());
}
TEST(Imgcodecs_Image, multipage_collection_read_pages_iterator)
{
const string root = cvtest::TS::ptr()->get_data_path();
const string filename = root + "readwrite/multipage.tif";
const string page_files[] = {
root + "readwrite/multipage_p1.tif",
root + "readwrite/multipage_p2.tif",
root + "readwrite/multipage_p3.tif",
root + "readwrite/multipage_p4.tif",
root + "readwrite/multipage_p5.tif",
root + "readwrite/multipage_p6.tif"
};
ImageCollection collection(filename, IMREAD_ANYCOLOR);
auto collectionBegin = collection.begin();
for(size_t i = 0; i < collection.size(); ++i, ++collectionBegin)
{
double diff = cv::norm(collectionBegin.operator*(), imread(page_files[i]), NORM_INF);
EXPECT_EQ(0., diff);
}
}
TEST(Imgcodecs_Image, multipage_collection_two_iterator)
{
const string root = cvtest::TS::ptr()->get_data_path();
const string filename = root + "readwrite/multipage.tif";
const string page_files[] = {
root + "readwrite/multipage_p1.tif",
root + "readwrite/multipage_p2.tif",
root + "readwrite/multipage_p3.tif",
root + "readwrite/multipage_p4.tif",
root + "readwrite/multipage_p5.tif",
root + "readwrite/multipage_p6.tif"
};
ImageCollection collection(filename, IMREAD_ANYCOLOR);
auto firstIter = collection.begin();
auto secondIter = collection.begin();
// Decode all odd pages then decode even pages -> 1, 0, 3, 2 ...
firstIter++;
for(size_t i = 1; i < collection.size(); i += 2, ++firstIter, ++firstIter, ++secondIter, ++secondIter) {
Mat mat = *firstIter;
double diff = cv::norm(mat, imread(page_files[i]), NORM_INF);
EXPECT_EQ(0., diff);
Mat evenMat = *secondIter;
diff = cv::norm(evenMat, imread(page_files[i-1]), NORM_INF);
EXPECT_EQ(0., diff);
}
}
TEST(Imgcodecs_Image, multipage_collection_operator_plusplus)
{
const string root = cvtest::TS::ptr()->get_data_path();
const string filename = root + "readwrite/multipage.tif";
// operator++ test
ImageCollection collection(filename, IMREAD_ANYCOLOR);
auto firstIter = collection.begin();
auto secondIter = firstIter++;
// firstIter points to second page, secondIter points to first page
double diff = cv::norm(*firstIter, *secondIter, NORM_INF);
EXPECT_NE(diff, 0.);
}
TEST(Imgcodecs_Image, multipage_collection_backward_decoding)
{
const string root = cvtest::TS::ptr()->get_data_path();
const string filename = root + "readwrite/multipage.tif";
const string page_files[] = {
root + "readwrite/multipage_p1.tif",
root + "readwrite/multipage_p2.tif",
root + "readwrite/multipage_p3.tif",
root + "readwrite/multipage_p4.tif",
root + "readwrite/multipage_p5.tif",
root + "readwrite/multipage_p6.tif"
};
ImageCollection collection(filename, IMREAD_ANYCOLOR);
EXPECT_EQ((size_t)6, collection.size());
// backward decoding -> 5,4,3,2,1,0
for(int i = (int)collection.size() - 1; i >= 0; --i)
{
cv::Mat ithPage = imread(page_files[i]);
EXPECT_FALSE(ithPage.empty());
double diff = cv::norm(collection[i], ithPage, NORM_INF);
EXPECT_EQ(diff, 0.);
}
for(int i = 0; i < (int)collection.size(); ++i)
{
collection.releaseCache(i);
}
double diff = cv::norm(collection[2], imread(page_files[2]), NORM_INF);
EXPECT_EQ(diff, 0.);
}
TEST(ImgCodecs, multipage_collection_decoding_range_based_for_loop_test)
{
const string root = cvtest::TS::ptr()->get_data_path();
const string filename = root + "readwrite/multipage.tif";
const string page_files[] = {
root + "readwrite/multipage_p1.tif",
root + "readwrite/multipage_p2.tif",
root + "readwrite/multipage_p3.tif",
root + "readwrite/multipage_p4.tif",
root + "readwrite/multipage_p5.tif",
root + "readwrite/multipage_p6.tif"
};
ImageCollection collection(filename, IMREAD_ANYCOLOR);
size_t index = 0;
for(auto &i: collection)
{
cv::Mat ithPage = imread(page_files[index]);
EXPECT_FALSE(ithPage.empty());
double diff = cv::norm(i, ithPage, NORM_INF);
EXPECT_EQ(0., diff);
++index;
}
EXPECT_EQ(index, collection.size());
index = 0;
for(auto &&i: collection)
{
cv::Mat ithPage = imread(page_files[index]);
EXPECT_FALSE(ithPage.empty());
double diff = cv::norm(i, ithPage, NORM_INF);
EXPECT_EQ(0., diff);
++index;
}
EXPECT_EQ(index, collection.size());
}
TEST(ImgCodecs, multipage_collection_two_iterator_operatorpp)
{
const string root = cvtest::TS::ptr()->get_data_path();
const string filename = root + "readwrite/multipage.tif";
ImageCollection imcol(filename, IMREAD_ANYCOLOR);
auto it0 = imcol.begin(), it1 = it0, it2 = it0;
vector<Mat> img(6);
for (int i = 0; i < 6; i++) {
img[i] = *it0;
it0->release();
++it0;
}
for (int i = 0; i < 3; i++) {
++it2;
}
for (int i = 0; i < 3; i++) {
auto img2 = *it2;
auto img1 = *it1;
++it2;
++it1;
EXPECT_TRUE(cv::norm(img2, img[i+3], NORM_INF) == 0);
EXPECT_TRUE(cv::norm(img1, img[i], NORM_INF) == 0);
}
}
}} // namespace

Loading…
Cancel
Save