【问题标题】:how to convert from cvMat to UIImage in objective-c?如何在objective-c中从cvMat转换为UIImage?
【发布时间】:2012-05-02 12:08:47
【问题描述】:

我正在使用 XCode 的 OpenCV 框架,并且想从 cvMat 或 IplImage 转换为 UIImage,如何做到这一点?谢谢。

【问题讨论】:

    标签: objective-c xcode opencv


    【解决方案1】:

    注意:大多数实现不能正确处理 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;
    }
    

    【讨论】:

    • 从 cv::Mat 转换为 UIImage 时,我不得不交换红色和蓝色通道。似乎opencv中的默认顺序是bgr。这是您需要的代码行:cvtColor(image, image, CV_BGR2RGB);
    • 当我使用这两种方法时,我的图像逆时针旋转了 90 度。这似乎不是正常的 Quartz 2D 颠倒问题。有没有人使用这些方法遇到过这种情况?
    • 在这里找到解决方案:stackoverflow.com/questions/14332687/…
    • @huesforalice 请注意,您可以使用kCGBitmapByteOrder32Little 来避免修改或复制原始矩阵:stackoverflow.com/a/35999152/71522
    • 我正在尝试同样的事情,我已经尝试过@huesforalice 在他的评论中所说的事情,但它正在改变图像的完整色彩空间。然后我尝试了另一个补丁,首先从 BGR2RGB 转换回 RGB2BGR,然后将其传递给转换回 UIImage,令人惊讶的是它起作用了...在调用 UIImageFromCVMat 之前添加这 2 行,cvtColor(cvMat, cvMat, CV_BGR2RGB); cvtColor(cvMat, cvMat, CV_RGB2BGR);
    【解决方案2】:

    从 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);
    

    【讨论】:

    • 不幸的是,MatToUIImage 还遭受到已接受答案的 cmets 中描述的红色和蓝色通道的交换。
    【解决方案3】:

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

    我见过的所有其他实现(包括 OpenCV 的文档)都是不正确的:它们没有正确地从 OpenCV 的 BGR 转换为 iOS 的 RGB,并且他们不考虑alpha 通道(如果存在)。见上面的cmetsbitmapInfo = …

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

    【讨论】:

    • 建议编辑: 1. 在转换为 NSData 之前检查 mat 是否连续 2. 使用[NSData dataWithBytesNoCopy:cvMat.data length:cvMat.elemSize() * cvMat.total() freeWhenDone:NO] 避免不必要地复制字节。
    • 你的回答与stackoverflow.com/a/10254561/7767664有何不同?
    【解决方案4】:

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

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

    【讨论】:

      【解决方案5】:

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

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

      【讨论】:

        【解决方案6】:

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

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

        注意:如果你的 UIImage 来自相机,你应该“标准化”它( iOS UIImagePickerController result image orientation after upload) 在转换为 cv::Mat 之前,因为 OpenCV 不考虑 Exif 数据。如果你不这样做,结果应该是错误的。

        【讨论】:

          【解决方案7】:

          作为一个类别:

          #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
          

          【讨论】:

            【解决方案8】:

            我在 UIImagecvMat 之间转换的经历如下:

            当我使用方法时:

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

            cv::MatUIImage及方法:

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

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

            最好的问候, 纳扎尔

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2014-10-14
              • 2017-01-06
              • 1970-01-01
              • 1970-01-01
              • 2018-03-23
              • 1970-01-01
              相关资源
              最近更新 更多