如何使用 Accelerate Framework 将 iOS 相机图像转换为灰度?
Posted
技术标签:
【中文标题】如何使用 Accelerate Framework 将 iOS 相机图像转换为灰度?【英文标题】:How-to convert an iOS camera image to greyscale using the Accelerate Framework? 【发布时间】:2014-01-18 17:02:38 【问题描述】:看起来这应该比我想象的要简单。
我在标准委托方法中返回了一个 AVFoundation
框架:
- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
fromConnection:(AVCaptureConnection *)connection
我想使用Accelerate.Framework
将帧转换为灰度。
框架中有一系列转换方法,包括vImageConvert_RGBA8888toPlanar8()
,看起来可能是我想看到的,但是我找不到任何如何使用它们的示例!
到目前为止,我有代码:
- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
fromConnection:(AVCaptureConnection *)connection
@autoreleasepool
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
/*Lock the image buffer*/
CVPixelBufferLockBaseAddress(imageBuffer,0);
/*Get information about the image*/
uint8_t *baseAddress = (uint8_t *)CVPixelBufferGetBaseAddress(imageBuffer);
size_t width = CVPixelBufferGetWidth(imageBuffer);
size_t height = CVPixelBufferGetHeight(imageBuffer);
size_t stride = CVPixelBufferGetBytesPerRow(imageBuffer);
// vImage In
Pixel_8 *bitmap = (Pixel_8 *)malloc(width * height * sizeof(Pixel_8));
const vImage_Buffer inImage = bitmap, height, width, stride ;
//How can I take this inImage and convert it to greyscale?????
//vImageConvert_RGBA8888toPlanar8()??? Is the correct starting format here??
所以我有两个问题:
(1) 在上面的代码中,RBGA8888
是正确的起始格式吗?
(2) 我怎样才能真正使Accelerate.Framework
调用转换为灰度?
【问题讨论】:
请帮我解决这个问题..***.com/q/38122040/3908884 【参考方案1】:这里有一个更简单的选择。如果您将相机采集格式更改为 YUV,那么您已经有了一个可以随意使用的灰度帧。设置数据输出时,请使用以下内容:
dataOutput.videoSettings = @ (id)kCVPixelBufferPixelFormatTypeKey : @(kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) ;
然后,您可以使用以下方法在捕获回调中访问 Y 平面:
CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
CVPixelBufferLockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly);
uint8_t *yPlane = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0);
... do stuff with your greyscale camera image ...
CVPixelBufferUnlockBaseAddress(pixelBuffer);
【讨论】:
从某种意义上说,是的。使用 YUV 的问题是现在你有两个问题。最初的问题和其他一切都需要 RGB 数据的问题。虽然在有限的情况下它可能就足够了。 所以操作员询问如何转换为灰度,而您的回答只是演示了如何获取像素缓冲区地址。【参考方案2】:vImage 方法是使用vImageMatrixMultiply_Planar8
和一个1x3 矩阵。
vImageConvert_RGBA8888toPlanar8
是用于将 RGBA8888 缓冲区转换为 4 个平面缓冲区的函数。这些由vImageMatrixMultiply_Planar8
使用。 vImageMatrixMultiply_ARGB8888
也可以一次性完成,但您的灰色通道将与结果中的其他三个通道交错。 vImageConvert_RGBA8888toPlanar8
本身不做任何数学运算。它所做的只是将交错的图像分成单独的图像平面。
如果您还需要调整伽玛,那么vImageConvert_AnyToAny()
可能是一个简单的选择。它将完成从 RGB 格式到灰度色彩空间的完全色彩管理转换。请参阅 vImage_Utilities.h。
不过,我更喜欢 Tarks 的回答。它只是让您不得不手动对亮度进行颜色管理(如果您关心的话)。
【讨论】:
【参考方案3】:使用 Accelerate vImage 将 BGRA 图像转换为灰度
此方法旨在说明如何使用 Accelerate 的 vImage
将 BGR 图像转换为灰度图像。您的图像很可能是 RGBA 格式,您需要相应地调整矩阵,但相机输出 BGRA,所以我在这里使用它。矩阵中的值与 OpenCV 中用于cvtColor 的值相同,您可能还可以使用其他值,例如luminosity。我假设您为结果分配了适当的内存量。在灰度的情况下,它只有 1 通道或用于 BGRA 的内存的 1/4。如果有人发现此代码有问题,请发表评论。
性能说明
以这种方式转换为灰度可能不是最快的。您应该检查环境中任何方法的性能。 Brad Larson 的 GPUImage 可能更快,甚至 OpenCV 的 cvtColor
可能更快。在任何情况下,您都希望删除对 malloc 的调用并释放中间缓冲区,并在应用程序生命周期内管理它们。否则,函数调用将由 malloc 和 free 支配。 Apple 的文档建议尽可能重用整个 vImage_Buffer。
您还可以阅读有关使用 NEON intrinsics 解决相同问题的信息。
最后,最快的方法是根本不转换。如果您从设备摄像头获取图像数据,则设备摄像头本身就是kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
格式。意思是,抓取第一个平面的数据(Y-Channel,luma)是获得灰度的最快方法。
BGRA 转灰度
- (void)convertBGRAFrame:(const CLPBasicVideoFrame &)bgraFrame toGrayscale:(CLPBasicVideoFrame &)grayscaleFrame
vImage_Buffer bgraImageBuffer =
.width = bgraFrame.width,
.height = bgraFrame.height,
.rowBytes = bgraFrame.bytesPerRow,
.data = bgraFrame.rawPixelData
;
void *intermediateBuffer = malloc(bgraFrame.totalBytes);
vImage_Buffer intermediateImageBuffer =
.width = bgraFrame.width,
.height = bgraFrame.height,
.rowBytes = bgraFrame.bytesPerRow,
.data = intermediateBuffer
;
int32_t divisor = 256;
// int16_t a = (int16_t)roundf(1.0f * divisor);
int16_t r = (int16_t)roundf(0.299f * divisor);
int16_t g = (int16_t)roundf(0.587f * divisor);
int16_t b = (int16_t)roundf(0.114f * divisor);
const int16_t bgrToGray[4 * 4] = b, 0, 0, 0,
g, 0, 0, 0,
r, 0, 0, 0,
0, 0, 0, 0 ;
vImage_Error error;
error = vImageMatrixMultiply_ARGB8888(&bgraImageBuffer, &intermediateImageBuffer, bgrToGray, divisor, NULL, NULL, kvImageNoFlags);
if (error != kvImageNoError)
NSLog(@"%s, vImage error %zd", __PRETTY_FUNCTION__, error);
vImage_Buffer grayscaleImageBuffer =
.width = grayscaleFrame.width,
.height = grayscaleFrame.height,
.rowBytes = grayscaleFrame.bytesPerRow,
.data = grayscaleFrame.rawPixelData
;
void *scratchBuffer = malloc(grayscaleFrame.totalBytes);
vImage_Buffer scratchImageBuffer =
.width = grayscaleFrame.width,
.height = grayscaleFrame.height,
.rowBytes = grayscaleFrame.bytesPerRow,
.data = scratchBuffer
;
error = vImageConvert_ARGB8888toPlanar8(&intermediateImageBuffer, &grayscaleImageBuffer, &scratchImageBuffer, &scratchImageBuffer, &scratchImageBuffer, kvImageNoFlags);
if (error != kvImageNoError)
NSLog(@"%s, vImage error %zd", __PRETTY_FUNCTION__, error);
free(intermediateBuffer);
free(scratchBuffer);
CLPBasicVideoFrame.h - 供参考
typedef struct
size_t width;
size_t height;
size_t bytesPerRow;
size_t totalBytes;
unsigned long pixelFormat;
void *rawPixelData;
CLPBasicVideoFrame;
我完成了灰度转换,但是当我在网上找到这本书名为Instant OpenCV for ios 时遇到了质量问题。我亲自拿起了一份,里面有很多宝石,虽然代码有点乱。从好的方面来说,它是一本价格非常合理的电子书。
我对那个矩阵很好奇。我玩弄了几个小时,试图弄清楚应该是什么安排。我原以为这些值应该在对角线上,但 Instant OpenCV 的家伙把它放在上面。
【讨论】:
就bgrToGray
矩阵而言,每个输入通道映射到bgrToGray
的一行,bgrToGray
的每一列映射到一个输出通道。在数学方面,bgraImageBuffer
中的每个像素都是 1x4 矩阵,bgrToGray
是 4x4 矩阵,vImageMatrixMultiply_ARGB8888()
执行点积,将结果(也是 1x4 矩阵)放入intermediateImageBuffer
。只有bgrToGray
的第一列很重要,因为vImageConvert_ARGB8888toPlanar8
将其他3 个通道丢弃到scratchBuffer
。我的答案通过使用vImageMatrixMultiply_ARGB8888ToPlanar8()
结合了这些步骤。【参考方案4】:
如果您需要使用 BGRA 视频流 - 您可以使用这种出色的转换 here
这是您需要采取的功能:
void neon_convert (uint8_t * __restrict dest, uint8_t * __restrict src, int numPixels)
int i;
uint8x8_t rfac = vdup_n_u8 (77);
uint8x8_t gfac = vdup_n_u8 (151);
uint8x8_t bfac = vdup_n_u8 (28);
int n = numPixels / 8;
// Convert per eight pixels
for (i=0; i < n; ++i)
uint16x8_t temp;
uint8x8x4_t rgb = vld4_u8 (src);
uint8x8_t result;
temp = vmull_u8 (rgb.val[0], bfac);
temp = vmlal_u8 (temp,rgb.val[1], gfac);
temp = vmlal_u8 (temp,rgb.val[2], rfac);
result = vshrn_n_u16 (temp, 8);
vst1_u8 (dest, result);
src += 8*4;
dest += 8;
更多优化(使用程序集)在链接中
【讨论】:
【参考方案5】:(1) 我对 iOS 相机框架的经验是使用 kCMPixelFormat_32BGRA
格式的图像,该格式与 ARGB8888 系列函数兼容。 (也可以使用其他格式。)
(2) 在 iOS 上从 BGR 转换为灰度的最简单方法是使用vImageMatrixMultiply_ARGB8888ToPlanar8()
:
https://developer.apple.com/documentation/accelerate/1546979-vimagematrixmultiply_argb8888top
这是一个用 Swift 编写的相当完整的示例。我假设 Objective-C 代码是相似的。
guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else
// TODO: report error
return
// Lock the image buffer
if (kCVReturnSuccess != CVPixelBufferLockBaseAddress(imageBuffer, CVPixelBufferLockFlags.readOnly))
// TODO: report error
return
defer
CVPixelBufferUnlockBaseAddress(imageBuffer, CVPixelBufferLockFlags.readOnly)
// Create input vImage_Buffer
let baseAddress = CVPixelBufferGetBaseAddress(imageBuffer)
let width = CVPixelBufferGetWidth(imageBuffer)
let height = CVPixelBufferGetHeight(imageBuffer)
let stride = CVPixelBufferGetBytesPerRow(imageBuffer)
var inImage = vImage_Buffer(data: baseAddress, height: UInt(height), width: UInt(width), rowBytes: stride)
// Create output vImage_Buffer
let bitmap = malloc(width * height)
var outImage = vImage_Buffer(data: bitmap, height: UInt(height), width: UInt(width), rowBytes: width)
defer
// Make sure to free unless the caller is responsible for this
free(bitmap)
// Arbitrary divisor to scale coefficients to integer values
let divisor: Int32 = 0x1000
let fDivisor = Float(divisor)
// Rec.709 coefficients
var coefficientsMatrix = [
Int16(0.0722 * fDivisor), // blue
Int16(0.7152 * fDivisor), // green
Int16(0.2126 * fDivisor), // red
0 // alpha
]
// Convert to greyscale
if (kvImageNoError != vImageMatrixMultiply_ARGB8888ToPlanar8(
&inImage, &outImage, &coefficientsMatrix, divisor, nil, 0, vImage_Flags(kvImageNoFlags)))
// TODO: report error
return
以上代码的灵感来自 Apple 的灰度转换教程,该教程可在以下链接中找到。如果需要,它还包括转换为CGImage
。请注意,它们假定 RGB 顺序而不是 BGR,并且它们仅提供 3 个系数而不是 4 个(错误?)
https://developer.apple.com/documentation/accelerate/vimage/converting_color_images_to_grayscale
【讨论】:
以上是关于如何使用 Accelerate Framework 将 iOS 相机图像转换为灰度?的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 Metal API 或 Accelerate Framework 绘制裁剪的位图?
如何使用 Accelerate Framework 将带有遮罩的图像组合成一个 UIImage?
有人可以解释这段代码如何使用 Accelerate Framework 将音量转换为分贝吗?
iOS - 使用 Accelerate.framework 对向量进行按位异或