iOS - масштабировать и обрезать CMSampleBufferRef / CVImageBufferRef - PullRequest
22 голосов
/ 13 декабря 2011

Я использую AVFoundation и получаю буфер сэмплов из AVCaptureVideoDataOutput, я могу записать его напрямую в videoWriter, используя:

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

    [self.videoWriterInput appendSampleBuffer:sampleBuffer];

}

Теперь я хочу обрезать и масштабировать изображение внутри CMSampleBufferRef, не преобразовывая его в UIImage или CGImageRef, потому что это замедляет производительность.

Ответы [ 5 ]

29 голосов
/ 08 апреля 2012

Если вы используете vimage, вы можете работать непосредственно с буферными данными, не преобразовывая их в любой формат изображения.

outImg содержит данные обрезанного и масштабированного изображения. Соотношение между outWidth и cropWidth устанавливает масштаб. vimage cropping

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);

Таким образом, установка cropX0 = 0 и cropY0 = 0 и cropWidth и cropHeight в исходный размер означает отсутствие обрезки (при использовании всего исходного изображения). Установка outWidth = cropWidth и outHeight = cropHeight приводит к отсутствию масштабирования. Обратите внимание, что inBuff.rowBytes всегда должен быть длиной полного исходного буфера, а не обрезанной длиной.

8 голосов
/ 13 декабря 2011

Вы можете рассмотреть возможность использования CoreImage (5.0+).

CIImage *ciImage = [CIImage imageWithCVPixelBuffer:CMSampleBufferGetImageBuffer(sampleBuffer)
                                           options:[NSDictionary dictionaryWithObjectsAndKeys:[NSNull null], kCIImageColorSpace, nil]];
ciImage = [[ciImage imageByApplyingTransform:myScaleTransform] imageByCroppingToRect:myRect];
6 голосов
/ 28 июля 2015

Примечание: я не заметил, что исходный вопрос также требовал масштабирования. Но в любом случае, для тех, кому просто нужно обрезать CMSampleBuffer, вот решение.

Буфер - это просто массив пикселей, так что вы можете непосредственно обработать буфер без использования vImage. Код написан на Swift, но я думаю, что легко найти эквивалент Objective-C.

Сначала убедитесь, что ваш CMSampleBuffer имеет формат BGRA. Если нет, то предустановка, которую вы используете, - это, вероятно, YUV, и испортит количество байтов в строках, которые позже будут использованы.

dataOutput = AVCaptureVideoDataOutput()
dataOutput.videoSettings = [
    String(kCVPixelBufferPixelFormatTypeKey): 
    NSNumber(value: kCVPixelFormatType_32BGRA)
]

Затем, когда вы получите буфер сэмпла:

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)
// now the cropped image is inside the context. 
// you can convert it back to CVPixelBuffer 
// using CVPixelBufferCreateWithBytes if you want.

CVPixelBufferUnlockBaseAddress(imageBuffer, .readOnly)

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

Если вы хотите обрезать из определенной позиции, добавьте следующий код:

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

и измените baseAddress в CGBitmapContextCreate на startAddress. Убедитесь, что ширина и высота исходного изображения не превышают

.
1 голос
/ 26 июня 2017

Попробуйте это на 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 голос
/ 16 декабря 2011

Для масштабирования вы можете сделать так, чтобы AVFoundation сделал это за вас. Смотрите мой недавний пост здесь . Установка значения для клавиши AVVideoWidth / AVVideoHeight приведет к масштабированию изображений, если они не имеют одинаковые размеры. Взгляните на свойства здесь . Что касается обрезки, я не уверен, что вы можете сделать так, чтобы AVFoundation сделал это за вас. Возможно, вам придется прибегнуть к использованию OpenGL или CoreImage. В верхнем сообщении есть пара хороших ссылок на этот ТАК * вопрос .

...