繁体   English   中英

如何在iOS 9中使用Swift从CMSampleBuffer中提取像素数据进行处理?

[英]How to extract pixel data for processing from CMSampleBuffer using Swift in iOS 9?

我正在Swift中编写一个应用程序,它使用Scandit条形码扫描SDK。 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

致命错误是试图从quartzImage解析UIImage。

width,height和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;

但是,我找不到允许使用CVPlanarPixelBufferInfo访问缓冲区信息的Swift实现...我尝试过的所有内容都失败了,所以我无法确定“Y”,“Cr”等的偏移量。

如何访问缓冲区中的像素数据? 这是SDK传递的CMSampleBuffer的问题,iOS9的问题,还是两者都有问题?

使用Codo的“提示”并在Scandit文档中集成Objective-C代码,我在Swift中制定了一个解决方案。 虽然我接受了Codo的答案,因为它有很大的帮助,但我也回答了我自己的问题,希望完整的解决方案可以帮助未来的人:

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.3MP图像,但代码执行速度非常快。 我坦率地承认我对Core Media框架没有深刻理解,但我相信这意味着代码正在GPU上执行。 但是,我会感谢任何关于代码的评论,以使其更有效,或者改善“迅捷”,因为我完全是一个业余爱好者。

这不是一个完整的答案,只是一些提示:

Scandit使用YCbCrBiPlanar格式。 它对于每个像素具有Y字节,并且对于每组2×2像素具有Cb和Cr字节。 Y值在第一个平面上,Cb和Cr值在第二个平面上。

如果图像是w × h像素大,那么第一个平面包含hw字节(并且每行可能有一些填充)。

第二个平面包含h / 2行w / 2对字节。 每对由Cb和Cr值组成。 同样,每一行最后都可能有一些填充。

因此,位置(x,y)处像素的Y值可以在地址处找到:

Y:baseAddressPlane1 + y * bytesPerRowPlane1 + x

并且位置(x,y)处的像素的值Cb和Cr可以在地址处找到:

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

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

除以2的除法是整数除法,丢弃小数部分。

暂无
暂无

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

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