Merge pull request #19780 from HarryDC:feature/index-multiimage-tiff

Add reading of specific images from multipage tiff

* Add reading of specific images from multipage tiff

* Fix build issues

* Add missing flag for gdal

* Fix unused param warning

* Remove duplicated code

* change public parameter type to int

* Fix warnings

* Fix parameter check
This commit is contained in:
Harald Scheirich 2021-04-23 16:48:32 -04:00 committed by GitHub
parent a53582d706
commit fcaeeac931
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 207 additions and 17 deletions

View File

@ -215,6 +215,26 @@ The function imreadmulti loads a multi-page image from the specified file into a
*/ */
CV_EXPORTS_W bool imreadmulti(const String& filename, CV_OUT std::vector<Mat>& mats, int flags = IMREAD_ANYCOLOR); CV_EXPORTS_W bool imreadmulti(const String& filename, CV_OUT std::vector<Mat>& mats, int flags = IMREAD_ANYCOLOR);
/** @brief Loads a of images of a multi-page image from a file.
The function imreadmulti loads a specified range from a multi-page image from the specified file into a vector of Mat objects.
@param filename Name of file to be loaded.
@param start Start index of the image to load
@param count Count number of images to load
@param flags Flag that can take values of cv::ImreadModes, default with cv::IMREAD_ANYCOLOR.
@param mats A vector of Mat objects holding each page, if more than one.
@sa cv::imread
*/
CV_EXPORTS_W bool imreadmulti(const String& filename, CV_OUT std::vector<Mat>& mats, int start, int count, int flags = IMREAD_ANYCOLOR);
/** @brief Returns the number of images inside the give file
The function imcount will return the number of pages in a multi-page image, or 1 for single-page images
@param filename Name of file to be loaded.
@param flags Flag that can take values of cv::ImreadModes, default with cv::IMREAD_ANYCOLOR.
*/
CV_EXPORTS_W size_t imcount(const String& filename, int flags = IMREAD_ANYCOLOR);
/** @brief Saves an image to a specified file. /** @brief Saves an image to a specified file.
The function imwrite saves the image to the specified file. The image format is chosen based on the The function imwrite saves the image to the specified file. The image format is chosen based on the

View File

@ -495,25 +495,19 @@ imread_( const String& filename, int flags, Mat& mat )
} }
/**
* Read an image into memory and return the information
*
* @param[in] filename File to load
* @param[in] flags Flags
* @param[in] mats Reference to C++ vector<Mat> object to hold the images
*
*/
static bool static bool
imreadmulti_(const String& filename, int flags, std::vector<Mat>& mats) imreadmulti_(const String& filename, int flags, std::vector<Mat>& mats, int start, int count)
{ {
/// Search for the relevant decoder to handle the imagery /// Search for the relevant decoder to handle the imagery
ImageDecoder decoder; ImageDecoder decoder;
CV_CheckGE(start, 0, "Start index cannont be < 0");
#ifdef HAVE_GDAL #ifdef HAVE_GDAL
if (flags != IMREAD_UNCHANGED && (flags & IMREAD_LOAD_GDAL) == IMREAD_LOAD_GDAL){ if (flags != IMREAD_UNCHANGED && (flags & IMREAD_LOAD_GDAL) == IMREAD_LOAD_GDAL) {
decoder = GdalDecoder().newDecoder(); decoder = GdalDecoder().newDecoder();
} }
else{ else {
#endif #endif
decoder = findDecoder(filename); decoder = findDecoder(filename);
#ifdef HAVE_GDAL #ifdef HAVE_GDAL
@ -521,10 +515,14 @@ imreadmulti_(const String& filename, int flags, std::vector<Mat>& mats)
#endif #endif
/// if no decoder was found, return nothing. /// if no decoder was found, return nothing.
if (!decoder){ if (!decoder) {
return 0; return 0;
} }
if (count < 0) {
count = std::numeric_limits<int>::max();
}
/// set the filename in the driver /// set the filename in the driver
decoder->setSource(filename); decoder->setSource(filename);
@ -532,7 +530,7 @@ imreadmulti_(const String& filename, int flags, std::vector<Mat>& mats)
try try
{ {
// read the header to make sure it succeeds // read the header to make sure it succeeds
if( !decoder->readHeader() ) if (!decoder->readHeader())
return 0; return 0;
} }
catch (const cv::Exception& e) catch (const cv::Exception& e)
@ -546,11 +544,22 @@ imreadmulti_(const String& filename, int flags, std::vector<Mat>& mats)
return 0; return 0;
} }
for (;;) int current = start;
while (current > 0)
{
if (!decoder->nextPage())
{
return false;
}
--current;
}
while (current < count)
{ {
// grab the decoded type // grab the decoded type
int type = decoder->type(); int type = decoder->type();
if( (flags & IMREAD_LOAD_GDAL) != IMREAD_LOAD_GDAL && flags != IMREAD_UNCHANGED ) if ((flags & IMREAD_LOAD_GDAL) != IMREAD_LOAD_GDAL && flags != IMREAD_UNCHANGED)
{ {
if ((flags & IMREAD_ANYDEPTH) == 0) if ((flags & IMREAD_ANYDEPTH) == 0)
type = CV_MAKETYPE(CV_8U, CV_MAT_CN(type)); type = CV_MAKETYPE(CV_8U, CV_MAT_CN(type));
@ -585,7 +594,7 @@ imreadmulti_(const String& filename, int flags, std::vector<Mat>& mats)
break; break;
// optionally rotate the data if EXIF' orientation flag says so // optionally rotate the data if EXIF' orientation flag says so
if( (flags & IMREAD_IGNORE_ORIENTATION) == 0 && flags != IMREAD_UNCHANGED ) if ((flags & IMREAD_IGNORE_ORIENTATION) == 0 && flags != IMREAD_UNCHANGED)
{ {
ApplyExifOrientation(decoder->getExifTag(ORIENTATION), mat); ApplyExifOrientation(decoder->getExifTag(ORIENTATION), mat);
} }
@ -595,6 +604,7 @@ imreadmulti_(const String& filename, int flags, std::vector<Mat>& mats)
{ {
break; break;
} }
++current;
} }
return !mats.empty(); return !mats.empty();
@ -636,9 +646,81 @@ bool imreadmulti(const String& filename, std::vector<Mat>& mats, int flags)
{ {
CV_TRACE_FUNCTION(); CV_TRACE_FUNCTION();
return imreadmulti_(filename, flags, mats); return imreadmulti_(filename, flags, mats, 0, -1);
} }
bool imreadmulti(const String& filename, std::vector<Mat>& mats, int start, int count, int flags)
{
CV_TRACE_FUNCTION();
return imreadmulti_(filename, flags, mats, start, count);
}
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;
return 0;
}
size_t result = 1;
while (decoder->nextPage())
{
++result;
}
return result;
}
size_t imcount(const String& filename, int flags)
{
CV_TRACE_FUNCTION();
return imcount_(filename, flags);
}
static bool imwrite_( const String& filename, const std::vector<Mat>& img_vec, static bool imwrite_( const String& filename, const std::vector<Mat>& img_vec,
const std::vector<int>& params, bool flipv ) const std::vector<int>& params, bool flipv )
{ {

View File

@ -358,6 +358,94 @@ TEST(Imgcodecs_Tiff, decode_black_and_write_image_pr17275_default)
EXPECT_EQ(CV_8UC3, img.type()) << cv::typeToString(img.type()); EXPECT_EQ(CV_8UC3, img.type()) << cv::typeToString(img.type());
} }
TEST(Imgcodecs_Tiff, count_multipage)
{
const string root = cvtest::TS::ptr()->get_data_path();
{
const string filename = root + "readwrite/multipage.tif";
ASSERT_EQ((size_t)6, imcount(filename));
}
{
const string filename = root + "readwrite/test32FC3_raw.tiff";
ASSERT_EQ((size_t)1, imcount(filename));
}
}
TEST(Imgcodecs_Tiff, read_multipage_indexed)
{
const string root = cvtest::TS::ptr()->get_data_path();
const string filename = root + "readwrite/multipage.tif";
const string page_files[] = {
"readwrite/multipage_p1.tif",
"readwrite/multipage_p2.tif",
"readwrite/multipage_p3.tif",
"readwrite/multipage_p4.tif",
"readwrite/multipage_p5.tif",
"readwrite/multipage_p6.tif"
};
const int page_count = sizeof(page_files) / sizeof(page_files[0]);
vector<Mat> single_pages;
for (int i = 0; i < page_count; i++)
{
// imread and imreadmulti have different default values for the flag
const Mat page = imread(root + page_files[i], IMREAD_ANYCOLOR);
single_pages.push_back(page);
}
ASSERT_EQ((size_t)page_count, single_pages.size());
{
SCOPED_TRACE("Edge Cases");
vector<Mat> multi_pages;
bool res = imreadmulti(filename, multi_pages, 0, 0);
// If we asked for 0 images and we successfully read 0 images should this be false ?
ASSERT_TRUE(res == false);
ASSERT_EQ((size_t)0, multi_pages.size());
res = imreadmulti(filename, multi_pages, 0, 123123);
ASSERT_TRUE(res == true);
ASSERT_EQ((size_t)6, multi_pages.size());
}
{
SCOPED_TRACE("Read all with indices");
vector<Mat> multi_pages;
bool res = imreadmulti(filename, multi_pages, 0, 6);
ASSERT_TRUE(res == true);
ASSERT_EQ((size_t)page_count, multi_pages.size());
for (int i = 0; i < page_count; i++)
{
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), multi_pages[i], single_pages[i]);
}
}
{
SCOPED_TRACE("Read one by one");
vector<Mat> multi_pages;
for (int i = 0; i < page_count; i++)
{
bool res = imreadmulti(filename, multi_pages, i, 1);
ASSERT_TRUE(res == true);
ASSERT_EQ((size_t)1, multi_pages.size());
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), multi_pages[0], single_pages[i]);
multi_pages.clear();
}
}
{
SCOPED_TRACE("Read multiple at a time");
vector<Mat> multi_pages;
for (int i = 0; i < page_count/2; i++)
{
bool res = imreadmulti(filename, multi_pages, i*2, 2);
ASSERT_TRUE(res == true);
ASSERT_EQ((size_t)2, multi_pages.size());
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), multi_pages[0], single_pages[i * 2]) << i;
EXPECT_PRED_FORMAT2(cvtest::MatComparator(0, 0), multi_pages[1], single_pages[i * 2 + 1]);
multi_pages.clear();
}
}
}
#endif #endif
}} // namespace }} // namespace