The imread and imdecode processing of EXIF orientation in opencv 3.2 is not uniform

Keywords: Linux OpenCV github

EXIF format (or JPEG, you can see the wiki explanation, for orientation problem, it is called EXIF format and JFIF distinguish) with orientation information in the picture (that is, the picture may actually rotate 90 or 180 degrees or 270 degrees). If the digital device is stored in EXIF format, even if the device rotates 90 or 180 degrees or 270 degrees during shooting, the picture will still look "positive" when opened with an image viewer that supports orientation information processing.

Under Linux, you can use the file command to see if the picture is in EXIF format, such as 2.jpeg, which is in EXIF format.

➜  Downloads file 2.jpeg 
2.jpeg: JPEG image data, EXIF standard
➜  Downloads file 2_decode.jpeg 
2_decode.jpeg: JPEG image data, JFIF standard 1.0

opencv 2.4.13 and previous 2.4.xx versions, either imread or imdecode, did not process orientation information. In version 3.2, imread correctly handles orientation information, but imdecode does not process orientation information. To enable imdecode to process orientation information correctly, either change the open CV source code, or first imwrite the image to disk and imread it again (imread correctly handles orientation information, imwrite has become JFIF format, there is no orientation information), and have not yet gone to github to confirm why the behavior of imread and imdecode is not uniform.--

Looking at the imread source code, you see that an ApplyExifOrientation function handles orientation information.

static void ApplyExifOrientation(const String& filename, Mat& img)

In version 3.2, imread source code:

static void ApplyExifOrientation(const String& filename, Mat& img)
{
    int orientation = IMAGE_ORIENTATION_TL;

    if (filename.size() > 0)
    {
        ExifReader reader( filename );
        if( reader.parse() )
        {
            ExifEntry_t entry = reader.getTag( ORIENTATION );
            if (entry.tag != INVALID_TAG)
            {
                orientation = entry.field_u16; //orientation is unsigned short, so check field_u16
            }
        }
    }

    switch( orientation )
    {
        case    IMAGE_ORIENTATION_TL: //0th row == visual top, 0th column == visual left-hand side
            //do nothing, the image already has proper orientation
            break;
        case    IMAGE_ORIENTATION_TR: //0th row == visual top, 0th column == visual right-hand side
            flip(img, img, 1); //flip horizontally
            break;
        case    IMAGE_ORIENTATION_BR: //0th row == visual bottom, 0th column == visual right-hand side
            flip(img, img, -1);//flip both horizontally and vertically
            break;
        case    IMAGE_ORIENTATION_BL: //0th row == visual bottom, 0th column == visual left-hand side
            flip(img, img, 0); //flip vertically
            break;
        case    IMAGE_ORIENTATION_LT: //0th row == visual left-hand side, 0th column == visual top
            transpose(img, img);
            break;
        case    IMAGE_ORIENTATION_RT: //0th row == visual right-hand side, 0th column == visual top
            transpose(img, img);
            flip(img, img, 1); //flip horizontally
            break;
        case    IMAGE_ORIENTATION_RB: //0th row == visual right-hand side, 0th column == visual bottom
            transpose(img, img);
            flip(img, img, -1); //flip both horizontally and vertically
            break;
        case    IMAGE_ORIENTATION_LB: //0th row == visual left-hand side, 0th column == visual bottom
            transpose(img, img);
            flip(img, img, 0); //flip vertically
            break;
        default:
            //by default the image read has normal (JPEG_ORIENTATION_TL) orientation
            break;
    }
}

/**
 * Read an image
 *
 *  This function merely calls the actual implementation above and returns itself.
 *
 * @param[in] filename File to load
 * @param[in] flags Flags you wish to set.
*/
Mat imread( const String& filename, int flags )
{
    /// create the basic container
    Mat img;

    /// load the data
    imread_( filename, flags, LOAD_MAT, &img );

    /// optionally rotate the data if EXIF' orientation flag says so
    if( (flags & IMREAD_IGNORE_ORIENTATION) == 0 && flags != IMREAD_UNCHANGED )
    {
        ApplyExifOrientation(filename, img);
    }

    /// return a reference to the data
    return img;
}

3.2 imdecode source code:

static void*
imdecode_( const Mat& buf, int flags, int hdrtype, Mat* mat=0 )
{
    CV_Assert(!buf.empty() && buf.isContinuous());
    IplImage* image = 0;
    CvMat *matrix = 0;
    Mat temp, *data = &temp;
    String filename;

    ImageDecoder decoder = findDecoder(buf);
    if( !decoder )
        return 0;

    if( !decoder->setSource(buf) )
    {
        filename = tempfile();
        FILE* f = fopen( filename.c_str(), "wb" );
        if( !f )
            return 0;
        size_t bufSize = buf.cols*buf.rows*buf.elemSize();
        fwrite( buf.ptr(), 1, bufSize, f );
        fclose(f);
        decoder->setSource(filename);
    }

    if( !decoder->readHeader() )
    {
        decoder.release();
        if ( !filename.empty() )
        {
            if ( remove(filename.c_str()) != 0 )
            {
                CV_Error( CV_StsError, "unable to remove temporary file" );
            }
        }
        return 0;
    }

    CvSize size;
    size.width = decoder->width();
    size.height = decoder->height();

    int type = decoder->type();
    if( (flags & IMREAD_LOAD_GDAL) != IMREAD_LOAD_GDAL && flags != IMREAD_UNCHANGED )
    {
        if( (flags & CV_LOAD_IMAGE_ANYDEPTH) == 0 )
            type = CV_MAKETYPE(CV_8U, CV_MAT_CN(type));

        if( (flags & CV_LOAD_IMAGE_COLOR) != 0 ||
           ((flags & CV_LOAD_IMAGE_ANYCOLOR) != 0 && CV_MAT_CN(type) > 1) )
            type = CV_MAKETYPE(CV_MAT_DEPTH(type), 3);
        else
            type = CV_MAKETYPE(CV_MAT_DEPTH(type), 1);
    }

    if( hdrtype == LOAD_CVMAT || hdrtype == LOAD_MAT )
    {
        if( hdrtype == LOAD_CVMAT )
        {
            matrix = cvCreateMat( size.height, size.width, type );
            temp = cvarrToMat(matrix);
        }
        else
        {
            mat->create( size.height, size.width, type );
            data = mat;
        }
    }
    else
    {
        image = cvCreateImage( size, cvIplDepth(type), CV_MAT_CN(type) );
        temp = cvarrToMat(image);
    }

    bool code = decoder->readData( *data );
    decoder.release();
    if ( !filename.empty() )
    {
        if ( remove(filename.c_str()) != 0 )
        {
            CV_Error( CV_StsError, "unable to remove temporary file" );
        }
    }

    if( !code )
    {
        cvReleaseImage( &image );
        cvReleaseMat( &matrix );
        if( mat )
            mat->release();
        return 0;
    }

    return hdrtype == LOAD_CVMAT ? (void*)matrix :
        hdrtype == LOAD_IMAGE ? (void*)image : (void*)mat;
}

Mat imdecode( InputArray _buf, int flags )
{
    Mat buf = _buf.getMat(), img;
    imdecode_( buf, flags, LOAD_MAT, &img );
    return img;
}

Posted by antwonw on Sun, 10 Feb 2019 23:12:18 -0800