猪小花1号2018-08-27 09:16FaceAlignment是个基于openCV 的人脸特征识别框架库,支通过iOS相机获取的32BGRA图像获取面部特征点;支持各个相机姿态(camera angle)识别人脸特征点。
传入通过相机获取的UIImage和rotationAngleDegrees
CGFloat degress = [[LOCMotionManager sharedMotionManager]rotationAngleDegrees];
NSString *result = [[MMFaceAlignmentor sharedInstance] detectAlignmentor:image camAngle:degress frontCamera:YES];
对应的结果为:
degress is 4.880134 result is {
"status": 1,
"faceNum": 1,
"faces": [{
"rects": "169 62 272 272 ",
"pts": "294,267;333,263;370,249;401,224;413,190;404,148;374,113;327,99;277,100;259,251;261,225;258,191;249,157;282,207;322,207;340,219;342,202;338,185;288,251;281,244;281,234;289,223;292,232;292,244;285,180;275,171;274,158;279,146;284,156;285,169;372,223;361,201;369,166;381,199;369,201;368,201"
}]}
即完成了对视频流一帧的处理 ,返回参数:
| 字段 | 说明 |
|---|---|
| status | 检测的返回结果状态,正常状态为1,出错状态为-1 |
| faceNum | 检测到的人脸的个数 |
| faces | 用于存储检测到的各个人脸相关信息的数组 |
| face | 人脸框的位置,分别为x,y,width,height,其中x表示距离图像左边框的像素点数量,y表示距离图像上方便框的像素点数量,width表示人脸框的宽度,height表示人脸框的高度。 |
| pts | 用于存放人脸中检测到的关键点,共36个关键点,按照xyxy的方式排列,x和y的含义同上。 |
MMTextDetector 是基于openCV和boost的文档检测框架,支持传入 RGB色彩空间的 UIImage来返回特征点(四个角点)或者矫正后的UIImage。包含以下功能:
以下为SDK使用案例:
/**
给定MMTextDetectorResult(四个角点),对图像做矫正,精度高。该方法运算耗时,处理连续帧会消耗大量资源
@param origRGBImage 输入图像,需要为RGB色彩空间
@param enhance 是否增强
@param detectorResult MMTextDetectorResult
@return 返回矫正后的图片
*/
- (UIImage *)deskew:(UIImage *)origRGBImage
enhance:(BOOL)enhance
detectorResult:(MMTextDetectorResult *)detectorResult;
/**
实时点位返回,精度比单张矫正低,但是运算效率高
@param image origRGBImage
@param maxLostFocus 最大丢帧参数:数值越大越不容易抖动;越小越敏感;默认为5
@return MMTextDetectorResult
*/
- (MMTextDetectorResult *)realTimeDetect:(UIImage *)image
maxLostFocus:(unsigned int)maxLostFocus;
ARKit使用视觉惯性里程计(VIO)追踪四周的环境。VIO结合相机传感器与CoreMotion数据,以高精度来跟踪对象的移动。ARKit封装了ARFrame、ARSession、ARCamera等类,将相机初始化和数据采集等做了进一步封装。ARCamera表示虚拟摄像头,用来虚拟设备的角度和位置。而ARSession 则是 AVCaptureSession 和 CMMotionManager 的封装。只要设定 ARSession 的ARSessionConfiguration 调用 run 方法即可。
CoreML 框架极大地降低了简化了iOS平台机器学习相关代码的搭建。一个很显然的对比就是CoreMLDemo 和 VGGNet-Metal 的实现,两者的实现结果一致,但是用Metal中的矩阵运算相关的代码比起以下简单几行核心代码就能集成VGG16模型的预测。
初始化代码 VNCoreMLRequest
request = VNCoreMLRequest(model: visionModel, completionHandler: requestDidComplete)
对捕捉的CVPixelBuffer进行预测
func predict(pixelBuffer: CVPixelBuffer) {
let handler = VNImageRequestHandler(cvPixelBuffer: pixelBuffer)
try? handler.perform([request])
}
获取结果
func requestDidComplete(request: VNRequest, error: Error?) {
if let observations = request.results as? [VNClassificationObservation] {
// 显示五种概率最高的结果
let top5 = observations.prefix(through: 4)
.map { ($0.identifier, Double($0.confidence)) }
DispatchQueue.main.async {
self.show(results: top5)
}
}
}
Google Translate 通过摄像头取词照片扫描等功能,实现了照片或实景翻译的功能,技术上看,是翻译+跟踪算法的结合。
在使用相机的时候,我们经常需要在iOS中处理各种格式的图片数据。比如从相机获取的bitmap可能是封装在CMSampleBuffer结构体中的,展示图片需要的图片需要转换成UIImage,而在OpenCV中处理又需要转换成mat矩阵。以下有一些简单的代码提示,可能对未太多接触iOS图像格式转换的开发者有些许帮助。一下对色彩空间YUV和RGB以及相机的源数据CMSampleBuffer以及openCV中的cvMat做一些简单介绍和格式转换处理。
CMSampleBuffer
CMSampleBuffer 是一个Core Foundation 对象,CMSampleBuffer包含了多媒体采样的一种封装(音频、视频、音视频混合等等)。用于传输媒体信号。该结构体位于 CoreVideo 框架中的 CVPixelBuffer.h 中。在iOS相机中,CMSampleBuffer 通常用来包含连续的多媒体流。在使用相机的时候,我们可以认为通过它可以获取到相机的每一帧源数据。需要注意的是CMSampleBuffer 获取的数据和相机启动时候设置相关,一个比较重要的参数是色彩空间。 iOS相机常用的色彩空间有 kCVPixelFormatType32BGRA(32 bit BGRA) 和kCVPixelFormatType420YpCbCr8Planar ( Planar Component Y'CbCr 8-bit 4:2:0. baseAddr points to a big-endian CVPlanarPixelBufferInfo_YCbCrPlanar struct )
YUV和RGB:
我们来看一看YUV在维基百科中的介绍
YUV,是一种颜色编码方法。常使用在各个影像处理元件中。YUV在对照片或影片编码时,考虑到人类的感知能力,允许降低色度的带宽。具体如何呢?
原来用RGB(R,G,B 都是 8bit unsigned) 模型,1个点需要8x3=24bits,(全采样后, YUV 仍各占 8bit )。YUV在不牺牲画质的情况下,按 4:2:0 采样后,仅需要 8+(8/2)+(8/0)=12bits,这样就把图像的数据压缩了一半。当然,以上格式如果是带alpha通道,还会再增加额外的开销。
RGB 是从颜色发光的原理来设计定的,通俗点说它的颜色混合方式就好像有红、绿、蓝三盏灯,当它们的光相互叠合的时候,色彩相混,而亮度却等于两者亮度之总和(两盏灯的亮度嘛!),越混合亮度越高,即加法混合。
YUV 与 RGB 转换公式 :
Y = 0.257R + 0.504G + 0.098B + 16
U = 0.148R - 0.291G + 0.439B + 128
V = 0.439R - 0.368G - 0.071B + 128
B = 1.164(Y - 16) + 2.018(U - 128)
G = 1.164(Y - 16) - 0.813(V - 128) - 0.391(U - 128)
R = 1.164(Y - 16) + 1.596(V - 128)
想了解更多YUV可以参考维基百科的和这篇文章。
cvMAT:
OpenCV中,保存图像像素信息的是数据结构Mat阵,Mat即Matrix,Mat的基本参数有长、宽、像素形态、像素深度、通道数量,iOS开发者可能需要了解这些,以及他和CMSampleBuffer以及UIImage之间的转换,具体到数据在openCV中的处理可能需要交给图像处理工程师。
如果需要将色彩空间为32BGRA的CMSampleBufferRef转成mat:
-(void)processBuffer:(CMSampleBufferRef)buffer
{
//1 获取CVImageBufferRef,锁住当前帧的地址
CVImageBufferRef imgBuf = CMSampleBufferGetImageBuffer(buffer);
CVPixelBufferLockBaseAddress(imgBuf, 0);
// 获取地址
void *imgBufAddr = CVPixelBufferGetBaseAddressOfPlane(imgBuf, 0);
// 获取width和height
int w = (int)CVPixelBufferGetWidth(imgBuf);
int h = (int)CVPixelBufferGetHeight(imgBuf);
// 创建mat
cv::Mat image;
image.create(h, w, CV_8UC4);
memcpy(image.data, imgBufAddr, w * h);
// 解锁
CVPixelBufferUnlockBaseAddress(imgBuf, 0);
//Use Mat here
}
YUV通道可以通过设置CV传入的参数CV_8UC4设置传出的Mat类型。
将UIImage转成cvMat
- (cv::Mat)cvMatFromUIImage:(UIImage *)image
{
CGColorSpaceRef colorSpace = CGImageGetColorSpace(image.CGImage);
CGFloat cols = image.size.width;
CGFloat rows = image.size.height;
cv::Mat cvMat(rows, cols, CV_8UC4); // 8 bits per component, 4 channels (color channels + alpha)
CGContextRef contextRef = CGBitmapContextCreate(cvMat.data, // Pointer to data
cols, // Width of bitmap
rows, // Height of bitmap
8, // Bits per component
cvMat.step[0], // Bytes per row
colorSpace, // Colorspace
kCGImageAlphaNoneSkipLast |
kCGBitmapByteOrderDefault); // Bitmap info flags
CGContextDrawImage(contextRef, CGRectMake(0, 0, cols, rows), image.CGImage);
CGContextRelease(contextRef);
return cvMat;
}
cvMat转UIImage
-(UIImage *)UIImageFromCVMat:(cv::Mat)cvMat
{
NSData *data = [NSData dataWithBytes:cvMat.data length:cvMat.elemSize()*cvMat.total()];
CGColorSpaceRef colorSpace;
if (cvMat.elemSize() == 1) {
colorSpace = CGColorSpaceCreateDeviceGray();
} else {
colorSpace = CGColorSpaceCreateDeviceRGB();
}
CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data);
// Creating CGImage from cv::Mat
CGImageRef imageRef = CGImageCreate(cvMat.cols, //width
cvMat.rows, //height
8, //bits per component
8 * cvMat.elemSize(), //bits per pixel
cvMat.step[0], //bytesPerRow
colorSpace, //colorspace
kCGImageAlphaNone|kCGBitmapByteOrderDefault,// bitmap info
provider, //CGDataProviderRef
NULL, //decode
false, //should interpolate
kCGRenderingIntentDefault //intent
);
// Getting UIImage from CGImage
UIImage *finalImage = [UIImage imageWithCGImage:imageRef];
CGImageRelease(imageRef);
CGDataProviderRelease(provider);
CGColorSpaceRelease(colorSpace);
return finalImage;
}
CMSampleBufferRef转UIImage
+ (UIImage *)imageFromSampleBuffer32BGRA:(CMSampleBufferRef)sampleBuffer
{
//1 获取CVImageBufferRef,锁住当前帧的地址
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
CVPixelBufferLockBaseAddress(imageBuffer,0);
//2 获取第1个通道的数据、BytesPerRow、width以及height
uint8_t *baseAddress = (uint8_t *)CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0);
size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer);
size_t width = CVPixelBufferGetWidth(imageBuffer);
size_t height = CVPixelBufferGetHeight(imageBuffer);
//3 设置色彩空间,创建CGContextRef,并从上下文中获取CGImageRef
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef newContext = CGBitmapContextCreate(baseAddress, width, height, 8, bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst);
CGImageRef newImage = CGBitmapContextCreateImage(newContext);
//4. 释放操作
CGContextRelease(newContext);
CGColorSpaceRelease(colorSpace);
CVPixelBufferUnlockBaseAddress(imageBuffer,0);
//5. 获取色彩空间为RGB的UIImage
UIImage *result = [UIImage imageWithCGImage:newImage];
return result;
}
如果是YUV通道的CMSampleBufferRef,我们需要先将YUV的数据转成RGB,该过程在现在的iOS设备上需要耗费毫秒级别的时间,如果是对性能敏感的连续帧之类的地方(如相机30*3ms = 90ms,将让画面变得卡顿),应当尽量避免此类操作,而从相机硬件直接采集RGB数据。具体的转换代码可以参考如下:
+(UIImage *)imageFromSampleBufferY420:(CMSampleBufferRef )sampleBuffer
{
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
CVPixelBufferLockBaseAddress(imageBuffer,0);
size_t width = CVPixelBufferGetWidth(imageBuffer);
size_t height = CVPixelBufferGetHeight(imageBuffer);
uint8_t *yBuffer = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0);
size_t yPitch = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 0);
uint8_t *cbCrBuffer = CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 1);
size_t cbCrPitch = CVPixelBufferGetBytesPerRowOfPlane(imageBuffer, 1);
//转换代码
int bytesPerPixel = 4;
uint8_t *rgbBuffer = malloc(width * height * bytesPerPixel);
for(int y = 0; y < height; y++) {
uint8_t *rgbBufferLine = &rgbBuffer[y * width * bytesPerPixel];
uint8_t *yBufferLine = &yBuffer[y * yPitch];
uint8_t *cbCrBufferLine = &cbCrBuffer[(y >> 1) * cbCrPitch];
for(int x = 0; x < width; x++) {
int16_t y = yBufferLine[x];
int16_t cb = cbCrBufferLine[x & ~1] - 128;
int16_t cr = cbCrBufferLine[x | 1] - 128;
uint8_t *rgbOutput = &rgbBufferLine[x*bytesPerPixel];
int16_t r = (int16_t)roundf( y + cr * 1.4 );
int16_t g = (int16_t)roundf( y + cb * -0.343 + cr * -0.711 );
int16_t b = (int16_t)roundf( y + cb * 1.765);
rgbOutput[0] = 0xff;
rgbOutput[1] = clamp(b);
rgbOutput[2] = clamp(g);
rgbOutput[3] = clamp(r);
}
}
//获取到rgbBuffer数据之后
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(rgbBuffer, width, height, 8, width * bytesPerPixel, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipLast);
CGImageRef quartzImage = CGBitmapContextCreateImage(context);
UIImage *image = [UIImage imageWithCGImage:quartzImage];
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
CGImageRelease(quartzImage);
free(rgbBuffer);
CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
return image;
}
相关阅读:
网易云新用户大礼包:https://www.163yun.com/gift
本文来自网易实践者社区,经作者金立涨授权发布。