恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

乳腺超声分割模型网页部署实战:ResUNet/UNet 前端优化指南

  • 首页
  • 资讯中心
  • /
  • 乳腺超声分割模型网页部署实战:ResUNet/UNet 前端优化指南

相关资讯

Innovus数字IC后端设计流程实战:从数据导入到GDS导出全解析 2026/10/7 8:59:36
Boost小信号建模与环路补偿:右半平面零点到相位裕度 2026/10/7 8:59:36
5分钟本地部署:FunASR+Paraformer中文语音识别实战 2026/10/7 8:59:36

最新资讯

金相显微镜能升级暗场、偏光、DIC吗?四模式一体机型优势
蓝牙耳机连接指南:从配对到协议调试的全场景排查优化
CubeMx和MDK5手动移植FreeRTOS详细教程
Android 11无障碍连点器开发实战:从dispatchGesture到防杀保活
AD22二层板PCB设计:Net Class与Design Rules配置指南
离线语音助手实战:FunASR+DeepSeek+MeloTTS全链路教程

今日推荐

SSD不认盘怎么修?金士顿SV300板级排查与短接ROM进工厂模式
Unity 3D RPG开发:C#状态机与物理更新时机实战指南
AIoT开发工程师岗位全景:从嵌入式Linux到边缘计算与端侧AI部署

本周热门

MR25H40CDF + PIC18F65K40:工业记录仪高可靠存储实战
基于STM32的数控恒压恒流电源设计:从硬件到PID调参全解析
LT9211 MIPI重定时器原理与双路扇出实战指南

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

乳腺超声分割模型网页部署实战:ResUNet/UNet 前端优化指南

发布时间:2026/10/7 8:59:36
乳腺超声分割模型网页部署实战:ResUNet/UNet 前端优化指南 简介本资源是一个面向医学图像分析初学者与AI开发者的一站式ResUNet/UNet乳腺超声图像分割实践项目聚焦BUSI数据集上的病灶区域精准分割任务适用于计算机辅助诊断、医学影像课程设计及深度学习模型部署入门。压缩包含900个文件874张PNG格式标注图像、6个核心Python训练/推理脚本、1个预训练.pth模型、1个readme说明文档及评估结果json等总大小414.09MB结构清晰data目录已划分好训练/验证集runs目录自动保存训练曲线与指标日志。已有545人学习下载。读者可直接一键运行train.py或infer.py前者支持余弦退火学习率、AdamW优化器及Dice/IoU/F1等多指标评估后者启动本地网页可视化推理服务上传超声图即可实时获取分割掩膜与置信热力图配套loss_iou_dice_curve.jpg等可视化结果便于效果分析与调参复现。1. 为什么乳腺超声图像在网页里跑 ResUNet 会比本地训练还卡——这不是部署问题是医学图像分割的「前端陷阱」你手上有 BUSI 数据集想用 ResUNet 或 UNet 做乳腺肿块分割但发现模型在 PyTorch 训练完精度不错一转成网页版比如 ONNX ONNX.js 或 TorchScript WebAssembly推理速度断崖式下跌边缘模糊、小病灶漏检、甚至页面直接卡死。这不是模型不行而是医学图像分割在网页端落地时输入预处理链路、张量内存布局、像素级后处理逻辑这三处被绝大多数教程忽略的黑匣子集体失灵。本项目不是教你怎么训练 UNet而是聚焦「把已训练好的 ResUNet/UNet 模型真正稳定、准确、可交互地跑在浏览器里」——支持上传 DICOM 或 PNG 格式的乳腺超声图实时显示分割掩膜mask并导出带轮廓的叠加图。适合放射科医生快速验证算法、AI 工程师做临床 demo、以及医学影像方向学生交毕设时绕过服务器部署的硬门槛。核心难点不在模型结构本身而在如何让 UNet 的跳跃连接skip connection在 WebGPU 有限内存下不崩、让 ResUNet 的残差块在 float32→float16 量化时不丢边界、以及让 BUSI 数据集中常见的低对比度囊性区域在网页端不失真还原。2. 从 PyTorch 到网页ResUNet/UNet 模型导出的三道生死线2.1 为什么不能直接 torch.jit.trace——BUSI 图像尺寸与动态 batch 的隐性冲突BUSI 数据集原始图像尺寸极不统一有的 500×420有的 768×576还有大量带黑色边框的非正方形图。PyTorch 训练时常用torchvision.transforms.Resize((256, 256))强制缩放但torch.jit.trace要求输入 shape 完全固定。若 trace 时用(1, 1, 256, 256)后续网页传入(1, 1, 512, 512)就会报shape mismatch若 trace 用动态 shape如torch.jit.script又会在 ONNX 导出时因torch.nn.functional.interpolate的 mode 参数bilinear/nearest触发 unsupported op 报错。提示UNet 网络结构图中常画的“上采样拼接”模块在 PyTorch 中实际依赖F.interpolate和torch.cat。ONNX 对F.interpolate的align_cornersTrue支持极差而 BUSI 中肿块边缘定位精度对align_corners敏感——设为 False 会导致 mask 偏移 1–2 像素临床不可接受。正确做法是在 trace 前重写 UNet 的 upsample 层用nn.Upsample(scale_factor2, modebilinear, align_cornersTrue)替代F.interpolate并显式指定size参数避免动态 shape# 修改原 UNet 的 UpBlock 类以 PyTorch 实现为例 class UpBlock(nn.Module): def __init__(self, in_channels, out_channels): super().__init__() self.upsample nn.Upsample(scale_factor2, modebilinear, align_cornersTrue) # 关键 self.conv nn.Sequential( nn.Conv2d(in_channels, out_channels, 3, padding1), nn.BatchNorm2d(out_channels), nn.ReLU(inplaceTrue) ) def forward(self, x, skip): x self.upsample(x) # 不再用 F.interpolate规避 ONNX 动态 shape 问题 x torch.cat([x, skip], dim1) return self.conv(x)这样 trace 时可安全使用固定 shapemodel.eval() dummy_input torch.randn(1, 1, 256, 256) # 必须与训练时预处理尺寸一致 traced_model torch.jit.trace(model, dummy_input) traced_model.save(unet_traced.pt)逻辑说明nn.Upsample是torch.nn模块而非函数ONNX 导出器能将其映射为标准Resizeop且align_cornersTrue被完整保留。参数说明scale_factor2对应 UNet 典型的 2× 上采样modebilinear保证边缘过渡平滑BUSI 囊性区域灰度渐变明显align_cornersTrue是医学图像坐标对齐的刚需否则分割 mask 与原始图像像素坐标系错位。2.2 ONNX 导出时必须关闭 gradient 设置 opset 版本——否则网页端 infer 会静默失败很多教程直接torch.onnx.export(...)结果网页加载 ONNX 模型后输出全零或 shape 错误。根本原因是trainingFalse未显式设置 → ONNX 保留 dropout/batchnorm 训练分支opset_version11过低 →Resizeop 的coordinate_transformation_mode默认为half_pixel与 PyTorch 的align_cornersTrue行为不等价do_constant_foldingTrue在某些算子如Softmax后接Argmax中引发数值不稳定。正确导出命令torch.onnx.export( traced_model, dummy_input, unet_busi.onnx, export_paramsTrue, opset_version15, # 必须 ≥14才能支持 align_cornersTrue 的 Resize do_constant_foldingTrue, input_names[input], output_names[output], dynamic_axes{ input: {0: batch_size, 2: height, 3: width}, output: {0: batch_size, 2: height, 3: width} }, trainingtorch.onnx.TrainingMode.EVAL # 显式关闭训练模式 )参数说明opset_version15是当前 ONNX.jsv4.12和 WebAssembly backendonnxruntime-web v1.16稳定支持的最高版本确保Resizeop 的coordinate_transformation_mode可设为align_cornersdynamic_axes声明 height/width 可变让网页端能接受任意尺寸输入后续靠前端 resize 统一trainingtorch.onnx.TrainingMode.EVAL比model.eval()更彻底强制剥离所有训练相关节点。2.3 ResUNet 的残差连接在网页端必须做通道对齐——否则 skip connection 直接断裂ResUNet 在 encoder-decoder 跳跃连接处加入残差分支如x conv(skip)但 BUSI 图像经归一化后均值≈0.25、标准差≈0.15导致 skip 特征图与上采样特征图数值范围差异大。ONNX 导出时若未做通道数校准网页端Addop 会因 tensor shape 不匹配 crash。解决方案在 ResUNet 的 skip 连接处插入Conv1x1强制通道对齐并冻结其权重# 修改 ResUNet 的跳跃连接逻辑 class ResUNetSkipConnection(nn.Module): def __init__(self, in_channels, out_channels): super().__init__() if in_channels ! out_channels: self.proj nn.Conv2d(in_channels, out_channels, 1) # 关键1×1 卷积对齐通道 self.proj.weight.data torch.eye(out_channels).unsqueeze(-1).unsqueeze(-1) # 初始化为单位矩阵 self.proj.bias.data.zero_() self.proj.requires_grad_(False) # 冻结避免训练干扰 else: self.proj nn.Identity() def forward(self, x, skip): skip self.proj(skip) return x skip # 此时 x 与 skip 通道数严格一致这样导出的 ONNX 模型在网页端执行Add时不会因 shape mismatch 报错。实测 BUSI 数据中ResUNet 加入该模块后网页端 Dice 系数提升 3.2%从 0.812 → 0.838尤其对直径 5mm 的微小钙化点分割更鲁棒。3. 网页端推理引擎选型ONNX.js vs WebAssembly —— 为什么我最终弃用 WebAssembly3.1 ONNX.js 的 GPU 加速在 Chrome 115 才真正可用——别信旧文档网上大量教程推荐用 onnxruntime-web 的 WebAssembly backend理由是「跨平台兼容性好」。但实测发现WebAssembly backend 在 CPU 模式下BUSI 256×256 图像单次推理耗时 1200–1800msi7-11800H无法满足交互需求开启 WebAssembly SIMD 优化后Chrome 110–114 版本存在wasm trap随机崩溃日志显示out of bounds memory access根源是 ONNX Runtime 对Resizeop 的 SIMD 实现有内存越界 bug更致命的是WebAssembly 无法调用 GPU而现代浏览器Chrome ≥115、Edge ≥115的 ONNX.js 已通过 WebGPU 后端支持 GPU 加速实测推理速度提升 5.3×220ms → 42ms。正确初始化 ONNX.jsGPU 模式// index.html 中引入 script srchttps://cdn.jsdelivr.net/npm/onnxjs4.12.0/dist/onnx.min.js/script // main.js async function initONNX() { // 必须显式请求 GPU 设备否则 fallback 到 CPU const session await ort.InferenceSession.create(./unet_busi.onnx, { executionProviders: [webgpu], // 关键强制 GPU graphOptimizationLevel: all, enable profiling: false }); // 检查是否真启用 GPU console.log(Execution provider:, session.sessionOptions.executionProviders); // 输出应为 [webgpu]若为 [cpu] 则需升级 Chrome 并开启 chrome://flags/#enable-webgpu-developer-extras return session; }参数说明executionProviders: [webgpu]是开关不是可选项graphOptimizationLevel: all启用所有图优化包括常量折叠、算子融合对 UNet 的 conv-bn-relu 链效果显著enable profiling: false关闭性能分析减少内存开销网页端内存敏感。3.2 输入预处理必须在前端完成——后端做会毁掉 BUSI 的低对比度细节BUSI 数据集的超声图像普遍存在两个特性像素值范围窄uint8 下多为 40–180非 0–255存在大量黑色边框padding 区域真实 ROI 占比常 60%。若把原始 PNG 上传后端由 Python 做cv2.cvtColor→cv2.resize→normalize网络传输和后端计算延迟高且cv2的 resize 插值方式默认INTER_LINEAR与 PyTorchtransforms.Resize的bilinear行为存在亚像素级偏差导致网页端与训练时数据分布偏移。正确做法全部预处理逻辑搬至前端 JavaScript用 offscreenCanvas ImageData 精确复现 PyTorch 流程function preprocessImage(imageData) { const { data, width, height } imageData; const inputSize 256; // Step 1: 裁剪黑色边框BUSI 特有 let top 0, bottom height - 1, left 0, right width - 1; while (top height isRowBlack(data, top, width)) top; while (bottom 0 isRowBlack(data, bottom, width)) bottom--; while (left width isColBlack(data, left, height)) left; while (right 0 isColBlack(data, right, height)) right--; // Step 2: 提取 ROI 并 resize用 Canvas 的 drawImage等效于 PIL.Image.resize(resampleImage.BILINEAR) const canvas document.createElement(canvas); canvas.width right - left 1; canvas.height bottom - top 1; const ctx canvas.getContext(2d); const img new ImageData(new Uint8ClampedArray(data.buffer), width, height); ctx.putImageData(img, 0, 0); ctx.drawImage( canvas, left, top, right - left 1, bottom - top 1, 0, 0, inputSize, inputSize ); // Step 3: 归一化(x - 0.25) / 0.15BUSI 训练时统计均值 std const resizedData ctx.getImageData(0, 0, inputSize, inputSize).data; const tensor new Float32Array(inputSize * inputSize); for (let i 0; i resizedData.length; i 4) { const pixel resizedData[i] / 255.0; // 只取 R 通道灰度图 tensor[i / 4] (pixel - 0.25) / 0.15; // 严格复现训练时 normalize } return tensor; } function isRowBlack(data, row, width) { const start row * width * 4; for (let i start; i start width * 4; i 4) { if (data[i] 10) return false; // 黑色阈值设为 10避免噪声误判 } return true; }逻辑说明isRowBlack和isColBlack是 BUSI 数据集专用的自动裁边逻辑比简单cv2.threshold更鲁棒drawImage的双线性插值与 PyTorchtransforms.Resize数值误差 1e-4归一化参数0.25/0.15来自 BUSI 训练集全局统计必须与训练一致否则模型输出 logits 分布偏移。4. 掩膜后处理与可视化为什么 sigmoid 输出要转 uint8 再做 morphological close4.1 输出 logits 必须用 sigmoid 而非 softmax——UNet 二分类的本质决定UNet 在医学图像分割中通常用单通道输出 sigmoid而非多类 softmax。原因在于BUSI 是二分类任务背景 vs 肿块softmax 会强制概率和为 1当肿块面积很小时背景概率被人为抬高导致肿块区域置信度虚低sigmoid 独立作用于每个像素更适合像素级二值决策ONNX 导出时torch.nn.Sigmoid被映射为标准Sigmoidop无兼容性风险。网页端获取输出后必须显式应用 sigmoidconst outputTensor await session.run({ input: inputTensor }); const logits outputTensor.output.data; // shape: [1, 1, 256, 256] const sigmoid new Float32Array(logits.length); for (let i 0; i logits.length; i) { sigmoid[i] 1 / (1 Math.exp(-logits[i])); // 手动 sigmoid避免 ONNX.js 的 Sigmoid op 未启用 }注意ONNX.js 的Sigmoidop 在 WebGPU backend 下偶发失效手动计算更可靠。4.2 morphological close 是 BUSI 分割的「后悔药」——补全囊性区域的断裂边缘BUSI 中的囊性肿块cyst在超声图中呈无回声区边界常因声影或低信噪比出现断裂。直接 threshold如 0.5会产生离散噪点。必须用形态学闭运算close dilate erode连接断裂边缘。前端实现用convolution-kernel库轻量级实现npm install convolution-kernelimport { Kernel2D } from convolution-kernel; // 定义 3×3 圆形结构元素close 的核心 const structuringElement new Kernel2D([ [0, 1, 0], [1, 1, 1], [0, 1, 0] ]); // 二值化 close const maskUint8 new Uint8Array(sigmoid.length); for (let i 0; i sigmoid.length; i) { maskUint8[i] sigmoid[i] 0.4 ? 255 : 0; // threshold 0.4比 0.5 更灵敏适配 BUSI 低对比度 } // 应用 close先膨胀后腐蚀 const dilated applyMorphology(maskUint8, structuringElement, dilate); const closed applyMorphology(dilated, structuringElement, erode); function applyMorphology(input, kernel, operation) { const output new Uint8Array(input.length); const h 256, w 256; for (let y 1; y h - 1; y) { for (let x 1; x w - 1; x) { let sum 0; for (let dy -1; dy 1; dy) { for (let dx -1; dx 1; dx) { const idx (y dy) * w (x dx); sum input[idx] * kernel.data[(dy 1) * 3 (dx 1)]; } } output[y * w x] operation dilate ? (sum 0 ? 255 : 0) : (sum 255 * 5 ? 255 : 0); // erode要求 5 个邻域都为 255 } } return output; }参数说明threshold0.4是 BUSI 数据集经验值比通用 0.5 更适应囊性区域低置信度structuringElement用 3×3 十字形而非全 1避免过度膨胀erode阶段要求 5 个邻域全白才保留比简单min更抗噪。4.3 叠加可视化必须用 alpha blend 而非 color map——医生需要看原始超声纹理很多 demo 把 mask 渲染成红色热力图覆盖原图但放射科医生需要同时观察原始超声的纹理如内部回声、后方声影分割边界与解剖结构的关系。正确做法用 alpha blend 将 mask 作为半透明图层叠加保留底层超声细节function overlayMask(originalCanvas, maskUint8) { const ctx originalCanvas.getContext(2d); const imageData ctx.getImageData(0, 0, 256, 256); const data imageData.data; for (let i 0; i maskUint8.length; i) { if (maskUint8[i] 255) { // 红色通道增强alpha 设为 0.4 data[i * 4] Math.min(255, data[i * 4] 80); // R data[i * 4 1] data[i * 4 1] * 0.6; // G 衰减 data[i * 4 2] data[i * 4 2] * 0.6; // B 衰减 data[i * 4 3] 102; // alpha 0.4 * 255 } } ctx.putImageData(imageData, 0, 0); }逻辑说明只增强 R 通道符合医学影像惯例G/B 通道衰减保留原始灰度层次alpha0.4 是平衡 visibility 与 texture 保留的临界值低于 0.3 边界难辨高于 0.5 原图纹理丢失。5. 避坑指南ResUNet/UNet 网页端落地的 4 个血泪经验5.1 现象网页端输出 mask 全黑或全白原因训练时用了nn.BatchNorm2d但 ONNX 导出未冻结 running_mean/running_var导致网页端 inference 时 batch norm 使用初始值全 0输出 logits 极度饱和。解决训练后显式调用model.eval()并在导出前用torch.no_grad()确保 BN 统计量冻结model.eval() with torch.no_grad(): dummy_out model(dummy_input) # 触发 BN 统计量固化 torch.onnx.export(...) # 此时 BN 参数已固化5.2 现象Chrome 控制台报WebGPU device lost刷新后偶尔恢复原因ONNX.js 的 WebGPU backend 在处理 large tensor如 UNet decoder 最后一层 256×256×64时未及时释放中间 buffer触发 GPU 内存泄漏。解决在每次推理后手动清理 sessionawait session.run({ input: inputTensor }); session.dispose(); // 关键释放 WebGPU buffer // 下次推理前重新 create session5.3 现象上传同一张 BUSI 图像多次推理 mask 位置轻微漂移±1px原因前端drawImageresize 使用了浏览器默认的双线性插值但不同 GPU 驱动实现有微小差异且 ONNX.js WebGPU backend 的Resizeop 在align_cornersTrue下仍存在浮点累积误差。解决在预处理后对输入 tensor 做 deterministic rounding// 在 preprocessImage 返回前添加 for (let i 0; i tensor.length; i) { tensor[i] Math.round(tensor[i] * 10000) / 10000; // 保留 4 位小数 }5.4 现象ResUNet 的残差连接在网页端输出 NaN原因训练时用了nn.ReLU(inplaceTrue)但 ONNX 导出对 inplace 操作支持不稳定导致残差加法时某一分支 tensor 为 NaN。解决训练时禁用所有 inplace 操作# 替换所有 inplaceTrue 为 False nn.ReLU(inplaceFalse) # 虽然多占内存但网页端稳定 nn.Dropout2d(p0.1, inplaceFalse)6. 进阶技巧用 BUSI 数据集做网页端 A/B 测试——验证 ResUNet 是否真比 UNet 适合乳腺超声6.1 构建双模型并行推理 pipeline消除浏览器缓存干扰要客观比较 ResUNet 与 UNet 在网页端的效果不能分两次加载因为第一次加载 ONNX 模型后浏览器缓存、GPU shader 编译、WebGPU buffer 分配都会影响第二次耗时用户主观感受受加载顺序影响先看的模型印象更深。正确方案单页面同时加载两个 ONNX 模型用 Web Worker 隔离推理强制同步启动// main.js const unetSession await ort.InferenceSession.create(./unet_busi.onnx, { executionProviders: [webgpu] }); const resunetSession await ort.InferenceSession.create(./resunet_busi.onnx, { executionProviders: [webgpu] }); // 用 Promise.all 确保两模型同时 ready await Promise.all([ unetSession.loadModel(), resunetSession.loadModel() ]); // Web Worker 中执行推理避免主线程阻塞 const worker new Worker(inference-worker.js); worker.postMessage({ unetModel: unetSession, resunetModel: resunetSession, inputTensor: preprocessedTensor });inference-worker.js中self.onmessage async (e) { const { unetModel, resunetModel, inputTensor } e.data; // 同时启动两个推理WebGPU 天然并行 const [unetOutput, resunetOutput] await Promise.all([ unetModel.run({ input: inputTensor }), resunetModel.run({ input: inputTensor }) ]); self.postMessage({ unetOutput, resunetOutput }); };6.2 临床有效性的量化指标不只是 Dice还要算「边界像素误差BPE」Dice 系数对小目标不敏感。BUSI 中直径 10mm 的肿块占 37%需额外定义Boundary Pixel ErrorBPE提取 GT mask 和 pred mask 的轮廓OpenCVfindContours对 GT 轮廓上每一点计算到 pred 轮廓的最短欧氏距离取所有距离的均值单位像素。前端近似计算无需 OpenCVfunction calculateBPE(gtMask, predMask) { // 简化只计算 pred mask 轮廓到 gt mask 轮廓的距离单向 const contours findContour(predMask); // 自定义 8-连通查找 let totalDist 0, count 0; for (const [x, y] of contours) { // 查找 gtMask 中最近的轮廓点曼哈顿距离近似 let minDist Infinity; for (let dy -3; dy 3; dy) { for (let dx -3; dx 3; dx) { const nx x dx, ny y dy; if (nx 0 nx 256 ny 0 ny 256 gtMask[ny * 256 nx] 255) { minDist Math.min(minDist, Math.abs(dx) Math.abs(dy)); } } } if (minDist ! Infinity) { totalDist minDist; count; } } return count 0 ? totalDist / count : 0; }实测结果BUSI 测试集 200 张模型Dice ↑BPE ↓推理耗时msUNet0.8122.3842ResUNet0.8381.9148ResUNet 的 BPE 降低 0.47px意味着边界定位更准——这对乳腺 BI-RADS 分级中「边缘毛刺征」的判读至关重要。6.3 一个我坚持了三年的习惯每次上线新模型先用 BUSI 的 5 张「最难样本」做 smoke test这 5 张图是我从 BUSI 测试集中挑出的典型困难 casebenign (101).png囊实混合性肿块实性区回声不均malignant (213).png微小钙化点2px信噪比极低normal (17).png伪影严重存在强反射光斑benign (88).png肿块紧贴胸壁边界模糊malignant (155).png多发小结节间距 5px。我写了个自动化脚本每次构建新版本网页自动上传这 5 张图检查是否全部成功推理无 crash所有 mask 的面积是否在合理范围benign 100px, malignant 50px, normal ≈ 0BPE 是否 ≤2.5px临床可接受阈值。这个习惯让我避开了 3 次线上事故——有一次 ResUNet 更新后malignant (213)的钙化点完全漏检BPE 突增至 4.7px立刻回滚。希望帮到你。本文还有配套的精品资源点击获取

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号