如何从旋转后的CVPixelBufferRef正确创建OpenCV矩阵?
解决Portrait模式下CVPixelBuffer转OpenCV Mat的像素错乱问题
问题核心:当你把AVCaptureConnection的videoOrientation设为Portrait时,捕获的CVPixelBuffer内存布局会和Landscape Right模式不同——不仅宽高逻辑上交换,行字节数(stride)也可能和你预期的width*4不一致,原代码直接用videoRect的宽高和默认stride=0,会导致内存读取错位,出现左上角小区域损坏、整体像素错乱的问题。
下面分两种你提到的像素格式给出修复代码:
1. 处理kCVPixelFormatType_32BGRA格式
CVPixelBufferLockBaseAddress(pixelBuffer, 0); // 1. 获取buffer的实际宽高(不要用videoRect的,Portrait模式下buffer的物理宽高是交换的) size_t bufferWidth = CVPixelBufferGetWidth(pixelBuffer); size_t bufferHeight = CVPixelBufferGetHeight(pixelBuffer); // 2. 获取正确的行字节数(关键!硬件捕获的buffer stride通常不等于width*4) size_t stride = CVPixelBufferGetBytesPerRow(pixelBuffer); // 3. 创建正确的OpenCV Mat,使用buffer的实际宽高和stride cv::Mat tempMat(bufferHeight, bufferWidth, CV_8UC4, CVPixelBufferGetBaseAddress(pixelBuffer), stride); // 4. 旋转Mat到Portrait方向(和MLModel的检测坐标对齐) cv::rotate(tempMat, mat, cv::ROTATE_90_CLOCKWISE); CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
2. 处理kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange格式(双平面YUV)
这种格式是分离的Y、CbCr两个平面,不能直接按单平面读取,需要分别提取后转换为BGRA:
CVPixelBufferLockBaseAddress(pixelBuffer, 0); size_t yWidth = CVPixelBufferGetWidthOfPlane(pixelBuffer, 0); size_t yHeight = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0); size_t uvWidth = CVPixelBufferGetWidthOfPlane(pixelBuffer, 1); size_t uvHeight = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1); size_t yStride = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0); size_t uvStride = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1); // 创建Y和UV通道的Mat cv::Mat yMat(yHeight, yWidth, CV_8UC1, CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 0), yStride); cv::Mat uvMat(uvHeight, uvWidth, CV_8UC2, CVPixelBufferGetBaseAddressOfPlane(pixelBuffer, 1), uvStride); // 合并YUV通道并转换为BGRA格式 cv::Mat yuvMat; cv::merge(std::vector<cv::Mat>{yMat, uvMat}, yuvMat); cv::cvtColor(yuvMat, mat, cv::COLOR_YUV2BGRA_NV12); // 旋转到Portrait方向匹配MLModel坐标 cv::rotate(mat, mat, cv::ROTATE_90_CLOCKWISE); CVPixelBufferUnlockBaseAddress(pixelBuffer, 0);
关键修复点说明
- 不要依赖videoRect的宽高:
AVCaptureConnection设置videoOrientation后,输出的CVPixelBuffer物理宽高是传感器原生的(通常为Landscape),逻辑方向通过旋转实现,必须用CVPixelBufferGetWidth/Height获取实际维度。 - 必须使用正确的stride:硬件加速的像素缓冲区为了内存对齐,行字节数会大于
width*通道数,直接用0会让OpenCV计算错误的stride,导致内存读取错位。 - 旋转Mat对齐坐标:你设置
videoOrientation为Portrait是为了让MLModel的归一化坐标和屏幕显示对齐,所以转换后的Mat需要旋转90度顺时针,才能和检测结果匹配。
内容的提问来源于stack exchange,提问作者StainlessSteelRat
相关产品推荐
相关产品推荐

