強(qiáng)腦-項(xiàng)目總結(jié)

強(qiáng)腦

Getting start

CocoaPods

Update SDK:

$ cd BrainProject
$ pod install

Usage

  • Use Command + B to build project.
  • Use Command + R to 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)方式如下:
#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ù)...
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒(méi)有地址/指針的概念1.2> 泛型1.3> 類(lèi)型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,681評(píng)論 1 32
  • 文章作者:Tyan博客:noahsnail.com | CSDN | 簡(jiǎn)書(shū) 聲明:作者翻譯論文僅為學(xué)習(xí),如...
    SnailTyan閱讀 6,719評(píng)論 0 14
  • 今天是1月10日,是個(gè)特殊的日子,是改變我人生命運(yùn)的日子!去年的今天,我與你"從一個(gè)簡(jiǎn)單的小事做起",簽下了...
    優(yōu)一lily閱讀 429評(píng)論 3 5
  • 很想寫(xiě)東西,但是總不知道從何開(kāi)始。 很喜歡看書(shū),從小開(kāi)始的喜好,好像是海綿,到處找書(shū)看,連大人放在閣樓的教科書(shū)也看...
    1314蘇易閱讀 280評(píng)論 2 2
  • 我們每個(gè)人都像個(gè)哲學(xué)家一樣思考著深?yuàn)W的到理,天天問(wèn)自己,我是誰(shuí)?我從哪里來(lái)?我要到哪里去? 就這樣我們自認(rèn)為有著目...
    嶺南玉閱讀 151評(píng)論 0 1

友情鏈接更多精彩內(nèi)容