29

I'm using the OpenCV framework with XCode and want to convert from cvMat or IplImage to UIImage, how to do that? Thanks.

Michal Kottman
  • 16,375
  • 3
  • 47
  • 62
chostDevil
  • 1,041
  • 5
  • 17
  • 24

8 Answers8

47

Note: most implementations don't correctly handle an alpha channel or convert from OpenCV's BGR pixel format to iOS's RGB.

This will correctly convert from cv::Mat to UIImage:

+(UIImage *)UIImageFromCVMat:(cv::Mat)cvMat {
    NSData *data = [NSData dataWithBytes:cvMat.data length:image.step.p[0]*image.rows];

    CGColorSpaceRef colorSpace;
    CGBitmapInfo bitmapInfo;

    if (cvMat.elemSize() == 1) {
        colorSpace = CGColorSpaceCreateDeviceGray();
        bitmapInfo = kCGImageAlphaNone | kCGBitmapByteOrderDefault;
    } else {
        colorSpace = CGColorSpaceCreateDeviceRGB();
        bitmapInfo = kCGBitmapByteOrder32Little | (
            cvMat.elemSize() == 3? kCGImageAlphaNone : kCGImageAlphaNoneSkipFirst
        );
    }

    CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data);

    // Creating CGImage from cv::Mat
    CGImageRef imageRef = CGImageCreate(
        cvMat.cols,                 //width
        cvMat.rows,                 //height
        8,                          //bits per component
        8 * cvMat.elemSize(),       //bits per pixel
        cvMat.step[0],              //bytesPerRow
        colorSpace,                 //colorspace
        bitmapInfo,                 // bitmap info
        provider,                   //CGDataProviderRef
        NULL,                       //decode
        false,                      //should interpolate
        kCGRenderingIntentDefault   //intent
    );

    // Getting UIImage from CGImage
    UIImage *finalImage = [UIImage imageWithCGImage:imageRef];
    CGImageRelease(imageRef);
    CGDataProviderRelease(provider);
    CGColorSpaceRelease(colorSpace);

    return finalImage; 
}

And to convert from UIImage to cv::Mat:

+ (cv::Mat)cvMatWithImage:(UIImage *)image
{
    CGColorSpaceRef colorSpace = CGImageGetColorSpace(image.CGImage);
    size_t numberOfComponents = CGColorSpaceGetNumberOfComponents(colorSpace);
    CGFloat cols = image.size.width;
    CGFloat rows = image.size.height;

    cv::Mat cvMat(rows, cols, CV_8UC4); // 8 bits per component, 4 channels
    CGBitmapInfo bitmapInfo = kCGImageAlphaNoneSkipLast | kCGBitmapByteOrderDefault;

    // check whether the UIImage is greyscale already
    if (numberOfComponents == 1){
        cvMat = cv::Mat(rows, cols, CV_8UC1); // 8 bits per component, 1 channels
        bitmapInfo = kCGImageAlphaNone | kCGBitmapByteOrderDefault;
    } 

    CGContextRef contextRef = CGBitmapContextCreate(cvMat.data,             // Pointer to backing data
                                                cols,                       // Width of bitmap
                                                rows,                       // Height of bitmap
                                                8,                          // Bits per component
                                                cvMat.step[0],              // Bytes per row
                                                colorSpace,                 // Colorspace
                                                bitmapInfo);              // Bitmap info flags

    CGContextDrawImage(contextRef, CGRectMake(0, 0, cols, rows), image.CGImage);
    CGContextRelease(contextRef);

    return cvMat;
}
Wehrdo
  • 560
  • 6
  • 12
sch
  • 27,436
  • 3
  • 68
  • 83
  • 16
    When converting from cv::Mat to UIImage, I had to swap the red and blue color channels. Seems like the default order in opencv is bgr. This is the line of code you need for this: cvtColor( image, image, CV_BGR2RGB); – huesforalice Jan 06 '13 at 16:56
  • 1
    When I use these two methods, my image gets rotated 90 degrees counterclockwise. This doesn't seem like the normal Quartz 2D upside down issue. Did anyone encounter this using these methods? – kailoon May 16 '13 at 21:09
  • 1
    Found solution here: http://stackoverflow.com/questions/14332687/converting-uiimage-to-cvmat – kailoon May 16 '13 at 21:16
  • I keep getting empty data with cvMatWithImage for whatever reason. I'm using opencv 3, not sure if that's the problem – user1297061 Jul 08 '14 at 02:35
  • 1
    @huesforalice note that you can use `kCGBitmapByteOrder32Little` to avoid modifying the original matrix or copying it: http://stackoverflow.com/a/35999152/71522 – David Wolever Mar 14 '16 at 22:18
  • @DavidWolever a quick question, why do you edit the accepted answer **and** add your own answer, which is identical? – Maxi Mus Aug 23 '16 at 13:27
  • @MaxiMus because I'd like a _bit_ of that sweet sweet rep for it, but also don't want everyone who (very reasonably) just takes the top answer to have incorrect code. – David Wolever Aug 23 '16 at 21:00
  • OK, that obviously makes sense. But then maybe it would be better to delete your own answer, because this is confusing. But generally, wouldn't it be better to leave the original answer instead of completely rewriting it, and let the comments and upvotes take care of the problem eventually? Because this way the original comments on the accepted answer of course don't have any relevance anymore, which is additionally confusing... (e.g. the red blue problem is solved with your code) – Maxi Mus Aug 25 '16 at 08:34
  • 1
    I were trying the same thing, I have tried the thing as @huesforalice told in his comment, but it was changing full colorspace of the image. Then i tried another patch, firsct converted from BGR2RGB then againg back to RGB2BGR and then passed it for converting to back to UIImage, and amazingly it worked... add this 2 line before calling UIImageFromCVMat, `cvtColor(cvMat, cvMat, CV_BGR2RGB); cvtColor(cvMat, cvMat, CV_RGB2BGR);` – Mehul Thakkar Sep 14 '16 at 11:20
  • your method `UIImageFromCVMat` returns UIImage with wrong colors – user924 Mar 03 '18 at 13:20
30

From opencv 2.4.6 on this functionality is already included. Just include

opencv2/highgui/ios.h

In OpenCV 3 this include has changed to:

opencv2/imgcodecs/ios.h

And you can use these functions:

UIImage* MatToUIImage(const cv::Mat& image);
void UIImageToMat(const UIImage* image, cv::Mat& m, bool alphaExist = false);
AxM
  • 622
  • 1
  • 7
  • 16
  • 2
    Unfortunately, `MatToUIImage` also suffers from the swapping of the red and blue color channels described in the comments of the accepted answer. – Maxi Mus Aug 23 '16 at 13:16
5

Here is the correct method to convert a cv::Mat to a UIImage.

Every other implementation I've seen — including OpenCV's documentation — is incorrect: they do not correctly convert from OpenCV's BGR to iOS's RGB, and they do not consider the alpha channel (if one exists). See comments above bitmapInfo = ….

+(UIImage *)UIImageFromCVMat:(cv::Mat)cvMat {
    NSData *data = [NSData dataWithBytes:cvMat.data length:cvMat.elemSize()*cvMat.total()];

    CGColorSpaceRef colorSpace;
    CGBitmapInfo bitmapInfo;

    if (cvMat.elemSize() == 1) {
        colorSpace = CGColorSpaceCreateDeviceGray();
        bitmapInfo = kCGImageAlphaNone | kCGBitmapByteOrderDefault;
    } else {
        colorSpace = CGColorSpaceCreateDeviceRGB();
        // OpenCV defaults to either BGR or ABGR. In CoreGraphics land,
        // this means using the "32Little" byte order, and potentially
        // skipping the first pixel. These may need to be adjusted if the
        // input matrix uses a different pixel format.
        bitmapInfo = kCGBitmapByteOrder32Little | (
            cvMat.elemSize() == 3? kCGImageAlphaNone : kCGImageAlphaNoneSkipFirst
        );
    }

    CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data);

    // Creating CGImage from cv::Mat
    CGImageRef imageRef = CGImageCreate(
        cvMat.cols,                 //width
        cvMat.rows,                 //height
        8,                          //bits per component
        8 * cvMat.elemSize(),       //bits per pixel
        cvMat.step[0],              //bytesPerRow
        colorSpace,                 //colorspace
        bitmapInfo,                 // bitmap info
        provider,                   //CGDataProviderRef
        NULL,                       //decode
        false,                      //should interpolate
        kCGRenderingIntentDefault   //intent
    );

    // Getting UIImage from CGImage
    UIImage *finalImage = [UIImage imageWithCGImage:imageRef];
    CGImageRelease(imageRef);
    CGDataProviderRelease(provider);
    CGColorSpaceRelease(colorSpace);

    return finalImage; 
}
David Wolever
  • 148,955
  • 89
  • 346
  • 502
  • Suggest for edit: 1. Check whether the mat is continuous before converting it to NSData 2. Use `[NSData dataWithBytesNoCopy:cvMat.data length:cvMat.elemSize() * cvMat.total() freeWhenDone:NO]` to avoid copying the bytes unnecessarily. – Siyuan Ren Jun 08 '17 at 08:52
  • how you answer different from https://stackoverflow.com/a/10254561/7767664? – user924 Mar 03 '18 at 13:27
4

You should consider using native OpenCV functions to convert forth and back :

#import <opencv2/imgcodecs/ios.h>
...
UIImage* MatToUIImage(const cv::Mat& image);
void UIImageToMat(const UIImage* image,
                         cv::Mat& m, bool alphaExist = false);
cdescours
  • 6,004
  • 3
  • 24
  • 30
2

Here I am mentioning all the needed conversion methods together.

Converting UIImage color to UIImage gray, without using opencv and only using iOS library functions:

- (UIImage *)convertImageToGrayScale:(UIImage *)image
{
    // Create image rectangle with current image width/height
    CGRect imageRect = CGRectMake(0, 0, image.size.width, image.size.height);

    // Grayscale color space
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceGray();

    // Create bitmap content with current image size and grayscale colorspace
    CGContextRef context = CGBitmapContextCreate(nil, image.size.width, image.size.height, 8, 0, colorSpace, kCGImageAlphaNone);

    // Draw image into current context, with specified rectangle
    // using previously defined context (with grayscale colorspace)
    CGContextDrawImage(context, imageRect, [image CGImage]);

    /* changes start here */
    // Create bitmap image info from pixel data in current context
    CGImageRef grayImage = CGBitmapContextCreateImage(context);

    // release the colorspace and graphics context
    CGColorSpaceRelease(colorSpace);
    CGContextRelease(context);

    // make a new alpha-only graphics context
    context = CGBitmapContextCreate(nil, image.size.width, image.size.height, 8, 0, nil, kCGImageAlphaOnly);

    // draw image into context with no colorspace
    CGContextDrawImage(context, imageRect, [image CGImage]);

    // create alpha bitmap mask from current context
    CGImageRef mask = CGBitmapContextCreateImage(context);

    // release graphics context
    CGContextRelease(context);

    // make UIImage from grayscale image with alpha mask
    UIImage *grayScaleImage = [UIImage imageWithCGImage:CGImageCreateWithMask(grayImage, mask) scale:image.scale orientation:image.imageOrientation];

    // release the CG images
    CGImageRelease(grayImage);
    CGImageRelease(mask);

    // return the new grayscale image
    return grayScaleImage;
}

Converting color UIImage to color cvMat. Please note that, you will find this piece of code in several links but there is a small modification here. Notice the portion "swap channels". This part is for keeping the color undisturbed otherwise the color channel got modified.

Also notice the following lines. These lines will help to keep the orientation of the image undisturbed.

    if  (image.imageOrientation == UIImageOrientationLeft
         || image.imageOrientation == UIImageOrientationRight) {
        cols = image.size.height;
        rows = image.size.width;
    }
- (cv::Mat)cvMatFromUIImage:(UIImage *)image
{
    CGColorSpaceRef colorSpace = CGImageGetColorSpace(image.CGImage);
    CGFloat cols = image.size.width;
    CGFloat rows = image.size.height;

    if  (image.imageOrientation == UIImageOrientationLeft
         || image.imageOrientation == UIImageOrientationRight) {
        cols = image.size.height;
        rows = image.size.width;
    }

    cv::Mat cvMat(rows, cols, CV_8UC4); // 8 bits per component, 4 channels (color channels + alpha)

    CGContextRef contextRef = CGBitmapContextCreate(cvMat.data,                 // Pointer to  data
                                                    cols,                       // Width of bitmap
                                                    rows,                       // Height of bitmap
                                                    8,                          // Bits per component
                                                    cvMat.step[0],              // Bytes per row
                                                    colorSpace,                 // Colorspace
                                                    kCGImageAlphaNoneSkipLast |
                                                    kCGBitmapByteOrderDefault); // Bitmap info flags

    CGContextDrawImage(contextRef, CGRectMake(0, 0, cols, rows), image.CGImage);
    CGContextRelease(contextRef);
    CGColorSpaceRelease(colorSpace);

    //--swap channels -- //
    std::vector<Mat> ch;
    cv::split(cvMat,ch);
    std::swap(ch[0],ch[2]);
    cv::merge(ch,cvMat);

    return cvMat;
}

Converting UIImage to cvMat gray. Notice the line

cv::Mat cvMat(rows, cols, CV_8UC4, Scalar(1,2,3,4)); // 8 bits per component, 4 channels

instead of

cv::Mat cvMat(rows, cols, CV_8UC1); // 8 bits per component, 1 channels

This line is needed otherwise the code will throw error

- (cv::Mat)cvMatGrayFromUIImage:(UIImage *)image
{
    CGColorSpaceRef colorSpace = CGImageGetColorSpace(image.CGImage);
    CGFloat cols = image.size.width;
    CGFloat rows = image.size.height;

  //  cv::Mat cvMat(rows, cols, CV_8UC1); // 8 bits per component, 1 channels
    cv::Mat cvMat(rows, cols, CV_8UC4, Scalar(1,2,3,4)); // 8 bits per component, 4 channels

    CGContextRef contextRef = CGBitmapContextCreate(cvMat.data,                 // Pointer to data
                                                    cols,                       // Width of bitmap
                                                    rows,                       // Height of bitmap
                                                    8,                          // Bits per component
                                                    cvMat.step[0],              // Bytes per row
                                                    colorSpace,                 // Colorspace
                                                    kCGImageAlphaNoneSkipLast |
                                                    kCGBitmapByteOrderDefault); // Bitmap info flags

    CGContextDrawImage(contextRef, CGRectMake(0, 0, cols, rows), image.CGImage);
    CGContextRelease(contextRef);
    CGColorSpaceRelease(colorSpace);
    return cvMat;
}

Now finally, converting cvMat (color,binary, gray) to UIImage (color, binary, gray). Notice the line :

UIImage *finalImage = [UIImage imageWithCGImage:imageRef scale:1 orientation:self.originalImage.imageOrientation];

This line will help to keep the original orientation of the image

ENJOY !!

-(UIImage *)UIImageFromCVMat:(cv::Mat)cvMat {
    NSData *data = [NSData dataWithBytes:cvMat.data length:cvMat.elemSize()*cvMat.total()];

    CGColorSpaceRef colorSpace;
    CGBitmapInfo bitmapInfo;

    if (cvMat.elemSize() == 1) {
        colorSpace = CGColorSpaceCreateDeviceGray();
        bitmapInfo = kCGImageAlphaNone | kCGBitmapByteOrderDefault;
    } else {
        colorSpace = CGColorSpaceCreateDeviceRGB();
        bitmapInfo = kCGBitmapByteOrder32Little | (
                                                   cvMat.elemSize() == 3? kCGImageAlphaNone : kCGImageAlphaNoneSkipFirst
                                                   );
    }

    CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data);

    // Creating CGImage from cv::Mat
    CGImageRef imageRef = CGImageCreate(
                                        cvMat.cols,                 //width
                                        cvMat.rows,                 //height
                                        8,                          //bits per component
                                        8 * cvMat.elemSize(),       //bits per pixel
                                        cvMat.step[0],              //bytesPerRow
                                        colorSpace,                 //colorspace
                                        bitmapInfo,                 // bitmap info
                                        provider,                   //CGDataProviderRef
                                        NULL,                       //decode
                                        false,                      //should interpolate
                                        kCGRenderingIntentDefault   //intent
                                        );

    // Getting UIImage from CGImage

    UIImage *finalImage = [UIImage imageWithCGImage:imageRef scale:1 orientation:self.originalImage.imageOrientation];
    CGImageRelease(imageRef);
    CGDataProviderRelease(provider);
    CGColorSpaceRelease(colorSpace);

    return finalImage;
}
Tanmoy Mondal
  • 449
  • 1
  • 6
  • 12
1

You should consider using native OpenCV functions to convert forth and back :

#import <opencv2/imgcodecs/ios.h>
...
UIImage* MatToUIImage(const cv::Mat& image);
void UIImageToMat(const UIImage* image,
                         cv::Mat& m, bool alphaExist = false);

Note: if your UIImage comes from the camera, you should 'normalize' it ( iOS UIImagePickerController result image orientation after upload) before converting to cv::Mat since OpenCV does not take into account Exif data. If you don't do that the result should be misoriented.

Community
  • 1
  • 1
cdescours
  • 6,004
  • 3
  • 24
  • 30
0

As a category:

#import <UIKit/UIKit.h>
#import <opencv2/core/core.hpp>
using namespace cv;

@interface UIImage (OCV)

-(id)initWithOImage:(cv::Mat)oImage;
-(cv::Mat)oImage;

@end

and

#import "UIImage+OCV.h"

@implementation UIImage (OCV)

-(id)initWithOImage:(cv::Mat)oImage
{
  NSData *data = [NSData dataWithBytes:oImage.data length:oImage.elemSize() * oImage.total()];

  CGColorSpaceRef colorSpace;

  if (oImage.elemSize() == 1) {
    colorSpace = CGColorSpaceCreateDeviceGray();
  } else {
    colorSpace = CGColorSpaceCreateDeviceRGB();
  }

  CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data);

  CGImageRef imageRef = CGImageCreate(oImage.cols,                                     // Width
                                      oImage.rows,                                     // Height
                                      8,                                              // Bits per component
                                      8 * oImage.elemSize(),                           // Bits per pixel
                                      oImage.step[0],                                  // Bytes per row
                                      colorSpace,                                     // Colorspace
                                      kCGImageAlphaNone | kCGBitmapByteOrderDefault,  // Bitmap info flags
                                      provider,                                       // CGDataProviderRef
                                      NULL,                                           // Decode
                                      false,                                          // Should interpolate
                                      kCGRenderingIntentDefault);                     // Intent

  self = [self initWithCGImage:imageRef];

  CGImageRelease(imageRef);
  CGDataProviderRelease(provider);
  CGColorSpaceRelease(colorSpace);

  return self;
}

-(cv::Mat)oImage
{
  CGColorSpaceRef colorSpace = CGImageGetColorSpace(self.CGImage);
  CGFloat cols = self.size.width;
  CGFloat rows = self.size.height;

  cv::Mat cvMat(rows, cols, CV_8UC4); // 8 bits per component, 4 channels

  CGContextRef contextRef = CGBitmapContextCreate(cvMat.data,                 // Pointer to backing data
                                                  cols,                       // Width of bitmap
                                                  rows,                       // Height of bitmap
                                                  8,                          // Bits per component
                                                  cvMat.step[0],              // Bytes per row
                                                  colorSpace,                 // Colorspace
                                                  kCGImageAlphaNoneSkipLast |
                                                  kCGBitmapByteOrderDefault); // Bitmap info flags

  CGContextDrawImage(contextRef, CGRectMake(0, 0, cols, rows), self.CGImage);
  CGContextRelease(contextRef);

  return cvMat;
}

@end
0

What I have experienced with converting between UIImage and cvMat is following:

When I used the method:

UIImage* MatToUIImage(const cv::Mat& image);

for converting cv::Mat to UIImage and the method:

void UIImageToMat(const UIImage* image, cv::Mat& m);

for converting UIImage to cv::Mat these methods didn't work correctly using the Simulator. After I deployed my app on a real device, there weren't any problems.

Best regards, Nazar

Nazar Medeiros
  • 437
  • 4
  • 10