iOS - 缩放和裁剪CMSampleBufferRef/CVImageBufferRef

28

我正在使用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个回答

36
如果您使用vimage,可以直接在缓冲区数据上工作,无需将其转换为任何图像格式。 outImg 包含裁剪和缩放后的图像数据。 outWidthcropWidth 之间的关系设置了缩放比例。 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 = 0cropY0 = 0设置为原始大小,并将cropWidthcropHeight设置为原始大小,意味着没有裁剪(使用整个原始图像)。将outWidth = cropWidthoutHeight = cropHeight设置为无缩放。请注意,inBuff.rowBytes应始终是完整源缓冲区的长度,而不是裁剪后的长度。

vImage没有裁剪功能。 - AlexeyVMP
vimage 没有专门的裁剪功能是正确的。但是通过设置缓冲区的大小,您可以与实际上任何函数一起应用裁剪,例如缩放。我已更新我的回答并附带示例。 - Sten
3
我知道这个问题/答案已经有些年头了,但是现在我该怎么把outBuff转换成CMSampleBuffer呢? - Nils Ziehn
4
尼尔斯,裁剪文件的数据在outImg中。你可以使用它来创建一个像素缓冲区,例如使用CVPixelBufferCreateWithBytes。然后你可以使用它来创建CMSampleBuffer。 - Sten
1
我已经添加了一张图片,而不是旧的PDF指南链接。 - Sten
显示剩余16条评论

11

注意:我没有注意到原问题还要求缩放。但是,对于那些只需要裁剪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

CGContext()中的baseAddress更改为startAddress。确保不超过原始图像的宽度和高度。


5
"不将其转换为UIImage或CGImageRef,因为那会降低性能速度。" - vkalit
2
我在CMSampleBufferRef中裁剪和缩放图像,而不将其转换为UIImage或CGImageRef。我只是将其保存为CGImageRef以供进一步使用(例如在屏幕上显示)。您可以随意处理裁剪后的上下文。 - yuji
嗨,黃昱嘉,最好的聯繫方式是什麼?想問一個快速的問題。謝謝! - Crashalot
非常好!我正在使用这段代码将4:3的样本缓冲区转换为16:9,使用的是Swift 2.2。非常感谢! - Ivan Lesko
1
如何快速将CIImage转换回CMSampleBuffer? - user924
显示剩余3条评论

9
您可以考虑使用CoreImage(5.0+)来处理相关技术。
CIImage *ciImage = [CIImage imageWithCVPixelBuffer:CMSampleBufferGetImageBuffer(sampleBuffer)
                                           options:[NSDictionary dictionaryWithObjectsAndKeys:[NSNull null], kCIImageColorSpace, nil]];
ciImage = [[ciImage imageByApplyingTransform:myScaleTransform] imageByCroppingToRect:myRect];

4
然后,我该如何将其转换回CMSampleBuffer,或者如何使用self.videoWriterInput将其写入文件? - vodkhang
CIContext有将CIImage光栅化为CVPixelBuffer的方法。 - Tony Million

2

尝试在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;
    }

我得到了一个扭曲的输出视频。你知道为什么吗? - jpulikkottil

1

对于缩放,您可以让AVFoundation为您完成。请参阅我最近的帖子这里。如果图像尺寸不同,则设置AVVideoWidth / AVVideoHeight键的值将缩放图像。查看属性这里。至于裁剪,我不确定您是否可以让AVFoundation为您完成。您可能需要使用OpenGL或CoreImage。在顶部帖子中有几个很好的链接SO问题


我可以让它自动缩放,但它不停地抱怨我内存不足,您可以在这里查看我的最新帖子https://dev59.com/Il7Va4cB1Zd3GeqPJ3ET 。看起来问题出在我不断改变大小。 - vodkhang

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接