كيفية الحصول على مكون Y من CMSAMPLEBUFFER نتج عن AVCAPTURESESSERASESE؟

StackOverflow https://stackoverflow.com/questions/4085474

  •  28-09-2019
  •  | 
  •  

سؤال

مرحبًا ، أحاول الوصول إلى البيانات الخام من كاميرا iPhone باستخدام Avcapturesession. أتابع الدليل الذي توفره Apple (رابط هنا).

تكون البيانات الأولية من SampleBuffer بتنسيق YUV (هل أنا صحيح هنا حول تنسيق إطار الفيديو الخام ؟؟) ، كيفية الحصول مباشرة على البيانات الخاصة بمكون y من البيانات الأولية المخزنة في العينة.

هل كانت مفيدة؟

المحلول

عند إعداد AvcaptureVideOdataOutput الذي يعيد إطارات الكاميرا الخام ، يمكنك تعيين تنسيق الإطارات باستخدام رمز مثل ما يلي:

[videoOutput setVideoSettings:[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:kCVPixelFormatType_32BGRA] forKey:(id)kCVPixelBufferPixelFormatTypeKey]];

في هذه الحالة ، تم تحديد تنسيق بكسل BGRA (لقد استخدمت هذا لمطابقة تنسيق اللون لملمس OpenGL ES). يحتوي كل بكسل في هذا التنسيق على بايت واحد للأزرق والأخضر والأحمر والألفا ، بهذا الترتيب. إن الذهاب مع هذا يجعل من السهل سحب مكونات الألوان ، لكنك تضحي بأداء قليل من خلال الحاجة إلى إجراء التحويل من مساحة ألوان YUV-Camera.

مساحات الألوان الأخرى المدعومة kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange و kCVPixelFormatType_420YpCbCr8BiPlanarFullRange على الأجهزة الأحدث و kCVPixelFormatType_422YpCbCr8 على iPhone 3G. ال VideoRange أو FullRange تشير اللاحقة ببساطة إلى ما إذا كانت البايتات يتم إرجاعها بين 16 - 235 لـ Y و 16 - 240 للأشعة فوق البنفسجية أو كاملة 0 - 255 لكل مكون.

أعتقد أن مساحة الألوان الافتراضية المستخدمة من قبل مثيل AvcaptureVideOdataOutput هو Yuv 4: 2: 0 Planar Colorspace (باستثناء iPhone 3G ، حيث يكون yuv 4: 2: 2 interleaved). هذا يعني أن هناك طائرتان لبيانات الصورة الموجودة في إطار الفيديو ، مع قدوم الطائرة 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 في هذا الإحداثيات.

عينة FindMyicone من Apple من 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.

إذا كنت بحاجة فقط إلى قناة Luminance ، فإنني أوصي بعدم استخدام تنسيق BGRA ، لأنه يأتي مع النفقات العامة للتحويل. تقترح Apple استخدام BGRA إذا كنت تقوم بعمل أشياء ، لكنك لا تحتاجها لاستخراج معلومات اللمعان. كما ذكر براد سابقًا ، فإن التنسيق الأكثر كفاءة هو تنسيق YUV-Itive الكاميرا.

ومع ذلك ، فإن استخراج البايتات اليمنى من المخزن المؤقت للعينة أمر صعب بعض الشيء ، خاصة فيما يتعلق بـ 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)
    }
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top