iOS cvimagebuffer zniekształcony z Avcapturessiondataoutput z Avcapturessionpresetphoto

Na wysokim poziomie, stworzyłem aplikację, która pozwala użytkownikowi skierować kamerę iPhone wokół i zobaczyć klatki wideo, które zostały przetworzone z efektami wizualnymi. Ponadto użytkownik może stuknąć przycisk, aby zrobić zamrożoną ramkę bieżącego podglądu jako zdjęcie w wysokiej rozdzielczości, które jest zapisane w bibliotece iPhone ' a.

Aby to zrobić, aplikacja postępuje zgodnie z tą procedurą:

1) Utwórz avcapturession

captureSession = [[AVCaptureSession alloc] init];
[captureSession setSessionPreset:AVCaptureSessionPreset640x480];

2) Podłącz AVCaptureDeviceInput za pomocą kamera zwrócona w tył.

videoInput = [[[AVCaptureDeviceInput alloc] initWithDevice:backFacingCamera error:&error] autorelease];
[captureSession addInput:videoInput];

3) Podłącz interfejs Avcaptustillimageoutput do sesji, aby móc przechwytywać nieruchome klatki w rozdzielczości zdjęć.

stillOutput = [[AVCaptureStillImageOutput alloc] init];
[stillOutput setOutputSettings:[NSDictionary
    dictionaryWithObject:[NSNumber numberWithInt:kCVPixelFormatType_32BGRA]
    forKey:(id)kCVPixelBufferPixelFormatTypeKey]];
[captureSession addOutput:stillOutput];

4) Podłącz AVCaptureVideoDataOutput do sesji, aby móc przechwytywać pojedyncze klatki wideo (Cvimagebuffery) w niższej rozdzielczości

videoOutput = [[AVCaptureVideoDataOutput alloc] init];
[videoOutput setVideoSettings:[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:kCVPixelFormatType_32BGRA] forKey:(id)kCVPixelBufferPixelFormatTypeKey]];
[videoOutput setSampleBufferDelegate:self queue:dispatch_get_main_queue()];
[captureSession addOutput:videoOutput];

5) gdy klatki wideo są przechwytywane, metoda delegata jest wywoływana z każdą nową klatką jako CVImageBuffer:

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
{
    CVImageBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
    [self.delegate processNewCameraFrame:pixelBuffer];
}

6) Następnie delegat przetwarza / rysuje oni:

- (void)processNewCameraFrame:(CVImageBufferRef)cameraFrame {
    CVPixelBufferLockBaseAddress(cameraFrame, 0);
    int bufferHeight = CVPixelBufferGetHeight(cameraFrame);
    int bufferWidth = CVPixelBufferGetWidth(cameraFrame);

    glClear(GL_COLOR_BUFFER_BIT);

    glGenTextures(1, &videoFrameTexture_);
    glBindTexture(GL_TEXTURE_2D, videoFrameTexture_);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bufferWidth, bufferHeight, 0, GL_BGRA, GL_UNSIGNED_BYTE, CVPixelBufferGetBaseAddress(cameraFrame));

    glBindBuffer(GL_ARRAY_BUFFER, [self vertexBuffer]);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, [self indexBuffer]);

    glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_SHORT, BUFFER_OFFSET(0));

    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    [[self context] presentRenderbuffer:GL_RENDERBUFFER];

    glDeleteTextures(1, &videoFrameTexture_);

    CVPixelBufferUnlockBaseAddress(cameraFrame, 0);
}
To wszystko działa i prowadzi do poprawnych rezultatów. Widzę podgląd wideo 640x480 przetwarzany przez OpenGL. Wygląda to tak:

PodglÄ…d poprawny 640x480

Jeśli jednak uchwycę zdjęcie z tej sesji, jego rozdzielczość będzie również wynosić 640x480. Chcę, aby była to wysoka rozdzielczość, więc w pierwszym kroku zmieniam wstępnie ustawioną linię na:

[captureSession setSessionPreset:AVCaptureSessionPresetPhoto];

To poprawnie rejestruje nieruchome obrazy w najwyższej rozdzielczości dla iPhone4 (2592x1936).

Jednak filmik podgląd (otrzymany przez delegata w krokach 5 i 6) wygląda teraz następująco:

Podgląd zdjęcia

Potwierdziłem, że wszystkie inne ustawienia predefiniowane (High, medium, low, 640x480 i 1280x720) wyświetlają się zgodnie z przeznaczeniem. Jednak ustawienie wstępne zdjęcia wydaje się wysyłać dane bufora w innym formacie.

Potwierdziłem również, że dane wysyłane do bufora w ustawieniu domyślnym zdjęcia są w rzeczywistości poprawnymi danymi obrazu, pobierając bufor i tworząc z niego interfejs zamiast wysyłać go do openGL:

CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(CVPixelBufferGetBaseAddress(cameraFrame), bufferWidth, bufferHeight, 8, bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst); 
CGImageRef cgImage = CGBitmapContextCreateImage(context); 
UIImage *anImage = [UIImage imageWithCGImage:cgImage];

Pokazuje niezakłóconą klatkę wideo.

Przeprowadziłem kilka poszukiwań i nie mogę tego naprawić. Mam przeczucie, że to kwestia formatu danych. Oznacza to, że uważam, że bufor jest ustawiony poprawnie, ale z formatem, którego ta linia nie rozumie:

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bufferWidth, bufferHeight, 0, GL_BGRA, GL_UNSIGNED_BYTE, CVPixelBufferGetBaseAddress(cameraFrame));

Moje przeczucie było takie, że zmiana zewnętrznego formatu z GL_BGRA na coś innego może pomóc, ale tak nie jest... i przez różne sposoby wygląda na to, że bufor jest rzeczywiście w GL_BGRA.

Czy ktoś wie, co tu się dzieje? A może masz jakieś wskazówki, jak Mogę przejść do debugowania, dlaczego tak się dzieje? (Bardzo dziwne jest to, że dzieje się to na iphone4, ale nie na iPhone 3GS ... oba uruchomione ios4. 3)
Author: genpfault, 2011-06-30

8 answers

To było straszne.

Jak zauważył Lio Ben-Kereth, padding jest 48, jak widać z debuggera

(gdb) po pixelBuffer
<CVPixelBuffer 0x2934d0 width=852 height=640 bytesPerRow=3456 pixelFormat=BGRA
# => 3456 - 852 * 4 = 48

OpenGL może to zrekompensować, ale OpenGL ES nie może (więcej informacji tutaj Podteksturowanie OpenGL)

Oto Jak to robiÄ™ w OpenGL ES:

(CVImageBufferRef)pixelBuffer   // pixelBuffer containing the raw image data is passed in

/* ... */
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, videoFrameTexture_);

int frameWidth = CVPixelBufferGetWidth(pixelBuffer);
int frameHeight = CVPixelBufferGetHeight(pixelBuffer);

size_t bytesPerRow, extraBytes;

bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer);
extraBytes = bytesPerRow - frameWidth*4;

GLubyte *pixelBufferAddr = CVPixelBufferGetBaseAddress(pixelBuffer);

if ( [[captureSession sessionPreset] isEqualToString:@"AVCaptureSessionPresetPhoto"] )
{

    glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, frameWidth, frameHeight, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL );

    for( int h = 0; h < frameHeight; h++ )
    {
        GLubyte *row = pixelBufferAddr + h * (frameWidth * 4 + extraBytes);
        glTexSubImage2D( GL_TEXTURE_2D, 0, 0, h, frameWidth, 1, GL_BGRA, GL_UNSIGNED_BYTE, row );
    }
}
else
{
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, frameWidth, frameHeight, 0, GL_BGRA, GL_UNSIGNED_BYTE, pixelBufferAddr);
}

Wcześniej używałem AVCaptureSessionPresetMedium i otrzymywałem 30fps. W AVCaptureSessionPresetPhoto dostaję 16fps na iPhone 4. Zapętlenie sub-tekstury nie ma wpływu na szybkość wyświetlania klatek.

I ' m korzystanie z iPhone 4 na iOS 5.

 13
Author: Dex,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:22

Po prostu narysuj tak.

size_t bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer);
int frameHeight = CVPixelBufferGetHeight(pixelBuffer);

GLubyte *pixelBufferAddr = CVPixelBufferGetBaseAddress(pixelBuffer);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (GLsizei)bytesPerRow / 4, (GLsizei)frameHeight, 0, GL_BGRA, GL_UNSIGNED_BYTE, pixelBufferAddr);
 5
Author: Sangwon Park,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-04-01 03:19:00

Dobre maty punktowe. Ale w rzeczywistości wyściółka jest większa, to:

bytesPerRow = 4 * bufferWidth + 48;

Działa świetnie na tylnym aparacie iphone 4 i rozwiązał problem, o którym poinformował sotangochips.

 2
Author: Lior Ben-Kereth,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-14 12:49:56

Dex, dzięki za doskonałą odpowiedź. Aby uczynić Twój kod bardziej ogólnym, zamieniłbym:

if ( [[captureSession sessionPreset] isEqualToString:@"AVCaptureSessionPresetPhoto"] )

Z

if ( extraBytes > 0 )
 1
Author: greg_p,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-06 22:53:05

Chyba znalazłem twoją odpowiedź i przykro mi, bo to nie jest dobra wiadomość.

Możesz sprawdzić ten link: http://developer.apple.com/library/mac/#documentation/AudioVideo/Conceptual/AVFoundationPG/Articles/04_MediaCapture.html

Konfigurowanie sesji

Symbol: Avcapturessionpresetphoto
Rozdzielczość: Zdjęcie.
Uwagi: Pełna rozdzielczość zdjęć. Nie jest to obsługiwane dla wyjścia wideo.

 1
Author: Jwlyan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-11 22:14:10

The sessionPresetPhoto jest ustawieniem do przechwytywania zdjęcia o najwyższej jakości. Kiedy używamy AVCaptureStillImageOutput z zaprogramowanym zdjęciem, ramka przechwycona ze strumienia wideo zawsze ma rozdzielczość ekranu iPada lub iPhone ' a. Miałem ten sam problem z iPadem Pro 12.9 cala, który ma rozdzielczość 2732 * 2048. Oznacza to, że klatka, którą przechwyciłem ze strumienia wideo, wynosiła 2732 * 2048, ale zawsze była zniekształcona i przesunięta. Próbowałem wyżej wymienionych rozwiązań, ale nie rozwiązało to mojego problemu. W końcu zdałem sobie sprawę, że szerokość ramki powinna być zawsze podzielna na 8, której 2732 nie jest. 2732/8 = 341.5. Obliczyłem więc modulo szerokości i 8. Jeżeli modulo nie jest równe zeru to dodaję go do szerokości. W tym przypadku 2732% 8 = 4 i wtedy dostaję 2732+4 = 2736. Dlatego ustawię tę szerokość ramki w CVPixelBufferCreate, aby zainicjować mój pixelBuffer (CVPixelBufferRef).

 1
Author: Aref Ariyapour,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-02 09:40:11

Bufor obrazu wydaje się zawierać pewne wypełnienia na końcu. Np.

bytesPerRow = 4 * bufferWidth + 12;

Jest to często wykonywane tak, aby każdy wiersz piksela zaczynał się od 16-bajtowego offsetu.

 0
Author: Mats,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-08-29 16:34:26

Użyj tego rozmiaru evereywhere w kodzie

 int width_16 = (int)yourImage.size.width - (int)yourImage.size.width%16; 
 int height_ = (int)(yourImage.size.height/yourImage.size.width * width_16) ;
 CGSize video_size_ = CGSizeMake(width_16, height_);
 0
Author: jjpp,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-05 04:40:48