保存深度值——小端序,位数,Android

accuireDepthImage

华为Mate Pro系列基本上前置摄像头都是有TOF的,也就是能够得到场景的深度信息,在华为的AR engine里提供了一个方法可以读取场景的深度值。

不过其官方文档里对这个方法的介绍很少,寥寥数语,前期也在这里踩了一些坑。Google的AR core对这个深度值做了详细的介绍:


得到的深度图是16位的,其中高3位是置信度,低13位是采样得到的深度值,并且排列顺序是小端序。第一张图说设高3位为0,但是我看了一下,其实是第二种情况。

知道这个信息之后,我们便可以使用如下代码保存深度值(二进制文件):

Image depthImage = arFrame.acquireDepthImage();
File f = new File(dir, numFrameStr + "_depth16.bin"); if(depthImage.getFormat() != ImageFormat.DEPTH16)
throw new RuntimeException("Expected image format is DEPTH16, but is:"+depthImage.getFormat()); ByteBuffer buffer = depthImage.getPlanes()[0].getBuffer();
try {
FileChannel fc = new FileOutputStream(f).getChannel();
fc.write(buffer);
fc.close();
} catch (IOException e) {
e.printStackTrace();
Log.i(TAG, "Error writing image depth16: " +f.getPath());
}

将二进制文件打开看一下:

文件是以16进制保存的,所以每四个数字代表一个深度值。取0x0020,0x2242转化为十进制的深度值看一下。

16进制 0x0020 0x2242 0xba42
二进制 0000 0000 0010 0000 0010 0010 0100 0010 1011 1010 0100 0010
由于是小端序,将高位字节拿到前面 0010 0000 0000 0000 0100 0010 0010 0010 0100 0010 1011 1010
将高三位的置信度设为0 0000 0000 0000 0000 0000 0010 0010 0010 0000 0010 1011 1010
十进制 0mm 546mm 698mm

最后我们将这个二进制文件转化为格式为CU_16的灰度图看一下:

效果还不错。

depthImag保存为图像

前面的保存的二进制文件是保存了置信度信息的,如果想要保存深度图就需要把高3位置信度信息设为0,才能保存。代码如下:

Image depthImg = arFrame.acquireDepthImage();
int width = depthImg.getWidth();
int height = depthImg.getHeight(); //ShortBuffer shortBuffer = depthImg.getPlanes()[0].getBuffer().asShortBuffer();
ShortBuffer shortBuffer = depthImg.getPlanes()[0].getBuffer().order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();
Bitmap disBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++) {
int index = (i * width + j);
shortBuffer.position(index);
short depthSample = shortBuffer.get();
// 获取深度值后13位
short depthRange = (short) (depthSample & 0x1FFF); // 拆分short数据成两个8位数据
int highByte = (depthRange & 0xFF00) >> 8; // 获取高8位
int lowByte = depthRange & 0x00FF; // 获取低8位
disBitmap.setPixel(j, i, Color.argb(255 , highByte, lowByte, 0));
}
}
try {
FileOutputStream out = new FileOutputStream(file);
disBitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
out.flush();
out.close();
//GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT);
} catch (Exception e) {
e.printStackTrace();
}

恢复深度值的时候就可以读出R通道的值,然后左移八位(乘256),再加上G通道的值。

注意这里有一个非常隐晦的BUG,就是如果使用的是第5行被注释的代码保存ShortBuffer,得到的将是大端序的值,现在一般的机器都是小端序列,如果使用大端序这将会导致一些错误。

可以看到小端法使用的是ShortBuffer shortBuffer = depthImg.getPlanes()[0].getBuffer().order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();

其值是准确的,而使用ShortBuffer shortBuffer = depthImg.getPlanes()[0].getBuffer().asShortBuffer();得到了错误的结果。

有些遗憾的是,我发现论坛里HMS 小助手提供的代码是有问题的:

这里应该使用 ShortBuffer shortDepthBuffer = plane.getBuffer().order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();

论坛里这个朋友也遇到了类似的问题:

最后建议在java中配置一下opencv直接保存为16位灰度图(推荐)

public static void writeDepth16binInPng16GrayscaleTum(String bin, int width, int height, String png) throws IOException {
byte[] bytes = Files.readAllBytes(Paths.get(bin));
ByteBuffer buffer = ByteBuffer.wrap(bytes);
buffer = buffer.order(ByteOrder.LITTLE_ENDIAN);
ShortBuffer sBuffer = buffer.asShortBuffer();
short[] depthTum = new short[width*height]; Mat mat = Mat.eye(height, width, CvType.CV_16UC1); //max is 65536 == 65meters / 16 bits = 2 bytes int i=0;
for (int h = 0; h < height; h++) {
for (int w = 0; w < width; w++) {
short depthSample = sBuffer.get(); //depth16[y*width + x];
short depthMm = (short) (depthSample & 0x1FFF);
// short depthConfidence = (short) ((depthSample >> 13) & 0x7);
depthTum[h*width+w] = (short)(depthMm * 5); //tum rgbd is 5==1mm / 5000==1m
}
} mat.put(0, 0, depthTum);
Imgcodecs.imwrite(png, mat);
//buffer.clear();
}

最后感谢remmel的精彩工作,提供了非常优秀的参考!

code

read_bin.cpp

//
// Created by xin on 23-11-15.
//
#include <iostream>
#include <opencv2/opencv.hpp>
#include <fstream>
#include <vector> int main() {
// 文件路径
std::string file_path = "/home/xin/Code/CLionProjects/depth_image/img/another/0_depth16.bin"; std::ifstream file(file_path, std::ios::binary);
if (!file.is_open()) {
std::cerr << "Failed to open the file." << std::endl;
return 1;
} // 读取文件内容到 vector
std::vector<uint16_t> depth_values;
uint16_t value; while (file.read(reinterpret_cast<char*>(&value), sizeof(value))) {
value = value & uint16_t(0x1FFF); // 这个是真实的深度值
value *= 5; // 为了更好的可视化,使得灰度图更亮一些
depth_values.push_back(value);
} file.close(); cv::Mat depth_image(180, 240, CV_16UC1);
// 将 depth_values 复制到 depth_image 中
std::memcpy(depth_image.data, depth_values.data(), depth_values.size() * sizeof(uint16_t)); cv::imwrite("DepthImage.png", depth_image);
cv::waitKey(0); return 0;
}

保存深度值——小端序,位数,Android,Huawei AR engine的更多相关文章

  1. 《Intel汇编第5版》 Intel CPU小端序

    一.MASM汇编器中的数据类型 二.Intel汇编中的立即数类型 三.定义有符号和无符号整数 四.小端序 内存中数据按照字节存储,一个4个字节无符号整数,其高位存储在低地址上,低位存储在高地址上. 比 ...

  2. java音视频编解码问题:16/24/32位位音频byte[]转换为小端序short[],int[],以byte[]转short[]为例

    前言:Java默认采用大端序存储方式,实际编码的音频数据是小端序,如果处理单8bit的音频当然不需要做转换,但是如果是16bit或者以上的就需要处理成小端序字节顺序. 注:大.小端序指的是字节的存储顺 ...

  3. C# 中大端序与小端序

    C# 中大端序与小端序 static void Main(string[] args) { uint value = 0x12345678; Console.WriteLine("原始字节序 ...

  4. c++——大端序,小端序的排列问题

    #include<iostream> using namespace std; union TestModel { int i; char ch; }; int main() { unio ...

  5. C/C++字节序(大端/小端)判断

    C/C++大端小端判断 说的是变量的高字节.低字节在内存地址中的排放顺序. 变量的高字节放到内存的低地址中(变量的低字节放到内存的高地址中)==>大端 变量的高字节放到内存的高地址中(变量的低字 ...

  6. 大端模式 VS 小端模式

    简单点说,就是字节的存储顺序,如果数据都是单字节的,那怎么存储无所谓了,但是对于多字节数据,比如int,double等,就要考虑存储的顺序了.注意字节序是硬件层面的东西,对于软件来说通常是透明的.再说 ...

  7. 大端小端转换,le32_to_cpu 和cpu_to_le32

    字节序 http://oss.org.cn/kernel-book/ldd3/ch11s04.html 小心不要假设字节序. PC 存储多字节值是低字节为先(小端为先, 因此是小端), 一些高级的平台 ...

  8. 【C#基础概念】字节顺序(大端、小端)

    字节顺序,又称端序或尾序(英語:Endianness),在计算机科学领域中,指電腦記憶體中或在数字通信链路中,组成多字节的字的字节的排列顺序. 例如假设上述变量x类型为int,位于地址0x100处,它 ...

  9. Android为TV端助力 同时setTag两次,保存多种值

    示例代码: view.setTag(R.string.action_settings,hodler.content); 接收两个值,一个是key值,必须是唯一值,而且要写在values/ids.xml ...

  10. 用C语言,如何判断主机是 大端还是小端(字节序)

    所谓大端就是指高位值在内存中放低位地址,所谓小端是指低位值在内存中放低位地址.比如 0x12345678 在大端机上是 12345678,在小端机上是 78564312,而一个主机是大端还是小端要看C ...

随机推荐

  1. OpenMMLab AI实战营 第四课笔记

    OpenMMLab AI实战营 第四课笔记 目录 OpenMMLab AI实战营 第四课笔记 目标检测与MMDetection 1.什么是目标检测 1.1 目标检测的应用 1.1.1 目标检测 in ...

  2. 如何禁止Chrome自动更新IDM扩展程序

    背景是使用学习版IDM下载器,版本6.41.2,地址备份:https://github.com/glucyzz/IDM 下载完成后导入chrome浏览器,但是发现挂了小猫之后浏览器立马就把此插件自动更 ...

  3. [LC952]按公因数计算最大组件大小

    题目描述 给定一个由不同正整数的组成的非空数组 nums ,考虑下面的图: 有 nums.length 个节点,按从 nums[0] 到 nums[nums.length - 1] 标记: 只有当 n ...

  4. Solution -「AGC 020F」Arcs on a Circle

    \(\mathscr{Description}\)   Link.   在一个周长为 \(c\) 的圆周上放置长度分别为 \(l_1,l_2,\cdots,l_n\) 的弧,每条弧的位置独立均匀随机. ...

  5. ESP32 idf常用脚本命令及git命令

    一.Linux环境 1.下载并安装相关的工具 ./install.sh 2.添加ESP-IDF工具到PATH中 . ./export.sh 3.打开配置界面 idf.py menuconfig 4.设 ...

  6. 0415-File类和FileFilter接口

    package demoFile; import java.io.File; import java.io.FileFilter; import java.io.FilenameFilter; imp ...

  7. DeepSeek,你是懂.NET的!

    这两天火爆出圈的话题,除了过年,那一定是DeepSeek!你是否也被刷屏了? DeepSeek 是什么 DeepSeek是一款由国内人工智能公司研发的大型语言模型,拥有强大的自然语言处理能力,能够理解 ...

  8. AI-启动

    前言 我们都知道AI可以帮助我们完成很多工作,同时也可以帮助我们快速生成一些繁琐的文档:本篇介绍接入一些开源的大预言模型: 准备 OpenAPI 首先需要了解下什么是OpenAPI,OpenAPI是一 ...

  9. python渗透脚本小子速成教程

    python代码即脚本,脚本小子即是python.python只有几个类:常量.字符串,API不可知的数,变量定义.常量是不变固定的,变量是可变的,字符串一般都是单引号''和双引号"&quo ...

  10. 2 本地部署DeepSeek模型构建本地知识库+联网搜索详细步骤

    在1 使用ollama完成DeepSeek本地部署中使用ollama完成deepSeek的本地部署和运行,此时我可以在PowerShell中通过对话的方式与DeepSeek交流,但此时本地模型不具备联 ...