我正在开发Android应用程序,该应用程序应该使用Google Camera的新深度图生成功能。
Google基本上描述了使用的元数据here
我可以访问大多数元数据,但遗憾的是最重要的数据被编码为extendedXmp,我无法让任何XMP解析库正确解析它!
我尝试过Commons-Imaging,元数据提取器和最近的Adobes XMPCore
XMPCore可能能够处理扩展版本,但是没有文档如何让它解析JPG文件中的数据,假设要传递原始XMP数据
是否有正确的XMP解析实现,包括JPG文件的扩展部分,或者我只是做错了什么?
这是我的尝试:
使用Commons-Imaging:
try {
String imageParser = new JpegImageParser().getXmpXml(new ByteSourceInputStream(imageStream, "img.jpg"), new HashMap<String, Object>());
Log.v(TAG, imageParser);
} catch (ImageReadException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
使用元数据提取器
Metadata metadata = ImageMetadataReader.readMetadata(
new BufferedInputStream(imageStream), false);
XmpDirectory xmp = metadata
.getDirectory(XmpDirectory.class);
XMPMeta xmpMeta = xmp.getXMPMeta();
String uri = "http://ns.google.com/photos/1.0/depthmap/";
Log.v(TAG, xmpMeta.doesPropertyExist(uri, "GDepth:Format") + " " );
try {
XMPProperty hasExtendedXMP = xmpMeta.getProperty("http://ns.adobe.com/xmp/note/", "xmpNote:HasExtendedXMP");
Log.v(TAG, hasExtendedXMP.getValue().toString() + " " + new String(Base64.decode(hasExtendedXMP.getValue().toString(), Base64.DEFAULT)));
} catch (XMPException e) {
e.printStackTrace();
}
答案 0 :(得分:6)
最初,Adobe没想到XMP数据长度会超过一个JPEG段(约64K)的限制,而他们的XMP规范声明XMP数据必须合二为一。后来,当他们发现单个JPEG APP1段不足以容纳XMP数据时,他们改变了他们的规范,以允许整个XMP数据的多个APP1段。数据分为两部分:标准XMP和ExtendedXMP。标准XMP部件是带有包装器的“普通”XMP结构,而ExtendedXMP部件没有包装器。 ExtendedXMP数据可以进一步划分为多个APP1。
以下引用来自Adobe XMP规范第3部分,适用于ExtendedXMP块作为JPEG APP1:
每个块都在单独的APP1标记内写入JPEG文件 分割。每个ExtendedXMP标记段包含:
- 以空值终止的签名字符串“http://ns.adobe.com/xmp/extension/”。
- 存储为32字节ASCII十六进制字符串的128位GUID,大写字母A-F,无空终止。 GUID是完整的128位MD5摘要 ExtendedXMP序列化。
- ExtendedXMP序列化的全长,为32位无符号整数
- 此部分的偏移量为32位无符号整数。
- ExtendedXMP的一部分
我们可以看到除了以null结尾的字符串作为ExtendedXMP数据的id之外,还有一个GUID,其值应与标准XMP部分中的值相同。偏移量用于连接ExtendedXMP的不同部分 - 因此ExtendedXMP APP1的顺序可能甚至不是有序的。然后是实际的数据部分,这就是为什么@Matt的答案需要一些方法来修复字符串。还有另一个值 - ExtendedXMP序列化的全长,它有两个用途:检查数据的完整性以及提供加入数据的缓冲区大小。
当我们找到一个ExtendedXMP段时,我们需要将当前数据与其他ExtendedXMP段连接起来,最后得到整个ExtendedXMP数据。然后,我们将两个XML树连接在一起(同样从标准XMP部件中删除GUID)以检索整个XMP数据。
我在Java中创建了一个库icafe,它可以提取和插入XMP以及ExtendedXMP。 ExtendedXMP的用例之一是Google的深度图数据,实际上是作为元数据隐藏在实际图像中的灰度图像,而在JPEG的情况下,作为XMP数据。深度图图像可以用于例如模糊原始图像。深度图数据通常很大,必须分成标准和扩展的XMP部分。整个数据是Base64编码的,可以是PNG格式。
以下是示例图像和提取的深度图:
原始图片来自here。
注意:最近我发现另一个website谈论Google Cardboard Camera应用程序,它可以利用JPEG XMP数据中嵌入的图像和音频。 ICAFE现在支持从这些图像中提取图像和音频。使用以下调用JPEGTweaker.extractDepthMap()
答案 1 :(得分:3)
我已经能够使用metadata-extractor库读取存储在XMP中的Picasa面部数据,并通过XMP属性读取迭代器:
try {
Metadata metadata = ImageMetadataReader.readMetadata(imageFile);
XmpDirectory xmpDirectory = metadata.getDirectory(XmpDirectory.class);
XMPMeta xmpMeta = xmpDirectory.getXMPMeta();
XMPIterator itr = xmpMeta.iterator();
while (itr.hasNext()) {
XMPPropertyInfo pi = (XMPPropertyInfo) itr.next();
if (pi != null && pi.getPath() != null) {
if ((pi.getPath().endsWith("stArea:w")) || (pi.getPath().endsWith("mwg-rs:Name")) || (pi.getPath().endsWith("stArea:h")))
System.out.println(pi.getValue().toString());
}
}
} catch (final NullPointerException npe) {
// ignore
}
答案 2 :(得分:1)
我遇到了同样的问题,我认为问题在于扩展数据存储在第二个xmpmeta部分中,例如,元数据提取器会跳过该部分。所以我能够做的是在字节流中搜索每个部分,看看它是否具有我期望的属性。我还发现,至少对于深度图数据,基本64编码的字符串显然被分成大约64 KB的部分,并且包括一些需要被移除的标头以便正确地解码字符串。下面的fixString函数很可能被知道分块信息的人所取代。这取决于https://www.adobe.com/devnet/xmp.html提供的xmpcore库。
import java.io.*;
import java.util.*;
import com.adobe.xmp.*;
import com.adobe.xmp.impl.*;
public class XMP
{
// An encoding should really be specified here, and for other uses of getBytes!
private static final byte[] OPEN_ARR = "<x:xmpmeta".getBytes();
private static final byte[] CLOSE_ARR = "</x:xmpmeta>".getBytes();
private static void copy(InputStream in, OutputStream out) throws IOException
{
int len = -1;
byte[] buf = new byte[1024];
while((len = in.read(buf)) >= 0)
{
out.write(buf, 0, len);
}
in.close();
out.close();
}
private static int indexOf(byte[] arr, byte[] sub, int start)
{
int subIdx = 0;
for(int x = start;x < arr.length;x++)
{
if(arr[x] == sub[subIdx])
{
if(subIdx == sub.length - 1)
{
return x - subIdx;
}
subIdx++;
}
else
{
subIdx = 0;
}
}
return -1;
}
private static String fixString(String str)
{
int idx = 0;
StringBuilder buf = new StringBuilder(str);
while((idx = buf.indexOf("http")) >= 0)
{
buf.delete(idx - 4, idx + 75);
}
return buf.toString();
}
private static String findDepthData(File file) throws IOException, XMPException
{
FileInputStream in = new FileInputStream(file);
ByteArrayOutputStream out = new ByteArrayOutputStream();
copy(in, out);
byte[] fileData = out.toByteArray();
int openIdx = indexOf(fileData, OPEN_ARR, 0);
while(openIdx >= 0)
{
int closeIdx = indexOf(fileData, CLOSE_ARR, openIdx + 1) + CLOSE_ARR.length;
byte[] segArr = Arrays.copyOfRange(fileData, openIdx, closeIdx);
XMPMeta meta = XMPMetaFactory.parseFromBuffer(segArr);
String str = meta.getPropertyString("http://ns.google.com/photos/1.0/depthmap/", "Data");
if(str != null)
{
return fixString(str);
}
openIdx = indexOf(fileData, OPEN_ARR, closeIdx + 1);
}
return null;
}
public static void main(String[] args) throws Exception
{
String data = findDepthData(new File(args[0]));
if(data != null)
{
byte[] imgData = Base64.decode(data.getBytes());
ByteArrayInputStream in = new ByteArrayInputStream(imgData);
FileOutputStream out = new FileOutputStream(new File("out.png"));
copy(in, out);
}
}
}