Как извлечь данные пикселей для обработки из CMSampleBuffer с помощью Swift в iOS 9?

Я пишу приложение в Swift, в котором используется SDK для сканирования штрих-кодов Scandit. SDK позволяет получить прямой доступ к кадрам камеры и предоставляет кадр в виде CMSampleBuffer. Они предоставляют документацию в Objective-C, с которой у меня возникают проблемы при работе в Swift. Я не знаю, связана ли проблема с портированием кода или с самим образцом буфера что-то не так, возможно, из-за изменений в Core Media с момента создания их документации.

Их API выставляет фрейм следующим образом (Objective-C):

interface YourViewController () <SBSProcessFrameDelegate>
...
- (void)barcodePicker:(SBSBarcodePicker*)barcodePicker
      didProcessFrame:(CMSampleBufferRef)frame
              session:(SBSScanSession*)session {
    // Process the frame yourself.
}

Основываясь на нескольких ответах здесь на SO, я пытаюсь обработать фрейм с помощью:

let imageBuffer = CMSampleBufferGetImageBuffer(frame)!
CVPixelBufferLockBaseAddress(imageBuffer, 0)
let baseAddress = CVPixelBufferGetBaseAddress(imageBuffer)

let width = CVPixelBufferGetWidth(imageBuffer)
let height = CVPixelBufferGetHeight(imageBuffer)
let bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer)

let colorSpace = CGColorSpaceCreateDeviceRGB()
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.NoneSkipFirst.rawValue | CGBitmapInfo.ByteOrder32Little.rawValue)
let context = CGBitmapContextCreate(baseAddress, width, height, 8, bytesPerRow, colorSpace, bitmapInfo.rawValue)

let quartzImage = CGBitmapContextCreateImage(context)
CVPixelBufferUnlockBaseAddress(imageBuffer,0)

let image = UIImage(CGImage: quartzImage!)

Но это не так с:

Jan 29 09:01:30  Scandit[1308] <Error>: CGBitmapContextCreate: invalid data bytes/row: should be at least 7680 for 8 integer bits/component, 3 components, kCGImageAlphaNoneSkipFirst.
Jan 29 09:01:30  Scandit[1308] <Error>: CGBitmapContextCreateImage: invalid context 0x0. If you want to see the backtrace, please set CG_CONTEXT_SHOW_BACKTRACE environmental variable.
fatal error: unexpectedly found nil while unwrapping an Optional value

Неустранимая ошибка при попытке разрешить UIImage из quartzImage,

Ширина, высота и bytesPerRow (по базовому адресу):

Width: 1920
Height: 1080
Bytes per row: 2904

Как передано от делегата, вот что буфер содержит в соответствии с CMSampleBufferGetFormatDescription(frame):

Optional(<CMVideoFormatDescription 0x1447dafa0 [0x1a1864b68]> {
    mediaType:'vide' 
    mediaSubType:'420f' 
    mediaSpecific: {
        codecType: '420f'       dimensions: 1920 x 1080 
    } 
    extensions: {<CFBasicHash 0x1447dba10 [0x1a1864b68]>{type = immutable dict, count = 6,
entries =>
    0 : <CFString 0x19d28b678 [0x1a1864b68]>{contents = "CVImageBufferYCbCrMatrix"} = <CFString 0x19d28b6b8 [0x1a1864b68]>{contents = "ITU_R_601_4"}
    1 : <CFString 0x19d28b7d8 [0x1a1864b68]>{contents = "CVImageBufferTransferFunction"} = <CFString 0x19d28b698 [0x1a1864b68]>{contents = "ITU_R_709_2"}
    2 : <CFString 0x19d2b65c0 [0x1a1864b68]>{contents = "CVBytesPerRow"} = <CFNumber 0xb00000000000b582 [0x1a1864b68]>{value = +2904, type = kCFNumberSInt32Type}
    3 : <CFString 0x19d2b6640 [0x1a1864b68]>{contents = "Version"} = <CFNumber 0xb000000000000022 [0x1a1864b68]>{value = +2, type = kCFNumberSInt32Type}
    5 : <CFString 0x19d28b758 [0x1a1864b68]>{contents = "CVImageBufferColorPrimaries"} = <CFString 0x19d28b698 [0x1a1864b68]>{contents = "ITU_R_709_2"}
    6 : <CFString 0x19d28b818 [0x1a1864b68]>{contents = "CVImageBufferChromaLocationTopField"} = <CFString 0x19d28b878 [0x1a1864b68]>{contents = "Center"}
}
}
})

Я понимаю, что здесь может быть несколько "плоскостей", но даже с:

let pixelBufferBytesPerRow0 = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 0)
let pixelBufferBytesPerRow1 = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 1)

дает:

Pixel buffer bytes per row (Plane 0): 1920
Pixel buffer bytes per row (Plane 1): 1920

Я не понимаю это несоответствие.

Я также попытался обработать каждый пиксель индивидуально, так как ясно, что в буфере содержится какой-то тип YCbCr, но он терпит неудачу во всех случаях, которые я пробовал. Scandit API предлагает (Objective-C):

// Get the buffer info for the YCbCrBiPlanar format.
void *baseAddress = CVPixelBufferGetBaseAddress(imageBuffer);
CVPlanarPixelBufferInfo_YCbCrBiPlanar *bufferInfo = (CVPlanarPixelBufferInfo_YCbCrBiPlanar *)baseAddress;

Но я не могу найти реализацию Swift, которая разрешает доступ к информации о буфере с помощью CVPlanarPixelBufferInfo... все, что я пробовал, терпит неудачу, поэтому я не могу определить смещение для "Y", "Cr" и т. Д.

Как я могу получить доступ к данным пикселей в буфере? Это проблема с CMSampleBuffer, который передает SDK, проблема с iOS9 или с обоими?

2 ответа

Решение

Это не полный ответ, только некоторые подсказки:

Scandit использует формат YCbCrBiPlanar. У него есть байт Y для каждого пикселя и байт Cb и Cr для каждой группы 2x2 пикселей. Значения Y находятся на первой плоскости, значения Cb и Cr на второй плоскости.

Если изображение имеет размер w x h пикселей, то первая плоскость содержит h строк по w байтов (и, возможно, некоторый отступ для каждой строки).

Вторая плоскость содержит h / 2 строки w / 2 пары байтов. Каждая пара состоит из значения Cb и Cr. Снова каждая строка может иметь отступ в конце.

Таким образом, значение Y для пикселя в позиции (x, y) можно найти по адресу:

Y: baseAddressPlane1 + y * bytesPerRowPlane1 + x

А значения Cb и Cr для пикселя в позиции (x, y) можно найти по адресу:

Cb: baseAddressPlane2 + (y / 2) * bytesPerRowPlan2 + (x / 2) * 2

Cr: baseAddressPlane2 + (y / 2) * bytesPerRowPlan2 + (x / 2) * 2 + 1

Деления на 2 являются целочисленными делениями, которые отбрасывают дробную часть.

Опираясь на "подсказки" Кодо и интегрируя его с кодом Objective-C в документации Scandit, я разработал решение в Swift. Хотя я принял ответ Кодо, так как он мне очень помог, я также отвечаю на свой вопрос в надежде, что полное решение поможет кому-то в будущем:

let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)!
CVPixelBufferLockBaseAddress(pixelBuffer, 0)
let lumaBaseAddress = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0)
let chromaBaseAddress = CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1)

let width = CVPixelBufferGetWidth(pixelBuffer)
let height = CVPixelBufferGetHeight(pixelBuffer)

let lumaBytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0)
let chromaBytesPerRow = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1)
let lumaBuffer = UnsafeMutablePointer<UInt8>(lumaBaseAddress)
let chromaBuffer = UnsafeMutablePointer<UInt8>(chromaBaseAddress)

var rgbaImage = [UInt8](count: 4*width*height, repeatedValue: 0)
for var x = 0; x < width; x++ {
    for var y = 0; y < height; y++ {
        let lumaIndex = x+y*lumaBytesPerRow
        let chromaIndex = (y/2)*chromaBytesPerRow+(x/2)*2
        let yp = lumaBuffer[lumaIndex]
        let cb = chromaBuffer[chromaIndex]
        let cr = chromaBuffer[chromaIndex+1]

        let ri = Double(yp)                                + 1.402   * (Double(cr) - 128)
        let gi = Double(yp) - 0.34414 * (Double(cb) - 128) - 0.71414 * (Double(cr) - 128)
        let bi = Double(yp) + 1.772   * (Double(cb) - 128)

        let r = UInt8(min(max(ri,0), 255))
        let g = UInt8(min(max(gi,0), 255))
        let b = UInt8(min(max(bi,0), 255))

        rgbaImage[(x + y * width) * 4] = b
        rgbaImage[(x + y * width) * 4 + 1] = g
        rgbaImage[(x + y * width) * 4 + 2] = r
        rgbaImage[(x + y * width) * 4 + 3] = 255
    }
}

let colorSpace = CGColorSpaceCreateDeviceRGB()
let dataProvider: CGDataProviderRef = CGDataProviderCreateWithData(nil, rgbaImage, 4 * width * height, nil)!
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.NoneSkipFirst.rawValue | CGBitmapInfo.ByteOrder32Little.rawValue)
let cgImage: CGImageRef = CGImageCreate(width, height, 8, 32, width * 4, colorSpace!, bitmapInfo, dataProvider, nil, true, CGColorRenderingIntent.RenderingIntentDefault)!
let image: UIImage = UIImage(CGImage: cgImage)
CVPixelBufferUnlockBaseAddress(pixelBuffer,0)

Несмотря на итерацию по всему 8.3-мегапиксельному образу, код выполняется очень быстро. Я свободно признаю, что у меня нет глубокого понимания сред Core Media, но я считаю, что это означает, что код выполняется на GPU. Но я был бы признателен за любые комментарии к коду, чтобы сделать его более эффективным или улучшить "стремительность", так как я полностью любитель.

Другие вопросы по тегам