将 UIImage 从 BGR 转换为 RGB

Nat*_*ega 3 python base64 opencv numpy swift

正如标题所暗示的,我在一些 UIImage 颜色空间转换方面遇到了一些麻烦。TL;DR 版本是我需要一种将 BGR 格式的 UIIMage 转换为 RGB 的方法。

这是我的应用程序中的事件流:

  1. 应用程序:获取图像
  2. 应用程序:转换为 base64 并发送到服务器
  3. 服务器:将 base64 转换为要使用的图像
  4. 服务器:将图像转换回 base64 字符串
  5. 服务器:将 base64 字符串发送到应用程序
  6. 应用程序:将 base64 字符串转换为 UIImage

服务器上测试图像的 RGB 版本

Test-Image 客户端的 BGR 版本

此时显示的 UIImage 为 BGR 格式。我最好的猜测是第 4 步出了问题,因为在那之前图像是 RGB 格式的(我已将其写入文件并进行了检查)。我已将代码添加到下面的第 4 步,仅供参考。我正在积极寻求更改 UIImage 客户端的色彩空间,但我不反对解决服务器端的问题。任何一种解决方案都可以。

第 2 步:将 UIIMage 转换为 base64 字符串

  let imageData: Data = UIImageJPEGRepresentation(map.image,0.95)!
  let base64EnCodedStr: String = imageData.base64EncodedString()
Run Code Online (Sandbox Code Playgroud)

第 3 步:将 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))
Run Code Online (Sandbox Code Playgroud)

第 4 步:将图像(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')
Run Code Online (Sandbox Code Playgroud)

编辑:

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

方案一:服务器端

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

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

解决方案 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!)
Run Code Online (Sandbox Code Playgroud)

dfd*_*dfd 6

这是一个非常简单的 CIKernel 来交换东西:

kernel vec4 swapRedAndGreenAmount(__sample s) {
    return s.bgra;
}
Run Code Online (Sandbox Code Playgroud)

这是使用它的 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!)
Run Code Online (Sandbox Code Playgroud)

请注意以下几点:

  • 这将适用于运行iOS 9 或更高版本的设备
  • 其次,几乎同样重要,这使用 CoreImage 和 GPU。因此,在模拟器上测试它可能需要几秒钟来渲染。但是在设备上它需要几微秒。
  • 我倾向于使用 a在结束之前CIContext创建CGImagea UIImage。您可以删除此步骤并直接从 aCIImage转到 a UIImage
  • 请原谅包装/展开,它是从旧代码转换而来的。你或许可以做得更好。

解释:

使用 CoreImage“内核”代码(在 iOS 11 之前只能是 GLSL 代码的子集),我编写了一个简单的方法CIColorKernel,它采用像素的 RGB 值并将像素颜色作为 GRB 返回。

ACIColorKernel被优化为一次处理单个像素,而无法访问它周围的像素。与此不同的是, aCIWarpKernel被优化为基于它周围的像素“扭曲”一个像素。这两个(或多或少)都是 a 的优化子类,在CIKerneliOS 11 和 Metal Performance Shaders 之前,它是最接近您openGLCoreImage.

最终编辑:

此解决方案的作用是使用 CoreImage 逐个交换像素的 RGB。它很快,因为它使用 GPU,看似很快(因为模拟器不会给你任何接近设备实时性能的东西),而且很简单(因为它把东西从 RGB 交换到 BGR)。

执行此操作的实际代码很简单。希望它可以作为那些想要使用 CoreImage 做更大的“幕后”事情的人的开始。

编辑(2021 年 2 月 25 日):

从 WWDC 2019 开始,Apple 已弃用 openGL,特别GLKit是支持MetalKit. 对于像这样的颜色内核,转换此代码相当简单。不过,Warp 内核稍微复杂一些。

至于Apple什么时候会“杀死”OpenGL 很难说。我们都知道总有一天UIKit会被弃用,但是(现在显示我的年龄)它可能不会在我有生之年。天啊。