Как получить компонент Y из CMSampleBuffer, полученного в результате AVCaptureSession?
Привет, я пытаюсь получить доступ к необработанным данным с камеры iphone, используя AVCaptureSession. Я следую инструкциям Apple ( ссылка здесь).
Необработанные данные из выборочного буфера находятся в формате YUV (Правильно ли я здесь о формате необработанных видеокадров??), как напрямую получить данные для компонента Y из необработанных данных, хранящихся в выборочном буфере.
4 ответа
При настройке AVCaptureVideoDataOutput, который возвращает необработанные кадры камеры, вы можете установить формат кадров, используя следующий код:
[videoOutput setVideoSettings:[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:kCVPixelFormatType_32BGRA] forKey:(id)kCVPixelBufferPixelFormatTypeKey]];
В этом случае указывается формат пикселей BGRA (я использовал его для сопоставления цветового формата для текстуры OpenGL ES). Каждый пиксель в этом формате имеет один байт для синего, зеленого, красного и альфа-канала в указанном порядке. Это позволяет легко вытаскивать цветовые компоненты, но вы жертвуете небольшой производительностью, делая необходимость преобразования из собственного пространства цветов YUV камеры.
Другие поддерживаемые цветовые пространства kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange
а также kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
на новых устройствах и kCVPixelFormatType_422YpCbCr8
на iPhone 3G. VideoRange
или же FullRange
Суффикс просто указывает, возвращаются ли байты между 16 - 235 для Y и 16 - 240 для УФ или полными 0 - 255 для каждого компонента.
Я полагаю, что цветовым пространством по умолчанию, используемым экземпляром AVCaptureVideoDataOutput, является плоское цветовое пространство YUV 4:2:0 (за исключением iPhone 3G, где это YUV 4:2:2 с чередованием). Это означает, что в видеокадре содержатся две плоскости данных изображения, причем Y-плоскость идет первой. Для каждого пикселя в вашем полученном изображении есть один байт для значения Y в этом пикселе.
Вы могли бы получить эти необработанные данные Y, реализовав что-то вроде этого в вашем обратном вызове делегата:
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
CVImageBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
CVPixelBufferLockBaseAddress(pixelBuffer, 0);
unsigned char *rawPixelBase = (unsigned char *)CVPixelBufferGetBaseAddress(pixelBuffer);
// Do something with the raw pixels here
CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
}
Затем вы можете определить местоположение в данных кадра для каждой координаты X, Y на изображении и вытащить байт, соответствующий компоненту Y с этой координатой.
Пример Apple FindMyiCone от WWDC 2010 (доступен вместе с видео) показывает, как обрабатывать необработанные данные BGRA из каждого кадра. Я также создал пример приложения, для которого вы можете загрузить здесь код, который выполняет отслеживание объектов на основе цвета с использованием живого видео с камеры iPhone. Оба показывают, как обрабатывать необработанные данные пикселей, но ни один из них не работает в цветовом пространстве YUV.
В дополнение к ответу Брэда и вашему собственному коду вы должны учесть следующее:
Поскольку ваше изображение имеет две отдельные плоскости, функция CVPixelBufferGetBaseAddress не будет возвращать базовый адрес плоскости, а скорее базовый адрес дополнительной структуры данных. Вероятно, из-за текущей реализации вы получаете адрес, достаточно близкий к первой плоскости, чтобы вы могли видеть изображение. Но это причина того, что он сдвинут и мусор вверху слева. Правильный способ получить первый самолет:
unsigned char *rowBase = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0);
Строка на изображении может быть длиннее ширины изображения (из-за округления). Вот почему есть отдельные функции для получения ширины и количества байтов в строке. У вас нет этой проблемы в данный момент. Но это может измениться со следующей версией iOS. Итак, ваш код должен быть:
int bufferHeight = CVPixelBufferGetHeight(pixelBuffer);
int bufferWidth = CVPixelBufferGetWidth(pixelBuffer);
int bytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0);
int size = bufferHeight * bytesPerRow ;
unsigned char *pixel = (unsigned char*)malloc(size);
unsigned char *rowBase = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0);
memcpy (pixel, rowBase, size);
Также обратите внимание, что ваш код с треском провалится на iPhone 3G.
Если вам нужен только канал яркости, я рекомендую не использовать формат BGRA, так как он идет с конвертацией. Apple предлагает использовать BGRA, если вы делаете рендеринг, но он вам не нужен для извлечения информации о яркости. Как уже упоминал Брэд, наиболее эффективным форматом является формат YUV, встроенный в камеру.
Тем не менее, извлечение правильных байтов из буфера семплов немного сложнее, особенно в отношении iPhone 3G с его чередованным форматом YUV 422. Итак, вот мой код, который отлично работает с iPhone 3G, 3GS, iPod Touch 4 и iPhone 4S.
#pragma mark -
#pragma mark AVCaptureVideoDataOutputSampleBufferDelegate Methods
#if !(TARGET_IPHONE_SIMULATOR)
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection;
{
// get image buffer reference
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
// extract needed informations from image buffer
CVPixelBufferLockBaseAddress(imageBuffer, 0);
size_t bufferSize = CVPixelBufferGetDataSize(imageBuffer);
void *baseAddress = CVPixelBufferGetBaseAddress(imageBuffer);
CGSize resolution = CGSizeMake(CVPixelBufferGetWidth(imageBuffer), CVPixelBufferGetHeight(imageBuffer));
// variables for grayscaleBuffer
void *grayscaleBuffer = 0;
size_t grayscaleBufferSize = 0;
// the pixelFormat differs between iPhone 3G and later models
OSType pixelFormat = CVPixelBufferGetPixelFormatType(imageBuffer);
if (pixelFormat == '2vuy') { // iPhone 3G
// kCVPixelFormatType_422YpCbCr8 = '2vuy',
/* Component Y'CbCr 8-bit 4:2:2, ordered Cb Y'0 Cr Y'1 */
// copy every second byte (luminance bytes form Y-channel) to new buffer
grayscaleBufferSize = bufferSize/2;
grayscaleBuffer = malloc(grayscaleBufferSize);
if (grayscaleBuffer == NULL) {
NSLog(@"ERROR in %@:%@:%d: couldn't allocate memory for grayscaleBuffer!", NSStringFromClass([self class]), NSStringFromSelector(_cmd), __LINE__);
return nil; }
memset(grayscaleBuffer, 0, grayscaleBufferSize);
void *sourceMemPos = baseAddress + 1;
void *destinationMemPos = grayscaleBuffer;
void *destinationEnd = grayscaleBuffer + grayscaleBufferSize;
while (destinationMemPos <= destinationEnd) {
memcpy(destinationMemPos, sourceMemPos, 1);
destinationMemPos += 1;
sourceMemPos += 2;
}
}
if (pixelFormat == '420v' || pixelFormat == '420f') {
// kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange = '420v',
// kCVPixelFormatType_420YpCbCr8BiPlanarFullRange = '420f',
// Bi-Planar Component Y'CbCr 8-bit 4:2:0, video-range (luma=[16,235] chroma=[16,240]).
// Bi-Planar Component Y'CbCr 8-bit 4:2:0, full-range (luma=[0,255] chroma=[1,255]).
// baseAddress points to a big-endian CVPlanarPixelBufferInfo_YCbCrBiPlanar struct
// i.e.: Y-channel in this format is in the first third of the buffer!
int bytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 0);
baseAddress = CVPixelBufferGetBaseAddressOfPlane(imageBuffer,0);
grayscaleBufferSize = resolution.height * bytesPerRow ;
grayscaleBuffer = malloc(grayscaleBufferSize);
if (grayscaleBuffer == NULL) {
NSLog(@"ERROR in %@:%@:%d: couldn't allocate memory for grayscaleBuffer!", NSStringFromClass([self class]), NSStringFromSelector(_cmd), __LINE__);
return nil; }
memset(grayscaleBuffer, 0, grayscaleBufferSize);
memcpy (grayscaleBuffer, baseAddress, grayscaleBufferSize);
}
// do whatever you want with the grayscale buffer
...
// clean-up
free(grayscaleBuffer);
}
#endif
Это просто кульминация тяжелой работы всех остальных, над и над другими потоками, преобразованной в swift 3 для тех, кто считает ее полезной.
func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputSampleBuffer sampleBuffer: CMSampleBuffer!, from connection: AVCaptureConnection!) {
if let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) {
CVPixelBufferLockBaseAddress(pixelBuffer, CVPixelBufferLockFlags.readOnly)
let pixelFormatType = CVPixelBufferGetPixelFormatType(pixelBuffer)
if pixelFormatType == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
|| pixelFormatType == kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange {
let bufferHeight = CVPixelBufferGetHeight(pixelBuffer)
let bufferWidth = CVPixelBufferGetWidth(pixelBuffer)
let lumaBytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0)
let size = bufferHeight * lumaBytesPerRow
let lumaBaseAddress = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0)
let lumaByteBuffer = unsafeBitCast(lumaBaseAddress, to:UnsafeMutablePointer<UInt8>.self)
let releaseDataCallback: CGDataProviderReleaseDataCallback = { (info: UnsafeMutableRawPointer?, data: UnsafeRawPointer, size: Int) -> () in
// https://developer.apple.com/reference/coregraphics/cgdataproviderreleasedatacallback
// N.B. 'CGDataProviderRelease' is unavailable: Core Foundation objects are automatically memory managed
return
}
if let dataProvider = CGDataProvider(dataInfo: nil, data: lumaByteBuffer, size: size, releaseData: releaseDataCallback) {
let colorSpace = CGColorSpaceCreateDeviceGray()
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.noneSkipFirst.rawValue)
let cgImage = CGImage(width: bufferWidth, height: bufferHeight, bitsPerComponent: 8, bitsPerPixel: 8, bytesPerRow: lumaBytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo, provider: dataProvider, decode: nil, shouldInterpolate: false, intent: CGColorRenderingIntent.defaultIntent)
let greyscaleImage = UIImage(cgImage: cgImage!)
// do what you want with the greyscale image.
}
}
CVPixelBufferUnlockBaseAddress(pixelBuffer, CVPixelBufferLockFlags.readOnly)
}
}