看起来这应该比我找到它更简单。
我在标准委托方法中有一个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
调用以转换为灰度?
答案 0 :(得分:4)
这里有一个更简单的选择。如果您将相机采集格式更改为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);
答案 1 :(得分:4)
vImage方法是使用vImageMatrixMultiply_Planar8
和1x3矩阵。
vImageConvert_RGBA8888toPlanar8
是用于将RGBA8888缓冲区转换为4个平面缓冲区的函数。这些由vImageMatrixMultiply_Planar8
使用。 vImageMatrixMultiply_ARGB8888
也会在一次通过中执行此操作,但您的灰色通道将与结果中的其他三个通道交错。 vImageConvert_RGBA8888toPlanar8
本身不做任何数学运算。它所做的就是将交错图像分离成单独的图像平面。
如果您还需要调整伽玛,那么vImageConvert_AnyToAny()
可能是最简单的选择。它将完成从RGB格式到灰度色彩空间的全色管理转换。请参见vImage_Utilities.h。
我喜欢Tarks更好的回答。它只是让你处于必须手动着色管理亮度的位置(如果你在意)。
答案 2 :(得分:3)
此方法旨在说明在将BGR图像转换为灰度时使用Accelerate vImage
。您的图像很可能是RGBA格式,您需要相应地调整矩阵,但相机会输出BGRA,所以我在这里使用它。矩阵中的值与OpenCV中cvtColor使用的值相同,您可以使用其他值,如luminosity。我假设你为结果提供了适当的内存量。在灰度的情况下,它仅用于BGRA的1通道或1/4的存储器。如果有人发现此代码存在问题,请发表评论。
以这种方式转换为灰度可能不是最快的。您应该检查环境中任何方法的性能。 Brad Larson的GPUImage可能更快,甚至OpenCV的cvtColor
。在任何情况下,您都希望删除对malloc的调用,并为中间缓冲区释放,并为应用程序生命周期管理它们。否则,函数调用将由malloc和free控制。 Apple的文档建议尽可能重用整个vImage_Buffer。
您还可以阅读NEON intrinsics解决相同问题的信息。
最后,最快的方法根本就没有转换。如果您从设备摄像头获取图像数据,则设备摄像头本身采用kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
格式。意思是,抓住第一架飞机的数据(Y-Channel,luma)是获得灰度的最快方法。
- (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);
}
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的人就像上面那样。
答案 3 :(得分:0)
如果您需要使用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;
}
}
更多优化(使用程序集)在链接
中