☰
基于Python的卫星云图识别:从HDF5数据到UNet分割实战
2026/10/12 12:23:56 网站建设 项目流程

简介:基于Python的卫星云层图像识别系统设计与实现,是一套面向课程实践与期末项目的完整工程包,适合计算机视觉、遥感处理方向的初学者与高校学生;系统覆盖图像预处理、特征提取与分类识别全流程,配有友好图形界面,代码注释详尽,兼具实用性与教学参考价值。压缩包共168个文件,体积62.33MB,以70个Python源码、44个pyc文件为主,另含yaml配置、csv数据集、PDF实验报告、PPT课件及sh脚本、zbak备份等,目录分层清晰,便于按模块查阅。包内附有完整分析文档与实验报告,可支撑从图像处理到模型训练的全过程复现。目前已有43人学习浏览,用户可直接运行样例数据观察不同算法下的分类效果,并结合注释修改参数进行扩展实验,对理解机器学习在遥感图像识别中的应用很有帮助。

1. 基于Python的卫星云层图像识别:为什么说它不是普通图像分类

一提到“图像识别”,多数人脑子里是猫狗分类、车牌识别、人脸检测。而卫星云层图像识别完全不是这么回事——它面对的是黑背景上的白斑、模糊的纹理边缘、昼夜交替的灰度漂移,以及极不平衡的正负样本。做传统CV出身的人第一次拿到葵花8或FY-4的L1级数据,经常会被文件名里的HDF5、经纬度网格和定标系数直接劝退。这个方向能解决的实际问题却很明确:把气象卫星的原始辐射数据,自动转换成“哪块是云、哪块是地面/海面、云顶大概多高”的判读结果,服务于云量估算、降水辅助判识、光伏功率预测和航空航线天气辅助决策。

本文不是把你往“调一个现成分类模型跑通demo”的路上带,而是给出一个能从数据预处理一直走到模型训练和推理评估的完整方案。你会看到为什么卫星云图不能用普通RGB图片的思路硬套,也会拿到可直接改的Python代码和参数建议。这套系统适合有Python基础、想入遥感图像识别方向的人,也适合已经在做气象业务系统、需要把人工判云换成自动化流程的工程师。下面直接进入正题,先从最关键的数据层拆起。

2. 数据是第一道坎:卫星云图的三个来源与读取方式

2.1 HDF5/NetCDF不是图片:先搞懂波段和定标

卫星云图的原始数据通常以HDF5或NetCDF格式分发,常见来源是葵花8/9号(Himawari)的AHI传感器、FY-4A/4B的AGRI传感器,以及MODIS的L1/L2产品。这些文件里存的不是已经渲染好的JPEG,而是每个像元的辐射计数值,通常叫DN值(Digital Number)。DN值需要通过定标系数转换成反射率或亮温,然后才能用于后续的模型输入。很多人在这第一步就翻车,原因是直接用cv2.imread去读.h5文件,得到的自然是报错。

正确做法是用h5py或xarray读取。以葵花8的“真彩色”合成和红外通道为例,你要先搞清楚自己需要哪些波段:可见光通道(Band 3,约0.64微米)看白天云系纹理最直观,红外通道(Band 13,约10.4微米)反映云顶亮温,夜间只能靠红外。做云检测最少需要“可见光+红外”的组合,如果做云顶高度反演,还要再多加几个水汽通道。

import h5py import numpy as np # 以Himawari-8 AHI L1数据为例 with h5py.File('HS_H08_20240701_0200_B13.h5', 'r') as f: # 常见的HDF5内部路径:/Data/波段名,不同来源可能不同 dn = f['/Data/IR'] # 红外波段计数值 dn = np.array(dn) # 读取定标系数,通常在属性中 slope = f['/Data/IR'].attrs['Slope'] intercept = f['/Data/IR'].attrs['Intercept'] # 读取经纬度网格 lon = np.array(f['/Geolocation/Longitude']) lat = np.array(f['/Geolocation/Latitude']) # 定标:DN -> 辐射值 -> 亮温(此处仅示意,真实公式以官方文档为准) radiance = dn * slope + intercept

代码说明:slope和intercept是逐像素或全局定标系数,不同卫星产品差异很大,务必先打印attrs里的全部属性确认。经纬度网格一般是一次性读取、后续重复使用,不需要每次训练都读一遍。

参数说明:DN数组一般是uint16类型,做减法或除法后要转成float32,否则会出现整型溢出或截断。另外,HDF5的压缩块读取性能对你的预处理速度影响极大,建议第一次读取时把所需波段一次性读入内存,避免训练时反复I/O。常见的坑是“内存占用爆炸”——一个全圆盘观测的波段是5500×5500像素,两个波段就能占掉几百MB,加上经纬度网格就更多。解决办法是裁剪区域而不是全盘加载,后面会讲到。

2.2 制作云图数据集:区域裁剪、云掩膜与样本均衡

原始L1数据不能直接送进网络训练,因为它的几何范围覆盖整个圆盘,而我们真正关注的地面区域可能只是某个省份或某个风电场周边。另一个问题是标签从哪里来。如果你做的是云检测,标签可以来自官方云掩膜产品,比如Himawari的CLP产品,也可以用人工标注的粗标签替代。这里有一个非常重要的经验:在起步阶段,别太纠结标签的“像素级完美”,先用能拿到的粗糙标签跑通全流程,比花两周精标一个完美数据集更有价值。

import numpy as np from PIL import Image def crop_to_region(band_data, lon, lat, lon_min, lon_max, lat_min, lat_max): # 将经纬度范围内的像素裁剪出来 mask = (lon >= lon_min) & (lon <= lon_max) & (lat >= lat_min) & (lat <= lat_max) rows = np.any(mask, axis=1) cols = np.any(mask, axis=0) return band_data[np.ix_(rows, cols)], rows, cols # 以华中地区为例 region_band, row_idx, col_idx = crop_to_region( radiance, lon, lat, 108, 116, 28, 34 ) # 转成8位图用于可视化或作为模型输入 region_8bit = ((region_band - region_band.min()) / (region_band.max() - region_band.min()) * 255).astype(np.uint8) Image.fromarray(region_8bit).save('region_vis.png')

逻辑说明:先用经纬度布尔掩码找出目标区域内所有像素的行列索引,再做一次性切片。np.ix_的用法是初学者最容易忽略的细节——如果直接用布尔数组去索引二维数组,得到的结果可能是一维展平的,而不是矩形切片。

参数说明:lon_min/lon_max等经纬度范围要根据业务需求设置。区域越小,模型越好训,但泛化到其他区域时掉点越明显。建议一开始用相对大的区域(例如整个中国东部),后面再逐步收窄。如果你做的是全球尺度任务,可以直接用等经纬度投影网格重采样,但这里不展开。

样本均衡问题在云检测中非常突出:晴空像素总是占多数,云像素在不同季节、不同下垫面的占比差异极大。为了解决这个问题,我通常会在训练集构建时做两种处理:一是计算数据集的云/晴空像素比例,超过1:5就做负样本下采样;二是用加权损失函数而不是简单换数据集。下面代码中的一行是关键——随机裁剪到256×256,它既降低了内存压力,又相当于做了数据增强。

def make_training_tiles(vis_band, ir_band, label_mask, tile_size=256, stride=128): tiles = [] h, w = vis_band.shape for y in range(0, h - tile_size, stride): for x in range(0, w - tile_size, stride): vis_tile = vis_band[y:y+tile_size, x:x+tile_size] ir_tile = ir_band[y:y+tile_size, x:x+tile_size] lbl_tile = label_mask[y:y+tile_size, x:x+tile_size] # 过滤掉标签过于单一的块,比如全是云或全是晴空 if np.mean(lbl_tile) < 0.05 or np.mean(lbl_tile) > 0.95: continue tiles.append((vis_tile, ir_tile, lbl_tile)) return tiles

这里的stride设置为128,意味着相邻图块有50%重叠,这是一种滑窗裁剪的标准做法,能显著增加样本量。过滤“标签极纯”的块是为了防止模型学到“整块全云”或“整块全晴”的捷径,逼它学边缘细节。

3. 模型选型与训练:为什么UNet是云层分割的主流骨架

3.1 从FCN到UNet的选型理由

云层识别的本质是像素级分类,也就是语义分割,而不是整图分类。你可以用ResNet或EfficientNet做云图分类,判断“这片区域多云还是少云”,但业务上更常见的是要知道云聚集在哪个经纬度范围、边界在哪。所以模型骨架的选择主要围绕分割网络展开。FCN是最早的端到端分割模型,但它的上采样过于粗糙,对云边界预测不够精细;U-Net通过跳层连接把深层语义信息和浅层细节融合在一起,在医学图像和遥感图像上都表现稳定,是目前做云检测最常用的基线模型。此外,DeepLabV3+的ASPP模块对多尺度云块(从积云到锋面云系)会有更好的适应能力,但实现复杂度更高。

一个重要的选型思路是:先跑通UNet,再换DeepLab。UNet收敛快、显存占用中等、调试方便,适合快速验证数据预处理和训练管线的正确性。DeepLabV3+虽然精度上限更高,但它的空洞卷积在边缘处理上反而容易出现过平滑,在云检测这种“边界即价值”的任务上未必是稳赢。

3.2 构建网络与损失函数:加入注意力机制的UNet实现

下面给一个可以直接跑的UNet变体——在经典UNet基础上,在最深层加入一个简单的通道注意力模块。通道注意力的作用是让模型自动增强“红外亮温差异”这个通道在云检测中的贡献,因为有些云在可见光下不明显但红外亮温极低。

import torch import torch.nn as nn import torch.nn.functional as F class ChannelAttention(nn.Module): def __init__(self, channels, reduction=8): super().__init__() self.fc = nn.Sequential( nn.AdaptiveAvgPool2d(1), nn.Conv2d(channels, channels // reduction, 1, bias=False), nn.ReLU(inplace=True), nn.Conv2d(channels // reduction, channels, 1, bias=False), nn.Sigmoid() ) def forward(self, x): return x * self.fc(x) class DoubleConv(nn.Module): def __init__(self, in_ch, out_ch): super().__init__() self.conv = nn.Sequential( nn.Conv2d(in_ch, out_ch, 3, padding=1), nn.BatchNorm2d(out_ch), nn.ReLU(inplace=True), nn.Conv2d(out_ch, out_ch, 3, padding=1), nn.BatchNorm2d(out_ch), nn.ReLU(inplace=True) ) def forward(self, x): return self.conv(x) class UNetWithAttention(nn.Module): def __init__(self, in_channels=3, out_channels=1): super().__init__() self.enc1 = DoubleConv(in_channels, 32) self.enc2 = DoubleConv(32, 64) self.enc3 = DoubleConv(64, 128) self.enc4 = DoubleConv(128, 256) self.pool = nn.MaxPool2d(2) self.attn = ChannelAttention(256) self.dec4 = DoubleConv(256 + 128, 128) self.dec3 = DoubleConv(128 + 64, 64) self.dec2 = DoubleConv(64 + 32, 32) self.out = nn.Conv2d(32, out_channels, 1) def forward(self, x): e1 = self.enc1(x) e2 = self.enc2(self.pool(e1)) e3 = self.enc3(self.pool(e2)) e4 = self.enc4(self.pool(e3)) e4 = self.attn(e4) # 通道注意力加在编码器最深层 d4 = F.interpolate(e4, scale_factor=2, mode='bilinear', align_corners=False) d4 = self.dec4(torch.cat([d4, e3], dim=1)) d3 = F.interpolate(d4, scale_factor=2, mode='bilinear', align_corners=False) d3 = self.dec3(torch.cat([d3, e2], dim=1)) d2 = F.interpolate(d3, scale_factor=2, mode='bilinear', align_corners=False) d2 = self.dec2(torch.cat([d2, e1], dim=1)) return torch.sigmoid(self.out(d2))

逻辑说明:输入是(batch, 3, 256, 256),三个通道可以设计为“可见光反射率、红外亮温、亮温差”,这样模型能学到不同通道的组合特征。输出经过sigmoid,变成0到1的云概率图。代码里最应该在意的细节是torch.cat时的维度对齐——跳层连接要求编码器特征图与解码器特征图的空间尺寸一致,使用F.interpolate而不是转置卷积做上采样,是为了减少棋盘格伪影。

参数说明:初始通道数设为32,如果你的GPU显存只有6GB,建议改成16;out_channels=1表示只做云/非云二分类,如果你要做“云/晴空/水体/陆地”多分类,改成4即可,但损失函数也要换成多类交叉熵。

损失函数方面推荐“BCE + Dice”组合,原因有两个:BCE能让模型快速收敛,对每个像素独立优化;Dice损失则直接优化IoU,缓解类别不平衡问题。实践中把两个损失按7:3混合比较常见。

def combined_loss(pred, target, bce_weight=0.7, dice_weight=0.3): bce = F.binary_cross_entropy(pred, target, reduction='mean') smooth = 1.0 pred_flat = pred.view(-1) target_flat = target.view(-1) dice = 1.0 - (2.0 * (pred_flat * target_flat).sum() + smooth) / ( pred_flat.sum() + target_flat.sum() + smooth ) return bce_weight * bce + dice_weight * dice

参数说明:smooth参数是为了防止分母为0,一般取1或1e-5。当数据集中云像素占比极小(比如低于5%)时,把dice_weight提高到0.5,否则模型会出现“全预测为晴空”的假阴性灾难。

3.3 训练参数与验证策略:不要用默认学习率硬冲

云图图像识别任务里,一个几乎必然踩坑的地方是学习率设置。很多人直接用Adam的默认学习率1e-3,然后发现Loss震荡到飞起。卫星云图的像素分布极其不均匀——大量暗背景(太空或深水)的梯度贡献几乎为零,而小块亮云会产生剧烈梯度。这种梯度差异下,1e-3的Adam很容易把BatchNorm统计量打乱。我一般会把初始学习率设在2e-4到5e-4之间,并配合ReduceLROnPlateau调度器。

from torch.optim.lr_scheduler import ReduceLROnPlateau optimizer = torch.optim.AdamW(model.parameters(), lr=2e-4, weight_decay=1e-4) scheduler = ReduceLROnPlateau( optimizer, mode='max', factor=0.5, patience=5, min_lr=1e-6 ) # 训练循环中,每个epoch结束后用验证集的IoU更新学习率 # scheduler.step(val_iou)

验证指标不能只看Accuracy。因为云像素占比常常只有20%~40%,一个“全预测为晴空”的模型也能拿到70%以上的Accuracy,这在业务上是完全不能用的。你应该同时监控IoU、Recall和Precision,业务上更关注Recall——云被漏掉是致命的,云被多报最多是标注成“可能有云”,危害小得多。

训练时的另一个技巧是数据增强。对卫星云图做垂直翻转、水平翻转和随机旋转90度都是安全的,因为云的物理形态不具备“上下”语义。但要注意:不要做随机裁剪缩放(Resize),那会让细小的卷云被拉伸成块状,改变云的物理纹理。亮度抖动也不宜过大,因为可见光反射率和红外亮温本身已经过定标,大尺度扰动会破坏辐射一致性。

4. 训练环境与工程坑:Python依赖和显存管理

4.1 环境搭建与依赖版本建议

图像识别系统的Python环境搭建没有太多玄学,关键是版本一致。PyTorch的API变化不算剧烈,但CUDA版本和cuDNN版本不同,可能会导致莫名其妙的显存报错。这里给一个我常用的环境配置流程,在Linux服务器上操作。

conda create -n satellite python=3.10 -y conda activate satellite pip install torch==2.2.0 torchvision --index-url https://download.pytorch.org/whl/cu121 pip install h5py numpy pillow matplotlib scikit-learn tqdm

这段命令的要点:Python版本选3.10比3.12更稳妥,因为一些老的HDF5底层库在3.12下编译容易出警告或兼容性问题。PyTorch版本要跟你的显卡驱动匹配,如果是RTX 30系/40系卡,CUDA 12.1的预编译包可以直接用;如果是老卡(如GTX 10系),要改用cu118版本。h5py和netCDF4是目前读取卫星数据的两个必装库,如果文件是NetCDF格式,还需要额外装netCDF4包。

一个容易被忽视的问题是内存管理。5500×5500像素的波段数据,在NumPy里是float32数组的话,单个波段占用约121MB,看起来不大,但要同时载入10个波段加上经纬度网格,峰值内存很容易突破2GB。如果你在本地Windows机器上跑,这可能是导致系统假死的原因。解决方法是提高预处理阶段的数据读取效率:优先读取你需要的波段子集,而不是整文件全量读取。

4.2 显存不足:梯度累积和混合精度的取舍

图像分割模型的显存消耗大头是特征图,而不全是模型参数。一个256×256输入、初始通道32的UNet,在batch_size=16时,显存占用约6~8GB(取决于PyTorch是否开启cudnn.benchmark)。如果你的GPU只有8GB,首选方案不是换小模型,而是把batch_size降到4并开启梯度累积。

accum_steps = 4 # 等效batch_size = 4 * 4 = 16 scaler = torch.cuda.amp.GradScaler() for i, (imgs, labels) in enumerate(trainloader): imgs, labels = imgs.cuda(), labels.cuda() with torch.cuda.amp.autocast(): preds = model(imgs) loss = combined_loss(preds, labels) scaler.scale(loss).backward() if (i + 1) % accum_steps == 0: scaler.step(optimizer) scaler.update() optimizer.zero_grad()

这个代码里最重要的是第i+1取模accum_steps的判断。混合精度(AMP)在UNet这类模型上收益非常明显,尤其是在Ampere架构及更新的GPU上,速度提升接近一倍,而且因为使用了GradScaler,不会出现梯度下溢。要注意的一点是BatchNorm在混合精度下偶尔会有数值波动,如果你发现训练到中后期验证指标上下跳动剧烈,可以先关掉AMP试试。

另外一个易被忽略的参数是num_workers。很多人图省事设成0,结果GPU利用率只有30%。合理做法是设成CPU核心数的一半。但如果数据读取环节本身存在瓶颈(比如从机械硬盘读取大HDF5文件),调大num_workers并不会缓解瓶颈,反而会让内存吃紧。这时候更好的做法是把数据预处理成离线npy或内存映射文件,训练时直接读npy,不再做HDF5解析。

5. 云图识别系统的常见问题与排查:从图像质量到坐标对齐

5.1 问题一:模型预测图出现“棋盘格”状伪影

现象:模型输出的云概率图有明显的方格感,尤其是云的边缘处,看起来像马赛克。

原因:最常见的是转置卷积叠加导致的“Deconvolution checkerboard”。有些实现会在解码器用ConvTranspose2d上采样,当卷积核大小与步长不匹配时会天然产生重叠不均匀区域。另一个次要原因是输入图像本身被压缩成有损JPEG,但一般来说影响不致命。

解决:把解码器的上采样全部换成F.interpolate(bilinear上采样)再接卷积层。这种组合在遥感图像分割里是更稳的套路,代价是理论上限略低,但实际效果通常更平滑。如果你必须用转置卷积,建议把kernel_size设为步长的2倍(如stride=2, kernel=4)来减小重叠问题。

5.2 问题二:训练Loss在下降,但IoU一直为0

现象:训练Loss从0.8降到0.3,看起来正常,但验证集IoU始终在0.1以下甚至为0。

原因:这种诡异情况通常源于标签与输入图像没有对齐。卫星云图经过地理定位后,不同波段间可能存在像素偏移,尤其是可见光和红外波段因为扫描时间差会有一两个像素的空间错位。你拿可见光图做输入、拿红外波段生成的云掩膜做标签,如果不做配准,模型永远学不到结构性映射。

解决:在数据预处理中加入波段间的配准步骤。最简单的做法是读取官方地理定位文件(如Himawari的LAT/LON网格),用scipy.ndimage.shift做亚像素平移对齐。更省事的做法是干脆只用同一波段的亮温阈值生成标签,避免跨波段错位。检测方法也简单:把可见光图和标签图叠加后,人工看云边界是否有系统性偏移,如果偏移超过2个像素,就得修数据。

5.3 问题三:夜间数据全部预测为“无云”

现象:模型在白天数据上表现很好,IoU能到0.7以上,但输入夜间红外图像时,输出概率图几乎全为0。

原因:训练集分布中白天样本占绝大多数,而且模型学到的云特征高度依赖可见光反射率。夜间没有可见光通道,模型失去最强特征后直接“摆烂”。这是跨域泛化问题,不是模型bug。

解决:不能只在训练集里加几个夜间样本完事,必须做“模态丢弃增强”。具体做法是在训练时随机把可见光通道置为0,强制模型学习红外通道的判云能力。这个技巧在业务系统中非常有效,因为它人工模拟了“夜间观测模态”。

# 数据增强:训练时随机丢弃可见光通道 def random_drop_vis(batch, drop_prob=0.3): if torch.rand(1).item() < drop_prob: batch[:, 0, :, :] = 0.0 # 假设第0通道是可见光 return batch

5.4 问题四:GPU显存明明足够,但报“CUDA out of memory”

现象:PyTorch报显存不足,但nvidia-smi显示显存还有空余。

原因:PyTorch的缓存分配器会预占部分显存,并且不会在每次张量释放时立刻归还给系统。同时,HDF5读取或NumPy数组如果不小心转移到CUDA,会产生隐式显存占用。还有一个典型场景是DataLoader的pin_memory=True设置与多进程读取的组合,会让每个worker各留一份缓存。

解决:在训练循环开头加入torch.cuda.empty_cache()是治标不治本的手段,真正有效的是减小batch_size、关闭pin_memory、并在加载数据时将数组显式用float32。另外检查代码中是否有不小心把经纬度网格也送到GPU的写法,那种大矩阵会白白吃掉几百MB。

5.5 问题五:小云团被完全漏检,但大云团识别良好

现象:锋面大云系的识别效果不错,但零散的积云或破碎层云几乎全部漏报。

原因:模型的感受野决定了大目标占优。UNet的深层特征擅长捕捉整体云系形态,但浅层特征对小目标响应不足,再加上损失函数中Dice部分对“小目标在全图中的占比极低”不敏感,导致小云团被当成背景噪声。

解决:一个立竿见影的手段是在损失函数中做“尺度加权”——计算每个连通域的像素数量,给面积小于阈值的云团区域的损失权重乘以2到3。另一个可选手段是在数据增强中加入随机遮挡(Random Erasing),强迫模型不依赖大面积连续云团的特征。

6. 一个可落地的进阶技巧:用云图时间序列做运动矢量外推,让系统从“看图识字”变成“预判未来”

最后用一个能直接提升系统价值的技巧收尾。纯粹做单帧云图分割,业务价值局限于“当前时刻的云量统计和云类型标注”。但如果把连续多帧云图的分割结果做配准和光流估计,就能得到云的移动方向和速度,进一步预测未来1到2小时的云覆盖变化。这对光伏功率预测、无人机航线规划和赛事气象保障都有直接作用。

实现思路是用Farneback稠密光流算法对两帧云概率图计算运动场,然后把当前云概率图按光流场进行偏移外推。Farneback在OpenCV里是现成的,不需要训练光流网络,对256×256的输入,CPU上处理速度也够实时。

import cv2 import numpy as np def extrapolate_cloud(prob_map, future_frame_count=3, flow_scale=0.3): # prob_map: 当前帧云概率图, 0~1 float32 img1 = (prob_map * 255).astype(np.uint8) # 模拟“下一帧”,实际业务中取上一帧或再前一帧 img2 = img1.copy() # 光流计算:从当前帧到下一帧 flow = cv2.calcOpticalFlowFarneback( img1, img2, None, pyr_scale=0.5, levels=3, winsize=21, iterations=3, poly_n=7, poly_sigma=1.5, flags=0 ) # 按光流方向外推 h, w = prob_map.shape grid_x, grid_y = np.meshgrid(np.arange(w), np.arange(h)) # 外推N帧,每帧按光流大小的一定比例平移 for _ in range(future_frame_count): map_x = (grid_x + flow[..., 0] * flow_scale).astype(np.float32) map_y = (grid_y + flow[..., 1] * flow_scale).astype(np.float32) prob_map = cv2.remap(prob_map, map_x, map_y, cv2.INTER_LINEAR) return prob_map

参数说明:winsize=21是一个比较折中的取值,窗口太小噪声大,窗口太大慢且细节丢失。flow_scale的取值取决于帧间隔时间,如果两帧间隔10分钟,外推1小时则scale取0.3左右,具体要按云速统计来标定。这个方法不是万能的,强对流云的快速发展它是预测不了的,但对层云、积云这类以平流运动为主的云系,效果足够实用。

我自己的习惯是在外推后叠加一个“云量变化缓冲带”——对预测出来的云边界往外膨胀3~5个像素,宁可报多不可报漏。这个习惯让我在多次实际应用中避免了“预测无云但实际被云盖住”的尴尬。

希望这套从数据处理、模型搭建到系统外推的完整流程,能帮你在卫星云图识别这个方向上少走弯路。去把你的第一张HDF5读出来,切成512×512的图块,跑一个20轮训练的UNet,你很快会发现:这不是一个“能不能做”的问题,而是一个“做到多好”的问题。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询