cmsamplebufferからyコンポーネントを取得する方法は、avcapturessessionから生じましたか?
-
28-09-2019 - |
質問
ねえ、私はAvcaptureSessionを使用してiPhoneカメラから生データにアクセスしようとしています。 Appleが提供するガイドに従います(ここにリンクします).
SampleBufferの生データはYUV形式です(RAWビデオフレーム形式についてここで正しいですか?)、SampleBufferに保存されている生データからYコンポーネントのデータを直接取得する方法。
解決
生のカメラフレームを返すavcapturevideodataoutputをセットアップする場合、次のようなコードを使用してフレームの形式を設定できます。
[videoOutput setVideoSettings:[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:kCVPixelFormatType_32BGRA] forKey:(id)kCVPixelBufferPixelFormatTypeKey]];
この場合、BGRAピクセル形式が指定されています(OpenGL ESテクスチャのカラー形式と一致するためにこれを使用しました)。その形式の各ピクセルには、その順に青、緑、赤、およびアルファ用の1つのバイトがあります。これを使用すると、カラーコンポーネントを簡単に引き出すことができますが、カメラネイティブのYuvカラースペースからの変換を行う必要があることで、少しのパフォーマンスを犠牲にします。
他のサポートされているカラースペースはそうです kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange
と kCVPixelFormatType_420YpCbCr8BiPlanarFullRange
新しいデバイスで kCVPixelFormatType_422YpCbCr8
iPhone3gで。 VideoRange
また FullRange
接尾辞は、バイトがyの場合は16〜235からUVで16〜240の間に返されるか、各コンポーネントで完全な0-255を返すかを示します。
avcapturevideodataoutputインスタンスで使用されるデフォルトのカラースペースは、yuv 4:2:0の平面カラースペースです(iphone 3gを除く、yuv 4:2:2インターリーブです)。これは、ビデオフレーム内に含まれる画像データの2つの平面があり、Y平面が最初に登場することを意味します。結果の画像のすべてのピクセルについて、そのピクセルにy値に1つのバイトがあります。
あなたはあなたのデリゲートコールバックにこのようなものを実装することにより、この生のデータを取得するでしょう:
- (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のFindMyConeサンプルから WWDC 2010 (ビデオとともにアクセス可能)は、各フレームから生のBGRAデータを処理する方法を示しています。サンプルアプリケーションも作成しました。 ここ, 、それは実行されます カラーベースのオブジェクト追跡 iPhoneのカメラからのライブビデオを使用します。どちらも生のピクセルデータを処理する方法を示していますが、これらはどちらもYUVカラースペースでは機能しません。
他のヒント
Bradの答えとあなた自身のコードに加えて、次のことを検討したい:
あなたの画像には2つの別々の平面があるので、機能は 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形式です。
ただし、サンプルバッファーから右バイトを抽出することは、特にInterleaved YUV 422形式を備えたiPhone 3Gに関しては少し難しいです。ここに私のコードがあります。これは、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)
}
}