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 = [email protected]
            val direction = Vector3.subtract(cameraPosition, nodePosition)
            [email protected] = 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

發佈了70 篇原創文章 · 獲贊 55 · 訪問量 28萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章