11
votes

Comment obtenir le composant Y de CMSampleBuffer résulte de l'avcapture?

Hey là-bas, j'essaie d'accéder aux données brutes de la caméra iPhone à l'aide d'AVCAPTURESession. Je suis le guide fourni par Apple ( Link ici ).

Les données brutes de l'échantillon sont au format YUV (ai-je correctement ici sur le format de cadre vidéo brut ??), Comment obtenir directement les données de la composante Y hors des données brutes stockées dans l'échantillon.


1 commentaires

Brad Larson et Codo m'a beaucoup aidé sur ce problème. Avec la combinaison de leurs réponses, je pourrais enfin atteindre mon objectif. Merci beaucoup, Brad Larson et Codo!


4 Réponses :


21
votes

Lors de la configuration de l'AVCaptureVideodataAutUtout qui renvoie les cadres de caméra bruts, vous pouvez définir le format des images à l'aide du code comme suit: xxx

Dans ce cas, un format de pixel BGRA est spécifié ( J'ai utilisé ceci pour assortir un format de couleur pour une texture OpenGL ES). Chaque pixel de ce format comporte un octet pour bleu, vert, rouge et alpha, dans cet ordre. Aller avec cela, il est facile de tirer des composants de couleur, mais vous sacrifiez une petite performance en ayant besoin de la conversion de l'espace de couleurs Yuv Caméra-natif YUV.

Autres espaces de couleurs pris en charge sont kcvpixelormattype_420ypcbcr8biplanarvideorange et kcvpiixelormattype_420ypcbcr8biplanarfullrange sur les nouveaux périphériques et kcvpixelformattype_422ypcbcr8 sur l'iPhone 3G. Le videorange ou FullRange Indique simplement si les octets sont renvoyés entre 16 et 235 pour Y et 16 - 240 pour UV ou 0 - 255 pour chaque composant.

Je crois que l'espace de courant par défaut utilisé par une instance AVCaptureVideDataAutUtPut est le YUV 4: 2: 0 Planar Colorspace (sauf sur l'iPhone 3G, où il est yuv 4: 2: 2 entrelacé). Cela signifie qu'il existe deux plans de données d'image contenus dans le cadre de la vidéo, avec l'avion Y arrivant en premier. Pour chaque pixel de votre image résultante, il y a un octet pour la valeur Y à ce pixel.

Vous obtiendrez dans ces données crues en mettant en œuvre quelque chose comme celui-ci dans votre délégué rappel: < Pré> xxx

Vous pouvez ensuite comprendre l'emplacement dans les données de l'image pour chaque coordonnée X, Y sur l'image et extraire l'octet de sortie correspondant au composant Y à cette coordonnée. < p> Échantillon FindMyicone d'Apple de WWDC 2010 (accessible avec les vidéos) montre comment Procédé des données BGRA brutes de chaque image. J'ai également créé une application d'exemple, que vous pouvez télécharger le code de ici , qui effectue Suivi de l'objet à base de couleurs < / a> Utiliser la vidéo en direct de la caméra de l'iPhone. Les deux montrent comment traiter les données de pixels brutes, mais aucun de ces travaux dans l'espace de couleurs YUV.



19
votes

En plus de la réponse de Brad, et votre propre code, vous souhaitez considérer les éléments suivants:

Étant donné que votre image dispose de deux plans distincts, la fonction cvpixelbuffergetBaseaddress ne retournera pas l'adresse de base de la plan mais plutôt l'adresse de base d'une structure de données supplémentaire. C'est probablement due à la mise en œuvre actuelle que vous obtenez une adresse suffisamment proche du premier plan afin que vous puissiez voir l'image. Mais c'est la raison pour laquelle il se déplace et a des ordures en haut à gauche. La bonne façon de recevoir le premier plan est la suivante: xxx

une ligne de l'image peut être plus longue que la largeur de l'image (en raison de l'arrondi). C'est pourquoi il existe des fonctions séparées pour obtenir la largeur et le nombre d'octets par ligne. Vous n'avez pas ce problème pour le moment. Mais cela pourrait changer avec la prochaine version d'iOS. Donc, votre code devrait être: xxx

Veuillez également noter que votre code échoue misérablement sur un iPhone 3G.


4 commentaires

Devrait-il ne pas être cvpixelbuffergetheightyPlane? Juste curieux.


Puisque nous savons que l'avion y a le même nombre de pixels que l'image, cela ne devrait pas faire une différence ici. Mais si nous avons accédé à l'avion UV qui a un nombre réduit de pixels, il serait essentiel d'utiliser CVPIXELBUFFERGETHEELLANE .


Cet article illustre quel type de bugs utilise cvpixelbuffergetBaseAddress à la place de cvpixelbuffergetBaseaddressofplane MKONRAD.net/2014/06/24/...


Pour les tampons planaires, CVPixelBuffergetBaseAdress renvoie un pointeur à une structure CVPLANARCOMPONENTINFO, ou NULL si aucune structure de ce type n'est présente. Donc, si votre mémoire tampon est plane, vous devez utiliser CVPIXELBUPERGETBASEADDRESSOFPLANE.



8
votes

Si vous n'avez besoin que du canal de luminance, je vous recommande d'utiliser le format BGRA, car il s'agit d'une surcharge de conversion. Apple suggère d'utiliser BGRA si vous faites des trucs de rendu, mais vous n'en avez pas besoin pour extraire les informations de luminance. Comme Brad déjà mentionné, le format le plus efficace est le format YUV de la caméra-natif.

Cependant, l'extraction des octets appropriés à partir du tampon d'échantillon est un peu délicat, notamment en ce qui concerne l'iPhone 3G avec son format YUV 422 entrelacé. Donc, voici mon code, qui fonctionne bien avec l'iPhone 3G, 3GS, iPod Touch 4 et iPhone 4s. P>

#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


3 commentaires

Bonjour, merci pour la réponse, je suis confronté au même problème. Une chose est que je veux aussi les composants CR et CB et je ne sais pas comment l'obtenir. J'essaie de faire un détecteur de peau et j'ai aussi besoin de ces valeurs comme je l'ai trouvée dans un autre poste. Je l'ai déjà fait utiliser le format de BGRA et la conversion après cela en YCBCR, mais je veux éviter que cette étape de conversion si possible afin d'augmenter le FPS. C'est pourquoi je veux obtenir des valeurs individuelles Y et CR pour chaque pixel de l'image. Des idées?


Comment avez-vous trouvé l'ordre d'octet pour le signal de composant? Le document que j'ai trouvé à partir de Microsoft l'a répertorié comme Y0CRY1CB.


J'ai trouvé un indice dans un fichier d'en-tête Apple. Je suis désolé, mais je ne peux plus vous en dire au fil de l'en-tête.



4
votes

Ceci est simplement le point culminant du travail acharné de tous les autres, au-dessus et sur d'autres threads, converti en Swift 3 pour quiconque le trouve utile. XXX


1 commentaires

Si la solution ci-dessus ne fonctionne pas pour quelqu'un, essayez d'utiliser let bitmapinfo = cgbitmapinfo (Rawvalue: cgimagebyteorderinfo.orderdefault.rawvalue) let bitmapinfo = cgbitmapinfo (RAWVALUE: CGIMACEALPHAINFO.NONESKIPFIRST.RAWVALUE) .