将UIImage从BGR转换为RGB

3
正如标题所示,我在UIImage的颜色空间转换方面遇到了一些问题。简而言之,我需要一种将BGR格式的UIImage转换为RGB的方法。
以下是我的应用程序中事件的流程:
1. 应用程序:获取图像 2. 应用程序:将其转换为base64并发送到服务器 3. 服务器:将base64转换为图像以使用 4. 服务器:将图像转换回base64字符串 5. 服务器:将base64字符串发送到应用程序 6. 应用程序:将base64字符串转换为UIImage 服务器上测试图像的RGB版本 客户端的测试图像的BGR版本 在这一点上,显示的UIImage是以BGR格式显示的。我最好的猜测是,在第4步出了问题,因为在那之前,图像是以RGB格式(我已经将其写入文件并进行了检查)的。我已经添加了第4步的代码供参考。我正在积极寻找在客户端更改UIImage颜色空间的方法,但我不反对在服务器端解决这个问题。任何一种解决方案都可以。
第2步:将UIImage转换为base64字符串
  let imageData: Data = UIImageJPEGRepresentation(map.image,0.95)!
  let base64EnCodedStr: String = imageData.base64EncodedString()

第三步:将base64字符串转换为PIL图像。
import io
import cv2
import base64 
import numpy as np
from PIL import Image

# Take in base64 string and return PIL image
def stringToImage(base64_string):
    imgdata = base64.b64decode(base64_string)
    return Image.open(io.BytesIO(imgdata))

第四步:将图像(numpy数组)转换回Base64字符串
# Convert a numpyArray into a base64 string in JPEG format
def imageToString(npArray):

    # convert array to PIL Image
    newImage = Image.fromarray(npArray.astype('uint8'), 'RGB')

    # convert to JPEG format
    file = io.BytesIO()
    newImage.save(file, format="JPEG")

    # reset file pointer to start
    file.seek(0)
    img_bytes = file.read()

    # encode data
    encodedData = base64.b64encode(img_bytes)

    return encodedData.decode('ascii')

编辑:

正如之前提到的,我可以在两个位置进行转换:服务器端或客户端。感谢对这个问题的回答,我能够找到这两种情况的解决方案。

解决方案1:服务器端

参考第4步中的代码,将该函数的第一行更改为以下内容:

 # convert array to PIL Image
 newImage = Image.fromarray( npArray[...,[2,1,0]] ) # swap color channels which converts BGR -> RGB

解决方案2:客户端

参考@dfd的解决方案。它写得很好,而且非常有效。这是我在我的应用程序中测试过的稍微改编版本(使用swift 4)。

let data =  NSData(base64Encoded: base64String, options: .ignoreUnknownCharacters)

let uiInput = UIImage(data: data! as Data)
let ciInput = CIImage(image: uiInput!)
let ctx = CIContext(options: nil)
let swapKernel = CIColorKernel( string:
    "kernel vec4 swapRedAndGreenAmount(__sample s) {" +
                 "return s.bgra;" +
     "}"
)
let ciOutput = swapKernel?.apply(withExtent: (ciInput?.extent)!, arguments: [ciInput as Any])
let cgImage = ctx.createCGImage(ciOutput!, from: (ciInput?.extent)!)
let rgbOutput = UIImage(cgImage: cgImage!)

你是说你从RGB开始,将其发送到服务器进行一些操作,当它回到客户端时变成了BGR?你想知道如何在返回客户端后将其转换回BGR? - Paolo
@Paolo:是的,除了最后一句话。它以BGR格式返回,我想将其转换回RGB格式。 - Nathan Ortega
你能展示一下如何将图像转换为base64,然后再转回图像吗?基本上是步骤2和3。很奇怪你得到了一个相反的顺序。服务器上翻转顺序的快速修复可能是遵循这个链接:https://dev59.com/3G445IYBdhLWcg3w3N2z - user887210
@ColGraff:我已经更新了我的问题,包括第2步和第3步的代码。 - Nathan Ortega
3个回答

6
这里有一个非常简单的CIKernel代码,用于交换图像内容:
kernel vec4 swapRedAndGreenAmount(__sample s) {
    return s.bgra;
}

这里是使用它的Swift代码:
let uiInput = UIImage(named: "myImage")
let ciInput = CIImage(image: uiInput!)
let ctx = CIContext(options: nil)
let swapKernel = CIColorKernel( source:
    "kernel vec4 swapRedAndGreenAmount(__sample s) {" +
        "return s.bgra;" +
    "}"
)
let ciOutput = swapKernel?.apply(extent: (ciInput?.extent)!, arguments: [ciInput as Any])
let cgImage = ctx.createCGImage(ciOutput!, from: (ciInput?.extent)!)
let uiOutput = UIImage(cgImage: cgImage!)

需要注意以下几点:

  • 此方法仅适用于运行iOS 9或更高版本的设备。
  • 其次,此方法使用CoreImage和GPU。因此,在模拟器上测试可能需要几秒钟才能渲染。但在设备上只需要微秒。
  • 我倾向于使用CIContext创建一个CGImage,然后最终获得UIImage。您也可以尝试跳过这一步骤,直接将CIImage转换为UIImage
  • 请忽略包装/拆包,它是从旧代码转换而来的。您可能可以做得更好。

说明:

使用CoreImage的“Kernel”代码(在iOS 11之前仅限于GLSL代码的子集),我编写了一个简单的CIColorKernel,它以像素的RGB值为输入,并将像素颜色作为GRB返回。

CIColorKernel被优化为一次处理一个像素,不能访问周围的像素。相比之下,CIWarpKernel被优化为根据周围的像素“扭曲”像素。这两者都是(或多或少)优化的CIKernel子类,直到iOS 11和Metal Performance Shaders为止,这是您最接近在CoreImage内部使用openGL的方法。

最终编辑:

这种解决方案所做的是使用CoreImage逐个交换像素的RGB。由于它使用GPU,所以速度很快,但在模拟器上会掩盖实际效果,而且简单(因为它只是将RGB与BGR交换)。

实现这一操作的代码很简单。希望这可以作为想要使用CoreImage进行更大“内部”处理的人的起点。

编辑 (2021年2月25日):

从WWDC 2019开始,苹果已将OpenGL - 特别是GLKit - 废弃,转而采用 MetalKit。对于像这样的颜色内核,将此代码转换为MetalKit相当简单。扭曲内核稍微棘手一些。

至于苹果何时将“杀死”OpenGL,难以说。我们都知道有一天UIKit也会被废弃,但(显示我的年龄)可能不会在我的有生之年。可能因人而异。


感谢您详细的回复,不幸的是,这并没有解决我的问题,它只是把背景变成了粉色。我觉得这是一个重要的细节,但我不知道该怎么处理。我已经使用您的代码上传了结果在这里 - Nathan Ortega
@NathanOrtega 看起来他交换了红色和绿色,尝试交换红色和蓝色,即使用 return s.bgra; 而不是 return s.grba; - Paolo
是的,我的错。那是我的代码需要那个。我会编辑我的回答。 - user7014451
Paolo:谢谢!就是这个问题!@dfd:你完成那个编辑后我会接受你的答案。 - Nathan Ortega
1
@AppDevGuy,我会添加一条有关弃用的注释。在我的辩护中,请注意苹果公司在我的回答近两年后弃用了OpenGL(我假设这就是你所指的)。至于内存泄漏?在我的使用中没有发现,但也许是实现方式的问题。感谢提醒! - user7014451
显示剩余4条评论

1

我认为使用CoreImageCoreGraphics没有办法实现,因为iOS在创建自定义颜色空间方面并没有太多的余地。然而,我从这篇文章中找到了一些可能有用的东西,它使用OpenCV:https://sriraghu.com/2017/06/04/computer-vision-in-ios-swiftopencv/。需要一些Objective-C,但是通过桥接头文件,一旦编写完代码,它就会被隐藏起来。

  • 添加一个新文件 -> 'Cocoa Touch Class',将其命名为 'OpenCVWrapper' 并设置 语言为 Objective-C。单击下一步,选择创建。当它提示创建桥接标头时,请点击“创建桥接标头”按钮。现在您可以观察到有三个文件被创建,分别是:OpenCVWrapper.h、OpenCVWrapper.m、-Bridging-Header.h。打开“-Bridging-Header.h”并添加以下行:#import “OpenCVWrapper.h”
  • 转到 'OpenCVWrapper.h' 文件并添加以下代码:
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@interface OpenCVWrapper: NSObject

+ (UIImage *) rgbImageFromBGRImage: (UIImage *) image;

@end

将OpenCVWrapper.m重命名为“OpenCVWrapper.mm”以支持C++,并添加以下代码:
#import "OpenCVWrapper.h"

// import necessary headers
#import <opencv2/core.hpp>
#import <opencv2/imgcodecs/ios.h>
#import <opencv2/imgproc/imgproc.hpp>

using namespace cv;

@implementation OpenCVWrapper

+ (UIImage *) rgbImageFromBGRImage: (UIImage *) image {
    // Convert UIImage to cv::Mat
    Mat inputImage; UIImageToMat(image, inputImage);
    // If input image has only one channel, then return image.
    if (inputImage.channels() == 1) return image;
    // Convert the default OpenCV's BGR format to RGB.
    Mat outputImage; cvtColor(inputImage, outputImage, CV_BGR2RGB);
    // Convert the BGR OpenCV Mat to UIImage and return it.
    return MatToUIImage(outputImage);
}

@end

这篇文章与链接文章的细微差别在于,他们将BGR转换为灰度图像,而我们将BGR转换为RGB(OpenCV有大量的转换可用,这是个好事情!)。

最后...

现在有了Objective-C类的桥接头文件,你可以在Swift中使用OpenCVWrapper
// assume bgrImage is your image from the server
let rgbImage = OpenCVWrapper.rgbImage(fromBGR: bgrImage)
// double check the syntax on this ^ I'm not 100% sure how the bridging header will convert it

我发布了一种使用CoreImage来实现这个的方法。它需要iOS 9,因为它使用了自定义内核。(如果只需要CoreImage本身,它仍然需要iOS 8。)这是我写的第一个内核,将近两年前,我刚刚测试过它。@Paolo,你的方法很有趣!我还没有使用过openCV。我会看看它的。 - user7014451

1
您可以使用底层的CGImage来创建您所需格式的CIImage。
func changeToRGBA8(image: UIImage) -> UIImage? {
  guard let cgImage = image.cgImage,
    let data = cgImage.dataProvider?.data else { return nil }
    let flipped = CIImage(bitmapData: data as Data,
                          bytesPerRow: cgImage.bytesPerRow,
                          size: CGSize(width: cgImage.width, height: cgImage.height),
                          format: kCIFormatRGBA8,
                          colorSpace: cgImage.colorSpace)
    return UIImage(ciImage: flipped)
}

唯一的问题在于,这仅适用于在第一次创建 UIImage 时使用了 CGImage 的情况!您也可以将其转换为 CIImage,然后再转换为 CGImage,但是同样的限制依然存在,它只能用于从 CIImage 创建的 UIImage
如果我有更好的答案,我将探讨并在此发布解决方法,以克服这个限制。

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