ARKit构建AR世界

本编主要讲使用ARKit进行构建AR世界并实现图片识别、平面捕捉、人脸识别功能并在真实世界中创建虚拟场景,从而达到虚实结合,这也是AR的本质。

一、AR场景的从无到有是如何实现的

1、ARkit通过摄像头捕捉摄像机中真实世界的画面
2、通过苹果的游戏引擎(3D引擎SceneKit, 2D引擎SpriktKit)加载渲染物体模型到虚拟世界。AR的展示脱离不开游戏引擎,如果脱离了游戏引擎的渲染,ARKit跟普通相机的作用就没什么区别。
3、将物体模型放置在AR场景中(在AR世界中模型需要有大小、远近、角度等等属性才能真实的展示出虚拟物体与真实世界的相互结合,那么其中主要应用传感器追踪和坐标识别以及坐标转换)

传感器追踪:追踪现实世界动态物体的六轴(X,Y,Z)的变化,(有位移、旋转)。(注:X,Y,Z为右手坐标系,方便记忆)
位移三轴决定物体的方位和大小
旋转三轴决定物体的显示形态和区域
AR世界构建原理.png

二、AR世界的构建

AR世界三要素:世界追踪器ARWorldTrackingConfiguration、AR场景视图ARSCNView、AR虚拟场景SCNScene。
代码:

#pragma mark - lazy load
- (SCNView *)sceneView{
    if (!_sceneView) {
        _sceneView = [[ARSCNView alloc] initWithFrame:CGRectMake(0, 0, [UIScreen mainScreen].bounds.size.width, [UIScreen mainScreen].bounds.size.height)];
        _sceneView.delegate = self;
    }
    return _sceneView;
}

- (ARWorldTrackingConfiguration *)configuration{
    if (!_configuration) {
        _configuration = [[ARWorldTrackingConfiguration alloc] init];
    }
    return _configuration;
}

- (SCNScene *)scene{
    if (!_scene) {
        _scene = [[SCNScene alloc] init];
    }
    return _scene;
}
@property (nonatomic, strong) ARSCNView *sceneView;
@property (nonatomic, strong) ARWorldTrackingConfiguration *configuration;//AR世界追踪
@property (nonatomic, strong) SCNScene *scene;

/**
 *  播放器对象
 */
@property (nonatomic, strong) AVPlayer *player;
@property (nonatomic, strong) SCNNode *playerParanNode;//视频播放器载体节点


- (void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
    
    [self initARSceneView];
    [self startARWorldTrackingConfiguration];
}

- (void)viewDidAppear:(BOOL)animated{
    [super viewDidAppear:animated];
}

- (void)viewWillDisappear:(BOOL)animated{
    [super viewWillDisappear:animated];
    [self.sceneView.session pause];
}

//初始化AR场景
- (void)initARSceneView{
    self.sceneView.scene = self.scene;
    [self.view addSubview:self.sceneView];
}

//开启AR世界追踪
- (void)startARWorldTrackingConfiguration{
    switch (self.arType) {
        case ARWorldTrackingConfigurationType_detectionImage:{//图片识别
            if (@available(iOS 11.3, *)) {
                //设置ARWorldTrackingConfiguration的detectionImage指向的目录(标示图文件夹)
                //注意:文件夹中的标示图必须设置其size大小,否则不能使用
                self.configuration.detectionImages = [ARReferenceImage referenceImagesInGroupNamed:@"ARDetectionImageResource" bundle:nil];
                //启动AR追踪
                [self.sceneView.session runWithConfiguration:self.configuration options:ARSessionRunOptionResetTracking | ARSessionRunOptionRemoveExistingAnchors];
            }
            break;
        }
        case ARWorldTrackingConfigurationType_planeDetection:{//平面识别
            if (@available(iOS 11.3, *)) {
                self.configuration.planeDetection = ARPlaneDetectionHorizontal; // | ARPlaneDetectionVertical;
                //启动AR追踪
                [self.sceneView.session runWithConfiguration:self.configuration options:ARSessionRunOptionResetTracking | ARSessionRunOptionRemoveExistingAnchors];
            }
            break;
        }
        case ARWorldTrackingConfigurationType_faceTracking:{//人脸检测
            if (@available(iOS 11.3, *)) {
                [self.sceneView.session runWithConfiguration:self.faceConfiguration];
            }
            break;
        }
        case ARWorldTrackingConfigurationType_faceTrackingBlendShapes:{//人脸检测 - 表情检测
            if (@available(iOS 11.3, *)) {
                [self.sceneView.session runWithConfiguration:self.faceConfiguration];
            }
            break;
        }
            
        default:{
            
            break;
        }
    }
    
}

到此,运行工程就能创建出AR追踪器,创建ARWorldTrackingConfiguration内部会创建相机,无需我们手动打开相机。
在Demo工程中展示了4中追踪场景如下:根据不同场景配置ARWorldTrackingConfiguration的运行追踪类型(看上面case中的追中场景)

//AR世界追踪场景
typedef enum : NSUInteger {
    ARWorldTrackingConfigurationType_detectionImage,//图片识别
    ARWorldTrackingConfigurationType_planeDetection,//平面捕捉
    ARWorldTrackingConfigurationType_faceTracking,//人脸识别
    ARWorldTrackingConfigurationType_faceTrackingBlendShapes,//表情检测
} ARWorldTrackingConfigurationType;

追中到真实世界中的场景后我们继承ARSCNView的ARSCNViewDelegate代理,ARKit会回调回来追踪到的节点信息。一下开始各种识别追踪的处理:

1、图片识别+视频流播放

图片识别成功后,处理以下回调方法,在目标图片中添加视频播放器节点,并进行视频资源的播放(imageHC01和0003为我添加在工程中的目标图片和视频资源的名称,可以根据需要进行修改)

//AR世界追踪回调 - 不断更新
- (void)renderer:(id <SCNSceneRenderer>)renderer didUpdateNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor {
    if (self.arType == ARWorldTrackingConfigurationType_detectionImage) {//图片识别
        ARImageAnchor *imageAnchor = (ARImageAnchor *)anchor;
        //获取参考标示图对象
        ARReferenceImage  *referenceImage = imageAnchor.referenceImage;
        if ([referenceImage.name isEqual:@"imageHC01"] || [referenceImage.name isEqualToString:@"0003"]) {//识别到指定标示图
            
            //暂停并移除原先添加的节点
            [self.player pause];
            [self.playerParanNode removeFromParentNode];
            
            //加载新的视频资源
            [self setPlayerVideoItemWithDetectionImageName:referenceImage.name];
            
            //在标示图上创建节点
            self.playerParanNode = [SCNNode new];
            SCNBox *box = [SCNBox boxWithWidth:referenceImage.physicalSize.width height:referenceImage.physicalSize.height length:0.001 chamferRadius:0];
            self.playerParanNode.geometry = box;//创建一个箱子放在节点上
            //将创建的子节点旋转到与图片贴合(右手坐标)
            self.playerParanNode.eulerAngles = SCNVector3Make(-M_PI/2, 0, 0);
            
            //将box的materials设置成player对象
            SCNMaterial *material = [[SCNMaterial alloc] init];
            material.diffuse.contents = self.player;
            self.playerParanNode.geometry.materials = @[material];
            
            //直接播放
            [self.player play];
            
            [node addChildNode:self.playerParanNode];
            
        }
    }
}

创建AVPlayer和加载资源

/**
 播放器对象

 @return AVPlayer
 */
-(AVPlayer *)player{
    if (!_player) {
        _player=[[AVPlayer alloc] init];
    }
    return _player;
}

//获取与识别图对应的视频路径
- (NSURL *)getPlayVideoUrl:(NSString *)videoName{
    NSString * urlStr = [[NSBundle mainBundle]pathForResource:[NSString stringWithFormat:@"%@.mp4",videoName] ofType:nil];
    NSURL *url=[NSURL fileURLWithPath:urlStr];
    return url;
}

//设置player预播放的视频资源
- (void)setPlayerVideoItemWithDetectionImageName:(NSString *)imageName{
    NSURL *videoUrl = [self getPlayVideoUrl:imageName];
    self.player = [AVPlayer playerWithURL:videoUrl];
}

图片识别效果:


图片识别.gif

2、平面捕捉+模型放置

如果要实现ARKit平面捕捉功能需要设置ARWorldTrackingConfiguration的planeDetection属性。可进行水平面捕捉和垂直平面捕捉:

//平面识别
self.configuration.planeDetection = ARPlaneDetectionHorizontal; // | ARPlaneDetectionVertical;
 //启动AR追踪
[self.sceneView.session runWithConfiguration:self.configuration options:ARSessionRunOptionResetTracking | ARSessionRunOptionRemoveExistingAnchors];

使用.scn格式模型,将模型放置在捕捉到的平面上。这里只是简单放置3D模型,后期可以对模型进行更多交互,包括模型动画、点击交互等操作。
捕捉到平面后,执行以下回调,操作模型

//AR世界追踪回调
- (void)renderer:(id<SCNSceneRenderer>)renderer didAddNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor{
    if (self.arType == ARWorldTrackingConfigurationType_planeDetection) {//平面捕捉
        if ([anchor isMemberOfClass:[ARPlaneAnchor class]]) {//识别到了平面
            //添加一个3D模型,ARKit只有捕捉能力,锚点只是一个空间位置
            //获取捕捉到的平面锚点
            ARPlaneAnchor *planeAnchor = (ARPlaneAnchor *)anchor;
//            //创建一个box,3D模型(系统捕捉到的平底是不规则的,这里将其缩放)
//            SCNBox *planBox = [SCNBox boxWithWidth:planeAnchor.extent.x * 0.5 height:0 length:planeAnchor.extent.x * 0.5 chamferRadius:0];
//            //使用Material渲染3D模型
//            planBox.firstMaterial.diffuse.contents = [UIColor redColor];
//            //创建3D模型节点
//            SCNNode *planeNode = [SCNNode nodeWithGeometry:planBox];
//            //设置节点的中心为捕捉到的中心点
//            planeNode.position = SCNVector3Make(planeAnchor.center.x, 0, planeAnchor.center.z);
//
//            //将3D模型添加到捕捉到的节点上(此时如果将模型设置有颜色,就可以看到3D长方体模型)
//            [node addChildNode:planeNode];
            
            //创建3D模型场景(将自定义模型展现出来)
            SCNScene *scene = [SCNScene sceneNamed:@"art.scnassets/vase/vase.scn"];
            //获取模型节点
            //一个场景有多个节点,所有场景有且只有一个根节点
            SCNNode *modelNode = scene.rootNode.childNodes.firstObject;
            //设置模型节点的位置为捕捉到平底的位置(默认为相机位置)
            modelNode.position = SCNVector3Make(planeAnchor.center.x, 0, planeAnchor.center.z);
            //将自定义模型节点添加到捕捉到的节点上
            [node addChildNode:modelNode];
        }
    }
}

平面捕捉效果:


平面捕捉效果.gif

3、人脸识别+人脸贴图
ARKit 1.5新特性新增了人脸识别功能需要ios11.3或更高系统支持。人脸识别与图片识别和平底捕捉所创建的追踪器有所区别,需要创建ARFaceTrackingConfiguration
创建ARFaceTrackingConfiguration:

@property (nonatomic, strong) ARConfiguration *faceConfiguration;//人脸识别追踪
- (ARConfiguration *)faceConfiguration{
    if (!_faceConfiguration) {
        _faceConfiguration = [[ARFaceTrackingConfiguration alloc] init];
        _faceConfiguration.lightEstimationEnabled = YES;
    }
    return _faceConfiguration;
}

//运行AR人脸识别追踪器
if (@available(iOS 11.3, *)) {
[self.sceneView.session runWithConfiguration:self.faceConfiguration];
}

人脸识别成功后,给人脸增加贴图。当人的表情发生变化(皱眉、张嘴等)时,需要不断更新贴图在人脸节点中的状态,以达到贴图与人脸表情同步的效果。

//人脸贴图节点
@property (nonatomic, strong) SCNNode *faceTextureMaskNode;

- (void)renderer:(id<SCNSceneRenderer>)renderer willUpdateNode:(SCNNode *)node forAnchor:(ARAnchor *)anchor{
    if (self.arType == ARWorldTrackingConfigurationType_faceTracking) {
        if (anchor && [anchor isKindOfClass:[ARFaceAnchor class]]) {//识别到人脸
            ARFaceAnchor *faceAnchor = (ARFaceAnchor *)anchor;
            
            if (!_faceTextureMaskNode) {
                [node addChildNode:self.faceTextureMaskNode];
            }
            
            //实时更新贴图
            ARSCNFaceGeometry *faceGeometry = (ARSCNFaceGeometry *)self.faceTextureMaskNode.geometry;
            if (faceGeometry && [faceGeometry isKindOfClass:[ARSCNFaceGeometry class]]) {
                [faceGeometry updateFromFaceGeometry:faceAnchor.geometry];
            }
        }
        
    }else if (self.arType == ARWorldTrackingConfigurationType_faceTrackingBlendShapes){//表情检测
        if (anchor && [anchor isKindOfClass:[ARFaceAnchor class]]) {//识别到人脸锚点
            ARFaceAnchor *faceAnchor = (ARFaceAnchor *)anchor;
            NSDictionary *blendShapes = faceAnchor.blendShapes;
            NSNumber *browInnerUp = blendShapes[ARBlendShapeLocationBrowInnerUp];//皱眉程度
            if ([browInnerUp floatValue] > 0.5) {
                NSLog(@"皱眉啦............");
                if (!_glassTextureNode) {
                    [node addChildNode:self.glassTextureNode];
                }
                ARSCNFaceGeometry *faceGeometry = (ARSCNFaceGeometry *)self.glassTextureNode.geometry;
                if (faceGeometry && [faceGeometry isKindOfClass:[ARSCNFaceGeometry class]]) {
                    [faceGeometry updateFromFaceGeometry:faceAnchor.geometry];
                }
            }
            
        }
    }
}

/**
 人脸贴图节点

 @return SCNNode
 */
- (SCNNode *)faceTextureMaskNode {
    if (!_faceTextureMaskNode) {
        id<MTLDevice> device = self.sceneView.device;
        ARSCNFaceGeometry *geometry = [ARSCNFaceGeometry faceGeometryWithDevice:device fillMesh:NO];
        SCNMaterial *material = geometry.firstMaterial;
        material.fillMode = SCNFillModeFill;
        material.diffuse.contents = [UIImage imageNamed:@"faceTexture.jpg"];
        _faceTextureMaskNode = [SCNNode nodeWithGeometry:geometry];
    }
    _faceTextureMaskNode.name = @"textureMask";
    return _faceTextureMaskNode;
}

人脸识别与人脸贴图效果:


人脸识别.gif

本文到此结束,后续将对AR场景中3D模型交互进行研究与实现。感谢您的阅读~

[本文demo下载:]https://github.com/heqican/HCARKit/tree/master

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,529评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,015评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,409评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,385评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,387评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,466评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,880评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,528评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,727评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,528评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,602评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,302评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,873评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,890评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,132评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,777评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,310评论 2 342

推荐阅读更多精彩内容