这个问题其实我今天在 stackoverflow 上提问了,但还一直没有人回答,我也没有找到类似的问题。如有违规,请@tinyfool 删帖。 我使用Face++的 iOS离线检测器来检测照片中的人脸,由于是直接检测 iPad 拍摄的图片,检测过程比较长,我希望在后台检测人脸,一旦检测到就更新到屏幕上,以下是我的代码。但检测到的人脸总是在最后一起更新,而不是我希望的那样,检测到一张就更新一张。
#import "FacesetController.h"
#import <AssetsLibrary/AssetsLibrary.h>
#import "FaceppLocalDetector.h"
#import "APIKey+APISecret.h"
#import "FaceppAPI.h"
@interface FacesetController ()
@property (nonatomic, strong) ALAssetsLibrary *photoLibrary;
@property (nonatomic, strong) NSMutableArray *allFaces;
//离线人脸检测器
@property (nonatomic, strong) FaceppLocalDetector *localDetector;
@end
@implementation FacesetController
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if (self.photoLibrary == nil) {
_photoLibrary = [[ALAssetsLibrary alloc] init];
}
if (self.allFaces == nil) {
_allFaces = [[NSMutableArray alloc] init];
}
if (self.localDetector == nil) {
NSDictionary *options = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:@NO, @20, FaceppDetectorAccuracyHigh, nil] forKeys:[NSArray arrayWithObjects:FaceppDetectorTracking, FaceppDetectorMinFaceSize, FaceppDetectorAccuracy, nil]];
_localDetector = [FaceppLocalDetector detectorOfOptions:options andAPIKey:_API_KEY];
}
ALAssetsGroupEnumerationResultsBlock assetsEnumerationBlock = ^(ALAsset *result, NSUInteger index, BOOL *stop){
NSLog(@"Block: assetsEnumerationBlock");
if (result) {
@autoreleasepool {
__autoreleasing ALAssetRepresentation *assetRepresentation = [result defaultRepresentation];
CGImageRef fullImage = [assetRepresentation fullResolutionImage];
__autoreleasing UIImage *fullResolutionImage = [UIImage imageWithCGImage:fullImage];
FaceppLocalResult *detectResult = [_localDetector detectWithImage:fullResolutionImage];
if (detectResult.faces.count > 0) {
NSLog(@"Find face in Photo: %@", assetRepresentation.filename);
for (FaceppLocalFace *face in detectResult.faces) {
CGImageRef faceCGImage = CGImageCreateWithImageInRect(fullImage, face.bounds);
__autoreleasing UIImage *faceImage = [UIImage imageWithCGImage:faceCGImage];
[self.allFaces addObject:faceImage];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:self.allFaces.count-1 inSection:0];
[self.collectionView insertItemsAtIndexPaths:[NSArray arrayWithObject:indexPath]];
CGImageRelease(faceCGImage);
}
CGImageRelease(fullImage);
NSLog(@"UPDATE THE MAIN SCREEN!");
//在主线程中更新屏幕
dispatch_async(dispatch_get_main_queue(), ^{
[self.collectionView reloadData];
});
}
}
}
};
// setup our failure view controller in case enumerateGroupsWithTypes fails
ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError *error) {
NSLog(@"Some thing wrong");
};
ALAssetsLibraryGroupsEnumerationResultsBlock listGroupBlock = ^(ALAssetsGroup *group, BOOL *stop) {
ALAssetsFilter *onlyPhotosFilter = [ALAssetsFilter allPhotos];
[group setAssetsFilter:onlyPhotosFilter];
[group enumerateAssetsUsingBlock:assetsEnumerationBlock];
};
// enumerate only photos
NSUInteger groupTypes = ALAssetsGroupAlbum | ALAssetsGroupEvent | ALAssetsGroupFaces | ALAssetsGroupSavedPhotos;
[self.photoLibrary enumerateGroupsWithTypes:groupTypes usingBlock:listGroupBlock failureBlock:failureBlock];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
NSLog(@"Method: %@", NSStringFromSelector(_cmd));
}
#pragma mark - UICollectionViewDelegate
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return self.allFaces.count;
}
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return 1;
}
#define kImageViewTag 1
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = @"faceCell";
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
if ([self.allFaces count]) {
UIImageView *faceView = (UIImageView *)[cell viewWithTag:kImageViewTag];
[faceView setImage:[self.allFaces objectAtIndex:[indexPath row]]];
}
return cell;
}
@end
嗯,再找找吧,不过话说即使是dropbox到现在这个版本后台自动备份照片都常常有问题,很多人在说啊,但是他们的工程师到现在都还没找出问题,后台自动刷新方面可能Apple还没成熟,或者是调取过于频繁,总之几分钟之内就会被kill掉,你死的也不冤...
http://www.zhihu.com/question/23019630/answer/23369396?utmcampaign=rss&utmmedium=rss&utmsource=rss&utmcontent=title ,推荐你看看,找bug的方法也很重要,毕竟是你自己的东西,good luck
你的assetsEnumerationBlock
是运行在主线程的,你在主线程for (FaceppLocalFace *face in detectResult.faces)
循环添加数据,结果自然是主线程得等你数据全部添加完,才有机会渲染。
正确做法是把你的[self.photoLibrary enumerateGroupsWithTypes:groupTypes usingBlock:listGroupBlock failureBlock:failureBlock];
这句调用放入后台执行
16楼 @seedante 你的self.allFaces
保存了所有图片,内存当然会爆...
对于每个Face,你应该保存的是图片的URL,以及face的bounds,然后在- collectionView:cellForItemAtIndexPath:
里,动态创建face image
__autoreleasing UIImage *faceImage = [UIImage imageWithCGImage:faceCGImage];
[self.allFaces addObject:faceImage];
你的self.faceImage
保持着对faceImage
的引用,所以faceImage
不会释放,你加不加__autoreleasing
都一个样
你的每个faceImage
都保持着对faceCGImage的引用,所以所有faceCGImage
的也不会释放,这就是你在Instruments里看到的CGImage
//faceInfo的结构是@{NSURL:NSValue(wrap a CGRect)}
NSDictionary *faceInfo = [self.allFaces objectAtIndex:[indexPath row]];
ALAssetsLibraryAssetForURLResultBlock assetAccessBlock = ^(ALAsset *asset){
CGRect headBounds = [[faceInfo allValues][0] CGRectValue];
ALAssetRepresentation *representation = [asset defaultRepresentation];
//这里的 faceCGImage 没有所有权,不用负责释放
CGImageRef fullCGImage = [representation fullResolutionImage];
//由 含create的函数生成,所有后面要负责释放所有权
CGImageRef faceCGImage = CGImageCreateWithImageInRect(fullCGImage, headBounds);
NSLog(@"fullCGImage Retain Count: %d", (int)CFGetRetainCount(fullCGImage));//这里引用数已经为2
UIImage *faceUIImage = [UIImage imageWithCGImage:faceCGImage scale:rep.scale orientation:rep.orientation];//faceCGImage 对象引用书加1
CGImageRelease(faceCGImage);
[faceView setImage:faceUIImage];
/*到了这里最大的 fullCGImage 引用数为2,我无权释放。而在这里我只能增大 cell 的面积来避免屏幕上出现过多 cell,但是一旦滚动视图,内存压力飙升,因为fullCGImage 实在太大了,立马崩溃。但我必须使用[representation fullResolutionImage]来,因为 faceInfo 里面的 CGRect是按fullResolutionImage 的尺寸来的。怎么在这里把 fullCGImage 和 faceCGImage 释放掉缓解内存压力呢,但是有 faceUIImage 在,前两者的内存就不会被废弃掉,虽然滚动视图时,他们都会被释放,但这时候来不及,APP 立马崩掉。有什么办法呢。我还能想到的办法是,在 viewDidLoad 或是 viewWillAppear 里检测到人脸后,在后台将人脸区域分割下来保存到数据库或是文件,然后在这里读取文件或是缓存,内存压力会小得多。 */
};
[self.photoLibrary assetForURL:[faceInfo allKeys][0] resultBlock:assetAccessBlock failureBlock:nil];
囧,发现先贴代码再插入文字就可以正常显示。请@syxc 指教。忘了说了,这是在(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
里的代码。在之前的代码里allFaces 里直接保存的人脸部分的 UIImage 对象,现在换成了 URL+人脸区域的 CGRect信息里,动态创建人脸部分的图像遇到的内存压力问题其实和之前的是一样的,挥之不去的 faceCGImage 无法释放。
CGImageRef faceCGImage = CGImageCreateWithImageInRect(fullCGImage, headBounds);
CGImageRef faceCopyCGImage = CGImageCreateCopy(faceCGImage);
CGImageRelease(faceCGImage);
UIImage *faceUIImage = [UIImage imageWithCGImage:faceCopyCGImage scale:rep.scale orientation:rep.orientation];
[faceView setImage:faceUIImage];
//NSLog(@"faceCGImage Retain Count: %d", (int)CFGetRetainCount(faceCGImage));
想了个方法,拷贝 faceCGImage 的一个副本,用作生成 faceUIImage,原来的 faceCGImage 释放,这样一来fullCGImage 也能释放掉。这样解决了及时释放内存的问题,但后期还是因为内存压力被系统 kill 了,这个我待会再搞清楚。刚开始保留了NSLog(@"faceCGImage Retain Count: %d", (int)CFGetRetainCount(faceCGImage));
这句的时候,由于faceCGImage已经被废弃,结果 CFGetRetainCount 出错。总之,之前的问题是解决了,虽然有新的问题。告知新的进展,感谢之前@sycx 的指教。还有@tinyfool 的论坛,我在 stackoverflow 提的问题至今还没有被人回答,@sycx 真是公司之宝啊。
CGImageRef (^flip)(CGImageRef sourceCGImage) = ^CGImageRef(CGImageRef sourceCGImage){
CGSize size = CGSizeMake(CGImageGetWidth(sourceCGImage), CGImageGetHeight(sourceCGImage));
UIGraphicsBeginImageContextWithOptions(size, NO, 0);
CGContextDrawImage(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, size.width, size.height), sourceCGImage);
CGImageRef result = [UIGraphicsGetImageFromCurrentImageContext() CGImage];
UIGraphicsEndImageContext();
return result;
};
ALAssetsLibraryAssetForURLResultBlock assetAccessBlock = ^(ALAsset *asset){
@autoreleasepool {
CGRect headBounds = [[faceInfo allValues][0] CGRectValue];
ALAssetRepresentation *rep = [asset defaultRepresentation];
CGImageRef fullCGImage = [rep fullResolutionImage];
CGImageRef faceCGImage = CGImageCreateWithImageInRect(fullCGImage, headBounds);
UIGraphicsBeginImageContextWithOptions(headBounds.size, NO, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextDrawImage(context, CGRectMake(0, 0, headBounds.size.width, headBounds.size.height), flip(faceCGImage));
UIImage *faceImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CGImageRelease(faceCGImage);
[faceView setImage:faceImage];
}
};
此事终于得到了解决,代码如上。解决的关键在不再直接基于 faceCGImage 生成 faceUIImage,因为这会造成 faceCGImage 无法被释放,而是将 faceCGImage 绘制在一个 context 中,从这个 context 里使用`UIGraphicsGetImageFromCurrentImageContext()`得到 faceUIImage,终于甩掉了 fullCGImage 这个大头。内存占用问题终于解决了,花了好长时间。另外这里有一个小问题,在 context 中绘制CGImageRef 后提取的 UIImage 是上下翻转的,那么再翻转一次就得到了正常的图像。