繁体   English   中英

将 UIImage 从 BGR 转换为 RGB

[英]Convert UIImage from BGR to RGB

正如标题所暗示的,我在一些 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()

第 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))

第 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')

编辑:

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

方案一:服务器端

参考步骤 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!)

这是一个非常简单的 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。 因此,在模拟器上测试它可能需要几秒钟来渲染。 但是在设备上它需要几微秒。
  • 在结束UIImage之前,我倾向于使用CIContext创建一个CGImage 可以删除此步骤并直接从CIImage转到UIImage
  • 请原谅包装/展开,它是从旧代码转换而来的。 你或许可以做得更好。

说明:

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

CIColorKernel被优化为一次处理单个像素,而无法访问它周围的像素。 与此不同的是, CIWarpKernel被优化为根据周围的像素“扭曲”一个像素。 这两个(或多或少)都是CIKernel优化子类,在 iOS 11 和 Metal Performance Shaders 之前,它是您最接近在CoreImage使用openGLCoreImage

最终编辑:

此解决方案的作用是使用 CoreImage 逐个交换像素的 RGB。 它很快,因为它使用 GPU,看似很快(因为模拟器不会在设备上为您提供接近实时性能的任何东西),而且很简单(因为它可以将事物从 RGB 转换为 BGR)。

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

编辑(2021 年 2 月 25 日):

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

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

我认为没有办法使用CoreImageCoreGraphics来做到这CoreImage ,因为 iOS 在创建自定义色彩空间方面没有给你太多余地。 但是,我从这篇文章中发现了一些可能有助于使用 OpenCV 的内容: https : //sriraghu.com/2017/06/04/computer-vision-in-ios-swiftopencv/ 它需要一点Objective-C,但有一个桥接头,一旦编写代码就会隐藏起来。

  • 添加一个新文件 -> 'Cocoa Touch Class',将其命名为 'OpenCVWrapper' 并将语言设置为 Objective-C。 单击下一步并选择创建。 当提示创建桥接头时,单击“创建桥接头”按钮。 现在您可以观察到创建了 3 个文件名: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

您可以使用底层 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但同样适用,它仅在UIImage是从CIImage创建的情况下才有效。

如果我有更好的答案,我会探索并在此处发布解决此限制的方法。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM