iOS

2015-12-16, ios

動画の1フレーム(CMSampleBuffer)の画素値を取得・操作する

元々はOpenCVのMatを使って画像の画素値を操作していたのですが、Swiftとの親和性だったりOpenCVそのものをiOSアプリに組み込むことのハードルの高さでつまずくことが多くなったので、OpenCVに頼らず画像の処理ができる(=画素値を弄れる)方法を検討してみました。3パターン試して、1だけうまくいっていますが一応3つとも掲載しています。

CMSampleBuffer型のsampleBuffer変数が渡されている(AVCaptureVideoDataOutputSampleBufferDelegateのcaptureOutputメソッド内で実行されている)ており、 このビューコントローラに存在するUIImageViewのimageに画像(UIImage)を渡すまでのコードです。

1.
CGImageを利用します。ポイントはCVPixelBufferGetBaseAddress(buffer)の戻り値をUnsafeMutablePointerにキャストする部分でしょうか。以下のサンプルではR値を他のレイヤにもコピーしてシンプルにグレースケール化(っぽく)しています。

if let buffer = CMSampleBufferGetImageBuffer(sampleBuffer) {
    CVPixelBufferLockBaseAddress(buffer, 0)

    let width = CVPixelBufferGetWidth(buffer)
    let height = CVPixelBufferGetHeight(buffer)
    let bytes = CVPixelBufferGetBytesPerRow(buffer)
    let base = UnsafeMutablePointer<UInt8>(CVPixelBufferGetBaseAddress(buffer))

    for(var y = 0; y < height; y++) {
        for(var x = 0; x < width; x++) {
            let offset = 4*((width*y)+x);
            base[offset] = base[offset] // R
            base[offset+1] = base[offset] // G
            base[offset+2] = base[offset] // B
            base[offset+3] = base[offset+3] // A
        }
    }

    let colorSpace = CGColorSpaceCreateDeviceRGB()

    let bi = CGBitmapInfo.ByteOrder32Little.rawValue | CGImageAlphaInfo.PremultipliedFirst.rawValue
    let context = CGBitmapContextCreate(base, width, height, 8, bytes, colorSpace,bi)
    if let cgImage = CGBitmapContextCreateImage(context) {
        let image = UIImage(CGImage: cgImage)

        CVPixelBufferUnlockBaseAddress(buffer, 0)
        self.imageView.image = image
    }
}

2.
UIGraphicsを利用します。こちらもUnsafeMutablePointerへのキャストが肝ですね。 この型を使うことで、なんと、実行時ではなくコンパイル時にSegmentation faultを検出できてしまうという。恐るべしSwift。

if let buffer = CMSampleBufferGetImageBuffer(sampleBuffer) {

    CVPixelBufferLockBaseAddress(buffer, 0)
    let width = CVPixelBufferGetWidth(buffer)
    let height = CVPixelBufferGetHeight(buffer)
    let base = UnsafeMutablePointer<UInt8>(CVPixelBufferGetBaseAddress(buffer))

    UIGraphicsBeginImageContext(CGSizeMake(CGFloat(width), CGFloat(height)));

    let c = UIGraphicsGetCurrentContext()
    let data = UnsafeMutablePointer<UInt8>(CGBitmapContextGetData(c))
    for(var y = 0; y < height; y++) {
        for(var x = 0; x < width; x++) {
            let offset = 4*((width*y)+x);
            data[offset] = base[offset] // R
            data[offset+1] = base[offset] // G
            data[offset+2] = base[offset] // B
            data[offset+3] = base[offset+3] // A
        }
    }

    self.imageView.image = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    CVPixelBufferUnlockBaseAddress(buffer, 0)
}

3.
これはちょっと違いますが参考まで。CIImageを介した方法です。 画素単位での操作は出来ない?雰囲気ですが、目的のフィルタや検出器が既にあることが わかっているなら圧倒的にシンプルで(恐らく)高速な解だと思われます。

if let buffer = CMSampleBufferGetImageBuffer(sampleBuffer) {
    let ciimage = CIImage(CVPixelBuffer: buffer)

    if let filter = CIFilter(name: "CISepiaTone") {
        filter.setValue(ciimage, forKey: kCIInputImageKey)
        filter.setValue(0.8, forKey: kCIInputIntensityKey)
        if let result:CIImage = filter.valueForKey(kCIOutputImageKey) as? CIImage {
            let image = UIImage(CIImage: result)
            self.imageView.image = image
        }
    }
}

参考URL

この記事は役に立ちましたか?