強(qiáng)腦
Getting start
CocoaPods
Update SDK:
$ cd BrainProject
$ pod install
Usage
- Use
Command + Bto build project. - Use
Command + Rto run project.
簡(jiǎn)介
強(qiáng)腦項(xiàng)目
整體架構(gòu)
- 項(xiàng)目框架:使用 UIKit 框架搭建。
- 網(wǎng)絡(luò)模塊:使用針對(duì)
AFNetworking
進(jìn)行再次封裝實(shí)現(xiàn)數(shù)據(jù)請(qǐng)求,使用YYModel實(shí)現(xiàn)json/Model映射。 - UI布局方式:StoryBoard/Xib和代碼+Masonry兩種方式。
- 持久緩存:根據(jù)不同場(chǎng)景使用UserDefaults+NSCoding對(duì)象歸檔plist存儲(chǔ)+Realm等。
- 設(shè)計(jì)模式:MVC模式。
文件目錄概況
| ___ BrainProject
| | ___ AppDelegate.m(項(xiàng)目初始化)
| | ___ Request(網(wǎng)絡(luò)模塊相關(guān))
| | | ___ CommonRequest(公用請(qǐng)求部分:封裝了上傳圖片/視頻等類(lèi)方法)
| | | ___ BaseRequest(封裝全局get/post請(qǐng)求方法,及域名統(tǒng)一管理)
| | | | __ KMRequestApi.h(全局域名管理,打包時(shí)需要注意請(qǐng)求的服務(wù)器地址)
| | | ___ Q_RAYRequest(Q_RAY部分網(wǎng)絡(luò)請(qǐng)求部分:...)
| | | ___ ShoesPlanRequest(ShoesPlan部分網(wǎng)絡(luò)請(qǐng)求部分:...)
| | | ___ TrainClassRequest(TrainClass部分網(wǎng)絡(luò)請(qǐng)求部分:...)
| | | ___ Brain(Brain部分網(wǎng)絡(luò)請(qǐng)求部分:...)
| | | ___ Shoes(Shoes部分網(wǎng)絡(luò)請(qǐng)求部分:...)
| | | ___ Robot(Robot部分網(wǎng)絡(luò)請(qǐng)求部分:...)
| | | ___ Login(Login部分網(wǎng)絡(luò)請(qǐng)求部分:手機(jī)號(hào)登錄/驗(yàn)證碼登錄/第三方登錄/注冊(cè)/找回密碼/退出登錄/...)
| | ___ Helpers
| | | ___ 待續(xù)...
| | ___ Protocol
| | | ___ 待續(xù)...
| | ___ Lib(使用的三方框架,單文件拖入項(xiàng)目中維護(hù)或者個(gè)別第三方庫(kù)不支持pod導(dǎo)入及升級(jí)才手動(dòng)導(dǎo)入)
| | | ___ OpenCV
| | | ___ SwiftyJSON
| | | ___ FDFullscreenPopGesture
| | | ___ MobPush
| | | ___ ShareSDK
| | ___ Extension(系統(tǒng)框架功能擴(kuò)展)
| | ___ Class
| | ___ Sources
| | ___ Tools
| | ___ BaseModule
| | ___ Common
第三方庫(kù)使用
項(xiàng)目使用 CocoaPods 管理使用三方框架。
建議: 網(wǎng)絡(luò),圖片加載,緩存等基礎(chǔ)組件庫(kù)可以使用,跟 UI 相關(guān)盡量只做參考不要引入項(xiàng)目導(dǎo)致不便于維護(hù)與更新。
// 目前項(xiàng)目中使用到的第三方庫(kù)
pod 'RealReachability'
pod 'MJRefresh'
pod 'SDWebImage'
pod 'YYCategories'
pod 'Masonry'
pod 'MBProgressHUD'
pod 'AFNetworking'
pod 'YYModel'
pod 'Bugly'
pod 'Realm'
pod 'YYText'
pod 'YYCache'
pod 'ZFPlayer', '~> 3.0'
pod 'ZFPlayer/ControlView', '~> 3.0'
pod 'ZFPlayer/AVPlayer', '~> 3.0'
pod 'TZImagePickerController'
Realm本地?cái)?shù)據(jù)庫(kù)使用說(shuō)明:
- 安裝realm;
使用cocoapods:
pod cache clean Realm
pod cache clean RealmSwift
pod deintegrate || rm -rf Pods
pod install --verbose
rm -rf ~/Library/Developer/Xcode/DerivedData
使用Carthage:
rm -rf Carthage
rm -rf ~/Library/Developer/Xcode/DerivedData
carthage update
- 構(gòu)建繼承自RLMObject的模型(注意設(shè)置主鍵及oc和swift的基本數(shù)據(jù)類(lèi)型轉(zhuǎn)換問(wèn)題);
- 增刪改查:
// 增
RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock:^{
[realm addOrUpdateObject:thispage];
}];
// 刪
[realm transactionWithBlock:^{
[realm deleteObject:thispage];
}];
// 改
QNPageModel *thisrepage = [[QNPageModel allObjects] firstObject];
[realm transactionWithBlock:^{
thisrepage.pagenum = 1000000;
}];
// 異步修改
// Query and update the result in another thread
dispatch_async(dispatch_queue_create("background", 0), ^{
@autoreleasepool {
QNPageModel *thisrepage2 = [[QNPageModel allObjects] firstObject];
RLMRealm *realm = [RLMRealm defaultRealm];
[realm beginWriteTransaction];
thisrepage2.pagenum = 3;
[realm commitWriteTransaction];
}
});
// 查 單個(gè)對(duì)象
QNPageModel *thisrepage1 = [[QNPageModel allObjects] firstObject];
NSLog(@"%@", thisrepage1);
// 查 一組對(duì)象
RLMResults<QNNoteModel *> *allnotes = [QNNoteModel allObjects];
NSLog(@"%@", allnotes);
// 查 nid == 1的對(duì)象
QNPageModel *nid1model= [QNNoteModel objectsWhere:@"nid == 1"].firstObject;
NSLog(@"%@", nid1model);
- 模型屬性變更涉及到的數(shù)據(jù)庫(kù)變更
場(chǎng)景1: 舊版本的模型firstName和lastName是兩個(gè)屬性,新版本由于接口升級(jí)合并成fullName屬性。
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
config.schemaVersion = 1;
config.migrationBlock = ^(RLMMigration *migration, uint64_t oldSchemaVersion) {
// We haven’t migrated anything yet, so oldSchemaVersion == 0
if (oldSchemaVersion < 1) {
// The enumerateObjects:block: method iterates
// over every 'Person' object stored in the Realm file
[migration enumerateObjects:Person.className
block:^(RLMObject *oldObject, RLMObject *newObject) {
// combine name fields into a single field
newObject[@"fullName"] = [NSString stringWithFormat:@"%@ %@",
oldObject[@"firstName"],
oldObject[@"lastName"]];
}];
}
};
[RLMRealmConfiguration setDefaultConfiguration:config];
場(chǎng)景2:屬性重命名(舊版本的模型age屬性,新版本由于接口升級(jí)改成yearsSinceBirth屬性)。
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
config.schemaVersion = 1;
config.migrationBlock = ^(RLMMigration *migration, uint64_t oldSchemaVersion) {
// We haven’t migrated anything yet, so oldSchemaVersion == 0
if (oldSchemaVersion < 1) {
// The renaming operation should be done outside of calls to `enumerateObjects:`.
[migration renamePropertyForClass:Person.className oldName:@"yearsSinceBirth" newName:@"age"];
}
};
[RLMRealmConfiguration setDefaultConfiguration:config];
- 數(shù)據(jù)庫(kù)線性遷移
- 實(shí)時(shí)同步
- 沖突解決
- 通知
// Observe Realm Notifications
token = [realm addNotificationBlock:^(NSString *notification, RLMRealm * realm) {
[myViewController updateUI];
}];
// 接收通知之后,局部刷新UI而不是重新加載所有內(nèi)容
- (void)viewDidLoad {
[super viewDidLoad];
// Observe RLMResults Notifications
__weak typeof(self) weakSelf = self;
self.notificationToken = [[Person objectsWhere:@"age > 5"]
addNotificationBlock:^(RLMResults<Person *> *results, RLMCollectionChange *changes, NSError *error) {
if (error) {
NSLog(@"Failed to open Realm on background worker: %@", error);
return;
}
UITableView *tableView = weakSelf.tableView;
// Initial run of the query will pass nil for the change information
if (!changes) {
[tableView reloadData];
return;
}
// Query results have changed, so apply them to the UITableView
[tableView beginUpdates];
[tableView deleteRowsAtIndexPaths:[changes deletionsInSection:0]
withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView insertRowsAtIndexPaths:[changes insertionsInSection:0]
withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView reloadRowsAtIndexPaths:[changes modificationsInSection:0]
withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView endUpdates];
}];
}
- (void)dealloc {
[self.notificationToken invalidate];
}
SDWebImage網(wǎng)絡(luò)圖片加載及緩存庫(kù) 底層實(shí)現(xiàn)原理及內(nèi)部實(shí)現(xiàn)過(guò)程:
- How To Use;
Objective-C:
#import <SDWebImage/SDWebImage.h>
...
[imageView sd_setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"]
placeholderImage:[UIImage imageNamed:@"placeholder.png"]];
Swift:
import SDWebImage
imageView.sd_setImage(with: URL(string: "http://www.domain.com/path/to/image.jpg"), placeholderImage: UIImage(named: "placeholder.png"))
- options選項(xiàng):
//失敗后重試
SDWebImageRetryFailed = 1 << 0,
//UI交互期間開(kāi)始下載,導(dǎo)致延遲下載比如UIScrollView減速。
SDWebImageLowPriority = 1 << 1,
//只進(jìn)行內(nèi)存緩存
SDWebImageCacheMemoryOnly = 1 << 2,
//這個(gè)標(biāo)志可以漸進(jìn)式下載,顯示的圖像是逐步在下載
SDWebImageProgressiveDownload = 1 << 3,
//刷新緩存
SDWebImageRefreshCached = 1 << 4,
//后臺(tái)下載
SDWebImageContinueInBackground = 1 << 5,
//NSMutableURLRequest.HTTPShouldHandleCookies = YES;
SDWebImageHandleCookies = 1 << 6,
//允許使用無(wú)效的SSL證書(shū)
//SDWebImageAllowInvalidSSLCertificates = 1 << 7,
//優(yōu)先下載
SDWebImageHighPriority = 1 << 8,
//延遲占位符
SDWebImageDelayPlaceholder = 1 << 9,
//改變動(dòng)畫(huà)形象
SDWebImageTransformAnimatedImage = 1 << 10
- 實(shí)現(xiàn)流程:
1.【setImageWithURL:placeholderImage:options:】-> 顯示placeholderImage,然后根據(jù)url開(kāi)始處理圖片;
2.【SDWebImageManager-downloadWithURL:delegate:options:userInfo:】-> SDImageCache從緩存查找圖片是否已經(jīng)下載,如果內(nèi)存緩存中存在則直接回調(diào)到SDWebImageManager用于展示;
3.【NSInvocationOperation】-> 生成 NSInvocationOperation 添加到隊(duì)列開(kāi)始從硬盤(pán)查找圖片是否已經(jīng)緩存;
4.【imageCache:didNotFindImageForKey:userInfo】-> 如果硬盤(pán)找不到,則回調(diào)notfind;
5.【SDWebImageDownloader】-> 由NSURLConnection來(lái)實(shí)現(xiàn)圖片下載,connection:didReceiveData: 中利用 ImageIO 做了按圖片下載進(jìn)度加載效果;
6.下載完成的圖片在一個(gè)NSOperationQueue中做圖片解碼處理,不會(huì)拖慢主線程UI;
7.解碼完成,回調(diào)顯示;
8.【SDImageCache】圖片內(nèi)存緩存和硬盤(pán)緩存同時(shí)保存,在一個(gè)單獨(dú)的NSInvocationOperation完成,避免拖慢主線程;
音視頻合成及編解碼實(shí)現(xiàn)過(guò)程:
- How To Use;
富文本模塊(錄音/插入圖片視頻):
- How To Use;
藍(lán)牙模塊:
- How To Use;
3d模塊:
- How To Use;
OpenCV及CoreImage模塊-矩形識(shí)別/裁剪/模糊匹配:
CoreImage實(shí)現(xiàn)矩形識(shí)別,實(shí)現(xiàn)步驟如下:
- CoreImage 下CIDetector.h自帶了四種識(shí)別功能
/* 人臉識(shí)別 */
CORE_IMAGE_EXPORT NSString* const CIDetectorTypeFace NS_AVAILABLE(10_7, 5_0);
/* 矩形邊緣識(shí)別 */
CORE_IMAGE_EXPORT NSString* const CIDetectorTypeRectangle NS_AVAILABLE(10_10, 8_0);
/* 二維碼識(shí)別 */
CORE_IMAGE_EXPORT NSString* const CIDetectorTypeQRCode NS_AVAILABLE(10_10, 8_0);
/* 文本識(shí)別 */
#if __OBJC2__
CORE_IMAGE_EXPORT NSString* const CIDetectorTypeText NS_AVAILABLE(10_11, 9_0);
- 邊緣檢測(cè):
[CIDetector detectorOfType:CIDetectorTypeRectangle context:nil options:@{CIDetectorAccuracy : CIDetectorAccuracyHigh}];
- 使用CAShapeLayer將邊緣繪制并顯示:
// 將圖像空間的坐標(biāo)系轉(zhuǎn)換成uikit坐標(biāo)系
TransformCIFeatureRect featureRect = [self transfromRealRectWithImageRect:imageRect topLeft:topLeft topRight:topRight bottomLeft:bottomLeft bottomRight:bottomRight];
// 邊緣識(shí)別路徑
UIBezierPath *path = [UIBezierPath new];
[path moveToPoint:featureRect.topLeft];
[path addLineToPoint:featureRect.topRight];
[path addLineToPoint:featureRect.bottomRight];
[path addLineToPoint:featureRect.bottomLeft];
[path closePath];
// 背景遮罩路徑
UIBezierPath *rectPath = [UIBezierPath bezierPathWithRect:CGRectMake(-5,-5,self.frame.size.width + 10,self.frame.size.height + 10)];
[rectPath setUsesEvenOddFillRule:YES];
[rectPath appendPath:path];
_rectOverlay.path = rectPath.CGPath;
使用CGImage-CGImageCreateWithImageInRect實(shí)現(xiàn)圖片裁剪,實(shí)現(xiàn)方式如下:
// imageRef
CGImageRef imageRef = image.CGImage;
// 傳入原圖片的imageRef以及rect獲取一個(gè)新的CGImageRef
CGImageRef topimageref = CGImageCreateWithImageInRect(imageRef, toprect);
// 使用新的topimageref生成一個(gè)UIImageView
UIImageView *topImage = [[UIImageView alloc]initWithImage:[[UIImage alloc] initWithCGImage:topimageref]];
使用OpenCV實(shí)現(xiàn)圖片匹配,實(shí)現(xiàn)方式如下:
- OpenCVManager工具類(lèi)封裝:
- 生成一張用矩形框標(biāo)記目標(biāo)的圖片
- 將CMSampleBufferRef轉(zhuǎn)為UIImage
- 將CMSampleBufferRef轉(zhuǎn)為cv::Mat
- 局部自適應(yīng)快速積分二值化方法
#pragma mark - 生成一張標(biāo)記目標(biāo)的圖片
+ (UIImage *)imageWithColor:(UIColor *)rectColor size:(CGSize)size rectArray:(NSArray *)rectArray{
CGRect rect = CGRectMake(0, 0, size.width, size.height);
// 1.開(kāi)啟圖片的圖形上下文
UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0.0);
// 2.獲取
CGContextRef cxtRef = UIGraphicsGetCurrentContext();
// 3.矩形框標(biāo)記顏色
//獲取目標(biāo)位置
for (NSInteger i = 0; i < rectArray.count; i++) {
NSValue *rectValue = rectArray[i];
CGRect targetRect = rectValue.CGRectValue;
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:targetRect cornerRadius:5];
//加路徑添加到上下文
CGContextAddPath(cxtRef, path.CGPath);
[rectColor setStroke];
[[UIColor clearColor] setFill];
//渲染上下文里面的路徑
/**
kCGPathFill, 填充
kCGPathStroke, 邊線
kCGPathFillStroke, 填充&邊線
*/
CGContextDrawPath(cxtRef,kCGPathFillStroke);
}
//填充透明色
CGContextSetFillColorWithColor(cxtRef, [UIColor clearColor].CGColor);
CGContextFillRect(cxtRef, rect);
// 4.獲取圖片
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
// 5.關(guān)閉圖形上下文
UIGraphicsEndImageContext();
// 6.返回圖片
return img;
}
#pragma mark - 將CMSampleBufferRef轉(zhuǎn)為cv::Mat
+(cv::Mat)bufferToMat:(CMSampleBufferRef) sampleBuffer{
CVImageBufferRef imgBuf = CMSampleBufferGetImageBuffer(sampleBuffer);
//鎖定內(nèi)存
CVPixelBufferLockBaseAddress(imgBuf, 0);
// get the address to the image data
void *imgBufAddr = CVPixelBufferGetBaseAddress(imgBuf);
// get image properties
int w = (int)CVPixelBufferGetWidth(imgBuf);
int h = (int)CVPixelBufferGetHeight(imgBuf);
// create the cv mat
cv::Mat mat(h, w, CV_8UC4, imgBufAddr, 0);
// //轉(zhuǎn)換為灰度圖像
// cv::Mat edges;
// cv::cvtColor(mat, edges, CV_BGR2GRAY);
//旋轉(zhuǎn)90度
cv::Mat transMat;
cv::transpose(mat, transMat);
//翻轉(zhuǎn),1是x方向,0是y方向,-1位Both
cv::Mat flipMat;
cv::flip(transMat, flipMat, 1);
CVPixelBufferUnlockBaseAddress(imgBuf, 0);
return flipMat;
}
#pragma mark - 將CMSampleBufferRef轉(zhuǎn)為UIImage
+ (UIImage *) imageFromSampleBuffer:(CMSampleBufferRef) sampleBuffer
{
// Get a CMSampleBuffer's Core Video image buffer for the media data
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
// Lock the base address of the pixel buffer
CVPixelBufferLockBaseAddress(imageBuffer, 0);
// Get the number of bytes per row for the pixel buffer
void *baseAddress = CVPixelBufferGetBaseAddress(imageBuffer);
// Get the number of bytes per row for the pixel buffer
size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer);
// Get the pixel buffer width and height
size_t width = CVPixelBufferGetWidth(imageBuffer);
size_t height = CVPixelBufferGetHeight(imageBuffer);
// Create a device-dependent RGB color space
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
// Create a bitmap graphics context with the sample buffer data
CGContextRef context = CGBitmapContextCreate(baseAddress, width, height, 8,
bytesPerRow, colorSpace, kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst);
//先獲取imgBuffer
CIImage *ciImage = [CIImage imageWithCVPixelBuffer:imageBuffer];
CIContext *temporaryContext = [CIContext contextWithOptions:nil];
CGImageRef videoImage = [temporaryContext
createCGImage:ciImage
fromRect:CGRectMake(0, 0,
CVPixelBufferGetWidth(imageBuffer),
CVPixelBufferGetHeight(imageBuffer))];
//再旋轉(zhuǎn)90度
CGAffineTransform transform = CGAffineTransformIdentity;
transform = CGAffineTransformTranslate(transform, 0, height);
transform = CGAffineTransformRotate(transform, -M_PI_2);
CGContextConcatCTM(context, transform);
CGContextDrawImage(context, CGRectMake(0,0,height,width), videoImage);
CGImageRelease(videoImage);
// Create a Quartz image from the pixel data in the bitmap graphics context
CGImageRef quartzImage = CGBitmapContextCreateImage(context);
// Unlock the pixel buffer
CVPixelBufferUnlockBaseAddress(imageBuffer,0);
// Free up the context and color space
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
// Create an image object from the Quartz image
UIImage *image = [UIImage imageWithCGImage:quartzImage];
// Release the Quartz image
CGImageRelease(quartzImage);
return (image);
}
// 局部自適應(yīng)快速積分二值化方法 https://blog.csdn.net/realizetheworld/article/details/46971143
+(cv::Mat)convToBinary:(cv::Mat) src{
cv::Mat dst;
cvtColor(src,dst,CV_BGR2GRAY);
int x1, y1, x2, y2;
int count=0;
long long sum=0;
int S=src.rows>>3; //劃分區(qū)域的大小S*S
int T=15; /*百分比,用來(lái)最后與閾值的比較。原文:If the value of the current pixel is t percent less than this average
then it is set to black, otherwise it is set to white.*/
int W=dst.cols;
int H=dst.rows;
long long **Argv;
Argv=new long long*[dst.rows];
for(int ii=0;ii<dst.rows;ii++)
{
Argv[ii]=new long long[dst.cols];
}
for(int i=0;i<W;i++)
{
sum=0;
for(int j=0;j<H;j++)
{
sum+=dst.at<uchar>(j,i);
if(i==0)
Argv[j][i]=sum;
else
Argv[j][i]=Argv[j][i-1]+sum;
}
}
for(int i=0;i<W;i++)
{
for(int j=0;j<H;j++)
{
x1=i-S/2;
x2=i+S/2;
y1=j-S/2;
y2=j+S/2;
if(x1<0)
x1=0;
if(x2>=W)
x2=W-1;
if(y1<0)
y1=0;
if(y2>=H)
y2=H-1;
count=(x2-x1)*(y2-y1);
sum=Argv[y2][x2]-Argv[y1][x2]-Argv[y2][x1]+Argv[y1][x1];
if((long long)(dst.at<uchar>(j,i)*count)<(long long)sum*(100-T)/100)
dst.at<uchar>(j,i)=0;
else
dst.at<uchar>(j,i)=255;
}
}
for (int i = 0 ; i < dst.rows; ++i)
{
delete [] Argv[i];
}
delete [] Argv;
return dst;
}
- 具體實(shí)現(xiàn)步驟:
- 原圖片轉(zhuǎn)化為灰度矩陣;
- 獲取視頻幀,處理成灰度矩陣;
- 圖像金字塔分級(jí)放大縮小匹配,最大0.8相機(jī)圖像,最小0.3tep圖像;
- 對(duì)比兩個(gè)圖像是否有相同區(qū)域;
- 保存當(dāng)前模版矩陣匹配的位置cv::Point;
//將圖片轉(zhuǎn)換為灰度的矩陣
-(cv::Mat)initTemplateImage:(NSString *)imgName{
UIImage *templateImage = [UIImage imageNamed:imgName];
cv::Mat tempMat;
UIImageToMat(templateImage, tempMat);
cv::cvtColor(tempMat, tempMat, CV_BGR2GRAY);
return tempMat;
}
#pragma mark - 獲取視頻幀,處理視頻
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection{
[NSThread sleepForTimeInterval:0.5];
cv::Mat imgMat;
imgMat = [OpenCVManager bufferToMat:sampleBuffer];
//判斷是否為空,否則返回
if (imgMat.empty() || self.templateMat.empty()) {
return;
}
//轉(zhuǎn)換為灰度圖像
cv::cvtColor(imgMat, imgMat, CV_BGR2GRAY);
UIImage *tempImg = MatToUIImage(imgMat);
//獲取標(biāo)記的矩形
NSArray *rectArr = [self compareByLevel:6 CameraInput:imgMat];
//轉(zhuǎn)換為圖片
UIImage *rectImg = [OpenCVManager imageWithColor:[UIColor redColor] size:tempImg.size rectArray:rectArr];
CGImageRef cgImage = rectImg.CGImage;
//在異步線程中,將任務(wù)同步添加至主線程,不會(huì)造成死鎖
dispatch_sync(dispatch_get_main_queue(), ^{
if (cgImage) {
self.tagLayer.contents = (__bridge id _Nullable)cgImage;
}
});
}
//圖像金字塔分級(jí)放大縮小匹配,最大0.8*相機(jī)圖像,最小0.3*tep圖像
-(NSArray *)compareByLevel:(int)level CameraInput:(cv::Mat) inputMat{
//相機(jī)輸入尺寸
int inputRows = inputMat.rows;
int inputCols = inputMat.cols;
//模板的原始尺寸
int tRows = self.templateMat.rows;
int tCols = self.templateMat.cols;
NSMutableArray *marr = [NSMutableArray array];
for (int i = 0; i < level; i++) {
//取循環(huán)次數(shù)中間值
int mid = level*0.5;
//目標(biāo)尺寸
cv::Size dstSize;
if (i<mid) {
//如果是前半個(gè)循環(huán),先縮小處理
dstSize = cv::Size(tCols*(1-i*0.2),tRows*(1-i*0.2));
}else{
//然后再放大處理比較
int upCols = tCols*(1+i*0.2);
int upRows = tRows*(1+i*0.2);
//如果超限會(huì)崩,則做判斷處理
if (upCols>=inputCols || upRows>=inputRows) {
upCols = tCols;
upRows = tRows;
}
dstSize = cv::Size(upCols,upRows);
}
//重置尺寸后的tmp圖像
cv::Mat resizeMat;
cv::resize(self.templateMat, resizeMat, dstSize);
//然后比較是否相同
BOOL cmpBool = [self compareInput:inputMat templateMat:resizeMat];
if (cmpBool) {
NSLog(@"匹配縮放級(jí)別level==%d",i);
CGRect rectF = CGRectMake(currentLoc.x, currentLoc.y, dstSize.width, dstSize.height);
NSValue *rValue = [NSValue valueWithCGRect:rectF];
[marr addObject:rValue];
break;
}
}
return marr;
}
/**
對(duì)比兩個(gè)圖像是否有相同區(qū)域
@return 有為Yes
*/
-(BOOL)compareInput:(cv::Mat) inputMat templateMat:(cv::Mat)tmpMat{
int result_rows = inputMat.rows - tmpMat.rows + 1;
int result_cols = inputMat.cols - tmpMat.cols + 1;
cv::Mat resultMat = cv::Mat(result_cols,result_rows,CV_32FC1);
cv::matchTemplate(inputMat, tmpMat, resultMat, cv::TM_CCOEFF_NORMED);
double minVal, maxVal;
cv::Point minLoc, maxLoc, matchLoc;
cv::minMaxLoc( resultMat, &minVal, &maxVal, &minLoc, &maxLoc, cv::Mat());
// matchLoc = maxLoc;
// NSLog(@"min==%f,max==%f",minVal,maxVal);
dispatch_async(dispatch_get_main_queue(), ^{
self.similarLevelLabel.text = [NSString stringWithFormat:@"相似度:%.2f",maxVal];
});
if (maxVal > 0.7) {
//有相似位置,返回相似位置的第一個(gè)點(diǎn)
currentLoc = maxLoc;
return YES;
}else{
return NO;
}
}
Vision+CoreML模塊:
CoreMedia+Vision+CoreML實(shí)現(xiàn)人體肢體關(guān)節(jié)檢測(cè),實(shí)現(xiàn)步驟如下:
- Core ML模型導(dǎo)入到項(xiàng)目中(Core ML模型的產(chǎn)生通過(guò)其他機(jī)器學(xué)習(xí)工具訓(xùn)練然后轉(zhuǎn)換成Core ML格式);
- 選擇該文件,Xcode會(huì)生成了模型類(lèi)的輸入輸出類(lèi)及一個(gè)主類(lèi),主類(lèi)包括model屬性和兩個(gè)prediction方法;
- Vision 框架會(huì)負(fù)責(zé)把我們熟悉的圖片格式轉(zhuǎn)換成GoogLeNetPlacesInput類(lèi)中使用到的CVPixelBuffer 類(lèi)型的 sceneImage。
- Vision 框架還會(huì)把 GoogLeNetPlacesOutput 屬性轉(zhuǎn)換為自己的 results 類(lèi)型,并管理對(duì) prediction方法的調(diào)用,所以在所有生成的代碼中,我們只會(huì)使用 model 屬性。
- 加載模型:
// 從生成的類(lèi)中加載 ML 模型
guard let model = try? VNCoreMLModel(for: GoogLeNetPlaces().model) else {
fatalError("can't load Places ML model")
}
- 創(chuàng)建請(qǐng)求:
// 創(chuàng)建一個(gè)帶有 completion handler 的 Vision 請(qǐng)求
let request = VNCoreMLRequest(model: model) { [weak self] request, error in
guard let results = request.results as? [VNClassificationObservation],
let topResult = results.first else {
fatalError("unexpected result type from VNCoreMLRequest")
}
// 在主線程上更新 UI
let article = (self?.vowels.contains(topResult.identifier.first!))! ? "an" : "a"
DispatchQueue.main.async { [weak self] in
self?.answerLabel.text = "\(Int(topResult.confidence * 100))% it's \(article) \(topResult.identifier)"
}
}
或者將請(qǐng)求寫(xiě)在visionModel的set方法里,如下:
var visionModel: VNCoreMLModel! {
didSet {
request = VNCoreMLRequest(model: visionModel, completionHandler: visionRequestDidComplete)
request.imageCropAndScaleOption = .scaleFill
}
}
- VNCoreMLRequest.results:
說(shuō)明:當(dāng)CoreML模型是分類(lèi)器,而不是預(yù)測(cè)器或圖像處理器時(shí),Vision框架返回的是VNClassificationObservation對(duì)象數(shù)組。
VideoCapture.swift:攝像頭開(kāi)啟及前后切換;
JointViewController.swift:加載model_cpm,請(qǐng)求分類(lèi);
網(wǎng)絡(luò)模塊設(shè)計(jì)
使用了 AFNetworking 處理所有的網(wǎng)絡(luò)請(qǐng)求, 使用 YYModel 做 JSON的解析和映射。
編程風(fēng)格
推薦使用 AOP 與 函數(shù)式 進(jìn)行編程,使用 Extension 對(duì)原有功能進(jìn)行擴(kuò)展。
打包上傳appstore
- 待續(xù)...