2016-06-05 7 views
10

Ich mache Echtzeit-Video-Verarbeitung auf iOS mit 120 fps und will zuerst Bild auf GPU (Downsample, konvertieren Farbe, etc., die nicht sind schnell genug auf der CPU) und später den Frame auf der CPU mit OpenCV nachbearbeiten.Processing Kamera Feed-Daten auf GPU (Metall) und CPU (OpenCV) auf iPhone

Was ist der schnellste Weg, Kamera Feed zwischen GPU und CPU mit Metal zu teilen?

Mit anderen Worten: das Rohr aussehen würde:

CMSampleBufferRef -> MTLTexture or MTLBuffer -> OpenCV Mat 

I CMSampleBufferRef bin Umwandlung -> MTLTexture die folgende Art und Weise

CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer); 

// textureRGBA 
{ 
    size_t width = CVPixelBufferGetWidth(pixelBuffer); 
    size_t height = CVPixelBufferGetHeight(pixelBuffer); 
    MTLPixelFormat pixelFormat = MTLPixelFormatBGRA8Unorm; 

    CVMetalTextureRef texture = NULL; 
    CVReturn status = CVMetalTextureCacheCreateTextureFromImage(NULL, _textureCache, pixelBuffer, NULL, pixelFormat, width, height, 0, &texture); 
    if(status == kCVReturnSuccess) { 
     textureBGRA = CVMetalTextureGetTexture(texture); 
     CFRelease(texture); 
    } 
} 

Nach meinem Metall-Shader ich konvertieren MTLTexture zu OpenCV finised ist

cv::Mat image; 
... 
CGSize imageSize = CGSizeMake(drawable.texture.width, drawable.texture.height); 
int imageByteCount = int(imageSize.width * imageSize.height * 4); 
int mbytesPerRow = 4 * int(imageSize.width); 

MTLRegion region = MTLRegionMake2D(0, 0, int(imageSize.width), int(imageSize.height)); 
CGSize resSize = CGSizeMake(drawable.texture.width, drawable.texture.height); 
[drawable.texture getBytes:image.data bytesPerRow:mbytesPerRow fromRegion:region mipmapLevel:0]; 

Einige Beobachtungen:

1) Leider MTLTexture.getBytes scheint teuer (das Kopieren von Daten von GPU CPU) und dauert etwa 5 ms auf meinem iPhone 5S, das zu viel ist, wenn die Verarbeitung bei ~ 100fps

2) ich einige Leute bemerkt? verwendet MTLBuffer statt MTLTexture mit folgenden Methode: metalDevice.newBufferWithLength(byteCount, options: .StorageModeShared) (siehe: Memory write performance - GPU CPU Shared Memory)

jedoch CMSampleBufferRef und begleitende CVPixelBufferRef wird von Corevideo ist zu erraten.

+0

Die GPU wird nicht für alle Auflösungen unterstützt. Ich weiß, es ist nicht deine Antwort. Ich gebe nur eine Information über GPU. –

+0

haben Sie versucht GPUImage https://github.com/BradLarson/GPUImage –

+0

Ich versuchte GPUImage, aber der größte Engpass ist die Übertragung von Daten von der GPU zur CPU. GPUImage verwendet OpenGL unter der guten und gegenüber der Metal-API kann keinen gemeinsamen Speicher haben. – pzo

Antwort

4

Der schnellste Weg, dies zu tun ist, eine MTLTexture zu verwenden, die von einem MTLBuffer unterstützt wird; Es ist eine spezielle Art von MTLTexture, die Speicher mit einem MTLBuffer teilt. Ihre C-Verarbeitung (openCV) wird jedoch ein oder zwei Frames hintereinander ausführen, dies ist unvermeidlich, da Sie die Befehle an die GPU senden müssen (encoding) und die GPU es rendern muss, wenn Sie waitUntilCompleted verwenden, um die GPU sicherzustellen ist fertig, dass nur die CPU kaut und verschwenderisch ist.

So wäre der Prozess: zuerst erstellen Sie den MTLBuffer, dann verwenden Sie die MTLBuffer-Methode "newTextureWithDescriptor: offset: bytesPerRow:", um die spezielle MTLTexture zu erstellen. Sie müssen vorher das spezielle MTLTexture erstellen (als Instanzvariable), dann müssen Sie eine Standard-Rendering-Pipeline einrichten (schneller als mit Compute-Shadern), die das aus dem CMSampleBufferRef erzeugte MTLTexture übernimmt und dieses in Ihre spezielle MTLTexture, in Mit diesem Durchlauf können Sie die Farbskalierung nach Bedarf in einem Arbeitsgang herunterskalieren und durchführen. Dann senden Sie den Befehlspuffer an die GPU, in einem nachfolgenden Durchlauf können Sie einfach [theMTLbuffer contents] aufrufen, um den Zeiger auf die Bytes zu ziehen, die Ihre spezielle MTLTexture für die Verwendung in openCV unterstützen.

Jede Technik, die das CPU/GPU-Verhalten zum Erliegen bringt, wird nie effizient sein, da die Hälfte der Zeit damit verbracht wird zu warten, dh die CPU wartet auf die GPU und die GPU muss auch auf die nächsten Codierungen warten Die GPU arbeitet so, dass die CPU den nächsten Frame encodieren und alle openCV-Arbeiten ausführen soll, anstatt darauf zu warten, bis die GPU fertig ist.

Wenn sich Leute normalerweise auf Echtzeitverarbeitung beziehen, beziehen sie sich normalerweise auf eine Verarbeitung mit Echtzeit-Feedback (visuell), alle modernen iOS-Geräte ab 4s haben eine 60Hz Bildschirmaktualisierungsrate, also jede Rückmeldung schneller präsentiert als das ist sinnlos, aber wenn Sie 2 Frames (bei 120Hz) benötigen, um 1 (bei 60Hz) zu machen, dann müssen Sie einen benutzerdefinierten Timer haben oder CADisplayLink modifizieren.

+0

Gute Tipp, dass GPU-Rendering (Textur-Shader) auf 60 fps beschränkt sein kann - sinnvoll. Ich brauche tatsächlich die geringste Latenzzeit - ich habe eine benutzerdefinierte, natürliche Benutzeroberfläche, die Sound als Feedback für den Benutzer verwendet, anstatt das Rendering zur Anzeige zu bringen. Es macht mir nichts aus, dass die CPU darauf wartet, dass die GPU fertig wird - ich möchte nur etwas Vorverarbeitung auf GPU verschieben (Kontrast anpassen, Filterfarbe anpassen), sie sind sehr schnell auf der GPU und ziemlich langsam auf der CPU (Ereignis mit NEON) unter Berücksichtigung meines engen Rechenbudgets . Kann nicht bewegen (scheint unmöglich?) Andere Teile zu GPU obwohl wie Konturenanalyse. Scheint, GPU ist eine Sackgasse für mich. – pzo

+0

Ich glaube nicht, dass es eine Sackgasse ist, zumindest wäre es relativ einfach, eine Pipeline mit 60 Hz aufzubauen, wo Sie Ihre Konturanalyse für jeden Frame kodieren und durchführen und gleichzeitig die GPU die notwendige Vorverarbeitung durchführt, sobald Sie habe es in Betrieb genommen und optimiert bei 60Hz (Metal Frame Debugger und Metal System Trace sind sehr nützliche Tools) versuchen Sie Jack es bis zu 120Hz. Ich habe nie versucht, Timer oder CADisplayLink so schnell zu verwenden, dass ich Ihnen nicht helfen kann, aber schauen Sie mal nach: http://stackoverflow.com/questions/23885638/change-interval-of-cadisplaylink. – Gary

+0

Ich kenne mich auch nicht sehr gut mit der Konturanalyse aus, aber mit der Berechnungsfunktionalität von Metall können Sie das ausführen, da Kontrastanpassung oder Größenanpassung keinen Einfluss auf die GPU haben (wenn der Filter komplex ist, verwenden Sie eine LUT)). Selbst mit Standard-Vertex- und Fragment-Shadern gibt es oft Tricks, um nicht-freundliche GPU-Sachen auf der GPU zu machen, ich implementierte einen verbundenen Komponenten-Beschriftungsalgorithmus mit Metall und war nicht zu weit von der C-Version für kleine Bilder entfernt – Gary