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
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
第四步: 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
positionResults.getPositionResults().get(0).getY());
List
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