mirror of
https://github.com/opencv/opencv.git
synced 2025-06-07 17:44:04 +08:00
new multipage image decoder api - ImageCollection
This commit is contained in:
parent
139c443770
commit
062cee2933
@ -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,58 +660,15 @@ 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) {
|
||||
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;
|
||||
}
|
||||
|
||||
/// 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;
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t result = 1;
|
||||
|
||||
|
||||
while (decoder->nextPage())
|
||||
{
|
||||
++result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
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…
Reference in New Issue
Block a user