背景
大疆智图API目前处于免费开放阶段,但官网的快速入门文档的代码是Python和Shell脚本混合的,我尝试写了全PYthon版本,有利于后续开发过程中的集成。
本文只涉及二维可见光重建。步骤及代码
1 获取 Token
从本地设备向服务器发送请求时,需要在大疆智图 API 中使用 AK/SK 认证,借助 HMAC 签名算法为开发者的请求消息添加签名。
返回值包含了accessKeyID、secretAccessKey、sessionToken、region、cloudBucketName、storePath、和callbackParam等参数。
import hmac
import hashlib
import base64
import requests
from datetime import datetime
import pytz
# DJI APP Key 和 Secret Key
DJI_APP_KEY="B"
DJI_SECRET_KEY="q"
# API 请求参数
HOST = "https://openapi-cn.dji.com"
URI = "/terra-rescon-be/v2/store/obtain_token"
HTTP_METHOD = "POST"
# 构造请求 Body (payload)
payload = """
{
"example_key": "example_value"
}
"""
def calculate_signature(content, secret_key):
"""
计算 HMAC-SHA256 签名
"""
signature = hmac.new(
secret_key.encode('utf-8'),
content.encode('utf-8'),
hashlib.sha256
).digest()
return base64.b64encode(signature).decode('utf-8')
def generate_digest(payload):
"""
生成请求 Body 的 SHA-256 Digest
"""
digest = base64.b64encode(hashlib.sha256(payload.encode('utf-8')).digest()).decode('utf-8')
return digest
# 当前 UTC 时间
utc_now = datetime.now(pytz.utc)
x_date = utc_now.strftime("%a, %d %b %Y %H:%M:%S GMT")
# HTTP 方法小写化
lower_method = HTTP_METHOD.lower()
# 生成 Digest
digest = generate_digest(payload)
# 构造签名内容
signature_content = f"date: {x_date}\n@request-target: {lower_method} {URI}\ndigest: SHA-256={digest}"
request_signature = calculate_signature(signature_content, DJI_SECRET_KEY)
# 完整请求 URL
url = f"{HOST}{URI}"
# 构造请求头
headers = {
"Date": x_date,
"Digest": f"SHA-256={digest}",
"Authorization": f'hmac username="{DJI_APP_KEY}", algorithm="hmac-sha256", headers="date @request-target digest", signature="{request_signature}"',
"Content-Type": "application/json;charset=UTF-8"
}
# 发送 POST 请求
response = requests.post(url, headers=headers, data=payload, verify=False) # 设置 verify=False 忽略 SSL 证书验证
# 输出结果
print("请求 URL:", url)
print("请求头:", headers)
print("请求 Body:", payload)
print("响应状态码:", response.status_code)
print("响应内容:", response.text)
将需要的参数从response.text中提取出来,以备后用。
import json
# 示例响应内容
response_content = response.text
# 将响应内容解析为 JSON
response_json = json.loads(response_content)
# 提取指定字段
cloud_name = response_json["data"]["cloudName"]
access_key_id = response_json["data"]["accessKeyID"]
access_key_secret= response_json["data"]["secretAccessKey"]
session_token = response_json["data"]["sessionToken"]
cloudBucketName=response_json["data"]["cloudBucketName"]
callback_param = response_json["data"]["callbackParam"]
store_path = response_json["data"]["storePath"]
# 打印提取的字段
print("Cloud Name:", cloud_name)
print("Access Key ID:", access_key_id)
print("access_key_secret:", access_key_secret)
print("Session Token:", session_token)
print("cloudBucketName:", cloudBucketName)
print("Callback Param:", callback_param)
print("Store Path:", store_path)
2 上传素材
数据需要上传到阿里云,中国的一个网址,其他地区的一个网址,因此需要安装对应的Python包。
pip install oss2
素材通过 Bucket和 store_path 建立与第一步的联系。
安装完毕后,设置本地目录,以及上一步Token中获取的值,然后将数据上传到对应的文件夹中。
#上传素材
import os
import oss2
endpoint = 'http://oss-cn-hangzhou.aliyuncs.com'
# 配置阿里云 OSS 访问信息
access_key_id = 'S'
access_key_secret = '2'
bucket_name = cloudBucketName
token=session_token
store_path = store_path
store_root_path = store_path[:store_path.rfind('/{fileName}')]
# 创建 STS 访问凭证
sts_auth = oss2.StsAuth(access_key_id, access_key_secret, token)
# 创建 OSS 客户端
bucket = oss2.Bucket(sts_auth, endpoint, bucket_name)
bucket.timeout = 600
# 本地文件夹路径
local_folder = r'M:\WT\DATA\DJI3_huailai'
# 遍历本地文件夹
uploaded_files = []
for root, dirs, files in os.walk(local_folder):
for file_name in files:
local_file_path = os.path.join(root, file_name)
relative_path = os.path.relpath(os.path.join(root, file_name), local_folder)
oss_file_path = os.path.join(store_root_path, relative_path)
oss_file_path = oss_file_path.replace("\\", "/")
put_result = bucket.put_object_from_file(oss_file_path, local_file_path)
etag = put_result.etag
# 保存结果的 etag,需要在 `关联文件` 步骤里用到
print(f"Uploaded: {local_file_path} -> {oss_file_path}, etag: {etag}")
container_file_path = relative_path.replace("\\", "/")
uploaded_files.append({"name": container_file_path, "etag": etag, "checksum": etag})
if uploaded_files:
import json
with open("./uploaded_files.json", "w") as f:
json.dump(uploaded_files, f)
上传后,会在本地目录生成一个文件 uploaded_files.json,保存着文件名和etag。
3 创建资源
返回值中的 resource uuid 将在后续使用。
#创建resource
import hmac
import hashlib
import base64
import requests
from datetime import datetime
import pytz
# DJI APP Key 和 Secret Key
DJI_APP_KEY="B"
DJI_SECRET_KEY="q"
# API 请求参数
HOST = "https://openapi-cn.dji.com"
URI = "/terra-rescon-be/v2/resources"
HTTP_METHOD = "POST"
# 请求 Body (payload)
payload = '{"name": "test_resource", "type": "map"}'
def calculate_signature(content, secret_key):
"""
计算 HMAC-SHA256 签名
"""
signature = hmac.new(
secret_key.encode('utf-8'),
content.encode('utf-8'),
hashlib.sha256
).digest()
return base64.b64encode(signature).decode('utf-8')
def generate_digest(payload):
"""
生成请求 Body 的 SHA-256 Digest
"""
digest = base64.b64encode(hashlib.sha256(payload.encode('utf-8')).digest()).decode('utf-8')
return digest
# 当前 UTC 时间
utc_now = datetime.now(pytz.utc)
x_date = utc_now.strftime("%a, %d %b %Y %H:%M:%S GMT")
# HTTP 方法小写化
lower_method = HTTP_METHOD.lower()
# 生成 Digest
digest = generate_digest(payload)
# 构造签名内容
signature_content = f"date: {x_date}\n@request-target: {lower_method} {URI}\ndigest: SHA-256={digest}"
request_signature = calculate_signature(signature_content, DJI_SECRET_KEY)
# 完整请求 URL
url = f"{HOST}{URI}"
# 构造请求头
headers = {
"Date": x_date,
"Digest": f"SHA-256={digest}",
"Authorization": f'hmac username="{DJI_APP_KEY}", algorithm="hmac-sha256", headers="date @request-target digest", signature="{request_signature}"',
"Content-Type": "application/json;charset=UTF-8"
}
# 发送 POST 请求
response = requests.post(url, headers=headers, data=payload, verify=False) # 设置 verify=False 忽略 SSL 证书验证
# 输出结果
print("请求 URL:", url)
print("请求头:", headers)
print("请求 Body:", payload)
print("响应状态码:", response.status_code)
print("响应内容:", response.text)
解析response.text,获取resource uuid
response_json = json.loads(response.text)
# 提取指定字段
resource_uuid = response_json["data"]["uuid"]
# 打印提取的字段
print("resource_uuid:", resource_uuid)
4 关联文件
将上传的素材和资源关联。
关系:资源的UUID与Token的callback_param
#关联文件
import base64
import hashlib
import hmac
import json
import time
import requests
dji_app_key = DJI_APP_KEY
dji_secret_key = DJI_SECRET_KEY.encode('utf-8')
uri = "/terra-rescon-be/v2/store/upload_callback"
resource_uuid = "7"
callback_param = "ek1"
host = "https://openapi-cn.dji.com"
url = host + uri
method = "POST"
def bind_batch_files(files):
payload = {
"resourceUUID": resource_uuid,
"callbackParam": callback_param,
"files": files,
}
digest = (
lambda x: base64.b64encode(hashlib.sha256(x.encode("utf-8")).digest()).decode(
"utf-8"
)
)(json.dumps(payload))
gmt_time = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(time.time()))
signing_string = f"date: {gmt_time}\n@request-target: {method.lower()} {uri}\ndigest: SHA-256={digest}"
signature = (
lambda secret, x: base64.b64encode(
hmac.new(secret, x.encode("utf-8"), hashlib.sha256).digest()
).decode("utf-8")
)(dji_secret_key, signing_string)
headers = {
"Content-Type": "application/json",
"Date": gmt_time,
"Digest": f"SHA-256={digest}",
"Authorization": f'hmac username="{dji_app_key}", algorithm="hmac-sha256", headers="date @request-target digest", signature="{signature}"',
}
r = requests.post(url, json=payload, headers=headers)
print(r.request.headers)
print(r.content)
print(r.headers)
if __name__ == "__main__":
with open("./uploaded_files.json", "r") as f:
uploaded_files = json.load(f)
mini_batch = []
for item in uploaded_files:
mini_batch.append(item)
if len(mini_batch) >= 50:
bind_batch_files(mini_batch)
mini_batch = []
if mini_batch:
bind_batch_files(mini_batch)
5 创建JOB
返回值中的 job uuid 将在将在后续使用。
import hmac
import hashlib
import base64
import requests
from datetime import datetime
import pytz
# DJI APP Key 和 Secret Key
# API 请求参数
HOST = "https://openapi-cn.dji.com"
URI = "/terra-rescon-be/v2/jobs"
HTTP_METHOD = "POST"
# 请求 Body (payload)
payload = '{"name": "test_name"}'
def calculate_signature(content, secret_key):
"""
计算 HMAC-SHA256 签名
"""
signature = hmac.new(
secret_key.encode('utf-8'),
content.encode('utf-8'),
hashlib.sha256
).digest()
return base64.b64encode(signature).decode('utf-8')
def generate_digest(payload):
"""
生成请求 Body 的 SHA-256 Digest
"""
digest = base64.b64encode(hashlib.sha256(payload.encode('utf-8')).digest()).decode('utf-8')
return digest
# 当前 UTC 时间
utc_now = datetime.now(pytz.utc)
x_date = utc_now.strftime("%a, %d %b %Y %H:%M:%S GMT")
# HTTP 方法小写化
lower_method = HTTP_METHOD.lower()
# 生成 Digest
digest = generate_digest(payload)
# 构造签名内容
signature_content = f"date: {x_date}\n@request-target: {lower_method} {URI}\ndigest: SHA-256={digest}"
request_signature = calculate_signature(signature_content, DJI_SECRET_KEY)
# 完整请求 URL
url = f"{HOST}{URI}"
# 构造请求头
headers = {
"Date": x_date,
"Digest": f"SHA-256={digest}",
"Authorization": f'hmac username="{DJI_APP_KEY}", algorithm="hmac-sha256", headers="date @request-target digest", signature="{request_signature}"',
"Content-Type": "application/json;charset=UTF-8"
}
# 发送 POST 请求
response = requests.post(url, headers=headers, data=payload, verify=False) # 设置 verify=False 忽略 SSL 证书验证
# 输出结果
print("请求 URL:", url)
print("请求头:", headers)
print("请求 Body:", payload)
print("响应状态码:", response.status_code)
print("响应内容:", response.text)
解析返回的内容,获取 job uuid
response_json = json.loads(response.text)
# 提取指定字段
job_uuid = response_json["data"]["uuid"]
# 打印提取的字段
print("job_uuid:", job_uuid)
6 启动 2D JOB
将JOB UUID 与 Resource UUID建立关联,启动JOB进行运算
import hmac
import hashlib
import base64
import requests
from datetime import datetime
import pytz
# DJI APP Key 和 Secret Key
# API 请求参数
HOST = "https://openapi-cn.dji.com"
URI = f"/terra-rescon-be/v2/jobs/{job_uuid}/start"
HTTP_METHOD = "POST"
# 请求 Body (payload)
payload = f"""
{{
"type": 14,
"resourceUuid": "{resource_uuid}",
"parameters": "{{\\"parameter\\":{{\\"map_mode\\":1,\\"quality_level\\":1,\\"output_geo_desc\\":{{\\"cs_type\\":\\"GEO_CS\\",\\"geo_cs\\":\\"EPSG:32650\\",\\"geo_cs_wkt\\":\\"\\",\\"override_vertical_cs\\":\\"\\"}}}}}}"
}}
"""
def calculate_signature(content, secret_key):
"""
计算 HMAC-SHA256 签名
"""
signature = hmac.new(
secret_key.encode('utf-8'),
content.encode('utf-8'),
hashlib.sha256
).digest()
return base64.b64encode(signature).decode('utf-8')
def generate_digest(payload):
"""
生成请求 Body 的 SHA-256 Digest
"""
digest = base64.b64encode(hashlib.sha256(payload.encode('utf-8')).digest()).decode('utf-8')
return digest
# 当前 UTC 时间
utc_now = datetime.now(pytz.utc)
x_date = utc_now.strftime("%a, %d %b %Y %H:%M:%S GMT")
# HTTP 方法小写化
lower_method = HTTP_METHOD.lower()
# 生成 Digest
digest = generate_digest(payload)
# 构造签名内容
signature_content = f"date: {x_date}\n@request-target: {lower_method} {URI}\ndigest: SHA-256={digest}"
request_signature = calculate_signature(signature_content, DJI_SECRET_KEY)
# 完整请求 URL
url = f"{HOST}{URI}"
# 构造请求头
headers = {
"Date": x_date,
"Digest": f"SHA-256={digest}",
"Authorization": f'hmac username="{DJI_APP_KEY}", algorithm="hmac-sha256", headers="date @request-target digest", signature="{request_signature}"',
"Content-Type": "application/json;charset=UTF-8"
}
# 发送 POST 请求
response = requests.post(url, headers=headers, data=payload, verify=False) # 设置 verify=False 忽略 SSL 证书验证
# 输出结果
print("请求 URL:", url)
print("请求头:", headers)
print("请求 Body:", payload)
print("响应状态码:", response.status_code)
print("响应内容:", response.text)
7 查询JOB状态
根据JOB UUID 查询JOB是否运算完成,运算完成会返回 结果资源的UUID(outputResourceUuid)
import hmac
import hashlib
import base64
import requests
from datetime import datetime
import pytz
# DJI APP Key 和 Secret Key
DJI_APP_KEY="B"
DJI_SECRET_KEY="q"
# API 请求参数
HOST = "https://openapi-cn.dji.com"
URI = f"/terra-rescon-be/v2/jobs/{job_uuid}"
HTTP_METHOD = "GET"
def calculate_signature(content, secret_key):
"""
计算 HMAC-SHA256 签名
"""
signature = hmac.new(
secret_key.encode('utf-8'),
content.encode('utf-8'),
hashlib.sha256
).digest()
return base64.b64encode(signature).decode('utf-8')
def generate_digest(payload=""):
"""
生成请求 Body 的 SHA-256 Digest (GET 请求通常没有 payload)
"""
digest = base64.b64encode(hashlib.sha256(payload.encode('utf-8')).digest()).decode('utf-8')
return digest
# 当前 UTC 时间
utc_now = datetime.now(pytz.utc)
x_date = utc_now.strftime("%a, %d %b %Y %H:%M:%S GMT")
# HTTP 方法小写化
lower_method = HTTP_METHOD.lower()
# 生成 Digest (GET 请求没有 payload)
payload = ""
digest = generate_digest(payload)
# 构造签名内容
signature_content = f"date: {x_date}\n@request-target: {lower_method} {URI}\ndigest: SHA-256={digest}"
request_signature = calculate_signature(signature_content, DJI_SECRET_KEY)
# 完整请求 URL
url = f"{HOST}{URI}"
# 构造请求头
headers = {
"Date": x_date,
"Digest": f"SHA-256={digest}",
"Authorization": f'hmac username="{DJI_APP_KEY}", algorithm="hmac-sha256", headers="date @request-target digest", signature="{request_signature}"',
"Content-Type": "application/json;charset=UTF-8"
}
# 发送 GET 请求
response = requests.get(url, headers=headers, verify=False) # 设置 verify=False 忽略 SSL 证书验证
# 输出结果
print("请求 URL:", url)
print("请求头:", headers)
print("响应状态码:", response.status_code)
print("响应内容:", response.text)
解析response.text,获得outputResourceUuid
response_json = json.loads(response.text)
# 提取指定字段
output_resource_uuid = response_json["data"]["outputResourceUuid"]
# 打印提取的字段
print("output_resource_uuid:", output_resource_uuid)
8 获取结果文件列表
根据output_resource_uuid 获取 结果文件列表files_uuid
import hmac
import hashlib
import base64
import requests
from datetime import datetime
import pytz
# DJI APP Key 和 Secret Key
# API 请求参数
HOST = "https://openapi-cn.dji.com"
URI = f"/terra-rescon-be/v2/resources/{output_resource_uuid}"
HTTP_METHOD = "GET"
def calculate_signature(content, secret_key):
"""
计算 HMAC-SHA256 签名
"""
signature = hmac.new(
secret_key.encode('utf-8'),
content.encode('utf-8'),
hashlib.sha256
).digest()
return base64.b64encode(signature).decode('utf-8')
def generate_digest(payload=""):
"""
生成请求 Body 的 SHA-256 Digest (GET 请求通常没有 payload)
"""
digest = base64.b64encode(hashlib.sha256(payload.encode('utf-8')).digest()).decode('utf-8')
return digest
# 当前 UTC 时间
utc_now = datetime.now(pytz.utc)
x_date = utc_now.strftime("%a, %d %b %Y %H:%M:%S GMT")
# HTTP 方法小写化
lower_method = HTTP_METHOD.lower()
# 生成 Digest (GET 请求没有 payload)
payload = ""
digest = generate_digest(payload)
# 构造签名内容
signature_content = f"date: {x_date}\n@request-target: {lower_method} {URI}\ndigest: SHA-256={digest}"
request_signature = calculate_signature(signature_content, DJI_SECRET_KEY)
# 完整请求 URL
url = f"{HOST}{URI}"
# 构造请求头
headers = {
"Date": x_date,
"Digest": f"SHA-256={digest}",
"Authorization": f'hmac username="{DJI_APP_KEY}", algorithm="hmac-sha256", headers="date @request-target digest", signature="{request_signature}"',
"Content-Type": "application/json;charset=UTF-8"
}
# 发送 GET 请求
response = requests.get(url, headers=headers, verify=False) # 设置 verify=False 忽略 SSL 证书验证
# 输出结果
print("请求 URL:", url)
print("请求头:", headers)
print("响应状态码:", response.status_code)
print("响应内容:", response.text)
解析 response.text 获得files_uuid
response_json = json.loads(response.text)
# 提取指定字段
files_uuid = response_json["data"]["fileUuids"]
# 打印提取的字段
print("files_uuid:", files_uuid)
9 下载结果文件
遍历files_uuid的文件列表,下载到本地的download_path路径。
import hmac
import hashlib
import base64
import requests
from datetime import datetime
import pytz
import os
import urllib.parse
# DJI APP Key 和 Secret Key
# API 主机地址
HOST = "https://openapi-cn.dji.com"
# 文件保存路径
download_path = "./downloaded_files2"
os.makedirs(download_path, exist_ok=True)
# UUID 列表(替换为实际的 UUID 列表)
file_uuids = files_uuid
def calculate_signature(content, secret_key):
"""
计算 HMAC-SHA256 签名
"""
signature = hmac.new(
secret_key.encode('utf-8'),
content.encode('utf-8'),
hashlib.sha256
).digest()
return base64.b64encode(signature).decode('utf-8')
def get_file_url(uuid):
"""
获取文件的 URL
"""
URI = f"/terra-rescon-be/v2/files/{uuid}"
HTTP_METHOD = "GET"
# 当前 UTC 时间
utc_now = datetime.now(pytz.utc)
x_date = utc_now.strftime("%a, %d %b %Y %H:%M:%S GMT")
# 构造 lowercased HTTP method
lower_method = HTTP_METHOD.lower()
# 构造 Digest 值
payload = "" # GET 请求没有 body,因此 payload 为空
digest = base64.b64encode(hashlib.sha256(payload.encode('utf-8')).digest()).decode('utf-8')
# 构造签名内容
signature_content = f"date: {x_date}\n@request-target: {lower_method} {URI}\ndigest: SHA-256={digest}"
# 计算签名
request_signature = calculate_signature(signature_content, DJI_SECRET_KEY)
# 完整请求 URL
url = f"{HOST}{URI}"
# 构造请求头
headers = {
"Date": x_date,
"Digest": f"SHA-256={digest}",
"Authorization": f'hmac username="{DJI_APP_KEY}", algorithm="hmac-sha256", headers="date @request-target digest", signature="{request_signature}"',
"Content-Type": "application/json;charset=UTF-8"
}
# 发送 GET 请求
response = requests.get(url, headers=headers, verify=False) # 设置 verify=False 忽略 SSL 证书验证
if response.status_code == 200:
# 提取返回 JSON 中的 URL 字段
response_data = response.json()
url = response_data.get("data", {}).get("url") # 提取 JSON 中的 "url" 字段
if url:
return url
else:
print(f"未找到 URL (UUID: {uuid}),响应数据: {response_data}")
return None
else:
print(f"请求失败 (UUID: {uuid}),HTTP 状态码: {response.status_code}")
print("响应内容:", response.text)
return None
def extract_path_from_url(url):
"""
从 URL 中提取文件夹和文件名信息
"""
# 解码 URL 中的路径部分
parsed_url = urllib.parse.urlparse(url)
path = urllib.parse.unquote(parsed_url.path)
# 去掉 OSS 中 bucket 名称的前缀(例如 `/74c50efc-...`)
path_parts = path.split("/", 2)
if len(path_parts) > 2:
relative_path = path_parts[2] # 从第三部分开始是相对路径
else:
relative_path = path
return relative_path
def download_file(file_url, root_path):
"""
根据文件路径信息创建嵌套文件夹并下载文件
"""
try:
# 从 URL 提取文件相对路径(包括文件夹和文件名)
relative_path = extract_path_from_url(file_url)
full_path = os.path.join(root_path, relative_path)
# 创建嵌套文件夹
os.makedirs(os.path.dirname(full_path), exist_ok=True)
# 下载文件
response = requests.get(file_url, stream=True)
response.raise_for_status() # 检查 HTTP 请求是否成功
# 保存文件
with open(full_path, "wb") as file:
for chunk in response.iter_content(chunk_size=1024):
file.write(chunk)
print(f"文件已成功下载: {full_path}")
except requests.RequestException as e:
print(f"下载文件时发生错误 (URL: {file_url}): {e}")
# 遍历 UUID 列表,获取文件 URL 并下载
for uuid in file_uuids:
print(f"正在处理文件 UUID: {uuid}")
file_url = get_file_url(uuid) # 获取文件 URL
if file_url:
print(f"获取到的文件 URL: {file_url}")
download_file(file_url, download_path) # 下载文件并保存到嵌套文件夹
else:
print(f"未能获取文件 URL: {uuid}")
引用文献:
[1] 大疆智图API