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

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

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

// package

import 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所需的IntBuffer
    public static IntBuffer intBufferUtil(int[] arr)
    {
        IntBuffer buffer;
        // 初始化ByteBuffer,长度为arr数组的长度*4,因为一个int占4字节
        ByteBuffer bb = ByteBuffer.allocateDirect(arr.length * 4);
        bb.order(ByteOrder.nativeOrder()); //数组排列用nativeOrder
        buffer = bb.asIntBuffer();
        buffer.put(arr);
        buffer.position(0);
        return buffer;
    }
    //将float[]数组转换为OpenGLES所需的FloatBuffer
    public 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

// package

import 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)*imgXYMagnification
    public static final int imgXYMagnification = 8;

    // 定义图像的顶点,形如(x,y,z,x,y,z...),长度为图像宽*高*3
    private float[] imageVertices;
    // 定义顶点的颜色,每个顶点的颜色长度为3,形如(r,g,b,a,r,g,b,a),长度为图像宽*高*4
    private 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++;
        }
        // 将立方体的顶点位置数据数组包装成FloatBuffer
        imageVerticesBuffer = 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-65535
        for (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;
            }
        }
    }

    @Override
    public 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);// 设置深度测试的类型
    }

    @Override
    public 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);//设置视窗空间大小
    }

    @Override
    public 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.0f
        gl.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 color
        int 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 {
    @Override
    protected 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;
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
    }
}

效果图如下: 

安卓使用opengles显示深度点云图或深度3D效果图_第1张图片

 安卓使用opengles显示深度点云图或深度3D效果图_第2张图片

安卓使用opengles显示深度点云图或深度3D效果图_第3张图片

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

你可能感兴趣的:(Android,camera,Java,android,图像处理,3d,着色器)