前言:因为想在安卓设备上显示深度图的3D效果画面,经过查找资料,发现使用opengles比较方便。本文基于opengles在安卓设备实现3D点云效果图显示,而且深度图上点的颜色由近及远,从红-黄-绿-蓝渐变,有点类似matlab的点云图。

一、字节数组工具类:BufferUtil.java

// packageimport java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;public class BufferUtil {//将int[]数组转换为OpenGLES所需的IntBufferpublic static IntBuffer intBufferUtil(int[] arr){IntBuffer buffer;// 初始化ByteBuffer,长度为arr数组的长度*4,因为一个int占4字节ByteBuffer bb = ByteBuffer.allocateDirect(arr.length * 4);bb.order(ByteOrder.nativeOrder()); //数组排列用nativeOrderbuffer = bb.asIntBuffer();buffer.put(arr);buffer.position(0);return buffer;}//将float[]数组转换为OpenGLES所需的FloatBufferpublic static FloatBuffer floatBufferUtil(float[] arr){FloatBuffer buffer;//初始化ByteBuffer,长度为arr数组的长度*4,因为一个float占4字节ByteBuffer bb = ByteBuffer.allocateDirect(arr.length * 4);bb.order(ByteOrder.nativeOrder());buffer = bb.asFloatBuffer();buffer.put(arr);buffer.position(0);return buffer;}/*** 读取到字节数组1** @param file* @return* @throws IOException*/public static byte[] toByteArray(File file) throws IOException {ByteArrayOutputStream bos = new ByteArrayOutputStream((int) file.length());BufferedInputStream in = null;try {in = new BufferedInputStream(new FileInputStream(file));int buf_size = 1024;byte[] buffer = new byte[buf_size];int len = 0;while (-1 != (len = in.read(buffer, 0, buf_size))) {bos.write(buffer, 0, len);}return bos.toByteArray();} catch (IOException e) {e.printStackTrace();throw e;} finally {if (in != null) {try {in.close();} catch (IOException e) {e.printStackTrace();}}if (bos != null) {bos.close();}}}/*** 大端、小端方式,两字节数据转short值(有符号)**/public static short getShort(byte[] b, boolean isBigEndian) {if (b == null || b.length <= 0 || b.length > 2) {return 0;}if (isBigEndian) {return (short) (((b[1] << 8) | b[0] & 0xff));} else {return (short) (((b[1] & 0xff) | b[0] << 8));}}
}

二、主要类,深度数据渲染类:DepthImageRender.java

// packageimport android.opengl.GLSurfaceView;
import android.util.Log;
//import xxx.BufferUtil;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;public class DepthImageRender implements GLSurfaceView.Renderer {// 最大深度public static float depthMax = 0;// 最小非0深度public static float depthMin = 0;//TODO 根据情况设置public static final int imgWidth = 720;//TODO 根据情况设置public static final int imgHeight = 1280;// 根据情况设置//TODO x、y轴坐标的范围(0.0~1.0)*imgXYMagnificationpublic static final int imgXYMagnification = 8;// 定义图像的顶点,形如(x,y,z,x,y,z...),长度为图像宽*高*3private float[] imageVertices;// 定义顶点的颜色,每个顶点的颜色长度为3,形如(r,g,b,a,r,g,b,a),长度为图像宽*高*4private int[] imageColors;// 定义Open GL ES绘制所需要的Buffer对象FloatBuffer imageVerticesBuffer;IntBuffer imageColorsBuffer;private boolean isAntiClockDir = true;// 图像旋转角度private float rotate = 0.0f;// 渐变色数组的长度private final int gradientColorsLength = 256*4;// 红--黄--绿--蓝渐变色中的红色分量private int[] gradientColorsR = new int[gradientColorsLength];// 红--黄--绿--蓝渐变色中的绿色分量private int[] gradientColorsG = new int[gradientColorsLength];// 红--黄--绿--蓝渐变色中的蓝色分量private int[] gradientColorsB = new int[gradientColorsLength];// 初始化时分4阶段,各阶段颜色值变化// 红色分量从65535-65535-->65535-0-->0-0-->0-0// 绿色分量从0-65535-->65535-65535-->65535-65535-->65535-0// 蓝色分量从0-0-->0-0-->0-65535-->65535-65535// 分量起始值和变化趋势(0:不变,1:上升,2:下降)private int[][] rgbWeightStart = new int[][]{{65535, 65535, 0, 0}, {0, 65535, 65535, 65535}, {0, 0, 0, 65535}};private int[][] rgbTrend = new int[][]{{0, 2, 0, 0}, {1, 0, 0, 2}, {0, 0, 1, 0}};// 颜色渐变跨度差private final int rgbTrendGap = 255;// TODO 也可设置成 GL10.GL_LINE_STRIP,效果会有所区别public static int depthDrawTypeDefault = GL10.GL_POINTS;public static int depthDrawType = depthDrawTypeDefault;public DepthImageRender() {generateGradientColors();}public void initImgVertices(byte[] rawFileBytes) {this.imageVertices = getImageDepthVertices(rawFileBytes, imgWidth, imgHeight);imageColors = new int[imageVertices.length * 4 / 3];int pixelIndex = 0;// 根据情况设置// TODO z轴放大倍数(深度值范围:(0.0~1.0)*depthMagnify),倍数大,立体感强int depthMagnify = 4;float depthGap = depthMax - depthMin;Log.i("TAG", "depthMax=" + depthMax + ", depthMin=" + depthMin);if (depthGap <= 0.0f) {depthGap = 1.0f;}for (int i = 0; i < imageColors.length; i += 4) {// 直接从数据读取的深度值float rawDepth = imageVertices[2 + pixelIndex * 3];// 转化后的z轴的值float verticesZDepth;if (rawDepth > 0.0f) {//做个转换,深度值小的,z轴数据更大。深度大的,z轴数据更小。在坐标系中才能显示正常立体图verticesZDepth = (1.0f - (((float) (rawDepth - depthMin)) / depthGap)) * depthMagnify;//重新赋值imageVertices[2 + pixelIndex * 3] = verticesZDepth;}
//            Log.e("TAG", "depth222=" + verticesZDepth);int colorR = 0x0, colorG = 0x0, colorB = 0x0, colorA = 0x0;int[] colors = getRGBAByDepth(rawDepth, depthMin, depthMax);if (colors != null && colors.length > 3) {colorR = colors[0];colorG = colors[1];colorB = colors[2];colorA = colors[3];}imageColors[i] = colorR;imageColors[i + 1] = colorG;imageColors[i + 2] = colorB;imageColors[i + 3] = colorA;pixelIndex++;}// 将立方体的顶点位置数据数组包装成FloatBufferimageVerticesBuffer = BufferUtil.floatBufferUtil(imageVertices);imageColorsBuffer = BufferUtil.intBufferUtil(imageColors);}private void generateGradientColors() {// 共4阶段,各阶段颜色值变化//红色分量从65535-65535-->65535-0-->0-0-->0-0//绿色分量从0-65535-->65535-65535-->65535-65535-->65535-0//蓝色分量从0-0-->0-0-->0-65535-->65535-65535for (int stage = 0; stage < 4; stage++) {int rWS = rgbWeightStart[0][stage];int rT = rgbTrend[0][stage];int gWS = rgbWeightStart[1][stage];int gT = rgbTrend[1][stage];int bWS = rgbWeightStart[2][stage];int bT = rgbTrend[2][stage];for (int color = 0; color < 256; color++) {if (rT == 1) {rWS += rgbTrendGap;} else if (rT == 2) {rWS -= rgbTrendGap;}gradientColorsR[stage * 256 + color] = rWS;if (gT == 1) {gWS += rgbTrendGap;} else if (gT == 2) {gWS -= rgbTrendGap;}gradientColorsG[stage * 256 + color] = gWS;if (bT == 1) {bWS += rgbTrendGap;} else if (bT == 2) {bWS -= rgbTrendGap;}gradientColorsB[stage * 256 + color] = bWS;}}}@Overridepublic void onSurfaceCreated(GL10 gl, EGLConfig config) {gl.glDisable(GL10.GL_DITHER);//关闭防抖gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT//设置透视修正, GL10.GL_FASTEST);gl.glClearColor(0, 0, 0, 0);gl.glShadeModel(GL10.GL_SMOOTH);// 设置为阴影平滑模式gl.glEnable(GL10.GL_DEPTH_TEST);// 启用深度测试gl.glDepthFunc(GL10.GL_LEQUAL);// 设置深度测试的类型}@Overridepublic void onSurfaceChanged(GL10 gl, int width, int height) {gl.glViewport(0, 0, width, height); //设置3D视窗的大小及位置gl.glMatrixMode(GL10.GL_PROJECTION); //设置矩阵模式设为投影矩阵gl.glLoadIdentity(); //初始化单位矩阵float ratio = (float) width / height; //计算视窗宽高比gl.glFrustumf(-ratio, ratio, -1, 1, 1, 10);//设置视窗空间大小}@Overridepublic void onDrawFrame(GL10 gl) {gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);//启用vertex shader 数据gl.glEnableClientState(GL10.GL_COLOR_ARRAY);//启用fragment shader数据gl.glMatrixMode(GL10.GL_MODELVIEW);//设置当前矩阵堆栈为模型堆栈gl.glLoadIdentity();// 跟图像缩放有关gl.glTranslatef(0.0f, 0.0f, -5.0f);// 沿着(0,1,0)向量为轴的方向旋转//TODO 旋转相关,若不旋转,则第一个参数设置成0.0fgl.glRotatef(rotate/*0.0f*/, 0.0f, 1.0f, 0.0f);// 设置顶点的位置数据gl.glVertexPointer(3, GL10.GL_FLOAT, 0, imageVerticesBuffer);// 设置顶点的颜色数据gl.glColorPointer(4, GL10.GL_FIXED, 0, imageColorsBuffer);// 第一个参数如果是 GL10.GL_LINE_STRIP 则点会相互连接(柱状图像),GL10.GL_POINTS 只画点gl.glDrawArrays(depthDrawType, 0, imageVerticesBuffer.capacity() / 3);gl.glFinish();//绘制结束gl.glDisableClientState(GL10.GL_COLOR_ARRAY);gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);if (isAntiClockDir) {if (rotate < 78.0f)rotate += 2.0f;if (rotate >= 78.0f) {isAntiClockDir = false;}} else {if (rotate >= 2.0f)rotate -= 2.0f;if (rotate <= 0.0f) {isAntiClockDir = true;}}}private int[] getRGBAByDepth(float rawDepth, float minDepth, float maxDepth) {// rgba colorint r = 0x0, g = 0x0, b = 0x0, a = 0x0;if (rawDepth <= 0.0f || minDepth <= 0.0f || maxDepth <= 0.0f) {return new int[] {r, g, b, a};}// 深度分层的层数int depthLayerNum = (int) (maxDepth - minDepth);// 当前深度所在的层int curDepthRelativeLayer = (int) (rawDepth - minDepth);// 渐变色每一层的索引跨度int gradientColorsLayerGap = gradientColorsLength / depthLayerNum;if (gradientColorsLayerGap * curDepthRelativeLayer >= 0&& gradientColorsLayerGap * curDepthRelativeLayer < gradientColorsLength) {r = gradientColorsR[gradientColorsLayerGap * curDepthRelativeLayer];g = gradientColorsG[gradientColorsLayerGap * curDepthRelativeLayer];b = gradientColorsB[gradientColorsLayerGap * curDepthRelativeLayer];}return new int[] {r, g, b, a};}/*** @param personBytes 原图的字节数组,每个像素16位存储* @param w 图像宽度* @param h 图像高度* */public float[] getImageDepthVertices(byte[] personBytes, int w, int h) {// 每个像素占3个长度float[] vertices = null;if (personBytes != null && personBytes.length > 0) {vertices = new float[personBytes.length * 3 / 2];
//            Log.i("tag", "personBytes length=" + personBytes.length);
//            Log.i("tag", "vertices length=" + vertices.length);int verticesIndex = 0;for (int row = 0; row < h; row++) {for (int col = 0; col < w; col++) {// opengl坐标,X轴向右,右手定则,Y向上,Z向外// x轴的值要进行缩小vertices[verticesIndex] = (float) (((float) col * imgXYMagnification / w) - imgXYMagnification / 2);// y轴的值要进行缩小vertices[verticesIndex + 1] = (float) ((-((float) row * imgXYMagnification / h)) + imgXYMagnification / 2);int firsBytesIndex = ((w * row) + col) * 2;// 根据情况设置// 根据深度存储方式去读取深度值,本例为16位小端存储的读取方式float depth = (float) ((personBytes[firsBytesIndex] & 0xFF)| ((personBytes[firsBytesIndex + 1] << 8) & 0xFF00));// 获取深度最大、最小值(非0)if (depth != 0) {if (depthMin == 0) {depthMin = depth;} else if (depth < depthMin) {depthMin = depth;}}if (depth > depthMax) {depthMax = depth;}vertices[verticesIndex + 2] = depth;verticesIndex += 3;}}}return vertices;}
}

三、显示点云图或3D效果图的活动类: Depth3DViewerActivity.java

// package import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
//import xxx.DepthImageRender;
//import xxx.BufferUtil;
import java.io.File;
import java.io.IOException;public class Depth3DViewerActivity extends AppCompatActivity {@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);//TODO 必须有读取SD卡权限GLSurfaceView glSurfaceView = new GLSurfaceView(this);setContentView(glSurfaceView);byte[] rawFileBytes = readPersonRawFile();if (rawFileBytes != null) {DepthImageRender myRender = new DepthImageRender();myRender.initImgVertices(rawFileBytes);glSurfaceView.setRenderer(myRender);} else {Log.e("Depth3DViewerActivity", "空数据!");finish();}}/**从深度图原图读取到字节数组,深度图为16位小端数据,深度数据在imagej中打开时,深度值小的越靠近相机,深度值大的远离相机**/public byte[] readPersonRawFile() {String filePath = "sdcard/depth_raw.raw";try {return BufferUtil.toByteArray(new File(filePath));} catch (IOException e) {e.printStackTrace();} catch (Exception e1) {e1.printStackTrace();}return null;}@Overrideprotected void onDestroy() {super.onDestroy();}
}

效果图如下:

安卓源码见我的gitee仓库(深度文件在工程根目录,放到sdcard根目录进行测试,设备要支持gpu):Depth3DViewer: 使用opengles,在安卓设备上显示深度图的3D效果。深度图文件在工程根目录,使用时放在sdcard根目录。 (gitee.com)

安卓使用opengles显示深度点云图或深度3D效果图相关推荐

  1. 关于微信公众号分享在安卓手机不显示分享图片的问题

    如果资源服务器是用的https 把https换成http 就可解决安卓手机不显示图片的问题 对于整站资源全部是用https的  可以用如下代码处理 shareImg.replace(/https/,' ...

  2. 安卓自定义布局显示流式搜索记录

    安卓自定义布局显示流式搜索记录 老规矩,先上效果图(环境:API 30 , AS 4.0) OKK,开始动手! 第一步:自定义流式布局 XFlowLayout ,继承ViewGroup,然后重写 on ...

  3. ue4 Niagara粒子打包安卓后不显示

    提问:ue4 Niagara粒子打包安卓后不显示今天用ue4中的Niagara粒子做了一个烟花的效果,但是打包到安卓手机后只能看到升上天空的粒子,看不到爆炸效果. 解答: 在打包 UE4 Niagar ...

  4. Python使用open3d或matplotlib库实现显示深度3D效果图

    前言:因为客户需要比较直观的看拍摄的深度图的3D效果,所以最好的方法是做成一个可执行的程序给客户,再者python库比较多,实现起来比较快,所以想着用python实现显示深度图的3D效果,代码在win ...

  5. 逍遥模拟器显示android,关于逍遥安卓模拟器不显示声音的解决方法,请看官方说明...

    关于逍遥安卓模拟器不显示声音的解决方法,请看官方说明 2020-03-27 有用户反映,使用逍遥安卓模拟器运行游戏过程中突然没有了声音,重新加载之后还是这样,这是为什么呢?针对这一问题,小编整理了具体 ...

  6. esp32-cam拍照上传,app inventor 制作安卓app实时显示

    esp32-cam拍照上传,app inventor 制作安卓app实时显示 1.ESP32-cam开发环境配置 2 .程序下载 连线 3. 控制台查看图片 第四.app 开发 1.ESP32-cam ...

  7. 安卓自定义电量显示图标

    安卓自定义电量显示图标-----使用广播监听电量的变化,获取实时电量并使用自定义view画出对应的图标,看图: 自定义view实现实时电量显示:BatteryView.java import andr ...

  8. 【深度学习】PyTorch深度学习训练可视化工具visdom

    PyTorch Author:louwill Machine Learning Lab 在进行深度学习实验时,能够可视化地对训练过程和结果进行展示是非常有必要的.除了Torch版本的TensorBoa ...

  9. 深度学习 图像分类_深度学习时代您应该阅读的10篇文章了解图像分类

    深度学习 图像分类 前言 (Foreword) Computer vision is a subject to convert images and videos into machine-under ...

最新文章

  1. 中秋节前,送一波福利
  2. R语言glm拟合logistic回归模型:模型评估(计算模型拟合的统计显著性)、模型评估(赤信息AIC指标计算)
  3. 《R语言游戏数据分析与挖掘》一3.4 小结
  4. JavaScript中的面向对象(1):对象创建模式
  5. SRS-DOLPHIN
  6. C语言笔试不好应该转专业吗,你认为大学里什么学科“难学”?过来人说出几门,考试难补考更难...
  7. python中str是什么_python的str()字符串类型的方法详解
  8. 编译vuejs html,VueJs(2)---VueJs开发环境的搭建和讲解index.html如何被渲染
  9. 雅马哈发电机换机油教程_康明斯柴油发电机组怠速一会就自动停机是什么故障...
  10. Oracle与MySQL的SQL语句区别
  11. vue通过识别字符串中的分号进行换行
  12. 计算机自我鉴定高中生200字,精编高中生自我鉴定200字左右3篇 高中自我鉴定200字...
  13. 什么人适合学平面设计?
  14. 3600000毫秒等于多少小时_一毫秒(一毫秒等于多少毫秒)
  15. NetTraffic网络流量监控工具
  16. [zz] 导致你创业失败的18个错误 [2007-05-03]
  17. 你用过的每款APP都具有这一特点,但你却不知道……
  18. 平板电脑性能测试软件,平板电脑跑分排行榜 最值得入手的都在这里
  19. Qt编写可视化大屏电子看板系统25-模块3设备监控
  20. 《C++ Primer》学习笔记

热门文章

  1. java高级架构师年薪,深度集成!
  2. JavaScript小游戏--翻牌记忆游戏
  3. 关于移动视频直播技术,关键干货都在这里了(三)编码和封装
  4. 【CSS-定位和浮动】
  5. Windows 10下MASM汇编语言环境搭建
  6. 如何通过移动广告平台实现手游推广
  7. STM32——跑马灯实现
  8. 浙江大学计算机2020分数线,2021年浙江大学录取分数线(含2019-2020分数线)
  9. 程序员读《三体》后看到了什么?
  10. 手把手学STM32(一)