ARCore---测量

ARCore

主要功能
运动跟踪:让手机可以理解和跟踪它相对于现实世界的位置。

环境理解:让手机可以检测各类表面(例如地面、咖啡桌或墙壁等水平、垂直和倾斜表面)的大小和位置。

光估测:让手机可以估测环境当前的光照条件。

增强图像:摄像头视野中检测到图像时,告诉您这些图像在 AR 会话中的物理位置。

面部识别:摄像头视野中检测到人脸时,告诉您人脸在 AR 会话中的物理位置及相关信息。
 

 

https://github.com/Terran-Marine/ARCoreMeasuredDistance

 

Android 使用Arcore 实现多点测距
已更新第二版,详情见github链接github源码 点这里 <==
主要使用了Anchor(锚点),Pose (姿势/姿态),Node(节点),Vector3(三维向量)


github源码 点这里 <==

1.准备
一台支持Arcore的手机
依赖arcore和sceneform
    implementation 'com.google.ar:core:1.5.0'
    implementation 'com.google.ar.sceneform:core:1.5.0'
    implementation 'com.google.ar.sceneform.ux:sceneform-ux:1.5.0'
1
2
3
-布局文件使用sceneform提供的fragment

      <fragment
        android:id="@+id/UI_ArSceneView"
        android:name="com.gj.arcoredraw.MyArFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
1
2
3
4
5
import com.blankj.utilcode.util.ToastUtils;
import com.google.ar.core.exceptions.UnavailableApkTooOldException;
import com.google.ar.core.exceptions.UnavailableArcoreNotInstalledException;
import com.google.ar.core.exceptions.UnavailableDeviceNotCompatibleException;
import com.google.ar.core.exceptions.UnavailableException;
import com.google.ar.core.exceptions.UnavailableSdkTooOldException;
import com.google.ar.sceneform.ux.ArFragment;

//可以直接使用ArFragment   我这里为了中文提示
public class MyArFragment extends ArFragment {
    @Override
    protected void handleSessionException(UnavailableException sessionException) {
        String message;
        if (sessionException instanceof UnavailableArcoreNotInstalledException) {
            message = "请安装ARCore";
        } else if (sessionException instanceof UnavailableApkTooOldException) {
            message = "请升级ARCore";
        } else if (sessionException instanceof UnavailableSdkTooOldException) {
            message = "请升级app";
        } else if (sessionException instanceof UnavailableDeviceNotCompatibleException) {
            message = "当前设备部不支持AR";
        } else {
            message = "未能创建AR会话,请查看机型适配,arcore版本与系统版本";
            String var3 = String.valueOf(sessionException);
        }
        ToastUtils.showLong(message);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
2.监听点击 生成锚点
**设置ArFragment的Tap监听 **
        (UI_ArSceneView as MyArFragment).setOnTapArPlaneListener { hitResult, plane, motionEvent ->
             val currentAnchor=hitResult.createAnchor()
        }

1
2
3
4
3.计算两个锚点之间的距离
val startPose = endAnchor.pose
val endPose = startAnchor.pose
val dx = startPose.tx() - endPose.tx()
val dy = startPose.ty() - endPose.ty()
val dz = startPose.tz() - endPose.tz()
val length = Math.sqrt((dx * dx + dy * dy + dz * dz).toDouble())
anchorInfoBean.dataText = "距离为${decimalFormat.format(length)}m"

1
2
3
4
5
6
7
8
4.UI 划线 (两个锚点在ui上连接划线)
private fun drawLine(firstAnchor: Anchor, secondAnchor: Anchor) {
    val firstAnchorNode = AnchorNode(firstAnchor)

    val secondAnchorNode = AnchorNode(secondAnchor)
    firstAnchorNode.setParent((UI_ArSceneView as MyArFragment).arSceneView.scene)
    val firstWorldPosition = firstAnchorNode.worldPosition
    val secondWorldPosition = secondAnchorNode.worldPosition
    val difference = Vector3.subtract(firstWorldPosition, secondWorldPosition)
    val directionFromTopToBottom = difference.normalized()
    val rotationFromAToB = Quaternion.lookRotation(directionFromTopToBottom, Vector3.up())
    MaterialFactory.makeOpaqueWithColor(this@MainActivity, com.google.ar.sceneform.rendering.Color(0f, 191f, 255f))
            .thenAccept { material ->
                val lineMode = ShapeFactory.makeCube(Vector3(0.01f, 0.01f, difference.length()), Vector3.zero(), material)
                val lineNode = Node()
                lineNode.setParent(firstAnchorNode)
                lineNode.renderable = lineMode
                lineNode.worldPosition = Vector3.add(firstWorldPosition, secondWorldPosition).scaled(0.5f)
                lineNode.worldRotation = rotationFromAToB
            }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
5.自定义Node 始终面向相机
 override fun onUpdate(p0: FrameTime?) {
        scene?.let { scene ->
            val cameraPosition = scene.camera.worldPosition
            val nodePosition = this@FaceToCameraNode.worldPosition
            val direction = Vector3.subtract(cameraPosition, nodePosition)
            this@FaceToCameraNode.worldRotation = Quaternion.lookRotation(direction, Vector3.up())
        }
    }
1
2
3
4
5
6
7
8
UI这里有点复杂,主要难点在Vector3(空间向量)这里.用的是数学知识了.
1.先用两个Anchor 获得 世界坐标(worldPosition)
2.使用向量计算两点空间差
3.使用起始Anchor ,生成一个Node.附加到arSceneView上
4.使用MaterialFactory 创建一个Material.再创建一个 Node使用刚才的材质,附加到之前的Node上.

github源码 点这里 <==

 

 

背景:需要识别物体,并且测量物体的尺寸
    思路:

识别物体

1、百度云,阿里云等有很多识别,如果有API识别,就用吧,比较容易,具体接入可以看官方文档

2、另外一种方式就是opencv分析了。这一点我也在研究,我现在识别的代码就不贴了。
 

物体尺寸测量
由于我们是根据图片识别物体,所以返回的是某一帧的图片上的像素点A(x1,y1)到B(x2,y2)。要转换成空间坐标系的点。再计算距离。这里我用的基于安卓的Arcore来实现的。

好吧,废话不多说,上代码,获取某一帧图,然后发送给识别检测的服务器
在Activity中

arFragment.getArSceneView().getScene().addOnUpdateListener(this::onUpdateFrame);
onUpdataFrame:

发送请求用的是okhttputil,在github上面搜一下就知道了
第一步:frame.acquireCameraImage();获得某一帧图片
第二步:OkHttpUtils.post()发送请求
第三步:List<HitResult> hitResults1 = arFragment.getArSceneView().getArFrame().hitTest(positionResults.getPositionResults().get(0).getX(), positionResults.getPositionResults().get(0).getY()); 根据返回的像素点得到空间碰撞的HitResults
第四步: showDistance(hitResults1.get(0), hitResults2.get(0));展示距离吧

private void onUpdateFrame(FrameTime frameTime) {
    Frame frame = arFragment.getArSceneView().getArFrame();
    // If there is no frame, just return.
    if (frame == null) {
        return;
    }
Image image = null;
try {
    image = frame.acquireCameraImage();
    Bitmap bitmap = imageToBitmap(image);
    if (bitmap != null) {
        File file = getFile(bitmap);
        OkHttpUtils.post()
                .addFile("file", "pic" + imageIndex + ".png", file)//
                .url("xxxxx")
                .build()
                .execute(new Callback() {
                    @Override
                    public Object parseNetworkResponse(Response response, int id) throws Exception {
                        try {
                            String str = response.body().string();
                            positionResults = new Gson().fromJson(str, PositionResults.class);
                            return positionResults;
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                        return null;
                    }
                    @Override
                    public void onError(Call call, Exception e, int id) {
                        System.out.println("error!!!!!");
                    }
                    @Override
                    public void onResponse(Object response, int id) {
                        System.out.println("response");
                    }
                });
                        List<HitResult> hitResults1 = arFragment.getArSceneView().getArFrame().hitTest(positionResults.getPositionResults().get(0).getX(),
                                positionResults.getPositionResults().get(0).getY());
                        List<HitResult> hitResults2 = arFragment.getArSceneView().getArFrame().hitTest(positionResults.getPositionResults().get(1).getX(),
                                positionResults.getPositionResults().get(1).getY());
                        if (hitResults1.size() > 0 && hitResults2.size() > 0) {
                            showDistance(hitResults1.get(0), hitResults2.get(0));
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
//                    sendFlag=true;
                if (null != image) {
                    image.close();
                }
            }
showDistance:

private void showDistance(HitResult hitResult1, HitResult hitResult2) {
    Anchor anchor1 = hitResult1.createAnchor();
    AnchorNode firstAnchorNode = new AnchorNode(anchor1);

    Anchor anchor2 = hitResult2.createAnchor();
    AnchorNode secondAnchorNode = new AnchorNode(anchor2);
    secondAnchorNode.setParent(arFragment.getArSceneView().getScene());

    double ddx = (firstAnchorNode.getWorldPosition().x - secondAnchorNode.getWorldPosition().x);
    double ddy = (firstAnchorNode.getWorldPosition().y - secondAnchorNode.getWorldPosition().y);
    double ddz = (firstAnchorNode.getWorldPosition().z - secondAnchorNode.getWorldPosition().z);

    float ndl = (float) Math.sqrt(ddx * ddx + ddy * ddy + ddz * ddz);
Toast toast1 =
        Toast.makeText(this, "dl is" + dl + "M,firstV:(" + firstV.x + "," + firstV.y + "," + firstV.z + "),secendV:("
                        + secondV.x + "," + secondV.y + "," + secondV.z + ")",
                Toast.LENGTH_LONG);
toast1.setGravity(Gravity.CENTER, 0, 0);
toast1.show();

但是这样还是不够直观,所以在两点之间画根直线吧

private void addLineBetweenPoints(Scene scene, Vector3 from, Vector3 to) {
    // prepare an anchor position
    Quaternion camQ = scene.getCamera().getWorldRotation();
    float[] f1 = new float[]{to.x, to.y, to.z};
    float[] f2 = new float[]{camQ.x, camQ.y, camQ.z, camQ.w};
    Pose anchorPose = new Pose(f1, f2);

    // make an ARCore Anchor
    Anchor anchor = arFragment.getArSceneView().getSession().createAnchor(anchorPose);
    // Node that is automatically positioned in world space based on the ARCore Anchor.
    AnchorNode anchorNode = new AnchorNode(anchor);
    anchorNode.setParent(scene);

    // Compute a line's length
    float lineLength = Vector3.subtract(from, to).length();

    // Prepare a color
    Color colorOrange = new Color(android.graphics.Color.parseColor("#ffa71c"));

    // 1. make a material by the color
    MaterialFactory.makeOpaqueWithColor(this, colorOrange)
            .thenAccept(material -> {
                // 2. make a model by the material
                ModelRenderable model = ShapeFactory.makeCylinder(0.0025f, lineLength,
                        new Vector3(0f, lineLength / 2, 0f), material);
                model.setShadowReceiver(false);
                model.setShadowCaster(false);

                // 3. make node
                Node node = new Node();
                node.setRenderable(model);
                node.setParent(anchorNode);

                // 4. set rotation
                final Vector3 difference = Vector3.subtract(to, from);
                final Vector3 directionFromTopToBottom = difference.normalized();
                final Quaternion rotationFromAToB =
                        Quaternion.lookRotation(directionFromTopToBottom, Vector3.up());
                node.setWorldRotation(Quaternion.multiply(rotationFromAToB,
                        Quaternion.axisAngle(new Vector3(1.0f, 0.0f, 0.0f), 90)));
            });
}
原文链接:https://blog.csdn.net/u010940131/article/details/103148616

 

本方案使用目前最火的AR库,实现测量真实世界纸箱的体积。设备支持ARCore、ARKit即可!

简要:通过AR提供的识别平面的功能,找到箱子所在的平面;在平面上标出箱子底部的三个顶点,这三个顶点就能确认箱子的底部面积;通过滑动条调节测量绘制出立方体模型,立方体模型体积即实物的体积(AR库已经实现了虚拟世界和真实世界的1:1比例)。

实现步骤简要:
平面识别
这是AR库提供的功能,打开摄像头后,拿着手机对着桌面来回平移一小段距离,即可把平面识别出来。识别平面效率跟手机移动方式有关,因为AR库识别平面是通过处理画面特征点和三角测量运算出来的。要注意的是:目标平面最好是纹理图案比较复杂的,空白平面和反光平面都会加大识别难度;另外,AR库为了做三角测量计算,手机需要平移,手机原地自转是很难识别出平面的。

绘制底面
绘制立方体底面需要找到箱底三个顶点,找顶点方式很多,我们项目最终方案是通过深度学习的方式,自动找出箱子的顶点二维信息,通过一些简单算法能把二维坐标转化三维坐标。这里讲述最容易实现的方式,就是手动找顶点,Unity有发射线的方法,手触摸手机屏幕,从摄像头发出一条射线,射线射在平面上,击中平面的交点就是我们要找的三维点信息。用这种方式击中箱底三个顶点,找到顶点的三维坐标信息。这三个点就能构建出三维空间中立方体的底面。

确定高度
绘制出底面后,我们就可以计算箱底面积了,但我们要测的是箱子体积,所以还要知道箱子的高度。我们是有方法直接找到高度的,在这先留一手,讲述最容易实现的方法。使用简单的方式实现,就是通过滑动条来确定高度,自动赋予一个高度给立方体模型即可。可看演示视频的效果。


演示视频:

Youku:
视频地址:https://v.youku.com/v_show/id_XMzczNDc3ODUwOA==.html?spm=a2hzp.8244740.0.0

YouTube:
===============================================================

后续开发了 《乐测AR》 项目

这是一款结合了增强现实技术(AR)与人工智能技术(AI),提供规则物体与非规则物体的体积测量的手机端APP。

《乐测AR》应用了目前最火热的增强现实与人工智能技术,即AR与AI技术。用户使用手机摄像头拍摄周边环境,通过AR的SLAM技术让手机理解真实环境,构建出虚拟的三维世界。同时,结合AI技术对图像进行处理,找到我们需要测量的物体(如纸箱)在虚拟三维世界的成像,里面包括该物体的位置与形状信息。最后根据这些信息进行三维重构,恢复出物体的形状,最终计算出该物体的体积。

视频演示:
优酷链接:https://v.youku.com/v_show/id_XNDExMTczNzk4OA==.html?spm=a2h0k.11417342.soresults.dtitle

爱奇艺链接:http://www.iqiyi.com/w_19s6mnc4at.html
原文链接:https://blog.csdn.net/killfunst/article/details/81132758

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值