0c04bbbea3
BGfilter服务调用改为传url,减少内存占用 Reviewed-on: https://git.genarrative.world/git/GenarrativeAI/Genarrative/pulls/91 Reviewed-by: 段舒康 <kdletters@qq.com> Co-authored-by: Linghong <ink29535@proton.me> Co-committed-by: Linghong <ink29535@proton.me>
167 lines
5.7 KiB
Rust
167 lines
5.7 KiB
Rust
//! 通用抠图冒烟验证:本地图片 → AuthorizeFileUpload 临时对象 → SegmentCommonImage → 下载结果。
|
||
//!
|
||
//! 运行(在 server-rs 目录下):
|
||
//! cargo run -p platform-matting --example segment_smoke -- "C:\path\to\input.png"
|
||
//!
|
||
//! 依赖仓库根目录 .env / .env.local / .env.secrets.local 中的 AK/SK
|
||
//!(`GENARRATIVE_ALIYUN_MATTING_*` 或 `ALIBABA_CLOUD_ACCESS_KEY_*`);
|
||
//! 可选 `GENARRATIVE_ALIYUN_MATTING_ENDPOINT`。临时 OSS bucket/endpoint 由
|
||
//! AuthorizeFileUpload 动态下发,不依赖自有 OSS 配置。
|
||
|
||
use std::path::{Path, PathBuf};
|
||
|
||
use platform_matting::{
|
||
DEFAULT_IMAGESEG_ENDPOINT, MattingClient, MattingConfig, SegmentCommonImageRequest,
|
||
SegmentReturnForm,
|
||
};
|
||
|
||
fn load_env_files() {
|
||
// example 的工作目录通常是 server-rs,.env 都在仓库根目录。
|
||
for candidate in [
|
||
".env",
|
||
".env.local",
|
||
".env.secrets.local",
|
||
"../.env",
|
||
"../.env.local",
|
||
"../.env.secrets.local",
|
||
] {
|
||
if Path::new(candidate).exists() {
|
||
let _ = dotenvy::from_path_override(candidate);
|
||
}
|
||
}
|
||
}
|
||
|
||
#[tokio::main]
|
||
async fn main() {
|
||
load_env_files();
|
||
|
||
let input_path = std::env::args().nth(1).unwrap_or_else(|| {
|
||
eprintln!("用法:cargo run -p platform-matting --example segment_smoke -- <图片路径>");
|
||
std::process::exit(1);
|
||
});
|
||
let input_bytes = std::fs::read(&input_path)
|
||
.unwrap_or_else(|error| panic!("读取测试图片失败({input_path}):{error}"));
|
||
println!(
|
||
"[1/5] 已读取测试图片:{input_path}({} 字节)",
|
||
input_bytes.len()
|
||
);
|
||
|
||
// SegmentCommonImage 要求分辨率低于 2000x2000,超限先等比缩小。
|
||
const MAX_EDGE: u32 = 1999;
|
||
let decoded = image::load_from_memory(&input_bytes).expect("测试图片应可解码");
|
||
let input_bytes = if decoded.width() > MAX_EDGE || decoded.height() > MAX_EDGE {
|
||
let resized = decoded.resize(MAX_EDGE, MAX_EDGE, image::imageops::FilterType::CatmullRom);
|
||
let mut buffer = std::io::Cursor::new(Vec::new());
|
||
resized
|
||
.write_to(&mut buffer, image::ImageFormat::Png)
|
||
.expect("缩放后的图片应可编码为 PNG");
|
||
println!(
|
||
" 分辨率 {}x{} 超限,已缩放到 {}x{}",
|
||
decoded.width(),
|
||
decoded.height(),
|
||
resized.width(),
|
||
resized.height()
|
||
);
|
||
buffer.into_inner()
|
||
} else {
|
||
input_bytes
|
||
};
|
||
|
||
let http_client = reqwest::Client::new();
|
||
|
||
// --- 调用通用抠图 ---
|
||
// key 优先级与 api-server 配置保持一致:抠图专用 → 官方 SDK 标准命名。
|
||
let (matting_key_id, matting_key_secret) = [
|
||
(
|
||
"GENARRATIVE_ALIYUN_MATTING_ACCESS_KEY_ID",
|
||
"GENARRATIVE_ALIYUN_MATTING_ACCESS_KEY_SECRET",
|
||
),
|
||
(
|
||
"ALIBABA_CLOUD_ACCESS_KEY_ID",
|
||
"ALIBABA_CLOUD_ACCESS_KEY_SECRET",
|
||
),
|
||
]
|
||
.iter()
|
||
.find_map(|(id_name, secret_name)| {
|
||
let id = std::env::var(id_name).ok()?;
|
||
let secret = std::env::var(secret_name).ok()?;
|
||
if id.trim().is_empty() || secret.trim().is_empty() {
|
||
return None;
|
||
}
|
||
println!(" 使用 {id_name} 调用抠图服务");
|
||
Some((id, secret))
|
||
})
|
||
.expect("未找到可用的抠图 AccessKey 环境变量");
|
||
let matting_config = MattingConfig::new(
|
||
std::env::var("GENARRATIVE_ALIYUN_MATTING_ENDPOINT")
|
||
.unwrap_or_else(|_| DEFAULT_IMAGESEG_ENDPOINT.to_string()),
|
||
matting_key_id,
|
||
matting_key_secret,
|
||
)
|
||
.expect("抠图配置应有效");
|
||
let matting_client = MattingClient::new(matting_config).expect("抠图客户端应可构建");
|
||
|
||
// 非上海地域输入按新版官方 SDK 的 AdvanceRequest 口径申请单对象 Policy 后上传。
|
||
let temp_url = matting_client
|
||
.upload_temp_image(input_bytes, "segment-input.png", "image/png")
|
||
.await
|
||
.expect("上传 AuthorizeFileUpload 临时对象应成功");
|
||
println!("[2/5] 已上传 AuthorizeFileUpload 临时对象");
|
||
println!("[3/5] 输入图 URL host:{}", host_of(&temp_url));
|
||
|
||
let result = matting_client
|
||
.segment_common_image(SegmentCommonImageRequest {
|
||
image_url: temp_url,
|
||
return_form: Some(SegmentReturnForm::Crop),
|
||
})
|
||
.await;
|
||
let result = match result {
|
||
Ok(result) => result,
|
||
Err(error) => {
|
||
eprintln!("[4/5] 通用抠图调用失败:{error}");
|
||
std::process::exit(1);
|
||
}
|
||
};
|
||
println!(
|
||
"[4/5] 通用抠图成功,RequestId={}",
|
||
result.request_id.as_deref().unwrap_or("unknown")
|
||
);
|
||
|
||
// --- 下载结果 ---
|
||
let output_bytes = http_client
|
||
.get(&result.image_url)
|
||
.send()
|
||
.await
|
||
.expect("下载抠图结果应成功")
|
||
.error_for_status()
|
||
.expect("抠图结果 URL 应返回 200")
|
||
.bytes()
|
||
.await
|
||
.expect("读取抠图结果字节应成功");
|
||
|
||
let output_path = build_output_path(&input_path);
|
||
std::fs::write(&output_path, &output_bytes).expect("写出抠图结果应成功");
|
||
println!(
|
||
"[5/5] 抠图结果已保存:{}({} 字节)",
|
||
output_path.display(),
|
||
output_bytes.len()
|
||
);
|
||
}
|
||
|
||
fn build_output_path(input_path: &str) -> PathBuf {
|
||
let input = Path::new(input_path);
|
||
let stem = input
|
||
.file_stem()
|
||
.and_then(|value| value.to_str())
|
||
.unwrap_or("segment-output");
|
||
input.with_file_name(format!("{stem}-matting.png"))
|
||
}
|
||
|
||
fn host_of(url: &str) -> String {
|
||
url.split("//")
|
||
.nth(1)
|
||
.and_then(|rest| rest.split('/').next())
|
||
.unwrap_or("unknown")
|
||
.to_string()
|
||
}
|