mirror of
https://github.com/zebrajr/opencv.git
synced 2025-12-06 12:19:50 +01:00
Merge pull request #27499 from vpisarev:image_io_with_metadata
Extend image I/O API with metadata support #27499 Covered with the PR: * AVIF encoder can write exif, xmp, icc * AVIF decoder can read exif * JPEG encoder can write exif * JPEG decoder can read exif * PNG encoder can write exif * PNG decoder can read exif This PR is a sort of preamble for #27488. I suggest to merge this one first to OpenCV 4.x, then promote this change to OpenCV 5.x and then provide extra API to read and write metadata in 5.x (or maybe 4.x) in a style similar to #27488. Maybe in that PR exif packing/unpacking should be done using a separate external API. That is, metadata reading and writing can/should be done in 2 steps: * [1] pack and then [2] embed exif into image at the encoding stage. * [1] extract and then [2] unpack exif at the decoding stage. ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [x] There is a reference to the original bug report and related work - [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [ ] The feature is well documented and sample code can be built with the project CMake
This commit is contained in:
parent
677c4ee42f
commit
66e5fce928
|
|
@ -251,6 +251,15 @@ enum ImwriteGIFCompressionFlags {
|
|||
IMWRITE_GIF_COLORTABLE_SIZE_256 = 8
|
||||
};
|
||||
|
||||
enum ImageMetadataType
|
||||
{
|
||||
IMAGE_METADATA_UNKNOWN = -1,
|
||||
IMAGE_METADATA_EXIF = 0,
|
||||
IMAGE_METADATA_XMP = 1,
|
||||
IMAGE_METADATA_ICCP = 2,
|
||||
IMAGE_METADATA_MAX = 2
|
||||
};
|
||||
|
||||
//! @} imgcodecs_flags
|
||||
|
||||
/** @brief Represents an animation with multiple frames.
|
||||
|
|
@ -360,6 +369,17 @@ The image passing through the img parameter can be pre-allocated. The memory is
|
|||
*/
|
||||
CV_EXPORTS_W void imread( const String& filename, OutputArray dst, int flags = IMREAD_COLOR_BGR );
|
||||
|
||||
/** @brief Reads an image from a file together with associated metadata.
|
||||
|
||||
The function imreadWithMetadata reads image from the specified file. It does the same thing as imread, but additionally reads metadata if the corresponding file contains any.
|
||||
@param filename Name of the file to be loaded.
|
||||
@param metadataTypes Output vector with types of metadata chucks returned in metadata, see ImageMetadataType.
|
||||
@param metadata Output vector of vectors or vector of matrices to store the retrieved metadata
|
||||
@param flags Flag that can take values of cv::ImreadModes
|
||||
*/
|
||||
CV_EXPORTS_W Mat imreadWithMetadata( const String& filename, CV_OUT std::vector<int>& metadataTypes,
|
||||
OutputArrayOfArrays metadata, int flags = IMREAD_ANYCOLOR);
|
||||
|
||||
/** @brief Loads a multi-page image from a file.
|
||||
|
||||
The function imreadmulti loads a multi-page image from the specified file into a vector of Mat objects.
|
||||
|
|
@ -508,6 +528,20 @@ It also demonstrates how to save multiple images in a TIFF file:
|
|||
CV_EXPORTS_W bool imwrite( const String& filename, InputArray img,
|
||||
const std::vector<int>& params = std::vector<int>());
|
||||
|
||||
/** @brief Saves an image to a specified file with metadata
|
||||
|
||||
The function imwriteWithMetadata saves the image to the specified file. It does the same thing as imwrite, but additionally writes metadata if the corresponding format supports it.
|
||||
@param filename Name of the file. As with imwrite, image format is determined by the file extension.
|
||||
@param img (Mat or vector of Mat) Image or Images to be saved.
|
||||
@param metadataTypes Vector with types of metadata chucks stored in metadata to write, see ImageMetadataType.
|
||||
@param metadata Vector of vectors or vector of matrices with chunks of metadata to store into the file
|
||||
@param params Format-specific parameters encoded as pairs (paramId_1, paramValue_1, paramId_2, paramValue_2, ... .) see cv::ImwriteFlags
|
||||
*/
|
||||
CV_EXPORTS_W bool imwriteWithMetadata( const String& filename, InputArray img,
|
||||
const std::vector<int>& metadataTypes,
|
||||
InputArrayOfArrays& metadata,
|
||||
const std::vector<int>& params = std::vector<int>());
|
||||
|
||||
//! @brief multi-image overload for bindings
|
||||
CV_WRAP static inline
|
||||
bool imwritemulti(const String& filename, InputArrayOfArrays img,
|
||||
|
|
@ -529,6 +563,22 @@ See cv::imread for the list of supported formats and flags description.
|
|||
*/
|
||||
CV_EXPORTS_W Mat imdecode( InputArray buf, int flags );
|
||||
|
||||
/** @brief Reads an image from a buffer in memory together with associated metadata.
|
||||
|
||||
The function imdecode reads an image from the specified buffer in the memory. If the buffer is too short or
|
||||
contains invalid data, the function returns an empty matrix ( Mat::data==NULL ).
|
||||
|
||||
See cv::imread for the list of supported formats and flags description.
|
||||
|
||||
@note In the case of color images, the decoded images will have the channels stored in **B G R** order.
|
||||
@param buf Input array or vector of bytes.
|
||||
@param metadataTypes Output vector with types of metadata chucks returned in metadata, see ImageMetadataType.
|
||||
@param metadata Output vector of vectors or vector of matrices to store the retrieved metadata
|
||||
@param flags The same flags as in cv::imread, see cv::ImreadModes.
|
||||
*/
|
||||
CV_EXPORTS_W Mat imdecodeWithMetadata( InputArray buf, CV_OUT std::vector<int>& metadataTypes,
|
||||
OutputArrayOfArrays metadata, int flags = IMREAD_ANYCOLOR );
|
||||
|
||||
/** @overload
|
||||
@param buf Input array or vector of bytes.
|
||||
@param flags The same flags as in cv::imread, see cv::ImreadModes.
|
||||
|
|
@ -567,6 +617,24 @@ CV_EXPORTS_W bool imencode( const String& ext, InputArray img,
|
|||
CV_OUT std::vector<uchar>& buf,
|
||||
const std::vector<int>& params = std::vector<int>());
|
||||
|
||||
/** @brief Encodes an image into a memory buffer.
|
||||
|
||||
The function imencode compresses the image and stores it in the memory buffer that is resized to fit the
|
||||
result. See cv::imwrite for the list of supported formats and flags description.
|
||||
|
||||
@param ext File extension that defines the output format. Must include a leading period.
|
||||
@param img Image to be compressed.
|
||||
@param metadataTypes Vector with types of metadata chucks stored in metadata to write, see ImageMetadataType.
|
||||
@param metadata Vector of vectors or vector of matrices with chunks of metadata to store into the file
|
||||
@param buf Output buffer resized to fit the compressed image.
|
||||
@param params Format-specific parameters. See cv::imwrite and cv::ImwriteFlags.
|
||||
*/
|
||||
CV_EXPORTS_W bool imencodeWithMetadata( const String& ext, InputArray img,
|
||||
const std::vector<int>& metadataTypes,
|
||||
InputArrayOfArrays metadata,
|
||||
CV_OUT std::vector<uchar>& buf,
|
||||
const std::vector<int>& params = std::vector<int>());
|
||||
|
||||
/** @brief Encodes array of images into a memory buffer.
|
||||
|
||||
The function is analog to cv::imencode for in-memory multi-page image compression.
|
||||
|
|
|
|||
|
|
@ -94,6 +94,10 @@ ExifEntry_t ExifReader::getTag(const ExifTagName tag) const
|
|||
return entry;
|
||||
}
|
||||
|
||||
const std::vector<unsigned char>& ExifReader::getData() const
|
||||
{
|
||||
return m_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Parsing the exif data buffer and prepare (internal) exif directory
|
||||
|
|
|
|||
|
|
@ -175,6 +175,10 @@ public:
|
|||
*/
|
||||
ExifEntry_t getTag( const ExifTagName tag ) const;
|
||||
|
||||
/**
|
||||
* @brief Get the whole exif buffer
|
||||
*/
|
||||
const std::vector<unsigned char>& getData() const;
|
||||
|
||||
private:
|
||||
std::vector<unsigned char> m_data;
|
||||
|
|
|
|||
|
|
@ -68,8 +68,8 @@ avifResult CopyToMat(const avifImage *image, int channels, bool useRGB , Mat *ma
|
|||
return avifImageYUVToRGB(image, &rgba);
|
||||
}
|
||||
|
||||
AvifImageUniquePtr ConvertToAvif(const cv::Mat &img, bool lossless,
|
||||
int bit_depth) {
|
||||
AvifImageUniquePtr ConvertToAvif(const cv::Mat &img, bool lossless, int bit_depth,
|
||||
const std::vector<std::vector<uchar> >& metadata) {
|
||||
CV_Assert(img.depth() == CV_8U || img.depth() == CV_16U);
|
||||
|
||||
const int width = img.cols;
|
||||
|
|
@ -112,6 +112,18 @@ AvifImageUniquePtr ConvertToAvif(const cv::Mat &img, bool lossless,
|
|||
result->yuvRange = AVIF_RANGE_FULL;
|
||||
}
|
||||
|
||||
if (!metadata.empty()) {
|
||||
const std::vector<uchar>& metadata_exif = metadata[IMAGE_METADATA_EXIF];
|
||||
const std::vector<uchar>& metadata_xmp = metadata[IMAGE_METADATA_XMP];
|
||||
const std::vector<uchar>& metadata_iccp = metadata[IMAGE_METADATA_ICCP];
|
||||
if (!metadata_exif.empty())
|
||||
avifImageSetMetadataExif(result, (const uint8_t*)metadata_exif.data(), metadata_exif.size());
|
||||
if (!metadata_exif.empty())
|
||||
avifImageSetMetadataXMP(result, (const uint8_t*)metadata_xmp.data(), metadata_xmp.size());
|
||||
if (!metadata_iccp.empty())
|
||||
avifImageSetProfileICC(result, (const uint8_t*)metadata_iccp.data(), metadata_iccp.size());
|
||||
}
|
||||
|
||||
avifRGBImage rgba;
|
||||
avifRGBImageSetDefaults(&rgba, result);
|
||||
if (img.channels() == 3) {
|
||||
|
|
@ -120,7 +132,7 @@ AvifImageUniquePtr ConvertToAvif(const cv::Mat &img, bool lossless,
|
|||
CV_Assert(img.channels() == 4);
|
||||
rgba.format = AVIF_RGB_FORMAT_BGRA;
|
||||
}
|
||||
rgba.rowBytes = img.step[0];
|
||||
rgba.rowBytes = (uint32_t)img.step[0];
|
||||
rgba.depth = bit_depth;
|
||||
rgba.pixels =
|
||||
const_cast<uint8_t *>(reinterpret_cast<const uint8_t *>(img.data));
|
||||
|
|
@ -287,6 +299,10 @@ bool AvifDecoder::nextPage() {
|
|||
AvifEncoder::AvifEncoder() {
|
||||
m_description = "AVIF files (*.avif)";
|
||||
m_buf_supported = true;
|
||||
m_support_metadata.assign((size_t)IMAGE_METADATA_MAX + 1, false);
|
||||
m_support_metadata[(size_t)IMAGE_METADATA_EXIF] = true;
|
||||
m_support_metadata[(size_t)IMAGE_METADATA_XMP] = true;
|
||||
m_support_metadata[(size_t)IMAGE_METADATA_ICCP] = true;
|
||||
encoder_ = avifEncoderCreate();
|
||||
}
|
||||
|
||||
|
|
@ -349,7 +365,7 @@ bool AvifEncoder::writeanimation(const Animation& animation,
|
|||
img.channels() == 1 || img.channels() == 3 || img.channels() == 4,
|
||||
"AVIF only supports 1, 3, 4 channels");
|
||||
|
||||
images.emplace_back(ConvertToAvif(img, do_lossless, bit_depth));
|
||||
images.emplace_back(ConvertToAvif(img, do_lossless, bit_depth, m_metadata));
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < images.size(); i++)
|
||||
|
|
|
|||
|
|
@ -58,11 +58,30 @@ BaseImageDecoder::BaseImageDecoder()
|
|||
m_frame_count = 1;
|
||||
}
|
||||
|
||||
bool BaseImageDecoder::haveMetadata(ImageMetadataType type) const
|
||||
{
|
||||
if (type == IMAGE_METADATA_EXIF)
|
||||
return !m_exif.getData().empty();
|
||||
return false;
|
||||
}
|
||||
|
||||
Mat BaseImageDecoder::getMetadata(ImageMetadataType type) const
|
||||
{
|
||||
if (type == IMAGE_METADATA_EXIF) {
|
||||
const std::vector<unsigned char>& exif = m_exif.getData();
|
||||
if (!exif.empty()) {
|
||||
Mat exifmat(1, (int)exif.size(), CV_8U, (void*)exif.data());
|
||||
return exifmat;
|
||||
}
|
||||
}
|
||||
return Mat();
|
||||
}
|
||||
|
||||
ExifEntry_t BaseImageDecoder::getExifTag(const ExifTagName tag) const
|
||||
{
|
||||
return m_exif.getTag(tag);
|
||||
}
|
||||
|
||||
bool BaseImageDecoder::setSource( const String& filename )
|
||||
{
|
||||
m_filename = filename;
|
||||
|
|
@ -140,6 +159,23 @@ bool BaseImageEncoder::setDestination( std::vector<uchar>& buf )
|
|||
return true;
|
||||
}
|
||||
|
||||
bool BaseImageEncoder::addMetadata(ImageMetadataType type, const Mat& metadata)
|
||||
{
|
||||
CV_Assert_N(type >= IMAGE_METADATA_EXIF, type <= IMAGE_METADATA_MAX);
|
||||
if (metadata.empty())
|
||||
return true;
|
||||
size_t itype = (size_t)type;
|
||||
if (itype >= m_support_metadata.size() || !m_support_metadata[itype])
|
||||
return false;
|
||||
if (m_metadata.empty())
|
||||
m_metadata.resize((size_t)IMAGE_METADATA_MAX+1);
|
||||
CV_Assert(metadata.elemSize() == 1);
|
||||
CV_Assert(metadata.isContinuous());
|
||||
const unsigned char* data = metadata.ptr<unsigned char>();
|
||||
m_metadata[itype].assign(data, data + metadata.total());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BaseImageEncoder::write(const Mat &img, const std::vector<int> ¶ms) {
|
||||
std::vector<Mat> img_vec(1, img);
|
||||
return writemulti(img_vec, params);
|
||||
|
|
|
|||
|
|
@ -69,6 +69,20 @@ public:
|
|||
*/
|
||||
virtual int type() const { return m_type; }
|
||||
|
||||
/**
|
||||
* @brief Checks whether file contains metadata of the certain type.
|
||||
* @param type The type of metadata to look for
|
||||
*/
|
||||
virtual bool haveMetadata(ImageMetadataType type) const;
|
||||
|
||||
/**
|
||||
* @brief Retrieves metadata (if any) of the certain kind.
|
||||
* If there is no such metadata, the method returns empty array.
|
||||
*
|
||||
* @param type The type of metadata to look for
|
||||
*/
|
||||
virtual Mat getMetadata(ImageMetadataType type) const;
|
||||
|
||||
/**
|
||||
* @brief Fetch a specific EXIF tag from the image's metadata.
|
||||
* @param tag The EXIF tag to retrieve.
|
||||
|
|
@ -205,6 +219,13 @@ public:
|
|||
*/
|
||||
virtual bool setDestination(std::vector<uchar>& buf);
|
||||
|
||||
/**
|
||||
* @brief Sets the metadata to write together with the image data
|
||||
* @param type The type of metadata to add
|
||||
* @param metadata The packed metadata (Exif, XMP, ...)
|
||||
*/
|
||||
virtual bool addMetadata(ImageMetadataType type, const Mat& metadata);
|
||||
|
||||
/**
|
||||
* @brief Encode and write the image data.
|
||||
* @param img The Mat object containing the image data to be encoded.
|
||||
|
|
@ -243,6 +264,8 @@ public:
|
|||
virtual void throwOnError() const;
|
||||
|
||||
protected:
|
||||
std::vector<std::vector<unsigned char> > m_metadata; // see IMAGE_METADATA_...
|
||||
std::vector<bool> m_support_metadata;
|
||||
String m_description; ///< Description of the encoder (e.g., format name, capabilities).
|
||||
String m_filename; ///< Destination file name for encoded data.
|
||||
std::vector<uchar>* m_buf; ///< Pointer to the buffer for encoded data if using memory-based destination.
|
||||
|
|
|
|||
|
|
@ -600,6 +600,8 @@ JpegEncoder::JpegEncoder()
|
|||
{
|
||||
m_description = "JPEG files (*.jpeg;*.jpg;*.jpe)";
|
||||
m_buf_supported = true;
|
||||
m_support_metadata.assign((size_t)IMAGE_METADATA_MAX + 1, false);
|
||||
m_support_metadata[(size_t)IMAGE_METADATA_EXIF] = true;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -815,6 +817,22 @@ bool JpegEncoder::write( const Mat& img, const std::vector<int>& params )
|
|||
|
||||
jpeg_start_compress( &cinfo, TRUE );
|
||||
|
||||
if (!m_metadata.empty()) {
|
||||
const std::vector<uchar>& metadata_exif = m_metadata[IMAGE_METADATA_EXIF];
|
||||
size_t exif_size = metadata_exif.size();
|
||||
if (exif_size > 0u) {
|
||||
const char app1_exif_prefix[] = {'E', 'x', 'i', 'f', '\0', '\0'};
|
||||
size_t app1_exif_prefix_size = sizeof(app1_exif_prefix);
|
||||
size_t data_size = exif_size + app1_exif_prefix_size;
|
||||
|
||||
std::vector<uchar> metadata_app1(data_size);
|
||||
uchar* data = metadata_app1.data();
|
||||
memcpy(data, app1_exif_prefix, app1_exif_prefix_size);
|
||||
memcpy(data + app1_exif_prefix_size, metadata_exif.data(), exif_size);
|
||||
jpeg_write_marker(&cinfo, JPEG_APP0 + 1, data, (unsigned)data_size);
|
||||
}
|
||||
}
|
||||
|
||||
if( doDirectWrite )
|
||||
{
|
||||
for( int y = 0; y < height; y++ )
|
||||
|
|
|
|||
|
|
@ -858,6 +858,8 @@ PngEncoder::PngEncoder()
|
|||
{
|
||||
m_description = "Portable Network Graphics files (*.png;*.apng)";
|
||||
m_buf_supported = true;
|
||||
m_support_metadata.assign((size_t)IMAGE_METADATA_MAX+1, false);
|
||||
m_support_metadata[IMAGE_METADATA_EXIF] = true;
|
||||
op_zstream1.zalloc = NULL;
|
||||
op_zstream2.zalloc = NULL;
|
||||
next_seq_num = 0;
|
||||
|
|
@ -989,6 +991,16 @@ bool PngEncoder::write( const Mat& img, const std::vector<int>& params )
|
|||
for( y = 0; y < height; y++ )
|
||||
buffer[y] = img.data + y*img.step;
|
||||
|
||||
if (!m_metadata.empty()) {
|
||||
std::vector<uchar>& exif = m_metadata[IMAGE_METADATA_EXIF];
|
||||
if (!exif.empty()) {
|
||||
writeChunk(f, "eXIf", exif.data(), (uint32_t)exif.size());
|
||||
}
|
||||
// [TODO] add xmp and icc. They need special handling,
|
||||
// see https://dev.exiv2.org/projects/exiv2/wiki/The_Metadata_in_PNG_files and
|
||||
// https://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html.
|
||||
}
|
||||
|
||||
png_write_image( png_ptr, buffer.data() );
|
||||
png_write_end( png_ptr, info_ptr );
|
||||
|
||||
|
|
|
|||
|
|
@ -410,6 +410,76 @@ static void ApplyExifOrientation(ExifEntry_t orientationTag, OutputArray img)
|
|||
}
|
||||
}
|
||||
|
||||
static void readMetadata(ImageDecoder& decoder,
|
||||
std::vector<int>* metadata_types,
|
||||
OutputArrayOfArrays metadata)
|
||||
{
|
||||
if (!metadata_types)
|
||||
return;
|
||||
int kind = metadata.kind();
|
||||
void* obj = metadata.getObj();
|
||||
std::vector<Mat>* matvector = nullptr;
|
||||
std::vector<std::vector<uchar> >* vecvector = nullptr;
|
||||
if (kind == _InputArray::STD_VECTOR_MAT) {
|
||||
matvector = (std::vector<Mat>*)obj;
|
||||
} else if (kind == _InputArray::STD_VECTOR_VECTOR) {
|
||||
int elemtype = metadata.type(0);
|
||||
CV_Assert(elemtype == CV_8UC1 || elemtype == CV_8SC1);
|
||||
vecvector = (std::vector<std::vector<uint8_t> >*)obj;
|
||||
} else {
|
||||
CV_Error(Error::StsBadArg,
|
||||
"unsupported metadata type, should be a vector of matrices or vector of byte vectors");
|
||||
}
|
||||
std::vector<Mat> src_metadata;
|
||||
for (int m = (int)IMAGE_METADATA_EXIF; m <= (int)IMAGE_METADATA_MAX; m++) {
|
||||
Mat mm = decoder->getMetadata((ImageMetadataType)m);
|
||||
if (!mm.empty()) {
|
||||
CV_Assert(mm.isContinuous());
|
||||
CV_Assert(mm.elemSize() == 1u);
|
||||
metadata_types->push_back(m);
|
||||
src_metadata.push_back(mm);
|
||||
}
|
||||
}
|
||||
size_t nmetadata = metadata_types->size();
|
||||
if (matvector) {
|
||||
matvector->resize(nmetadata);
|
||||
for (size_t m = 0; m < nmetadata; m++)
|
||||
src_metadata[m].copyTo(matvector->at(m));
|
||||
} else {
|
||||
vecvector->resize(nmetadata);
|
||||
for (size_t m = 0; m < nmetadata; m++) {
|
||||
const Mat& mm = src_metadata[m];
|
||||
const uchar* data = (uchar*)mm.data;
|
||||
vecvector->at(m).assign(data, data + mm.total());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static const char* metadataTypeToString(ImageMetadataType type)
|
||||
{
|
||||
return type == IMAGE_METADATA_EXIF ? "Exif" :
|
||||
type == IMAGE_METADATA_XMP ? "XMP" :
|
||||
type == IMAGE_METADATA_ICCP ? "ICC Profile" : "???";
|
||||
}
|
||||
|
||||
static void addMetadata(ImageEncoder& encoder,
|
||||
const std::vector<int>& metadata_types,
|
||||
InputArrayOfArrays metadata)
|
||||
{
|
||||
size_t nmetadata_chunks = metadata_types.size();
|
||||
for (size_t i = 0; i < nmetadata_chunks; i++) {
|
||||
ImageMetadataType metadata_type = (ImageMetadataType)metadata_types[i];
|
||||
bool ok = encoder->addMetadata(metadata_type, metadata.getMat((int)i));
|
||||
if (!ok) {
|
||||
std::string desc = encoder->getDescription();
|
||||
CV_LOG_WARNING(NULL, "Imgcodecs: metadata of type '"
|
||||
<< metadataTypeToString(metadata_type)
|
||||
<< "' is not supported when encoding '"
|
||||
<< desc << "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read an image into memory and return the information
|
||||
*
|
||||
|
|
@ -419,11 +489,15 @@ static void ApplyExifOrientation(ExifEntry_t orientationTag, OutputArray img)
|
|||
*
|
||||
*/
|
||||
static bool
|
||||
imread_( const String& filename, int flags, OutputArray mat )
|
||||
imread_( const String& filename, int flags, OutputArray mat,
|
||||
std::vector<int>* metadata_types, OutputArrayOfArrays metadata)
|
||||
{
|
||||
/// Search for the relevant decoder to handle the imagery
|
||||
ImageDecoder decoder;
|
||||
|
||||
if (metadata_types)
|
||||
metadata_types->clear();
|
||||
|
||||
#ifdef HAVE_GDAL
|
||||
if(flags != IMREAD_UNCHANGED && (flags & IMREAD_LOAD_GDAL) == IMREAD_LOAD_GDAL ){
|
||||
decoder = GdalDecoder().newDecoder();
|
||||
|
|
@ -509,6 +583,8 @@ imread_( const String& filename, int flags, OutputArray mat )
|
|||
CV_CheckTrue(original_ptr == real_mat.data, "Internal imread issue");
|
||||
success = true;
|
||||
}
|
||||
|
||||
readMetadata(decoder, metadata_types, metadata);
|
||||
}
|
||||
catch (const cv::Exception& e)
|
||||
{
|
||||
|
|
@ -662,7 +738,24 @@ Mat imread( const String& filename, int flags )
|
|||
Mat img;
|
||||
|
||||
/// load the data
|
||||
imread_( filename, flags, img );
|
||||
imread_( filename, flags, img, nullptr, noArray() );
|
||||
|
||||
/// return a reference to the data
|
||||
return img;
|
||||
}
|
||||
|
||||
Mat imreadWithMetadata( const String& filename,
|
||||
std::vector<int>& metadata_types,
|
||||
OutputArrayOfArrays metadata,
|
||||
int flags )
|
||||
{
|
||||
CV_TRACE_FUNCTION();
|
||||
|
||||
/// create the basic container
|
||||
Mat img;
|
||||
|
||||
/// load the data
|
||||
imread_( filename, flags, img, &metadata_types, metadata );
|
||||
|
||||
/// return a reference to the data
|
||||
return img;
|
||||
|
|
@ -673,7 +766,7 @@ void imread( const String& filename, OutputArray dst, int flags )
|
|||
CV_TRACE_FUNCTION();
|
||||
|
||||
/// load the data
|
||||
imread_(filename, flags, dst);
|
||||
imread_(filename, flags, dst, nullptr, noArray());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -946,6 +1039,8 @@ size_t imcount(const String& filename, int flags)
|
|||
|
||||
|
||||
static bool imwrite_( const String& filename, const std::vector<Mat>& img_vec,
|
||||
const std::vector<int>& metadata_types,
|
||||
InputArrayOfArrays metadata,
|
||||
const std::vector<int>& params_, bool flipv )
|
||||
{
|
||||
bool isMultiImg = img_vec.size() > 1;
|
||||
|
|
@ -981,6 +1076,8 @@ static bool imwrite_( const String& filename, const std::vector<Mat>& img_vec,
|
|||
}
|
||||
|
||||
encoder->setDestination( filename );
|
||||
addMetadata(encoder, metadata_types, metadata);
|
||||
|
||||
#if CV_VERSION_MAJOR < 5 && defined(HAVE_IMGCODEC_HDR)
|
||||
bool fixed = false;
|
||||
std::vector<int> params_pair(2);
|
||||
|
|
@ -1055,7 +1152,26 @@ bool imwrite( const String& filename, InputArray _img,
|
|||
img_vec.push_back(_img.getMat());
|
||||
|
||||
CV_Assert(!img_vec.empty());
|
||||
return imwrite_(filename, img_vec, params, false);
|
||||
return imwrite_(filename, img_vec, {}, noArray(), params, false);
|
||||
}
|
||||
|
||||
bool imwriteWithMetadata( const String& filename, InputArray _img,
|
||||
const std::vector<int>& metadata_types,
|
||||
InputArrayOfArrays metadata,
|
||||
const std::vector<int>& params )
|
||||
{
|
||||
CV_TRACE_FUNCTION();
|
||||
|
||||
CV_Assert(!_img.empty());
|
||||
|
||||
std::vector<Mat> img_vec;
|
||||
if (_img.isMatVector() || _img.isUMatVector())
|
||||
_img.getMatVector(img_vec);
|
||||
else
|
||||
img_vec.push_back(_img.getMat());
|
||||
|
||||
CV_Assert(!img_vec.empty());
|
||||
return imwrite_(filename, img_vec, metadata_types, metadata, params, false);
|
||||
}
|
||||
|
||||
static bool imwriteanimation_(const String& filename, const Animation& animation, const std::vector<int>& params)
|
||||
|
|
@ -1140,8 +1256,13 @@ bool imencodeanimation(const String& ext, const Animation& animation, std::vecto
|
|||
}
|
||||
|
||||
static bool
|
||||
imdecode_( const Mat& buf, int flags, Mat& mat )
|
||||
imdecode_( const Mat& buf, int flags, Mat& mat,
|
||||
std::vector<int>* metadata_types,
|
||||
OutputArrayOfArrays metadata )
|
||||
{
|
||||
if (metadata_types)
|
||||
metadata_types->clear();
|
||||
|
||||
CV_Assert(!buf.empty());
|
||||
CV_Assert(buf.isContinuous());
|
||||
CV_Assert(buf.checkVector(1, CV_8U) > 0);
|
||||
|
|
@ -1231,6 +1352,7 @@ imdecode_( const Mat& buf, int flags, Mat& mat )
|
|||
{
|
||||
if (decoder->readData(mat))
|
||||
success = true;
|
||||
readMetadata(decoder, metadata_types, metadata);
|
||||
}
|
||||
catch (const cv::Exception& e)
|
||||
{
|
||||
|
|
@ -1274,7 +1396,7 @@ Mat imdecode( InputArray _buf, int flags )
|
|||
CV_TRACE_FUNCTION();
|
||||
|
||||
Mat buf = _buf.getMat(), img;
|
||||
if (!imdecode_(buf, flags, img))
|
||||
if (!imdecode_(buf, flags, img, nullptr, noArray()))
|
||||
img.release();
|
||||
|
||||
return img;
|
||||
|
|
@ -1286,12 +1408,24 @@ Mat imdecode( InputArray _buf, int flags, Mat* dst )
|
|||
|
||||
Mat buf = _buf.getMat(), img;
|
||||
dst = dst ? dst : &img;
|
||||
if (imdecode_(buf, flags, *dst))
|
||||
if (imdecode_(buf, flags, *dst, nullptr, noArray()))
|
||||
return *dst;
|
||||
else
|
||||
return cv::Mat();
|
||||
}
|
||||
|
||||
Mat imdecodeWithMetadata( InputArray _buf, std::vector<int>& metadata_types,
|
||||
OutputArrayOfArrays metadata, int flags )
|
||||
{
|
||||
CV_TRACE_FUNCTION();
|
||||
|
||||
Mat buf = _buf.getMat(), img;
|
||||
if (!imdecode_(buf, flags, img, &metadata_types, metadata))
|
||||
img.release();
|
||||
|
||||
return img;
|
||||
}
|
||||
|
||||
static bool
|
||||
imdecodemulti_(const Mat& buf, int flags, std::vector<Mat>& mats, int start, int count)
|
||||
{
|
||||
|
|
@ -1447,8 +1581,10 @@ bool imdecodemulti(InputArray _buf, int flags, CV_OUT std::vector<Mat>& mats, co
|
|||
}
|
||||
}
|
||||
|
||||
bool imencode( const String& ext, InputArray _img,
|
||||
std::vector<uchar>& buf, const std::vector<int>& params_ )
|
||||
bool imencodeWithMetadata( const String& ext, InputArray _img,
|
||||
const std::vector<int>& metadata_types,
|
||||
InputArrayOfArrays metadata,
|
||||
std::vector<uchar>& buf, const std::vector<int>& params_ )
|
||||
{
|
||||
CV_TRACE_FUNCTION();
|
||||
|
||||
|
|
@ -1517,6 +1653,7 @@ bool imencode( const String& ext, InputArray _img,
|
|||
code = encoder->setDestination(filename);
|
||||
CV_Assert( code );
|
||||
}
|
||||
addMetadata(encoder, metadata_types, metadata);
|
||||
|
||||
try {
|
||||
if (!isMultiImg)
|
||||
|
|
@ -1553,6 +1690,12 @@ bool imencode( const String& ext, InputArray _img,
|
|||
return code;
|
||||
}
|
||||
|
||||
bool imencode( const String& ext, InputArray img,
|
||||
std::vector<uchar>& buf, const std::vector<int>& params_ )
|
||||
{
|
||||
return imencodeWithMetadata(ext, img, {}, noArray(), buf, params_);
|
||||
}
|
||||
|
||||
bool imencodemulti( const String& ext, InputArrayOfArrays imgs,
|
||||
std::vector<uchar>& buf, const std::vector<int>& params)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -148,7 +148,246 @@ const std::vector<std::string> exif_files
|
|||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(Imgcodecs, Exif,
|
||||
testing::ValuesIn(exif_files));
|
||||
testing::ValuesIn(exif_files));
|
||||
|
||||
static Mat makeCirclesImage(Size size, int type, int nbits)
|
||||
{
|
||||
Mat img(size, type);
|
||||
img.setTo(Scalar::all(0));
|
||||
RNG& rng = theRNG();
|
||||
int maxval = (int)(1 << nbits);
|
||||
for (int i = 0; i < 100; i++) {
|
||||
int x = rng.uniform(0, img.cols);
|
||||
int y = rng.uniform(0, img.rows);
|
||||
int radius = rng.uniform(5, std::min(img.cols, img.rows)/5);
|
||||
int b = rng.uniform(0, maxval);
|
||||
int g = rng.uniform(0, maxval);
|
||||
int r = rng.uniform(0, maxval);
|
||||
circle(img, Point(x, y), radius, Scalar(b, g, r), -1, LINE_AA);
|
||||
}
|
||||
return img;
|
||||
}
|
||||
|
||||
#ifdef HAVE_AVIF
|
||||
TEST(Imgcodecs_Avif, ReadWriteWithExif)
|
||||
{
|
||||
static const uchar exif_data[] = {
|
||||
'M', 'M', 0, '*', 0, 0, 0, 8, 0, 10, 1, 0, 0, 4, 0, 0, 0, 1, 0, 0, 5,
|
||||
0, 1, 1, 0, 4, 0, 0, 0, 1, 0, 0, 2, 208, 1, 2, 0, 3, 0, 0, 0, 1,
|
||||
0, 10, 0, 0, 1, 18, 0, 3, 0, 0, 0, 1, 0, 1, 0, 0, 1, 14, 0, 2, 0, 0,
|
||||
0, '"', 0, 0, 0, 176, 1, '1', 0, 2, 0, 0, 0, 7, 0, 0, 0, 210, 1, 26,
|
||||
0, 5, 0, 0, 0, 1, 0, 0, 0, 218, 1, 27, 0, 5, 0, 0, 0, 1, 0, 0, 0,
|
||||
226, 1, '(', 0, 3, 0, 0, 0, 1, 0, 2, 0, 0, 135, 'i', 0, 4, 0, 0, 0,
|
||||
1, 0, 0, 0, 134, 0, 0, 0, 0, 0, 3, 144, 0, 0, 7, 0, 0, 0, 4, '0', '2',
|
||||
'2', '1', 160, 2, 0, 4, 0, 0, 0, 1, 0, 0, 5, 0, 160, 3, 0, 4, 0, 0,
|
||||
0, 1, 0, 0, 2, 208, 0, 0, 0, 0, 'S', 'a', 'm', 'p', 'l', 'e', ' ', '1', '0',
|
||||
'-', 'b', 'i', 't', ' ', 'i', 'm', 'a', 'g', 'e', ' ', 'w', 'i', 't', 'h', ' ',
|
||||
'm', 'e', 't', 'a', 'd', 'a', 't', 'a', 0, 'O', 'p', 'e', 'n', 'C', 'V', 0, 0,
|
||||
0, 0, 0, 'H', 0, 0, 0, 1, 0, 0, 0, 'H', 0, 0, 0, 1
|
||||
};
|
||||
|
||||
int avif_nbits = 10;
|
||||
int avif_speed = 10;
|
||||
int avif_quality = 85;
|
||||
int imgdepth = avif_nbits > 8 ? CV_16U : CV_8U;
|
||||
int imgtype = CV_MAKETYPE(imgdepth, 3);
|
||||
const string outputname = cv::tempfile(".avif");
|
||||
Mat img = makeCirclesImage(Size(1280, 720), imgtype, avif_nbits);
|
||||
|
||||
std::vector<int> metadata_types = {IMAGE_METADATA_EXIF};
|
||||
std::vector<std::vector<uchar> > metadata(1);
|
||||
metadata[0].assign(exif_data, exif_data + sizeof(exif_data));
|
||||
|
||||
std::vector<int> write_params = {
|
||||
IMWRITE_AVIF_DEPTH, avif_nbits,
|
||||
IMWRITE_AVIF_SPEED, avif_speed,
|
||||
IMWRITE_AVIF_QUALITY, avif_quality
|
||||
};
|
||||
|
||||
imwriteWithMetadata(outputname, img, metadata_types, metadata, write_params);
|
||||
std::vector<uchar> compressed;
|
||||
imencodeWithMetadata(outputname, img, metadata_types, metadata, compressed, write_params);
|
||||
|
||||
std::vector<int> read_metadata_types, read_metadata_types2;
|
||||
std::vector<std::vector<uchar> > read_metadata, read_metadata2;
|
||||
Mat img2 = imreadWithMetadata(outputname, read_metadata_types, read_metadata, IMREAD_UNCHANGED);
|
||||
Mat img3 = imdecodeWithMetadata(compressed, read_metadata_types2, read_metadata2, IMREAD_UNCHANGED);
|
||||
EXPECT_EQ(img2.cols, img.cols);
|
||||
EXPECT_EQ(img2.rows, img.rows);
|
||||
EXPECT_EQ(img2.type(), imgtype);
|
||||
EXPECT_EQ(read_metadata_types, read_metadata_types2);
|
||||
EXPECT_GE(read_metadata_types.size(), 1u);
|
||||
EXPECT_EQ(read_metadata, read_metadata2);
|
||||
EXPECT_EQ(read_metadata_types[0], IMAGE_METADATA_EXIF);
|
||||
EXPECT_EQ(read_metadata_types.size(), read_metadata.size());
|
||||
EXPECT_EQ(read_metadata[0], metadata[0]);
|
||||
EXPECT_EQ(cv::norm(img2, img3, NORM_INF), 0.);
|
||||
double mse = cv::norm(img, img2, NORM_L2SQR)/(img.rows*img.cols);
|
||||
EXPECT_LT(mse, 1500);
|
||||
remove(outputname.c_str());
|
||||
}
|
||||
#endif // HAVE_AVIF
|
||||
|
||||
TEST(Imgcodecs_Jpeg, ReadWriteWithExif)
|
||||
{
|
||||
static const uchar exif_data[] = {
|
||||
'M', 'M', 0, '*', 0, 0, 0, 8, 0, 10, 1, 0, 0, 4, 0, 0, 0, 1, 0, 0, 5,
|
||||
0, 1, 1, 0, 4, 0, 0, 0, 1, 0, 0, 2, 208, 1, 2, 0, 3, 0, 0, 0, 1,
|
||||
0, 8, 0, 0, 1, 18, 0, 3, 0, 0, 0, 1, 0, 1, 0, 0, 1, 14, 0, 2, 0, 0,
|
||||
0, '!', 0, 0, 0, 176, 1, '1', 0, 2, 0, 0, 0, 7, 0, 0, 0, 210, 1, 26,
|
||||
0, 5, 0, 0, 0, 1, 0, 0, 0, 218, 1, 27, 0, 5, 0, 0, 0, 1, 0, 0, 0,
|
||||
226, 1, '(', 0, 3, 0, 0, 0, 1, 0, 2, 0, 0, 135, 'i', 0, 4, 0, 0, 0,
|
||||
1, 0, 0, 0, 134, 0, 0, 0, 0, 0, 3, 144, 0, 0, 7, 0, 0, 0, 4, '0', '2',
|
||||
'2', '1', 160, 2, 0, 4, 0, 0, 0, 1, 0, 0, 5, 0, 160, 3, 0, 4, 0, 0,
|
||||
0, 1, 0, 0, 2, 208, 0, 0, 0, 0, 'S', 'a', 'm', 'p', 'l', 'e', ' ', '8', '-',
|
||||
'b', 'i', 't', ' ', 'i', 'm', 'a', 'g', 'e', ' ', 'w', 'i', 't', 'h', ' ', 'm',
|
||||
'e', 't', 'a', 'd', 'a', 't', 'a', 0, 0, 'O', 'p', 'e', 'n', 'C', 'V', 0, 0,
|
||||
0, 0, 0, 'H', 0, 0, 0, 1, 0, 0, 0, 'H', 0, 0, 0, 1
|
||||
};
|
||||
|
||||
int jpeg_quality = 95;
|
||||
int imgtype = CV_MAKETYPE(CV_8U, 3);
|
||||
const string outputname = cv::tempfile(".jpeg");
|
||||
Mat img = makeCirclesImage(Size(1280, 720), imgtype, 8);
|
||||
|
||||
std::vector<int> metadata_types = {IMAGE_METADATA_EXIF};
|
||||
std::vector<std::vector<uchar> > metadata(1);
|
||||
metadata[0].assign(exif_data, exif_data + sizeof(exif_data));
|
||||
|
||||
std::vector<int> write_params = {
|
||||
IMWRITE_JPEG_QUALITY, jpeg_quality
|
||||
};
|
||||
|
||||
imwriteWithMetadata(outputname, img, metadata_types, metadata, write_params);
|
||||
std::vector<uchar> compressed;
|
||||
imencodeWithMetadata(outputname, img, metadata_types, metadata, compressed, write_params);
|
||||
|
||||
std::vector<int> read_metadata_types, read_metadata_types2;
|
||||
std::vector<std::vector<uchar> > read_metadata, read_metadata2;
|
||||
Mat img2 = imreadWithMetadata(outputname, read_metadata_types, read_metadata, IMREAD_UNCHANGED);
|
||||
Mat img3 = imdecodeWithMetadata(compressed, read_metadata_types2, read_metadata2, IMREAD_UNCHANGED);
|
||||
EXPECT_EQ(img2.cols, img.cols);
|
||||
EXPECT_EQ(img2.rows, img.rows);
|
||||
EXPECT_EQ(img2.type(), imgtype);
|
||||
EXPECT_EQ(read_metadata_types, read_metadata_types2);
|
||||
EXPECT_GE(read_metadata_types.size(), 1u);
|
||||
EXPECT_EQ(read_metadata, read_metadata2);
|
||||
EXPECT_EQ(read_metadata_types[0], IMAGE_METADATA_EXIF);
|
||||
EXPECT_EQ(read_metadata_types.size(), read_metadata.size());
|
||||
EXPECT_EQ(read_metadata[0], metadata[0]);
|
||||
EXPECT_EQ(cv::norm(img2, img3, NORM_INF), 0.);
|
||||
double mse = cv::norm(img, img2, NORM_L2SQR)/(img.rows*img.cols);
|
||||
EXPECT_LT(mse, 80);
|
||||
remove(outputname.c_str());
|
||||
}
|
||||
|
||||
TEST(Imgcodecs_Png, ReadWriteWithExif)
|
||||
{
|
||||
static const uchar exif_data[] = {
|
||||
'M', 'M', 0, '*', 0, 0, 0, 8, 0, 10, 1, 0, 0, 4, 0, 0, 0, 1, 0, 0, 5,
|
||||
0, 1, 1, 0, 4, 0, 0, 0, 1, 0, 0, 2, 208, 1, 2, 0, 3, 0, 0, 0, 1,
|
||||
0, 8, 0, 0, 1, 18, 0, 3, 0, 0, 0, 1, 0, 1, 0, 0, 1, 14, 0, 2, 0, 0,
|
||||
0, '!', 0, 0, 0, 176, 1, '1', 0, 2, 0, 0, 0, 7, 0, 0, 0, 210, 1, 26,
|
||||
0, 5, 0, 0, 0, 1, 0, 0, 0, 218, 1, 27, 0, 5, 0, 0, 0, 1, 0, 0, 0,
|
||||
226, 1, '(', 0, 3, 0, 0, 0, 1, 0, 2, 0, 0, 135, 'i', 0, 4, 0, 0, 0,
|
||||
1, 0, 0, 0, 134, 0, 0, 0, 0, 0, 3, 144, 0, 0, 7, 0, 0, 0, 4, '0', '2',
|
||||
'2', '1', 160, 2, 0, 4, 0, 0, 0, 1, 0, 0, 5, 0, 160, 3, 0, 4, 0, 0,
|
||||
0, 1, 0, 0, 2, 208, 0, 0, 0, 0, 'S', 'a', 'm', 'p', 'l', 'e', ' ', '8', '-',
|
||||
'b', 'i', 't', ' ', 'i', 'm', 'a', 'g', 'e', ' ', 'w', 'i', 't', 'h', ' ', 'm',
|
||||
'e', 't', 'a', 'd', 'a', 't', 'a', 0, 0, 'O', 'p', 'e', 'n', 'C', 'V', 0, 0,
|
||||
0, 0, 0, 'H', 0, 0, 0, 1, 0, 0, 0, 'H', 0, 0, 0, 1
|
||||
};
|
||||
|
||||
int png_compression = 3;
|
||||
int imgtype = CV_MAKETYPE(CV_8U, 3);
|
||||
const string outputname = cv::tempfile(".png");
|
||||
Mat img = makeCirclesImage(Size(1280, 720), imgtype, 8);
|
||||
|
||||
std::vector<int> metadata_types = {IMAGE_METADATA_EXIF};
|
||||
std::vector<std::vector<uchar> > metadata(1);
|
||||
metadata[0].assign(exif_data, exif_data + sizeof(exif_data));
|
||||
|
||||
std::vector<int> write_params = {
|
||||
IMWRITE_PNG_COMPRESSION, png_compression
|
||||
};
|
||||
|
||||
imwriteWithMetadata(outputname, img, metadata_types, metadata, write_params);
|
||||
std::vector<uchar> compressed;
|
||||
imencodeWithMetadata(outputname, img, metadata_types, metadata, compressed, write_params);
|
||||
|
||||
std::vector<int> read_metadata_types, read_metadata_types2;
|
||||
std::vector<std::vector<uchar> > read_metadata, read_metadata2;
|
||||
Mat img2 = imreadWithMetadata(outputname, read_metadata_types, read_metadata, IMREAD_UNCHANGED);
|
||||
Mat img3 = imdecodeWithMetadata(compressed, read_metadata_types2, read_metadata2, IMREAD_UNCHANGED);
|
||||
EXPECT_EQ(img2.cols, img.cols);
|
||||
EXPECT_EQ(img2.rows, img.rows);
|
||||
EXPECT_EQ(img2.type(), imgtype);
|
||||
EXPECT_EQ(read_metadata_types, read_metadata_types2);
|
||||
EXPECT_GE(read_metadata_types.size(), 1u);
|
||||
EXPECT_EQ(read_metadata, read_metadata2);
|
||||
EXPECT_EQ(read_metadata_types[0], IMAGE_METADATA_EXIF);
|
||||
EXPECT_EQ(read_metadata_types.size(), read_metadata.size());
|
||||
EXPECT_EQ(read_metadata[0], metadata[0]);
|
||||
EXPECT_EQ(cv::norm(img2, img3, NORM_INF), 0.);
|
||||
double mse = cv::norm(img, img2, NORM_L2SQR)/(img.rows*img.cols);
|
||||
EXPECT_EQ(mse, 0); // png is lossless
|
||||
remove(outputname.c_str());
|
||||
}
|
||||
|
||||
static size_t locateString(const uchar* exif, size_t exif_size, const std::string& pattern)
|
||||
{
|
||||
size_t plen = pattern.size();
|
||||
for (size_t i = 0; i + plen <= exif_size; i++) {
|
||||
if (exif[i] == pattern[0] && memcmp(&exif[i], pattern.c_str(), plen) == 0)
|
||||
return i;
|
||||
}
|
||||
return 0xFFFFFFFFu;
|
||||
}
|
||||
|
||||
typedef std::tuple<std::string, size_t, std::string, size_t> ReadExif_Sanity_Params;
|
||||
typedef testing::TestWithParam<ReadExif_Sanity_Params> ReadExif_Sanity;
|
||||
|
||||
TEST_P(ReadExif_Sanity, Check)
|
||||
{
|
||||
std::string filename = get<0>(GetParam());
|
||||
size_t exif_size = get<1>(GetParam());
|
||||
std::string pattern = get<2>(GetParam());
|
||||
size_t ploc = get<3>(GetParam());
|
||||
|
||||
const string root = cvtest::TS::ptr()->get_data_path();
|
||||
filename = root + filename;
|
||||
|
||||
std::vector<int> metadata_types;
|
||||
std::vector<Mat> metadata;
|
||||
Mat img = imreadWithMetadata(filename, metadata_types, metadata, 1);
|
||||
|
||||
EXPECT_EQ(img.type(), CV_8UC3);
|
||||
ASSERT_GE(metadata_types.size(), 1u);
|
||||
EXPECT_EQ(metadata_types.size(), metadata.size());
|
||||
const Mat& exif = metadata[IMAGE_METADATA_EXIF];
|
||||
EXPECT_EQ(exif.type(), CV_8U);
|
||||
EXPECT_EQ(exif.total(), exif_size);
|
||||
ASSERT_GE(exif_size, 26u); // minimal exif should take at least 26 bytes
|
||||
// (the header + IDF0 with at least 1 entry).
|
||||
EXPECT_TRUE(exif.data[0] == 'I' || exif.data[0] == 'M');
|
||||
EXPECT_EQ(exif.data[0], exif.data[1]);
|
||||
EXPECT_EQ(locateString(exif.data, exif_size, pattern), ploc);
|
||||
}
|
||||
|
||||
static const std::vector<ReadExif_Sanity_Params> exif_sanity_params
|
||||
{
|
||||
#ifdef HAVE_JPEG
|
||||
{"readwrite/testExifOrientation_3.jpg", 916, "Photoshop", 120},
|
||||
#endif
|
||||
#ifdef OPENCV_IMGCODECS_PNG_WITH_EXIF
|
||||
{"readwrite/testExifOrientation_5.png", 112, "ExifTool", 102},
|
||||
#endif
|
||||
#ifdef HAVE_AVIF
|
||||
{"readwrite/testExifOrientation_7.avif", 913, "Photoshop", 120},
|
||||
#endif
|
||||
};
|
||||
|
||||
INSTANTIATE_TEST_CASE_P(Imgcodecs, ReadExif_Sanity,
|
||||
testing::ValuesIn(exif_sanity_params));
|
||||
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,18 @@ class imread_test(NewOpenCVTests):
|
|||
cv.imread(path, img)
|
||||
self.assertEqual(cv.norm(ref, img, cv.NORM_INF), 0.0)
|
||||
|
||||
def test_imread_with_meta(self):
|
||||
path = self.extraTestDataPath + '/highgui/readwrite/testExifOrientation_1.jpg'
|
||||
img, meta_types, meta_data = cv.imreadWithMetadata(path)
|
||||
self.assertTrue(img is not None)
|
||||
self.assertTrue(meta_types is not None)
|
||||
self.assertTrue(meta_data is not None)
|
||||
|
||||
path = self.extraTestDataPath + '/highgui/readwrite/testExifOrientation_1.png'
|
||||
img, meta_types, meta_data = cv.imreadWithMetadata(path)
|
||||
self.assertTrue(img is not None)
|
||||
self.assertTrue(meta_types is not None)
|
||||
self.assertTrue(meta_data is not None)
|
||||
|
||||
if __name__ == '__main__':
|
||||
NewOpenCVTests.bootstrap()
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user