From ba5ddd649957e42e61f3ebba620bf503e55e9c60 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Thu, 4 Apr 2019 00:35:08 +0000 Subject: [PATCH 1/3] imgcodecs(tiff): sanitize tiff decoder - more checks - drop separate branches for 32FC1/32FC3(read) handling - added for 32F/64F non-compressed - added tests for 32FC3 (RAW + hdr SGILOG compression) - added test 64FC1 - dump tiff errors on stderr --- modules/imgcodecs/src/grfmt_tiff.cpp | 666 ++++++++++++--------------- modules/imgcodecs/src/grfmt_tiff.hpp | 7 +- modules/imgcodecs/src/utils.cpp | 43 +- modules/imgcodecs/src/utils.hpp | 38 +- modules/imgcodecs/test/test_tiff.cpp | 62 ++- 5 files changed, 396 insertions(+), 420 deletions(-) diff --git a/modules/imgcodecs/src/grfmt_tiff.cpp b/modules/imgcodecs/src/grfmt_tiff.cpp index 7fa02562ad..b1f3d8192c 100644 --- a/modules/imgcodecs/src/grfmt_tiff.cpp +++ b/modules/imgcodecs/src/grfmt_tiff.cpp @@ -48,6 +48,8 @@ #include "precomp.hpp" #ifdef HAVE_TIFF +#include + #include "grfmt_tiff.hpp" #include @@ -61,23 +63,58 @@ using namespace tiff_dummy_namespace; namespace cv { +#define CV_TIFF_CHECK_CALL(call) \ + if (0 == (call)) { \ + CV_LOG_WARNING(NULL, "OpenCV TIFF(line " << __LINE__ << "): failed " #call); \ + CV_Error(Error::StsError, "OpenCV TIFF: failed " #call); \ + } + +#define CV_TIFF_CHECK_CALL_INFO(call) \ + if (0 == (call)) { \ + CV_LOG_INFO(NULL, "OpenCV TIFF(line " << __LINE__ << "): failed optional call: " #call ", ignoring"); \ + } + +#define CV_TIFF_CHECK_CALL_DEBUG(call) \ + if (0 == (call)) { \ + CV_LOG_DEBUG(NULL, "OpenCV TIFF(line " << __LINE__ << "): failed optional call: " #call ", ignoring"); \ + } + +static void cv_tiffCloseHandle(void* handle) +{ + TIFFClose((TIFF*)handle); +} + +static void cv_tiffErrorHandler(const char* module, const char* fmt, va_list ap) +{ + if (cv::utils::logging::getLogLevel() < cv::utils::logging::LOG_LEVEL_DEBUG) + return; + // TODO cv::vformat() with va_list parameter + fprintf(stderr, "OpenCV TIFF: "); + if (module != NULL) + fprintf(stderr, "%s: ", module); + fprintf(stderr, "Warning, "); + vfprintf(stderr, fmt, ap); + fprintf(stderr, ".\n"); +} + +static bool cv_tiffSetErrorHandler_() +{ + TIFFSetErrorHandler(cv_tiffErrorHandler); + TIFFSetWarningHandler(cv_tiffErrorHandler); + return true; +} + +static bool cv_tiffSetErrorHandler() +{ + static bool v = cv_tiffSetErrorHandler_(); + return v; +} static const char fmtSignTiffII[] = "II\x2a\x00"; static const char fmtSignTiffMM[] = "MM\x00\x2a"; -static int grfmt_tiff_err_handler_init = 0; -static void GrFmtSilentTIFFErrorHandler( const char*, const char*, va_list ) {} - TiffDecoder::TiffDecoder() { - m_tif = 0; - if( !grfmt_tiff_err_handler_init ) - { - grfmt_tiff_err_handler_init = 1; - - TIFFSetErrorHandler( GrFmtSilentTIFFErrorHandler ); - TIFFSetWarningHandler( GrFmtSilentTIFFErrorHandler ); - } m_hdr = false; m_buf_supported = true; m_buf_pos = 0; @@ -86,12 +123,7 @@ TiffDecoder::TiffDecoder() void TiffDecoder::close() { - if( m_tif ) - { - TIFF* tif = (TIFF*)m_tif; - TIFFClose( tif ); - m_tif = 0; - } + m_tif.release(); } TiffDecoder::~TiffDecoder() @@ -113,11 +145,13 @@ bool TiffDecoder::checkSignature( const String& signature ) const int TiffDecoder::normalizeChannelsNumber(int channels) const { + CV_Assert(channels <= 4); return channels > 4 ? 4 : channels; } ImageDecoder TiffDecoder::newDecoder() const { + cv_tiffSetErrorHandler(); return makePtr(); } @@ -201,8 +235,8 @@ bool TiffDecoder::readHeader() { bool result = false; - TIFF* tif = static_cast(m_tif); - if (!m_tif) + TIFF* tif = static_cast(m_tif.get()); + if (!tif) { // TIFFOpen() mode flags are different to fopen(). A 'b' in mode "rb" has no effect when reading. // http://www.remotesensing.org/libtiff/man/TIFFOpen.3tiff.html @@ -221,25 +255,30 @@ bool TiffDecoder::readHeader() { tif = TIFFOpen(m_filename.c_str(), "r"); } + if (tif) + m_tif.reset(tif, cv_tiffCloseHandle); + else + m_tif.release(); } - if( tif ) + if (tif) { uint32 wdth = 0, hght = 0; uint16 photometric = 0; - m_tif = tif; - if( TIFFGetField( tif, TIFFTAG_IMAGEWIDTH, &wdth ) && - TIFFGetField( tif, TIFFTAG_IMAGELENGTH, &hght ) && - TIFFGetField( tif, TIFFTAG_PHOTOMETRIC, &photometric )) + CV_TIFF_CHECK_CALL(TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &wdth)); + CV_TIFF_CHECK_CALL(TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &hght)); + CV_TIFF_CHECK_CALL(TIFFGetField(tif, TIFFTAG_PHOTOMETRIC, &photometric)); + { - uint16 bpp=8, ncn = photometric > 1 ? 3 : 1; - TIFFGetField( tif, TIFFTAG_BITSPERSAMPLE, &bpp ); - TIFFGetField( tif, TIFFTAG_SAMPLESPERPIXEL, &ncn ); + bool isGrayScale = photometric == PHOTOMETRIC_MINISWHITE || photometric == PHOTOMETRIC_MINISBLACK; + uint16 bpp = 8, ncn = isGrayScale ? 1 : 3; + CV_TIFF_CHECK_CALL(TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, &bpp)); + CV_TIFF_CHECK_CALL_DEBUG(TIFFGetField(tif, TIFFTAG_SAMPLESPERPIXEL, &ncn)); m_width = wdth; m_height = hght; - if((bpp == 32 && ncn == 3) || photometric == PHOTOMETRIC_LOGLUV) + if (ncn == 3 && photometric == PHOTOMETRIC_LOGLUV) { m_type = CV_32FC3; m_hdr = true; @@ -256,23 +295,23 @@ bool TiffDecoder::readHeader() switch(bpp) { case 1: - m_type = CV_MAKETYPE(CV_8U, photometric > 1 ? wanted_channels : 1); + m_type = CV_MAKETYPE(CV_8U, !isGrayScale ? wanted_channels : 1); result = true; break; case 8: - m_type = CV_MAKETYPE(CV_8U, photometric > 1 ? wanted_channels : 1); + m_type = CV_MAKETYPE(CV_8U, !isGrayScale ? wanted_channels : 1); result = true; break; case 16: - m_type = CV_MAKETYPE(CV_16U, photometric > 1 ? wanted_channels : 1); + m_type = CV_MAKETYPE(CV_16U, !isGrayScale ? wanted_channels : 1); result = true; break; case 32: - m_type = CV_MAKETYPE(CV_32F, photometric > 1 ? 3 : 1); + m_type = CV_MAKETYPE(CV_32F, wanted_channels); result = true; break; case 64: - m_type = CV_MAKETYPE(CV_64F, photometric > 1 ? 3 : 1); + m_type = CV_MAKETYPE(CV_64F, wanted_channels); result = true; break; default: @@ -290,206 +329,210 @@ bool TiffDecoder::readHeader() bool TiffDecoder::nextPage() { // Prepare the next page, if any. - return m_tif && - TIFFReadDirectory(static_cast(m_tif)) && + return !m_tif.empty() && + TIFFReadDirectory(static_cast(m_tif.get())) && readHeader(); } bool TiffDecoder::readData( Mat& img ) { - if(m_hdr && img.type() == CV_32FC3) + int type_ = img.type(); + int depth = CV_MAT_DEPTH(type_); + + CV_Assert(!m_tif.empty()); + TIFF* tif = (TIFF*)m_tif.get(); + + uint16 photometric = (uint16)-1; + CV_TIFF_CHECK_CALL(TIFFGetField(tif, TIFFTAG_PHOTOMETRIC, &photometric)); + + if (m_hdr && depth >= CV_32F) { - return readData_32FC3(img); + CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_SGILOGDATAFMT, SGILOGDATAFMT_FLOAT)); } - if(img.type() == CV_32FC1) - { - return readData_32FC1(img); - } - bool result = false; + bool color = img.channels() > 1; - if( img.depth() != CV_8U && img.depth() != CV_16U && img.depth() != CV_32F && img.depth() != CV_64F ) - return false; + CV_CheckType(type_, depth == CV_8U || depth == CV_16U || depth == CV_32F || depth == CV_64F, ""); - if( m_tif && m_width && m_height ) + if (m_width && m_height) { - TIFF* tif = (TIFF*)m_tif; - uint32 tile_width0 = m_width, tile_height0 = 0; - int x, y, i; - int is_tiled = TIFFIsTiled(tif); - uint16 photometric; - TIFFGetField( tif, TIFFTAG_PHOTOMETRIC, &photometric ); - uint16 bpp = 8, ncn = photometric > 1 ? 3 : 1; - TIFFGetField( tif, TIFFTAG_BITSPERSAMPLE, &bpp ); - TIFFGetField( tif, TIFFTAG_SAMPLESPERPIXEL, &ncn ); + int is_tiled = TIFFIsTiled(tif) != 0; + bool isGrayScale = photometric == PHOTOMETRIC_MINISWHITE || photometric == PHOTOMETRIC_MINISBLACK; + uint16 bpp = 8, ncn = isGrayScale ? 1 : 3; + CV_TIFF_CHECK_CALL(TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, &bpp)); + CV_TIFF_CHECK_CALL_DEBUG(TIFFGetField(tif, TIFFTAG_SAMPLESPERPIXEL, &ncn)); uint16 img_orientation = ORIENTATION_TOPLEFT; - TIFFGetField( tif, TIFFTAG_ORIENTATION, &img_orientation); + CV_TIFF_CHECK_CALL_DEBUG(TIFFGetField(tif, TIFFTAG_ORIENTATION, &img_orientation)); bool vert_flip = (img_orientation == ORIENTATION_BOTRIGHT) || (img_orientation == ORIENTATION_RIGHTBOT) || (img_orientation == ORIENTATION_BOTLEFT) || (img_orientation == ORIENTATION_LEFTBOT); const int bitsPerByte = 8; int dst_bpp = (int)(img.elemSize1() * bitsPerByte); int wanted_channels = normalizeChannelsNumber(img.channels()); - if(dst_bpp == 8) + if (dst_bpp == 8) { char errmsg[1024]; - if(!TIFFRGBAImageOK( tif, errmsg )) + if (!TIFFRGBAImageOK(tif, errmsg)) { + CV_LOG_WARNING(NULL, "OpenCV TIFF: TIFFRGBAImageOK: " << errmsg); close(); return false; } } - if( (!is_tiled) || - (is_tiled && - TIFFGetField( tif, TIFFTAG_TILEWIDTH, &tile_width0 ) && - TIFFGetField( tif, TIFFTAG_TILELENGTH, &tile_height0 ))) - { - if(!is_tiled) - TIFFGetField( tif, TIFFTAG_ROWSPERSTRIP, &tile_height0 ); + uint32 tile_width0 = m_width, tile_height0 = 0; - if( tile_width0 <= 0 ) + if (is_tiled) + { + CV_TIFF_CHECK_CALL(TIFFGetField(tif, TIFFTAG_TILEWIDTH, &tile_width0)); + CV_TIFF_CHECK_CALL(TIFFGetField(tif, TIFFTAG_TILELENGTH, &tile_height0)); + } + else + { + // optional + CV_TIFF_CHECK_CALL_DEBUG(TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &tile_height0)); + } + + { + if (tile_width0 == 0) tile_width0 = m_width; - if( tile_height0 <= 0 || - (!is_tiled && tile_height0 == std::numeric_limits::max()) ) + if (tile_height0 == 0 || + (!is_tiled && tile_height0 == std::numeric_limits::max()) ) tile_height0 = m_height; - if(dst_bpp == 8) { + if (dst_bpp == 8) + { // we will use TIFFReadRGBA* functions, so allocate temporary buffer for 32bit RGBA bpp = 8; ncn = 4; } - const size_t buffer_size = (bpp/bitsPerByte) * ncn * tile_height0 * tile_width0; - AutoBuffer _buffer( buffer_size ); + else if (dst_bpp == 32 || dst_bpp == 64) + { + CV_Assert(ncn == img.channels()); + CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_IEEEFP)); + } + const size_t buffer_size = (bpp / bitsPerByte) * ncn * tile_height0 * tile_width0; + AutoBuffer _buffer(buffer_size); uchar* buffer = _buffer.data(); ushort* buffer16 = (ushort*)buffer; - float* buffer32 = (float*)buffer; - double* buffer64 = (double*)buffer; int tileidx = 0; - for( y = 0; y < m_height; y += tile_height0 ) + for (int y = 0; y < m_height; y += (int)tile_height0) { - int tile_height = tile_height0; + int tile_height = std::min((int)tile_height0, m_height - y); - if( y + tile_height > m_height ) - tile_height = m_height - y; + const int img_y = vert_flip ? m_height - y - tile_height : y; - uchar* data = img.ptr(vert_flip ? m_height - y - tile_height : y); - - for( x = 0; x < m_width; x += tile_width0, tileidx++ ) + for(int x = 0; x < m_width; x += (int)tile_width0, tileidx++) { - int tile_width = tile_width0, ok; + int tile_width = std::min((int)tile_width0, m_width - x); - if( x + tile_width > m_width ) - tile_width = m_width - x; - - switch(dst_bpp) + switch (dst_bpp) { case 8: { - uchar * bstart = buffer; - if( !is_tiled ) - ok = TIFFReadRGBAStrip( tif, y, (uint32*)buffer ); + uchar* bstart = buffer; + if (!is_tiled) + { + CV_TIFF_CHECK_CALL(TIFFReadRGBAStrip(tif, y, (uint32*)buffer)); + } else { - ok = TIFFReadRGBATile( tif, x, y, (uint32*)buffer ); - //Tiles fill the buffer from the bottom up + CV_TIFF_CHECK_CALL(TIFFReadRGBATile(tif, x, y, (uint32*)buffer)); + // Tiles fill the buffer from the bottom up bstart += (tile_height0 - tile_height) * tile_width0 * 4; } - if( !ok ) - { - close(); - return false; - } - for( i = 0; i < tile_height; i++ ) - if( color ) + for (int i = 0; i < tile_height; i++) + { + if (color) { if (wanted_channels == 4) { - icvCvt_BGRA2RGBA_8u_C4R( bstart + i*tile_width0*4, 0, - data + x*4 + img.step*(tile_height - i - 1), 0, - cvSize(tile_width,1) ); + icvCvt_BGRA2RGBA_8u_C4R(bstart + i*tile_width0*4, 0, + img.ptr(img_y + tile_height - i - 1, x), 0, + Size(tile_width, 1) ); } else { - icvCvt_BGRA2BGR_8u_C4C3R( bstart + i*tile_width0*4, 0, - data + x*3 + img.step*(tile_height - i - 1), 0, - cvSize(tile_width,1), 2 ); + icvCvt_BGRA2BGR_8u_C4C3R(bstart + i*tile_width0*4, 0, + img.ptr(img_y + tile_height - i - 1, x), 0, + Size(tile_width, 1), 2); } } else + { icvCvt_BGRA2Gray_8u_C4C1R( bstart + i*tile_width0*4, 0, - data + x + img.step*(tile_height - i - 1), 0, - cvSize(tile_width,1), 2 ); + img.ptr(img_y + tile_height - i - 1, x), 0, + Size(tile_width, 1), 2); + } + } break; } case 16: { - if( !is_tiled ) - ok = (int)TIFFReadEncodedStrip( tif, tileidx, (uint32*)buffer, buffer_size ) >= 0; - else - ok = (int)TIFFReadEncodedTile( tif, tileidx, (uint32*)buffer, buffer_size ) >= 0; - - if( !ok ) + if (!is_tiled) { - close(); - return false; + CV_TIFF_CHECK_CALL((int)TIFFReadEncodedStrip(tif, tileidx, (uint32*)buffer, buffer_size) >= 0); + } + else + { + CV_TIFF_CHECK_CALL((int)TIFFReadEncodedTile(tif, tileidx, (uint32*)buffer, buffer_size) >= 0); } - for( i = 0; i < tile_height; i++ ) + for (int i = 0; i < tile_height; i++) { - if( color ) + if (color) { - if( ncn == 1 ) + if (ncn == 1) { icvCvt_Gray2BGR_16u_C1C3R(buffer16 + i*tile_width0*ncn, 0, - (ushort*)(data + img.step*i) + x*3, 0, - cvSize(tile_width,1) ); + img.ptr(img_y + i, x), 0, + Size(tile_width, 1)); } - else if( ncn == 3 ) + else if (ncn == 3) { icvCvt_RGB2BGR_16u_C3R(buffer16 + i*tile_width0*ncn, 0, - (ushort*)(data + img.step*i) + x*3, 0, - cvSize(tile_width,1) ); + img.ptr(img_y + i, x), 0, + Size(tile_width, 1)); } else if (ncn == 4) { if (wanted_channels == 4) { icvCvt_BGRA2RGBA_16u_C4R(buffer16 + i*tile_width0*ncn, 0, - (ushort*)(data + img.step*i) + x * 4, 0, - cvSize(tile_width, 1)); + img.ptr(img_y + i, x), 0, + Size(tile_width, 1)); } else { icvCvt_BGRA2BGR_16u_C4C3R(buffer16 + i*tile_width0*ncn, 0, - (ushort*)(data + img.step*i) + x * 3, 0, - cvSize(tile_width, 1), 2); + img.ptr(img_y + i, x), 0, + Size(tile_width, 1), 2); } } else { icvCvt_BGRA2BGR_16u_C4C3R(buffer16 + i*tile_width0*ncn, 0, - (ushort*)(data + img.step*i) + x*3, 0, - cvSize(tile_width,1), 2 ); + img.ptr(img_y + i, x), 0, + Size(tile_width, 1), 2); } } else { if( ncn == 1 ) { - memcpy((ushort*)(data + img.step*i)+x, + memcpy(img.ptr(img_y + i, x), buffer16 + i*tile_width0*ncn, - tile_width*sizeof(buffer16[0])); + tile_width*sizeof(ushort)); } else { icvCvt_BGRA2Gray_16u_CnC1R(buffer16 + i*tile_width0*ncn, 0, - (ushort*)(data + img.step*i) + x, 0, - cvSize(tile_width,1), ncn, 2 ); + img.ptr(img_y + i, x), 0, + Size(tile_width, 1), ncn, 2); } } } @@ -500,120 +543,43 @@ bool TiffDecoder::readData( Mat& img ) case 64: { if( !is_tiled ) - ok = (int)TIFFReadEncodedStrip( tif, tileidx, buffer, buffer_size ) >= 0; + { + CV_TIFF_CHECK_CALL((int)TIFFReadEncodedStrip(tif, tileidx, buffer, buffer_size) >= 0); + } else - ok = (int)TIFFReadEncodedTile( tif, tileidx, buffer, buffer_size ) >= 0; - - if( !ok || ncn != 1 ) { - close(); - return false; - } - - for( i = 0; i < tile_height; i++ ) - { - if(dst_bpp == 32) - { - memcpy((float*)(data + img.step*i)+x, - buffer32 + i*tile_width0*ncn, - tile_width*sizeof(buffer32[0])); - } - else - { - memcpy((double*)(data + img.step*i)+x, - buffer64 + i*tile_width0*ncn, - tile_width*sizeof(buffer64[0])); - } + CV_TIFF_CHECK_CALL((int)TIFFReadEncodedTile(tif, tileidx, buffer, buffer_size) >= 0); } + Mat m_tile(Size(tile_width0, tile_height0), CV_MAKETYPE((dst_bpp == 32) ? CV_32F : CV_64F, ncn), buffer); + Rect roi_tile(0, 0, tile_width, tile_height); + Rect roi_img(x, img_y, tile_width, tile_height); + if (!m_hdr && ncn == 3) + cvtColor(m_tile(roi_tile), img(roi_img), COLOR_RGB2BGR); + else if (!m_hdr && ncn == 4) + cvtColor(m_tile(roi_tile), img(roi_img), COLOR_RGBA2BGRA); + else + m_tile(roi_tile).copyTo(img(roi_img)); break; } default: { - close(); - return false; + CV_Assert(0 && "OpenCV TIFF: unsupported depth"); } - } - } - } - - result = true; + } // switch (dst_bpp) + } // for x + } // for y } } - return result; -} - -bool TiffDecoder::readData_32FC3(Mat& img) -{ - int rows_per_strip = 0, photometric = 0; - if(!m_tif) - { - return false; - } - TIFF *tif = static_cast(m_tif); - TIFFGetField(tif, TIFFTAG_ROWSPERSTRIP, &rows_per_strip); - TIFFGetField( tif, TIFFTAG_PHOTOMETRIC, &photometric ); - TIFFSetField(tif, TIFFTAG_SGILOGDATAFMT, SGILOGDATAFMT_FLOAT); - int size = 3 * m_width * m_height * sizeof (float); - tstrip_t strip_size = 3 * m_width * rows_per_strip; - float *ptr = img.ptr(); - for (tstrip_t i = 0; i < TIFFNumberOfStrips(tif); i++, ptr += strip_size) - { - TIFFReadEncodedStrip(tif, i, ptr, size); - size -= strip_size * sizeof(float); - } - close(); - if(photometric == PHOTOMETRIC_LOGLUV) + if (m_hdr && depth >= CV_32F) { + CV_Assert(photometric == PHOTOMETRIC_LOGLUV); cvtColor(img, img, COLOR_XYZ2BGR); } - else - { - cvtColor(img, img, COLOR_RGB2BGR); - } return true; } -bool TiffDecoder::readData_32FC1(Mat& img) -{ - if(!m_tif) - { - return false; - } - TIFF *tif = static_cast(m_tif); - - uint32 img_width, img_height; - TIFFGetField(tif,TIFFTAG_IMAGEWIDTH, &img_width); - TIFFGetField(tif,TIFFTAG_IMAGELENGTH, &img_height); - if(img.size() != Size(img_width,img_height)) - { - close(); - return false; - } - tsize_t scanlength = TIFFScanlineSize(tif); - tdata_t buf = _TIFFmalloc(scanlength); - float* data; - bool result = true; - for (uint32 row = 0; row < img_height; row++) - { - if (TIFFReadScanline(tif, buf, row) != 1) - { - result = false; - break; - } - data=(float*)buf; - for (uint32 i=0; i(row,i) = data[i]; - } - } - _TIFFfree(buf); - close(); - - return result; -} - ////////////////////////////////////////////////////////////////////////////////////////// TiffEncoder::TiffEncoder() @@ -633,7 +599,7 @@ ImageEncoder TiffEncoder::newEncoder() const bool TiffEncoder::isFormatSupported( int depth ) const { - return depth == CV_8U || depth == CV_16U || depth == CV_32F; + return depth == CV_8U || depth == CV_16U || depth == CV_32F || depth == CV_64F; } void TiffEncoder::writeTag( WLByteStream& strm, TiffTag tag, @@ -656,6 +622,8 @@ public: TIFF* open () { + // do NOT put "wb" as the mode, because the b means "big endian" mode, not "binary" mode. + // http://www.remotesensing.org/libtiff/man/TIFFOpen.3tiff.html return TIFFClientOpen( "", "w", reinterpret_cast(this), &TiffEncoderBufHelper::read, &TiffEncoderBufHelper::write, &TiffEncoderBufHelper::seek, &TiffEncoderBufHelper::close, &TiffEncoderBufHelper::size, @@ -721,35 +689,39 @@ private: toff_t m_buf_pos; }; -static void readParam(const std::vector& params, int key, int& value) +static bool readParam(const std::vector& params, int key, int& value) { - for(size_t i = 0; i + 1 < params.size(); i += 2) - if(params[i] == key) + for (size_t i = 0; i + 1 < params.size(); i += 2) + { + if (params[i] == key) { - value = params[i+1]; - break; + value = params[i + 1]; + return true; } + } + return false; } bool TiffEncoder::writeLibTiff( const std::vector& img_vec, const std::vector& params) { // do NOT put "wb" as the mode, because the b means "big endian" mode, not "binary" mode. // http://www.remotesensing.org/libtiff/man/TIFFOpen.3tiff.html - TIFF* pTiffHandle; + TIFF* tif = NULL; TiffEncoderBufHelper buf_helper(m_buf); if ( m_buf ) { - pTiffHandle = buf_helper.open(); + tif = buf_helper.open(); } else { - pTiffHandle = TIFFOpen(m_filename.c_str(), "w"); + tif = TIFFOpen(m_filename.c_str(), "w"); } - if (!pTiffHandle) + if (!tif) { return false; } + cv::Ptr tif_cleanup(tif, cv_tiffCloseHandle); //Settings that matter to all images int compression = COMPRESSION_LZW; @@ -768,7 +740,29 @@ bool TiffEncoder::writeLibTiff( const std::vector& img_vec, const std::vect const Mat& img = img_vec[page]; int channels = img.channels(); int width = img.cols, height = img.rows; - int depth = img.depth(); + int type = img.type(); + int depth = CV_MAT_DEPTH(type); + CV_CheckType(type, depth == CV_8U || depth == CV_16U || depth == CV_32F || depth == CV_64F, ""); + CV_CheckType(type, channels >= 1 && channels <= 4, ""); + + CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, width)); + CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_IMAGELENGTH, height)); + + if (img_vec.size() > 1) + { + CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_SUBFILETYPE, FILETYPE_PAGE)); + CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_PAGENUMBER, page, img_vec.size())); + } + + int compression_param = -1; // OPENCV_FUTURE + if (type == CV_32FC3 && (!readParam(params, IMWRITE_TIFF_COMPRESSION, compression_param) || compression_param == COMPRESSION_SGILOG)) + { + if (!write_32FC3_SGILOG(img, tif)) + return false; + continue; + } + + int page_compression = compression; int bitsPerChannel = -1; switch (depth) @@ -783,9 +777,20 @@ bool TiffEncoder::writeLibTiff( const std::vector& img_vec, const std::vect bitsPerChannel = 16; break; } + case CV_32F: + { + bitsPerChannel = 32; + page_compression = COMPRESSION_NONE; + break; + } + case CV_64F: + { + bitsPerChannel = 64; + page_compression = COMPRESSION_NONE; + break; + } default: { - TIFFClose(pTiffHandle); return false; } } @@ -795,57 +800,42 @@ bool TiffEncoder::writeLibTiff( const std::vector& img_vec, const std::vect int rowsPerStrip = (int)((1 << 13) / fileStep); readParam(params, TIFFTAG_ROWSPERSTRIP, rowsPerStrip); + rowsPerStrip = std::max(1, std::min(height, rowsPerStrip)); - if (rowsPerStrip < 1) - rowsPerStrip = 1; + int colorspace = channels > 1 ? PHOTOMETRIC_RGB : PHOTOMETRIC_MINISBLACK; - if (rowsPerStrip > height) - rowsPerStrip = height; + CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, bitsPerChannel)); + CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_COMPRESSION, page_compression)); + CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, colorspace)); + CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, channels)); + CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG)); + CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, rowsPerStrip)); - int colorspace = channels > 1 ? PHOTOMETRIC_RGB : PHOTOMETRIC_MINISBLACK; + CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_SAMPLEFORMAT, depth >= CV_32F ? SAMPLEFORMAT_IEEEFP : SAMPLEFORMAT_UINT)); - if (!TIFFSetField(pTiffHandle, TIFFTAG_IMAGEWIDTH, width) - || !TIFFSetField(pTiffHandle, TIFFTAG_IMAGELENGTH, height) - || !TIFFSetField(pTiffHandle, TIFFTAG_BITSPERSAMPLE, bitsPerChannel) - || !TIFFSetField(pTiffHandle, TIFFTAG_COMPRESSION, compression) - || !TIFFSetField(pTiffHandle, TIFFTAG_PHOTOMETRIC, colorspace) - || !TIFFSetField(pTiffHandle, TIFFTAG_SAMPLESPERPIXEL, channels) - || !TIFFSetField(pTiffHandle, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG) - || !TIFFSetField(pTiffHandle, TIFFTAG_ROWSPERSTRIP, rowsPerStrip) - || (img_vec.size() > 1 && ( - !TIFFSetField(pTiffHandle, TIFFTAG_SUBFILETYPE, FILETYPE_PAGE) - || !TIFFSetField(pTiffHandle, TIFFTAG_PAGENUMBER, page, img_vec.size() ))) - ) + if (page_compression != COMPRESSION_NONE) { - TIFFClose(pTiffHandle); - return false; + CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_PREDICTOR, predictor)); } - if (compression != COMPRESSION_NONE && !TIFFSetField(pTiffHandle, TIFFTAG_PREDICTOR, predictor)) + if (resUnit >= RESUNIT_NONE && resUnit <= RESUNIT_CENTIMETER) { - TIFFClose(pTiffHandle); - return false; + CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_RESOLUTIONUNIT, resUnit)); } - - if (((resUnit >= RESUNIT_NONE && resUnit <= RESUNIT_CENTIMETER) && !TIFFSetField(pTiffHandle, TIFFTAG_RESOLUTIONUNIT, resUnit)) - || (dpiX >= 0 && !TIFFSetField(pTiffHandle, TIFFTAG_XRESOLUTION, (float)dpiX)) - || (dpiY >= 0 && !TIFFSetField(pTiffHandle, TIFFTAG_YRESOLUTION, (float)dpiY)) - ) + if (dpiX >= 0) { - TIFFClose(pTiffHandle); - return false; + CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_XRESOLUTION, (float)dpiX)); + } + if (dpiY >= 0) + { + CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_YRESOLUTION, (float)dpiY)); } - // row buffer, because TIFFWriteScanline modifies the original data! - size_t scanlineSize = TIFFScanlineSize(pTiffHandle); + size_t scanlineSize = TIFFScanlineSize(tif); AutoBuffer _buffer(scanlineSize + 32); - uchar* buffer = _buffer.data(); - if (!buffer) - { - TIFFClose(pTiffHandle); - return false; - } + uchar* buffer = _buffer.data(); CV_DbgAssert(buffer); + Mat m_buffer(Size(width, 1), CV_MAKETYPE(depth, channels), buffer, (size_t)scanlineSize); for (int y = 0; y < height; ++y) { @@ -859,122 +849,54 @@ bool TiffEncoder::writeLibTiff( const std::vector& img_vec, const std::vect case 3: { - if (depth == CV_8U) - icvCvt_BGR2RGB_8u_C3R( img.ptr(y), 0, buffer, 0, cvSize(width, 1)); - else - icvCvt_BGR2RGB_16u_C3R( img.ptr(y), 0, (ushort*)buffer, 0, cvSize(width, 1)); + cvtColor(img(Rect(0, y, width, 1)), (const Mat&)m_buffer, COLOR_BGR2RGB); break; } case 4: { - if (depth == CV_8U) - icvCvt_BGRA2RGBA_8u_C4R( img.ptr(y), 0, buffer, 0, cvSize(width, 1)); - else - icvCvt_BGRA2RGBA_16u_C4R( img.ptr(y), 0, (ushort*)buffer, 0, cvSize(width, 1)); + cvtColor(img(Rect(0, y, width, 1)), (const Mat&)m_buffer, COLOR_BGRA2RGBA); break; } default: { - TIFFClose(pTiffHandle); - return false; + CV_Assert(0); } } - int writeResult = TIFFWriteScanline(pTiffHandle, buffer, y, 0); - if (writeResult != 1) - { - TIFFClose(pTiffHandle); - return false; - } + CV_TIFF_CHECK_CALL(TIFFWriteScanline(tif, buffer, y, 0) == 1); } - TIFFWriteDirectory(pTiffHandle); - + CV_TIFF_CHECK_CALL(TIFFWriteDirectory(tif)); } - TIFFClose(pTiffHandle); return true; } -bool TiffEncoder::write_32FC3(const Mat& _img) +bool TiffEncoder::write_32FC3_SGILOG(const Mat& _img, void* tif_) { + TIFF* tif = (TIFF*)tif_; + CV_Assert(tif); + Mat img; cvtColor(_img, img, COLOR_BGR2XYZ); - TIFF* tif; - - TiffEncoderBufHelper buf_helper(m_buf); - if ( m_buf ) + //done by caller: CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, img.cols)); + //done by caller: CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_IMAGELENGTH, img.rows)); + CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 3)); + CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 32)); + CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_SGILOG)); + CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_LOGLUV)); + CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG)); + CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_SGILOGDATAFMT, SGILOGDATAFMT_FLOAT)); + CV_TIFF_CHECK_CALL(TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, 1)); + const int strip_size = 3 * img.cols; + for (int i = 0; i < img.rows; i++) { - tif = buf_helper.open(); + CV_TIFF_CHECK_CALL(TIFFWriteEncodedStrip(tif, i, (tdata_t)img.ptr(i), strip_size * sizeof(float)) != (tsize_t)-1); } - else - { - tif = TIFFOpen(m_filename.c_str(), "w"); - } - - if (!tif) - { - return false; - } - TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, img.cols); - TIFFSetField(tif, TIFFTAG_IMAGELENGTH, img.rows); - TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 3); - TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_SGILOG); - TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_LOGLUV); - TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); - TIFFSetField(tif, TIFFTAG_SGILOGDATAFMT, SGILOGDATAFMT_FLOAT); - TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, 1); - int strip_size = 3 * img.cols; - float *ptr = const_cast(img.ptr()); - for (int i = 0; i < img.rows; i++, ptr += strip_size) - { - TIFFWriteEncodedStrip(tif, i, ptr, strip_size * sizeof(float)); - } - TIFFClose(tif); - return true; -} - -bool TiffEncoder::write_32FC1(const Mat& _img) -{ - - TIFF* tif; - - TiffEncoderBufHelper buf_helper(m_buf); - if ( m_buf ) - { - tif = buf_helper.open(); - } - else - { - tif = TIFFOpen(m_filename.c_str(), "w"); - } - - if (!tif) - { - return false; - } - - TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, _img.cols); - TIFFSetField(tif, TIFFTAG_IMAGELENGTH, _img.rows); - TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 1); - TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 32); - TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK); - TIFFSetField(tif, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_IEEEFP); - TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE); - for (uint32 row = 0; row < (uint32)_img.rows; row++) - { - if (TIFFWriteScanline(tif, (tdata_t)_img.ptr(row), row, 1) != 1) - { - TIFFClose(tif); - return false; - } - } - TIFFWriteDirectory(tif); - TIFFClose(tif); - + CV_TIFF_CHECK_CALL(TIFFWriteDirectory(tif)); return true; } @@ -985,18 +907,10 @@ bool TiffEncoder::writemulti(const std::vector& img_vec, const std::vector< bool TiffEncoder::write( const Mat& img, const std::vector& params) { - int depth = img.depth(); + int type = img.type(); + int depth = CV_MAT_DEPTH(type); - if(img.type() == CV_32FC3) - { - return write_32FC3(img); - } - if(img.type() == CV_32FC1) - { - return write_32FC1(img); - } - - CV_Assert(depth == CV_8U || depth == CV_16U); + CV_CheckType(type, depth == CV_8U || depth == CV_16U || depth == CV_32F || depth == CV_64F, ""); std::vector img_vec; img_vec.push_back(img); diff --git a/modules/imgcodecs/src/grfmt_tiff.hpp b/modules/imgcodecs/src/grfmt_tiff.hpp index cd1b55ed02..ee5bcb7018 100644 --- a/modules/imgcodecs/src/grfmt_tiff.hpp +++ b/modules/imgcodecs/src/grfmt_tiff.hpp @@ -106,10 +106,8 @@ public: ImageDecoder newDecoder() const CV_OVERRIDE; protected: - void* m_tif; + cv::Ptr m_tif; int normalizeChannelsNumber(int channels) const; - bool readData_32FC3(Mat& img); - bool readData_32FC1(Mat& img); bool m_hdr; size_t m_buf_pos; @@ -139,8 +137,7 @@ protected: int count, int value ); bool writeLibTiff( const std::vector& img_vec, const std::vector& params ); - bool write_32FC3( const Mat& img ); - bool write_32FC1( const Mat& img ); + bool write_32FC3_SGILOG(const Mat& img, void* tif); private: TiffEncoder(const TiffEncoder &); // copy disabled diff --git a/modules/imgcodecs/src/utils.cpp b/modules/imgcodecs/src/utils.cpp index 6aeb631060..14cce8314b 100644 --- a/modules/imgcodecs/src/utils.cpp +++ b/modules/imgcodecs/src/utils.cpp @@ -42,6 +42,8 @@ #include "precomp.hpp" #include "utils.hpp" +namespace cv { + int validateToInt(size_t sz) { int valueInt = (int)sz; @@ -56,7 +58,7 @@ int validateToInt(size_t sz) void icvCvt_BGR2Gray_8u_C3C1R( const uchar* rgb, int rgb_step, uchar* gray, int gray_step, - CvSize size, int _swap_rb ) + Size size, int _swap_rb ) { int i; int swap_rb = _swap_rb ? 2 : 0; @@ -75,7 +77,7 @@ void icvCvt_BGR2Gray_8u_C3C1R( const uchar* rgb, int rgb_step, void icvCvt_BGRA2Gray_16u_CnC1R( const ushort* rgb, int rgb_step, ushort* gray, int gray_step, - CvSize size, int ncn, int _swap_rb ) + Size size, int ncn, int _swap_rb ) { int i; int swap_rb = _swap_rb ? 2 : 0; @@ -94,7 +96,7 @@ void icvCvt_BGRA2Gray_16u_CnC1R( const ushort* rgb, int rgb_step, void icvCvt_BGRA2Gray_8u_C4C1R( const uchar* rgba, int rgba_step, uchar* gray, int gray_step, - CvSize size, int _swap_rb ) + Size size, int _swap_rb ) { int i; int swap_rb = _swap_rb ? 2 : 0; @@ -112,7 +114,7 @@ void icvCvt_BGRA2Gray_8u_C4C1R( const uchar* rgba, int rgba_step, void icvCvt_Gray2BGR_8u_C1C3R( const uchar* gray, int gray_step, - uchar* bgr, int bgr_step, CvSize size ) + uchar* bgr, int bgr_step, Size size ) { int i; for( ; size.height--; gray += gray_step ) @@ -127,7 +129,7 @@ void icvCvt_Gray2BGR_8u_C1C3R( const uchar* gray, int gray_step, void icvCvt_Gray2BGR_16u_C1C3R( const ushort* gray, int gray_step, - ushort* bgr, int bgr_step, CvSize size ) + ushort* bgr, int bgr_step, Size size ) { int i; for( ; size.height--; gray += gray_step/sizeof(gray[0]) ) @@ -143,7 +145,7 @@ void icvCvt_Gray2BGR_16u_C1C3R( const ushort* gray, int gray_step, void icvCvt_BGRA2BGR_8u_C4C3R( const uchar* bgra, int bgra_step, uchar* bgr, int bgr_step, - CvSize size, int _swap_rb ) + Size size, int _swap_rb ) { int i; int swap_rb = _swap_rb ? 2 : 0; @@ -163,7 +165,7 @@ void icvCvt_BGRA2BGR_8u_C4C3R( const uchar* bgra, int bgra_step, void icvCvt_BGRA2BGR_16u_C4C3R( const ushort* bgra, int bgra_step, ushort* bgr, int bgr_step, - CvSize size, int _swap_rb ) + Size size, int _swap_rb ) { int i; int swap_rb = _swap_rb ? 2 : 0; @@ -182,7 +184,7 @@ void icvCvt_BGRA2BGR_16u_C4C3R( const ushort* bgra, int bgra_step, void icvCvt_BGRA2RGBA_8u_C4R( const uchar* bgra, int bgra_step, - uchar* rgba, int rgba_step, CvSize size ) + uchar* rgba, int rgba_step, Size size ) { int i; for( ; size.height--; ) @@ -200,7 +202,7 @@ void icvCvt_BGRA2RGBA_8u_C4R( const uchar* bgra, int bgra_step, } void icvCvt_BGRA2RGBA_16u_C4R( const ushort* bgra, int bgra_step, - ushort* rgba, int rgba_step, CvSize size ) + ushort* rgba, int rgba_step, Size size ) { int i; for( ; size.height--; ) @@ -220,7 +222,7 @@ void icvCvt_BGRA2RGBA_16u_C4R( const ushort* bgra, int bgra_step, void icvCvt_BGR2RGB_8u_C3R( const uchar* bgr, int bgr_step, - uchar* rgb, int rgb_step, CvSize size ) + uchar* rgb, int rgb_step, Size size ) { int i; for( ; size.height--; ) @@ -237,7 +239,7 @@ void icvCvt_BGR2RGB_8u_C3R( const uchar* bgr, int bgr_step, void icvCvt_BGR2RGB_16u_C3R( const ushort* bgr, int bgr_step, - ushort* rgb, int rgb_step, CvSize size ) + ushort* rgb, int rgb_step, Size size ) { int i; for( ; size.height--; ) @@ -256,7 +258,7 @@ void icvCvt_BGR2RGB_16u_C3R( const ushort* bgr, int bgr_step, typedef unsigned short ushort; void icvCvt_BGR5552Gray_8u_C2C1R( const uchar* bgr555, int bgr555_step, - uchar* gray, int gray_step, CvSize size ) + uchar* gray, int gray_step, Size size ) { int i; for( ; size.height--; gray += gray_step, bgr555 += bgr555_step ) @@ -273,7 +275,7 @@ void icvCvt_BGR5552Gray_8u_C2C1R( const uchar* bgr555, int bgr555_step, void icvCvt_BGR5652Gray_8u_C2C1R( const uchar* bgr565, int bgr565_step, - uchar* gray, int gray_step, CvSize size ) + uchar* gray, int gray_step, Size size ) { int i; for( ; size.height--; gray += gray_step, bgr565 += bgr565_step ) @@ -290,7 +292,7 @@ void icvCvt_BGR5652Gray_8u_C2C1R( const uchar* bgr565, int bgr565_step, void icvCvt_BGR5552BGR_8u_C2C3R( const uchar* bgr555, int bgr555_step, - uchar* bgr, int bgr_step, CvSize size ) + uchar* bgr, int bgr_step, Size size ) { int i; for( ; size.height--; bgr555 += bgr555_step ) @@ -308,7 +310,7 @@ void icvCvt_BGR5552BGR_8u_C2C3R( const uchar* bgr555, int bgr555_step, void icvCvt_BGR5652BGR_8u_C2C3R( const uchar* bgr565, int bgr565_step, - uchar* bgr, int bgr_step, CvSize size ) + uchar* bgr, int bgr_step, Size size ) { int i; for( ; size.height--; bgr565 += bgr565_step ) @@ -326,7 +328,7 @@ void icvCvt_BGR5652BGR_8u_C2C3R( const uchar* bgr565, int bgr565_step, void icvCvt_CMYK2BGR_8u_C4C3R( const uchar* cmyk, int cmyk_step, - uchar* bgr, int bgr_step, CvSize size ) + uchar* bgr, int bgr_step, Size size ) { int i; for( ; size.height--; ) @@ -346,7 +348,7 @@ void icvCvt_CMYK2BGR_8u_C4C3R( const uchar* cmyk, int cmyk_step, void icvCvt_CMYK2Gray_8u_C4C1R( const uchar* cmyk, int cmyk_step, - uchar* gray, int gray_step, CvSize size ) + uchar* gray, int gray_step, Size size ) { int i; for( ; size.height--; ) @@ -371,7 +373,7 @@ void CvtPaletteToGray( const PaletteEntry* palette, uchar* grayPalette, int entr int i; for( i = 0; i < entries; i++ ) { - icvCvt_BGR2Gray_8u_C3C1R( (uchar*)(palette + i), 0, grayPalette + i, 0, cvSize(1,1) ); + icvCvt_BGR2Gray_8u_C3C1R( (uchar*)(palette + i), 0, grayPalette + i, 0, Size(1,1) ); } } @@ -598,6 +600,9 @@ uchar* FillGrayRow1( uchar* data, uchar* indices, int len, uchar* palette ) return data; } +} // namespace + +using namespace cv; CV_IMPL void cvConvertImage( const CvArr* srcarr, CvArr* dstarr, int flags ) @@ -652,7 +657,7 @@ cvConvertImage( const CvArr* srcarr, CvArr* dstarr, int flags ) uchar *s = src->data.ptr, *d = dst->data.ptr; int s_step = src->step, d_step = dst->step; int code = src_cn*10 + dst_cn; - CvSize size = {src->cols, src->rows}; + Size size(src->cols, src->rows); if( CV_IS_MAT_CONT(src->type & dst->type) ) { diff --git a/modules/imgcodecs/src/utils.hpp b/modules/imgcodecs/src/utils.hpp index 6e0ec95826..43eb907f76 100644 --- a/modules/imgcodecs/src/utils.hpp +++ b/modules/imgcodecs/src/utils.hpp @@ -42,6 +42,8 @@ #ifndef _UTILS_H_ #define _UTILS_H_ +namespace cv { + int validateToInt(size_t step); template static inline @@ -68,53 +70,53 @@ struct PaletteEntry void icvCvt_BGR2Gray_8u_C3C1R( const uchar* bgr, int bgr_step, uchar* gray, int gray_step, - CvSize size, int swap_rb=0 ); + Size size, int swap_rb=0 ); void icvCvt_BGRA2Gray_8u_C4C1R( const uchar* bgra, int bgra_step, uchar* gray, int gray_step, - CvSize size, int swap_rb=0 ); + Size size, int swap_rb=0 ); void icvCvt_BGRA2Gray_16u_CnC1R( const ushort* bgra, int bgra_step, ushort* gray, int gray_step, - CvSize size, int ncn, int swap_rb=0 ); + Size size, int ncn, int swap_rb=0 ); void icvCvt_Gray2BGR_8u_C1C3R( const uchar* gray, int gray_step, - uchar* bgr, int bgr_step, CvSize size ); + uchar* bgr, int bgr_step, Size size ); void icvCvt_Gray2BGR_16u_C1C3R( const ushort* gray, int gray_step, - ushort* bgr, int bgr_step, CvSize size ); + ushort* bgr, int bgr_step, Size size ); void icvCvt_BGRA2BGR_8u_C4C3R( const uchar* bgra, int bgra_step, uchar* bgr, int bgr_step, - CvSize size, int swap_rb=0 ); + Size size, int swap_rb=0 ); void icvCvt_BGRA2BGR_16u_C4C3R( const ushort* bgra, int bgra_step, ushort* bgr, int bgr_step, - CvSize size, int _swap_rb ); + Size size, int _swap_rb ); void icvCvt_BGR2RGB_8u_C3R( const uchar* bgr, int bgr_step, - uchar* rgb, int rgb_step, CvSize size ); + uchar* rgb, int rgb_step, Size size ); #define icvCvt_RGB2BGR_8u_C3R icvCvt_BGR2RGB_8u_C3R void icvCvt_BGR2RGB_16u_C3R( const ushort* bgr, int bgr_step, - ushort* rgb, int rgb_step, CvSize size ); + ushort* rgb, int rgb_step, Size size ); #define icvCvt_RGB2BGR_16u_C3R icvCvt_BGR2RGB_16u_C3R void icvCvt_BGRA2RGBA_8u_C4R( const uchar* bgra, int bgra_step, - uchar* rgba, int rgba_step, CvSize size ); + uchar* rgba, int rgba_step, Size size ); #define icvCvt_RGBA2BGRA_8u_C4R icvCvt_BGRA2RGBA_8u_C4R void icvCvt_BGRA2RGBA_16u_C4R( const ushort* bgra, int bgra_step, - ushort* rgba, int rgba_step, CvSize size ); + ushort* rgba, int rgba_step, Size size ); #define icvCvt_RGBA2BGRA_16u_C4R icvCvt_BGRA2RGBA_16u_C4R void icvCvt_BGR5552Gray_8u_C2C1R( const uchar* bgr555, int bgr555_step, - uchar* gray, int gray_step, CvSize size ); + uchar* gray, int gray_step, Size size ); void icvCvt_BGR5652Gray_8u_C2C1R( const uchar* bgr565, int bgr565_step, - uchar* gray, int gray_step, CvSize size ); + uchar* gray, int gray_step, Size size ); void icvCvt_BGR5552BGR_8u_C2C3R( const uchar* bgr555, int bgr555_step, - uchar* bgr, int bgr_step, CvSize size ); + uchar* bgr, int bgr_step, Size size ); void icvCvt_BGR5652BGR_8u_C2C3R( const uchar* bgr565, int bgr565_step, - uchar* bgr, int bgr_step, CvSize size ); + uchar* bgr, int bgr_step, Size size ); void icvCvt_CMYK2BGR_8u_C4C3R( const uchar* cmyk, int cmyk_step, - uchar* bgr, int bgr_step, CvSize size ); + uchar* bgr, int bgr_step, Size size ); void icvCvt_CMYK2Gray_8u_C4C1R( const uchar* ycck, int ycck_step, - uchar* gray, int gray_step, CvSize size ); + uchar* gray, int gray_step, Size size ); void FillGrayPalette( PaletteEntry* palette, int bpp, bool negative = false ); bool IsColorPalette( PaletteEntry* palette, int bpp ); @@ -136,4 +138,6 @@ CV_INLINE bool isBigEndian( void ) return (((const int*)"\0\x1\x2\x3\x4\x5\x6\x7")[0] & 255) != 0; } +} // namespace + #endif/*_UTILS_H_*/ diff --git a/modules/imgcodecs/test/test_tiff.cpp b/modules/imgcodecs/test/test_tiff.cpp index 76a2cd4cd4..add15ff681 100644 --- a/modules/imgcodecs/test/test_tiff.cpp +++ b/modules/imgcodecs/test/test_tiff.cpp @@ -158,12 +158,68 @@ TEST(Imgcodecs_Tiff, readWrite_32FC1) ASSERT_TRUE(cv::imwrite(filenameOutput, img)); const Mat img2 = cv::imread(filenameOutput, IMREAD_UNCHANGED); - ASSERT_EQ(img2.type(),img.type()); - ASSERT_EQ(img2.size(),img.size()); - EXPECT_GE(1e-3, cvtest::norm(img, img2, NORM_INF | NORM_RELATIVE)); + ASSERT_EQ(img2.type(), img.type()); + ASSERT_EQ(img2.size(), img.size()); + EXPECT_LE(cvtest::norm(img, img2, NORM_INF | NORM_RELATIVE), 1e-3); EXPECT_EQ(0, remove(filenameOutput.c_str())); } +TEST(Imgcodecs_Tiff, readWrite_64FC1) +{ + const string root = cvtest::TS::ptr()->get_data_path(); + const string filenameInput = root + "readwrite/test64FC1.tiff"; + const string filenameOutput = cv::tempfile(".tiff"); + const Mat img = cv::imread(filenameInput, IMREAD_UNCHANGED); + ASSERT_FALSE(img.empty()); + ASSERT_EQ(CV_64FC1, img.type()); + + ASSERT_TRUE(cv::imwrite(filenameOutput, img)); + const Mat img2 = cv::imread(filenameOutput, IMREAD_UNCHANGED); + ASSERT_EQ(img2.type(), img.type()); + ASSERT_EQ(img2.size(), img.size()); + EXPECT_LE(cvtest::norm(img, img2, NORM_INF | NORM_RELATIVE), 1e-3); + EXPECT_EQ(0, remove(filenameOutput.c_str())); +} + +TEST(Imgcodecs_Tiff, readWrite_32FC3_SGILOG) +{ + const string root = cvtest::TS::ptr()->get_data_path(); + const string filenameInput = root + "readwrite/test32FC3_sgilog.tiff"; + const string filenameOutput = cv::tempfile(".tiff"); + const Mat img = cv::imread(filenameInput, IMREAD_UNCHANGED); + ASSERT_FALSE(img.empty()); + ASSERT_EQ(CV_32FC3, img.type()); + + ASSERT_TRUE(cv::imwrite(filenameOutput, img)); + const Mat img2 = cv::imread(filenameOutput, IMREAD_UNCHANGED); + ASSERT_EQ(img2.type(), img.type()); + ASSERT_EQ(img2.size(), img.size()); + EXPECT_LE(cvtest::norm(img, img2, NORM_INF | NORM_RELATIVE), 0.01); + EXPECT_EQ(0, remove(filenameOutput.c_str())); +} + +TEST(Imgcodecs_Tiff, readWrite_32FC3_RAW) +{ + const string root = cvtest::TS::ptr()->get_data_path(); + const string filenameInput = root + "readwrite/test32FC3_raw.tiff"; + const string filenameOutput = cv::tempfile(".tiff"); + const Mat img = cv::imread(filenameInput, IMREAD_UNCHANGED); + ASSERT_FALSE(img.empty()); + ASSERT_EQ(CV_32FC3, img.type()); + + std::vector params; + params.push_back(IMWRITE_TIFF_COMPRESSION); + params.push_back(1/*COMPRESSION_NONE*/); + + ASSERT_TRUE(cv::imwrite(filenameOutput, img, params)); + const Mat img2 = cv::imread(filenameOutput, IMREAD_UNCHANGED); + ASSERT_EQ(img2.type(), img.type()); + ASSERT_EQ(img2.size(), img.size()); + EXPECT_LE(cvtest::norm(img, img2, NORM_INF | NORM_RELATIVE), 1e-3); + EXPECT_EQ(0, remove(filenameOutput.c_str())); +} + + //================================================================================================== typedef testing::TestWithParam Imgcodecs_Tiff_Modes; From aa167434e6e277917bc8b7aa8d940c316f63ce19 Mon Sep 17 00:00:00 2001 From: BALACHANDAR S Date: Thu, 4 Apr 2019 20:07:45 +0530 Subject: [PATCH 2/3] Merge pull request #14252 from balachandarsv:master-mac-openvino-support * Mac support for op inference engine Adding condition to check for mac and add corresponding libraries * Adding mac support in test cases --- modules/dnn/src/op_inf_engine.cpp | 2 ++ modules/dnn/test/test_ie_models.cpp | 2 ++ 2 files changed, 4 insertions(+) diff --git a/modules/dnn/src/op_inf_engine.cpp b/modules/dnn/src/op_inf_engine.cpp index 788a15e2f4..7e6fcaf1c6 100644 --- a/modules/dnn/src/op_inf_engine.cpp +++ b/modules/dnn/src/op_inf_engine.cpp @@ -784,6 +784,8 @@ void InfEngineBackendNet::initPlugin(InferenceEngine::ICNNNetwork& net) continue; #ifdef _WIN32 std::string libName = "cpu_extension" + suffixes[i] + ".dll"; + #elif defined(__APPLE__) + std::string libName = "libcpu_extension" + suffixes[i] + ".dylib"; #else std::string libName = "libcpu_extension" + suffixes[i] + ".so"; #endif // _WIN32 diff --git a/modules/dnn/test/test_ie_models.cpp b/modules/dnn/test/test_ie_models.cpp index ea4633bce2..be463789e6 100644 --- a/modules/dnn/test/test_ie_models.cpp +++ b/modules/dnn/test/test_ie_models.cpp @@ -172,6 +172,8 @@ void runIE(Target target, const std::string& xmlPath, const std::string& binPath continue; #ifdef _WIN32 std::string libName = "cpu_extension" + suffixes[i] + ".dll"; +#elif defined(__APPLE__) + std::string libName = "libcpu_extension" + suffixes[i] + ".dylib"; #else std::string libName = "libcpu_extension" + suffixes[i] + ".so"; #endif // _WIN32 From ab21dc6d4750088bffd6aed14c018359ff4ec535 Mon Sep 17 00:00:00 2001 From: mehlukas Date: Thu, 4 Apr 2019 16:44:03 +0200 Subject: [PATCH 3/3] Merge pull request #14245 from mehlukas:3.4-fixtutorial * improve thresholding tutorial, fix grammar issues and incorrections * keep full list of simple thresholding types --- .../py_thresholding/py_thresholding.markdown | 104 +++++++++--------- 1 file changed, 49 insertions(+), 55 deletions(-) diff --git a/doc/py_tutorials/py_imgproc/py_thresholding/py_thresholding.markdown b/doc/py_tutorials/py_imgproc/py_thresholding/py_thresholding.markdown index 896b5f7d0c..3b9c1f5989 100644 --- a/doc/py_tutorials/py_imgproc/py_thresholding/py_thresholding.markdown +++ b/doc/py_tutorials/py_imgproc/py_thresholding/py_thresholding.markdown @@ -4,20 +4,21 @@ Image Thresholding {#tutorial_py_thresholding} Goal ---- -- In this tutorial, you will learn Simple thresholding, Adaptive thresholding, Otsu's thresholding - etc. -- You will learn these functions : **cv.threshold**, **cv.adaptiveThreshold** etc. +- In this tutorial, you will learn Simple thresholding, Adaptive thresholding and Otsu's thresholding. +- You will learn the functions **cv.threshold** and **cv.adaptiveThreshold**. Simple Thresholding ------------------- -Here, the matter is straight forward. If pixel value is greater than a threshold value, it is -assigned one value (may be white), else it is assigned another value (may be black). The function -used is **cv.threshold**. First argument is the source image, which **should be a grayscale -image**. Second argument is the threshold value which is used to classify the pixel values. Third -argument is the maxVal which represents the value to be given if pixel value is more than (sometimes -less than) the threshold value. OpenCV provides different styles of thresholding and it is decided -by the fourth parameter of the function. Different types are: +Here, the matter is straight forward. For every pixel, the same threshold value is applied. +If the pixel value is smaller than the threshold, it is set to 0, otherwise it is set to a maximum value. +The function **cv.threshold** is used to apply the thresholding. +The first argument is the source image, which **should be a grayscale image**. +The second argument is the threshold value which is used to classify the pixel values. +The third argument is the maximum value which is assigned to pixel values exceeding the threshold. +OpenCV provides different types of thresholding which is given by the fourth parameter of the function. +Basic thresholding as described above is done by using the type cv.THRESH_BINARY. +All simple thresholding types are: - cv.THRESH_BINARY - cv.THRESH_BINARY_INV @@ -25,12 +26,12 @@ by the fourth parameter of the function. Different types are: - cv.THRESH_TOZERO - cv.THRESH_TOZERO_INV -Documentation clearly explain what each type is meant for. Please check out the documentation. +See the documentation of the types for the differences. -Two outputs are obtained. First one is a **retval** which will be explained later. Second output is -our **thresholded image**. +The method returns two outputs. +The first is the threshold that was used and the second output is the **thresholded image**. -Code : +This code compares the different simple thresholding types: @code{.py} import cv2 as cv import numpy as np @@ -53,34 +54,31 @@ for i in xrange(6): plt.show() @endcode -@note To plot multiple images, we have used plt.subplot() function. Please checkout Matplotlib docs -for more details. +@note To plot multiple images, we have used the plt.subplot() function. Please checkout the matplotlib docs for more details. -Result is given below : +The code yields this result: ![image](images/threshold.jpg) Adaptive Thresholding --------------------- -In the previous section, we used a global value as threshold value. But it may not be good in all -the conditions where image has different lighting conditions in different areas. In that case, we go -for adaptive thresholding. In this, the algorithm calculate the threshold for a small regions of the -image. So we get different thresholds for different regions of the same image and it gives us better -results for images with varying illumination. +In the previous section, we used one global value as a threshold. +But this might not be good in all cases, e.g. if an image has different lighting conditions in different areas. +In that case, adaptive thresholding thresholding can help. +Here, the algorithm determines the threshold for a pixel based on a small region around it. +So we get different thresholds for different regions of the same image which gives better results for images with varying illumination. -It has three ‘special’ input params and only one output argument. +Additionally to the parameters described above, the method cv.adaptiveThreshold three input parameters: -**Adaptive Method** - It decides how thresholding value is calculated. - - cv.ADAPTIVE_THRESH_MEAN_C : threshold value is the mean of neighbourhood area. - - cv.ADAPTIVE_THRESH_GAUSSIAN_C : threshold value is the weighted sum of neighbourhood - values where weights are a gaussian window. +The **adaptiveMethod** decides how the threshold value is calculated: + - cv.ADAPTIVE_THRESH_MEAN_C: The threshold value is the mean of the neighbourhood area minus the constant **C**. + - cv.ADAPTIVE_THRESH_GAUSSIAN_C: The threshold value is a gaussian-weighted sum of the neighbourhood + values minus the constant **C**. -**Block Size** - It decides the size of neighbourhood area. +The **blockSize** determines the size of the neighbourhood area and **C** is a constant that is subtracted from the mean or weighted sum of the neighbourhood pixels. -**C** - It is just a constant which is subtracted from the mean or weighted mean calculated. - -Below piece of code compares global thresholding and adaptive thresholding for an image with varying +The code below compares global thresholding and adaptive thresholding for an image with varying illumination: @code{.py} import cv2 as cv @@ -106,33 +104,30 @@ for i in xrange(4): plt.xticks([]),plt.yticks([]) plt.show() @endcode -Result : +Result: ![image](images/ada_threshold.jpg) -Otsu’s Binarization +Otsu's Binarization ------------------- -In the first section, I told you there is a second parameter **retVal**. Its use comes when we go -for Otsu’s Binarization. So what is it? +In global thresholding, we used an arbitrary chosen value as a threshold. +In contrast, Otsu's method avoids having to choose a value and determines it automatically. -In global thresholding, we used an arbitrary value for threshold value, right? So, how can we know a -value we selected is good or not? Answer is, trial and error method. But consider a **bimodal -image** (*In simple words, bimodal image is an image whose histogram has two peaks*). For that -image, we can approximately take a value in the middle of those peaks as threshold value, right ? -That is what Otsu binarization does. So in simple words, it automatically calculates a threshold -value from image histogram for a bimodal image. (For images which are not bimodal, binarization -won’t be accurate.) +Consider an image with only two distinct image values (*bimodal image*), where the histogram would only consist of two peaks. +A good threshold would be in the middle of those two values. +Similarly, Otsu's method determines an optimal global threshold value from the image histogram. -For this, our cv.threshold() function is used, but pass an extra flag, cv.THRESH_OTSU. **For -threshold value, simply pass zero**. Then the algorithm finds the optimal threshold value and -returns you as the second output, retVal. If Otsu thresholding is not used, retVal is same as the -threshold value you used. +In order to do so, the cv.threshold() function is used, where cv.THRESH_OTSU is passed as an extra flag. +The threshold value can be chosen arbitrary. +The algorithm then finds the optimal threshold value which is returned as the first output. -Check out below example. Input image is a noisy image. In first case, I applied global thresholding -for a value of 127. In second case, I applied Otsu’s thresholding directly. In third case, I -filtered image with a 5x5 gaussian kernel to remove the noise, then applied Otsu thresholding. See -how noise filtering improves the result. +Check out the example below. +The input image is a noisy image. +In the first case, global thresholding with a value of 127 is applied. +In the second case, Otsu's thresholding is applied directly. +In the third case, the image is first filtered with a 5x5 gaussian kernel to remove the noise, then Otsu thresholding is applied. +See how noise filtering improves the result. @code{.py} import cv2 as cv import numpy as np @@ -167,17 +162,17 @@ for i in xrange(3): plt.title(titles[i*3+2]), plt.xticks([]), plt.yticks([]) plt.show() @endcode -Result : +Result: ![image](images/otsu.jpg) -### How Otsu's Binarization Works? +### How does Otsu's Binarization work? This section demonstrates a Python implementation of Otsu's binarization to show how it works actually. If you are not interested, you can skip this. Since we are working with bimodal images, Otsu's algorithm tries to find a threshold value (t) which -minimizes the **weighted within-class variance** given by the relation : +minimizes the **weighted within-class variance** given by the relation: \f[\sigma_w^2(t) = q_1(t)\sigma_1^2(t)+q_2(t)\sigma_2^2(t)\f] @@ -186,7 +181,7 @@ where \f[q_1(t) = \sum_{i=1}^{t} P(i) \quad \& \quad q_2(t) = \sum_{i=t+1}^{I} P(i)\f]\f[\mu_1(t) = \sum_{i=1}^{t} \frac{iP(i)}{q_1(t)} \quad \& \quad \mu_2(t) = \sum_{i=t+1}^{I} \frac{iP(i)}{q_2(t)}\f]\f[\sigma_1^2(t) = \sum_{i=1}^{t} [i-\mu_1(t)]^2 \frac{P(i)}{q_1(t)} \quad \& \quad \sigma_2^2(t) = \sum_{i=t+1}^{I} [i-\mu_2(t)]^2 \frac{P(i)}{q_2(t)}\f] It actually finds a value of t which lies in between two peaks such that variances to both classes -are minimum. It can be simply implemented in Python as follows: +are minimal. It can be simply implemented in Python as follows: @code{.py} img = cv.imread('noisy2.png',0) blur = cv.GaussianBlur(img,(5,5),0) @@ -220,7 +215,6 @@ for i in xrange(1,256): ret, otsu = cv.threshold(blur,0,255,cv.THRESH_BINARY+cv.THRESH_OTSU) print( "{} {}".format(thresh,ret) ) @endcode -*(Some of the functions may be new here, but we will cover them in coming chapters)* Additional Resources --------------------