将UIImage转换为cv::Mat

32

我有一张UIImage图像,是从iPhone相机拍摄的。现在我想将这个UIImage转换成cv :: Mat(OpenCV)。我正在使用以下代码行来实现此目的:

-(cv::Mat)CVMat
{

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

这段代码在UIImage为横向模式时工作正常,但当我使用相同的代码处理从纵向模式拍摄的照片时,图片会向右旋转90度。

我是iOS和Objective C的新手,无法弄清楚其中问题所在。

有人能告诉我代码哪里出了问题,或者我漏掉了什么吗。

3个回答

45

这是因为UIImage实际上不是竖向的。所有使用iPhone相机拍摄的照片在其原始位图状态下都是横向的,例如3264宽 x 2488高。 "竖向"照片通过图像中设置的方向EXIF标志来显示为此类形式,例如,照片库应用程序根据此标志和相机的显示方向旋转图像。

该标志还影响UIImage报告其宽度和高度属性的方式,对于被标记为竖向的图像,它们会从其位图值中进行置换。

cv::Mat没有关心任何这些。这意味着,(i)当转换为cv::Mat时,竖向图像将使其size.widthsize.height值进行置换,并且(ii)当从cv::Mat转换回来时,您将失去方向标志。

UIImagecv::Mat转换时处理最简单的方法是,如果图像被标记为竖向,则交换宽度和高度值:

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

当从cv::Mat翻译回UIImage时,您需要恢复方向标志。假设您的cv::Mat -> UIImage代码包含以下内容:

self = [self initWithCGImage:imageRef];

您可以使用此方法替代,并根据原始方向重置。

self = [self initWithCGImage:imageRef scale:1 orientation:orientation];

@tomriddle_1234 - 我的答案中的代码是设计用于添加到问题中的代码之前,即调用CGBitmapContextCreate函数之前。该函数使用colsrows来指定位图上下文的尺寸。 - foundry
谢谢,我错过了问题代码。我找到了他问题的另一个解决方案,他可以将图像规范化为https://dev59.com/sm035IYBdhLWcg3wbPY5#10611036 - tomriddle_1234
@tomriddle_1234 - 如果你只是想旋转图像,那么这个解决方案是可以的。但是,如果你还要将其转换为不同的位图格式(如本问题所述),那么你最终会绘制两次位图,这是不必要的低效率。 - foundry
你说得对,我知道OpenCV中有UIImageToMat函数,但它无法处理这样的EXIF信息。由于我的背景是OpenCV而不是iOS,所以这个问题让我感到困惑。规范化可能比UIImageToMat的替代方案更容易理解。 - tomriddle_1234
@foundry 我之前也问过一个类似的问题,关于在iOS上构建OpenCV时出现错误的问题。如果您能看一下这个问题,我会非常感激:https://stackoverflow.com/questions/45534183/opencv-error-assertions-failed-on-ios - fi12
显示剩余2条评论

17

您应该考虑使用本机OpenCV函数来实现前后转换:

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

注意:如果您的UIImage来自相机,则在将其转换为cv :: Mat之前,应“规范化”它(iOS UIImagePickerController上传后的结果图像方向),因为OpenCV不考虑Exif数据。如果您不这样做,则结果可能会出现方向错误。


2

我试图将一张肖像的UIImage转换为Mat,然后再转回来,但是在这篇文章中,我尝试了两种方法都遇到了问题。

  • 使用MatToUIImageUIImageToMat的本地方法导致图像旋转。
  • 使用CGContext的自定义方法最终出现了奇怪的颜色错误(不仅仅是RGB / BGR交换)。红色变成了红色,蓝色变成了粉色,绿色变成了黄色。

最终我使用了本地的OpenCV带有旋转功能的方法:

#import <opencv2/imgcodecs/ios.h>

- (cv::Mat)convertUIImageToCVMat:(UIImage *)image
{
    cv::Mat imageMat;
    UIImageToMat(image, imageMat);
    cv::rotate(imageMat, imageMat, cv::ROTATE_90_CLOCKWISE);
    return imageMat;
}

- (UIImage *)convertCVMatToUIImage:(cv::Mat)cvMat
{
    UIImage *image = MatToUIImage(cvMat);
    return image;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接