iOS-skalowanie i przycinanie CMSampleBufferRef/CVImageBufferRef

Używam AVFoundation i pobieram bufor próbki z AVCaptureVideoDataOutput, mogę go zapisać bezpośrednio do videowritera używając:

- (void)writeBufferFrame:(CMSampleBufferRef)sampleBuffer {
    CMTime lastSampleTime = CMSampleBufferGetPresentationTimeStamp(sampleBuffer);    
    if(self.videoWriter.status != AVAssetWriterStatusWriting)
    {
        [self.videoWriter startWriting];
        [self.videoWriter startSessionAtSourceTime:lastSampleTime];
    }

    [self.videoWriterInput appendSampleBuffer:sampleBuffer];

}

Chcę teraz przyciąć i przeskalować obraz wewnątrz CMSampleBufferRef bez przekształcania go w UIImage lub CGImageRef, ponieważ spowalnia to wydajność.

Author: vodkhang, 2011-12-13

5 answers

Jeśli używasz vimage, możesz pracować bezpośrednio na danych bufora bez konwersji do dowolnego formatu obrazu.

OutImg zawiera przycięte i skalowane dane obrazu. Zależność między outWidth i cropWidth ustawia skalowanie. przycinanie vimage
int cropX0, cropY0, cropHeight, cropWidth, outWidth, outHeight;

CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);                   
CVPixelBufferLockBaseAddress(imageBuffer,0);
void *baseAddress = CVPixelBufferGetBaseAddress(imageBuffer);
size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer);

vImage_Buffer inBuff;                       
inBuff.height = cropHeight;
inBuff.width = cropWidth;
inBuff.rowBytes = bytesPerRow;

int startpos = cropY0*bytesPerRow+4*cropX0;
inBuff.data = baseAddress+startpos;

unsigned char *outImg= (unsigned char*)malloc(4*outWidth*outHeight);
vImage_Buffer outBuff = {outImg, outHeight, outWidth, 4*outWidth};

vImage_Error err = vImageScale_ARGB8888(&inBuff, &outBuff, NULL, 0);
if (err != kvImageNoError) NSLog(@" error %ld", err);

Ustawienie cropX0 = 0 i cropY0 = 0 oraz cropWidth i cropHeight na oryginalny rozmiar oznacza brak kadrowania (przy użyciu całego oryginalnego obrazu). Ustawienie outWidth = cropWidth i outHeight = cropHeight nie powoduje skalowania. Zauważ, że inBuff.rowbajty powinny być zawsze długością pełnego bufora źródłowego, a nie długością przycięcia.

 24
Author: Sten,
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-10-31 17:32:32

Możesz rozważyć użycie CoreImage (5.0+).

CIImage *ciImage = [CIImage imageWithCVPixelBuffer:CMSampleBufferGetImageBuffer(sampleBuffer)
                                           options:[NSDictionary dictionaryWithObjectsAndKeys:[NSNull null], kCIImageColorSpace, nil]];
ciImage = [[ciImage imageByApplyingTransform:myScaleTransform] imageByCroppingToRect:myRect];
 8
Author: Cliff,
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-12-13 18:32:27

Notice

Ostatnio musiałem napisać tę funkcję ponownie i okazało się, że ta metoda nie działa już (przynajmniej nie mogłem jej uruchomić na iOS 10.3.1). Obraz wyjściowy nie zostanie wyrównany. Zgaduję, że to przez złe bytesPerRow.


Oryginalna Odpowiedź

Bufor jest po prostu tablicą pikseli, więc można go przetwarzać bezpośrednio bez użycia vImage. Kod jest napisany w języku Swift, ale myślę, że łatwo jest znajdź odpowiednik Objective-C.

Swift 3

let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)!

CVPixelBufferLockBaseAddress(imageBuffer, .readOnly)

let baseAddress = CVPixelBufferGetBaseAddress(imageBuffer)
let bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer)
let cropWidth = 640
let cropHeight = 640
let colorSpace = CGColorSpaceCreateDeviceRGB()

let context = CGContext(data: baseAddress, width: cropWidth, height: cropHeight, bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: CGImageAlphaInfo.noneSkipFirst.rawValue | CGBitmapInfo.byteOrder32Little.rawValue)

CVPixelBufferUnlockBaseAddress(imageBuffer, .readOnly)

// create image
let cgImage: CGImage = context!.makeImage()!
let image = UIImage(cgImage: cgImage)

Swift 2

let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)!

CVPixelBufferLockBaseAddress(imageBuffer, 0)

let baseAddress = CVPixelBufferGetBaseAddress(imageBuffer)
let bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer)
let cropWidth = 640
let cropHeight = 640
let colorSpace = CGColorSpaceCreateDeviceRGB()

let context = CGBitmapContextCreate(baseAddress, cropWidth, cropHeight, 8, bytesPerRow, colorSpace, CGImageAlphaInfo.NoneSkipFirst.rawValue | CGBitmapInfo.ByteOrder32Little.rawValue)

// create image
let cgImage: CGImageRef = CGBitmapContextCreateImage(context)!
let image = UIImage(CGImage: cgImage)

Jeśli chcesz przyciąć z określonej pozycji, Dodaj następujący kod:

// calculate start position
let bytesPerPixel = 4
let startPoint = [ "x": 10, "y": 10 ]
let startAddress = baseAddress + startPoint["y"]! * bytesPerRow + startPoint["x"]! * bytesPerPixel

I zmienić baseAddress w CGBitmapContextCreate na startAddress. Upewnij się, że nie przekroczysz szerokości i wysokości obrazu początkowego.

 5
Author: yuji,
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-07-04 02:16:27

Dla skalowania możesz mieć AVFoundation zrobić to za Ciebie. Zobacz mój ostatni post tutaj . Ustawienie wartości dla klawisza AVVideoWidth / AVVideoHeight spowoduje skalowanie obrazów, jeśli nie mają tych samych wymiarów. Spójrz na właściwości tutaj. co do kadrowania nie jestem pewien, czy możesz mieć AVFoundation zrobić to za Ciebie. Być może będziesz musiał użyć OpenGL lub CoreImage. W górnym poście jest kilka dobrych linków do tego więc pytanie.

 1
Author: Steve McFarlin,
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 11:47:01

Spróbuj na Swift3

func resize(_ destSize: CGSize)-> CVPixelBuffer? {
        guard let imageBuffer = CMSampleBufferGetImageBuffer(self) else { return nil }
        // Lock the image buffer
        CVPixelBufferLockBaseAddress(imageBuffer, CVPixelBufferLockFlags(rawValue: 0))
        // Get information about the image
        let baseAddress = CVPixelBufferGetBaseAddress(imageBuffer)
        let bytesPerRow = CGFloat(CVPixelBufferGetBytesPerRow(imageBuffer))
        let height = CGFloat(CVPixelBufferGetHeight(imageBuffer))
        let width = CGFloat(CVPixelBufferGetWidth(imageBuffer))
        var pixelBuffer: CVPixelBuffer?
        let options = [kCVPixelBufferCGImageCompatibilityKey:true,
                       kCVPixelBufferCGBitmapContextCompatibilityKey:true]
        let topMargin = (height - destSize.height) / CGFloat(2)
        let leftMargin = (width - destSize.width) * CGFloat(2)
        let baseAddressStart = Int(bytesPerRow * topMargin + leftMargin)
        let addressPoint = baseAddress!.assumingMemoryBound(to: UInt8.self)
        let status = CVPixelBufferCreateWithBytes(kCFAllocatorDefault, Int(destSize.width), Int(destSize.height), kCVPixelFormatType_32BGRA, &addressPoint[baseAddressStart], Int(bytesPerRow), nil, nil, options as CFDictionary, &pixelBuffer)
        if (status != 0) {
            print(status)
            return nil;
        }
        CVPixelBufferUnlockBaseAddress(imageBuffer,CVPixelBufferLockFlags(rawValue: 0))
        return pixelBuffer;
    }
 1
Author: wu qiuhao,
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-06-27 13:18:42