如何在objective-c中从cvMat转换为UIImage?

时间:2012-04-20 22:00:32

标签: objective-c xcode opencv

我在XCode上使用OpenCV框架并希望从cvMat或IplImage转换为UIImage,该怎么做?感谢。

8 个答案:

答案 0 :(得分:42)

注意:大多数实现都无法正确处理Alpha通道或从OpenCV的BGR像素格​​式转换为iOS的RGB。

这将正确地从cv::Mat转换为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; 
}

要从UIImage转换为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;
}

答案 1 :(得分:27)

从opencv 2.4.6开始,此功能已包含在内。 只需加入

opencv2/highgui/ios.h

即可

在OpenCV 3中,此包含已更改为:

opencv2/imgcodecs/ios.h

您可以使用以下功能:

UIImage* MatToUIImage(const cv::Mat& image);
void UIImageToMat(const UIImage* image, cv::Mat& m, bool alphaExist = false);

答案 2 :(得分:5)

以下是将cv::Mat转换为UIImage正确方法。

我见过的所有其他实施 - 包括OpenCV的文档 - 不正确:它们无法正确转换为OpenCV&#B; BGR到iOS的RGB,他们不考虑alpha通道(如果存在)。请参阅上面的评论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; 
}

答案 3 :(得分:5)

您应该考虑使用原生OpenCV函数来转换前后:

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

答案 4 :(得分:2)

我在这里提到所有需要的转换方法。

将UIImage颜色转换为UIImage灰色,不使用opencv且仅使用iOS库函数:

- (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;
}

将颜色UIImage转换为颜色cvMat。请注意,您可以在多个链接中找到这段代码,但这里有一个小修改。注意部分“交换渠道”。这部分是为了保持颜色不受干扰,否则颜色通道会被修改。

另请注意以下几行。这些线条有助于保持图像的方向不受干扰。

    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;
}

将UIImage转换为cvMat灰色。注意这一行

  

cv :: Mat cvMat(rows,cols,CV_8UC4,Scalar(1,2,3,4)); //每位8位   组件,4个频道

而不是

  

cv :: Mat cvMat(rows,cols,CV_8UC1); //每个组件8位,1   信道

此行是必需的,否则代码将抛出错误

- (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;
}

现在最后,将cvMat(颜色,二进制,灰色)转换为UIImage(颜色,二进制,灰色)。注意这一行:

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

此行有助于保持图像的原始方向

享受!!

-(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;
}

答案 5 :(得分:1)

作为一个类别:

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

@interface UIImage (OCV)

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

@end

#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

答案 6 :(得分:0)

我在 UIImage cvMat 之间进行转换的经历如下:

当我使用该方法时:

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

cv :: Mat 转换为 UIImage 以及方法:

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

UIImage 转换为 cv :: Mat ,这些方法无法使用模拟器正常工作。 在真实设备上部署我的应用程序后,没有任何问题。

祝你好运, 纳扎尔

答案 7 :(得分:0)

您应该考虑使用原生OpenCV函数来转换前后:

 function saveRow(oTable, nRow) {
            debugger
            var jqInputs = $('input', nRow);
            var jqselect = $('select', nRow);
            console.log(jqselect)
            // oTable.fnUpdate(jqInputs[0].value, nRow, 0, false);
            var selecteditem = jqselect[1]["selectedOptions"][0]["childNodes"][0]["data"];
            oTable.fnUpdate(jqselect[0].value, nRow, 1, false);
            oTable.fnUpdate(selecteditem, nRow, 2, false);
            oTable.fnUpdate(jqselect[2].value, nRow, 3, false);
}

注意:如果您的UIImage来自相机,您应该“正常化”它( 转换为#import <opencv2/imgcodecs/ios.h> ... UIImage* MatToUIImage(const cv::Mat& image); void UIImageToMat(const UIImage* image, cv::Mat& m, bool alphaExist = false); 之前的iOS UIImagePickerController result image orientation after upload),因为OpenCV没有考虑Exif数据。如果你不这样做,结果应该是错误的。