保存深度值——小端序,位数,Android,Huawei AR engine
保存深度值——小端序,位数,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的更多相关文章
- 《Intel汇编第5版》 Intel CPU小端序
一.MASM汇编器中的数据类型 二.Intel汇编中的立即数类型 三.定义有符号和无符号整数 四.小端序 内存中数据按照字节存储,一个4个字节无符号整数,其高位存储在低地址上,低位存储在高地址上. 比 ...
- java音视频编解码问题:16/24/32位位音频byte[]转换为小端序short[],int[],以byte[]转short[]为例
前言:Java默认采用大端序存储方式,实际编码的音频数据是小端序,如果处理单8bit的音频当然不需要做转换,但是如果是16bit或者以上的就需要处理成小端序字节顺序. 注:大.小端序指的是字节的存储顺 ...
- C# 中大端序与小端序
C# 中大端序与小端序 static void Main(string[] args) { uint value = 0x12345678; Console.WriteLine("原始字节序 ...
- c++——大端序,小端序的排列问题
#include<iostream> using namespace std; union TestModel { int i; char ch; }; int main() { unio ...
- C/C++字节序(大端/小端)判断
C/C++大端小端判断 说的是变量的高字节.低字节在内存地址中的排放顺序. 变量的高字节放到内存的低地址中(变量的低字节放到内存的高地址中)==>大端 变量的高字节放到内存的高地址中(变量的低字 ...
- 大端模式 VS 小端模式
简单点说,就是字节的存储顺序,如果数据都是单字节的,那怎么存储无所谓了,但是对于多字节数据,比如int,double等,就要考虑存储的顺序了.注意字节序是硬件层面的东西,对于软件来说通常是透明的.再说 ...
- 大端小端转换,le32_to_cpu 和cpu_to_le32
字节序 http://oss.org.cn/kernel-book/ldd3/ch11s04.html 小心不要假设字节序. PC 存储多字节值是低字节为先(小端为先, 因此是小端), 一些高级的平台 ...
- 【C#基础概念】字节顺序(大端、小端)
字节顺序,又称端序或尾序(英語:Endianness),在计算机科学领域中,指電腦記憶體中或在数字通信链路中,组成多字节的字的字节的排列顺序. 例如假设上述变量x类型为int,位于地址0x100处,它 ...
- Android为TV端助力 同时setTag两次,保存多种值
示例代码: view.setTag(R.string.action_settings,hodler.content); 接收两个值,一个是key值,必须是唯一值,而且要写在values/ids.xml ...
- 用C语言,如何判断主机是 大端还是小端(字节序)
所谓大端就是指高位值在内存中放低位地址,所谓小端是指低位值在内存中放低位地址.比如 0x12345678 在大端机上是 12345678,在小端机上是 78564312,而一个主机是大端还是小端要看C ...
随机推荐
- linux 安装 Ollama 框架
概述 Ollama 是一款旨在简化大语言模型(LLM)本地部署的工具,支持 Windows.Linux 和 MacOS 系统.它提供了一个用户友好的环境,让开发者可以轻松地运行和调优如 Qwen.Ll ...
- Android开发快速入门iOS开发概览
注:本文同步发布于微信公众号:stringwu的互联网杂谈 Android开发快速入门iOS开发概览 1 前言 笔者总结了自己在拥有Android开发的相关基础后入门iOS开发时遇到的点点滴滴给其他想 ...
- WPF Play Image slider animation using Storyboard
using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using Sys ...
- 一文搞懂SaaS架构建设流程:业务战略设计、架构蓝图设计、领域系统架构设计、架构治理与实施
大家好,我是汤师爷~ SaaS架构建设是一项复杂的系统工程,不仅需要技术层面的实现,更要从业务战略.架构设计.治理与实施等多个维度进行全面规划. 一个成功的SaaS架构可以帮助企业降低IT成本.提升业 ...
- linux:MariaDB安装
介绍 链接 安装 查看系统中是否已安装 rpm -qa | grep -i mariadb 返回结果类似如下内容,则表示已有 MariaDB 的包 为避免安装版本不同造成冲突,请执行以下命令移除已安装 ...
- Kotlin:定义参数是函数的函数、函数内联、具名函数的函数引用
- 一体机场景ceph高可用介绍
本文分享自天翼云开发者社区<一体机场景ceph高可用介绍>,作者:b****n 一体机场景使用ceph开源架构作为存储系统的主体架构,原生方案支持存储数据高可用性,包括副本数可以灵活控制/ ...
- oracle之sqlplus删除键不能用
方法一 1.终端命令,临时有效,重连失效 stty erase ^H 2.配置环境变量,永久有效 vi -oracle/.bash_profile stty erase ^H source -orac ...
- Presto-JDBC使用
一.简介 PrestoConnection并不能提供一个持久的Socket连接,而是创建一个OkHttpClient与Presto按照HTTP1.1协议进行通信,并且PrestoConnection仅 ...
- Luogu P7077 CSP-S2020 函数调用 题解 [ 蓝 ] [ 拓扑排序 ] [ 动态规划 ] [ 数学 ]
函数调用:个人非常喜欢的一道拓扑题. 转化 这题一共有三种操作,不太好搞.而第一个函数看起来就比较可做,第三个函数显然就是让你拓扑转移,于是我们考虑第二个操作怎么处理. 当我们进行一个操作一后,假设当 ...