1.图片横向显示
功能要求需要开启手机的前置摄像头拍摄图片,设置的前置摄像头分辨率是1280 * 720,但是发现显示图片是是1280 * 720,但是图片成90旋转。要想正常显示,需要对图片进行旋转处理。但是为什么会得到横向的图片?
原因
至于为什么图片是方向是反的呢,因为定义的captureOutput是AVCaptureVideoDataOutput
类型,如果是AVCaptureStillImageOutput
就不存在这个问题。在头文件AVCaptureSession.h
有AVCaptureVideoOrientation
枚举类型定义视频方位的定义。而这个视频默认的方向是AVCaptureVideoOrientationLandscapeRight
,所以得到的图片是横向。
typedef NS_ENUM(NSInteger, AVCaptureVideoOrientation) {
AVCaptureVideoOrientationPortrait = 1,
AVCaptureVideoOrientationPortraitUpsideDown = 2,
AVCaptureVideoOrientationLandscapeRight = 3,
AVCaptureVideoOrientationLandscapeLeft = 4,
} NS_AVAILABLE(10_7, 4_0) __TVOS_PROHIBITED;
解决方法
方法一 在didOutputSampleBuffer
回调函数中,对照片进行旋转处理。
Pixel_8888 bgColor = {0,0,0,0};
vImage_Buffer ibuff = {srcBuff,height,width,bytesPerRow};// 原始图片
vImage_Buffer ubuff = {outBuff,height,width,bytesPerRow};// 结果图片
vImage_Error err = vImageRotate90_ARGB8888(&ibuff, &ubuff, 1, bgColor, 0);
方法二 通过设置AVCaptureConnection
从根源上解决图片方向问题。
AVCaptureConnection *videoConnection1 = [self connectionWithMediaType:AVMediaTypeVideo fromConnections:[_videoCapture1 connections]];
if ([videoConnection1 isVideoOrientationSupported]){
[videoConnection1 setVideoOrientation:AVCaptureVideoOrientationPortrait];
}
在AVCaptureConnection
对象有一个属性叫做videoOrientation
这是一个AVCaptureVideoOrientation
。可以通过这个设置这个改变视频的方向。
2.图片缩放
通过摄像头得到的图片需要放在指定大小,指定位置的区域。这就要对原始图片进行缩放处理。主要有3种情况:
CSImageDrawFrameScaleToFillWidth
:在新的边框填充图片,确保不变形,自定义宽度,高度自适应。
CSImageDrawFrameScaleToFillHeight
:在新的边框填充图片,确保不变形,自定义高度,宽度自适应。
- (UIImage *)reSizeImage:(NSData *)imgData withSize:(CGSize)size{
// 保证高度 宽度自定义
UIImage *image = [[UIImage alloc] initWithData:imgData];
UIGraphicsBeginImageContext(size);
[image drawInRect:CGRectMake(0, 0, size.width, size.height)];
UIImage *reSizeImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return reSizeImage;
}
图片按比例压缩,客户端的处理是高度自定义,宽度按原比例自适应。但是发现按比例压缩之后的图片较原图片,感觉模糊很多,之前以为是错觉发现完全不是,就是模糊了。这个模糊让人感觉很奇怪,于是review的代码。
感觉代码处理没有什么问题,为什么图片是变模糊呢?
UIKIT_EXTERN void UIGraphicsBeginImageContext(CGSize size);
UIKIT_EXTERN void UIGraphicsBeginImageContextWithOptions(CGSize size, BOOL opaque, CGFloat scale) NS_AVAILABLE_IOS(4_0);
原因在于UIGraphicsBeginImageContext
中默认scale的系数是为1.0,在iPhone4以上设备就会产生模糊。为什么这个scale系数的设置会对图片产生模糊的效果呢,因为随着设备的分辨的提升,之前scale = 1.0代表当前设备的分辨率是320 * 480(就是iPhone4设备之前的分辨率)而测试的机子是iPhone6它相应的scale = 2.0 (640 * 960)所以图片在上面显示就会出现模糊。因此我们选择一个可以设置scale的函数。为了不产生模糊应该使用UIGraphicsBeginImageContextWithOptions
来替代UIGraphicsBeginImageContext
CSImageDrawFrameFillFull
:就是不管宽高比例多少,都会对图片进行拉伸,不确保不变形,不确保不压缩,使图片能够充满整个目标区域。
前面两种情况其实就是按某一比例缩放图片,但是只能保证满足宽高的某一个条件,不能够填满全部,但是图片不会变形。第三种,确保图片能够填满整个区域,但是为了适应区域的大小,图片就有可能会被裁剪或者部分被填充。
3.图片剪裁
这里的图片剪裁,主要有矩形区域裁剪和指定路线的裁剪。
矩形区域 :矩形区域的裁剪很简单,给出所需要裁剪的区域坐标就可以了。
- (UIImage*)redrawImage:(UIImage*)image size:(CGSize)size
{
UIGraphicsBeginImageContextWithOptions(size, NO, [[UIScreen mainScreen] scale]);
[image drawInRect:ccr(0, 0, size.width, size.height)];
UIImage* ret = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return ret;
}
指定线路: 指定线路的区域裁剪比矩形裁剪会复杂一点,有点像PS中用磁性套索进行圈图,通过指定一系列的关键点来控制扣出的图片区域,这个需要一个path来绘制这样的线路。将所有的点添加到path中,然后通过CGPathGetBoundingBox获取剪裁区域的边框。其它跟矩形图片剪裁类似。
4.图片格式转换
在定义一个AVCaptureVideoDataOutput
的时候是可以设置它的一个输出图片格式,通过属性变量videoSettings
设置kCVPixelBufferPixelFormatTypeKey
,这个key-value是必须要设置的。其中value的值是非常的多, 这里kCVPixelBufferPixelFormatTypeKey
是指解码后图片的格式。怎样选择这个值,因为看文档还有很多其它的选项提供可以选择,但是并非全部都支持。这个值的选择跟机器硬件解码有很大的关系,选择更有效的格式,处理速度更快。现在基本推荐使用的格式都是kCVPixelFormatType_32BGRA
,只能说这个值对当前硬件解码是最优最高效的。图片的格式转化大部分用到OpenCV,OpenCV提供了一些接口函数,比如将图片转化成Mat结构(UIImageToMa())
UIImage - > Mat
static void UIImageToMat(const UIImage* image, cv::Mat& m)
{
CGColorSpaceRef colorSpace = CGImageGetColorSpace(image.CGImage);
CGFloat cols = image.size.width;
CGFloat rows = image.size.height;
m.create(rows, cols, CV_8UC4); // 8 bits per component, 4 channels
CGContextRef contextRef = CGBitmapContextCreate(m.data, m.cols, m.rows, 8,
m.step[0], colorSpace, kCGImageAlphaNoneSkipLast | kCGBitmapByteOrderDefault);
CGContextDrawImage(contextRef, CGRectMake(0, 0, cols, rows), image.CGImage);
CGContextRelease(contextRef);
}
Mat - > UIImage
- (UIImage*)getImageFromCVMat:(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
);
UIImage* finalImage = [UIImage imageWithCGImage:imageRef];
CGImageRelease(imageRef);
CGDataProviderRelease(provider);
CGColorSpaceRelease(colorSpace);
return finalImage;
}
这是图片格式转化,还有转换图片的色彩空间cv::cvtColor(image, image, CV_BGRA2RGB)
文档还有很多种选择输出,找到你需要的即可。
2016.8.11更新
用iPhone前置摄像头拍摄出来的图片跟摄像头看上去是镜像的,找了几种方法,但发现好像不是每个方法都适用,发现最简单的一种方法解决我的问题。
/*
typedef NS_ENUM(NSInteger, UIImageOrientation) {
UIImageOrientationUp, // default orientation
UIImageOrientationDown, // 180 deg rotation
UIImageOrientationLeft, // 90 deg CCW
UIImageOrientationRight, // 90 deg CW
UIImageOrientationUpMirrored, // as above but image mirrored along other axis. horizontal flip
UIImageOrientationDownMirrored, // horizontal flip
UIImageOrientationLeftMirrored, // vertical flip
UIImageOrientationRightMirrored, // vertical flip
};
*/
UIImage *iamge = [[UIImage alloc] initWithData:imgData];
UIImage *flippedImage = [UIImage imageWithCGImage:imagData.CGImage scale:1.0 orientation:UIImageOrientationUpMirrored];
还有一种更简便的方法,设置AVCaptureConnection
参数videoMirrored这个参数。
[self.captureOutput connectionWithMediaType:AVMediaTypeVideo].videoMirrored = NO;