1. 星点为什么是后期里的“钉子户”:从手动去星到深度学习
1.1 星点问题的本质
你拍过几张深空照片之后一定会发现一个很扎心的事实:真正让你后期无从下手的往往不是天体本身,而是密密麻麻的星点。猎户座大星云(M42)中间那几颗亮星一过曝,整个核心区域的高光细节就被“吃掉”了;即使是曝光控制很好的窄带图,几千颗星点叠在云气结构上,原本细腻的暗尘埃、细丝、外晕全被掩盖。你拉一次曲线,星点先亮起来,天体细节反而沉下去了。
这个问题的根源在于:星点与深空天体在像素尺度上是“同一类东西”——都是亮的、有梯度的小面积区域。传统后期算法很难把它们彻底分开。对初学者来说,最简单的思路是用 Photoshop 的“污点修复画笔”或者仿制图章一把一把地修,但星场里动辄几百上千颗星,纯手工修到后面基本就是体力劳动;更重要的是,手工去星很难保证每一颗星都处理得自然,修完经常出现“星点没了但地景也糊了”的违和感。
我见过不少同好用 Morphological Transformation(形态学变换)这类工具去星:它本质上是靠局部最小值/最大值滤波,把星点当作“明锐的结构”压下去。对这种工具,参数不好调的痛点非常明显——半径小了星点压不掉,半径大了云气细节跟着一起“磨皮”。尤其是那些亮度跨度很大的星场,亮星周围还会有明显的光晕残留,处理完满图都是伪影。这个问题最终需要一个能从“语义”层面区分“恒星”和“云气”的算法来解决。
1.2 传统去星工具为什么难用
在 StarNet 出来以前,我自己的处理流程里最依赖的工具是 Photoshop 里的Minimum Filter加Median的组合:通过高半径最小值滤波提取星点蒙版,然后对着蒙版做收缩、羽化,再反选之后压星。这个流程遇到的典型问题有三个:
- 星点亮度差异太大,一个固定半径的蒙版很难同时框住亮星和暗星,亮星的衍射芒会切除不干净,暗星又根本不会进入蒙版。
- 星点密集区域的蒙版连成片,去星之后云气上经常留一块一块的“凹痕”,后续必须反复补修。 3.处理时长星点周围会产生黑圈(halo),就是因为蒙版边缘过渡不自然,本质上是在“挖”而不是在“分离”。
说白了,传统工具都是在像素层面试图削弱星点,而没有一个模型真正理解“这是一颗星”“那是云气”。所以你会发现调参调得越细,等于人肉告诉软件哪里是星,哪里不是星,效率和上限都被局限住了。这也让很多天文摄影同好干脆放弃去星,直接带星拉伸,结果就是天体细节一直无法得到比较干净的呈现。
1.3 StarNet:一个把去星做成“图层分离”的模型
StarNet 入坑后第一感受是:它把去星从“图像修复”变成了“图像分离”。你输入一张普通的 RGB 深空图像,它直接输出一张没有星点的星云/星系图。这不是某一颗星修得好不好的问题,而是整个“星点层”被完整剥离了,云气、尘埃带、星系旋臂、暗星云这些结构基本不发生形变。
从机器学习角度看,StarNet 的做法是训练了一个卷积神经网络来做回归:输入含星图像,输出无星版本。它不产生蒙版,不生成“星点层”,而是直接给出一张干净的背景图层。正因为输出是一张完整的无星图,你可以顺势得到另一个图层——用原图减去无星图,就是几乎只包含恒星和衍射芒的“星点层”。这个能力让我后期的工作方式发生了很大改变:我可以在无星图上放心地拉伸、降噪、做反卷积,再把星点层单独处理后再叠加回去,两者互不干扰。
这篇文章会围绕 StarNet 的原理、安装、命令行使用、双图层后期思路以及我实际踩过的具体问题展开,内容更适合正在受“星点压制”困扰、或者已经开始接触 PixInsight 但还没有系统用过神经网络去星工具的同好。下面先把模型本身跑清楚。
2. StarNet 模型的原理考察:全卷积网络、训练数据与版本差异
2.1 全卷积网络一次推理直接输出无星版本
StarNet 的模型结构并不复杂,但选择这个结构的原因非常关键。它使用的是一个全卷积网络(Fully Convolutional Network,FCN),网络里没有全连接层,也就是说输入和输出都是图像,而不是某个分类标签或一组坐标。这个设计让模型在推理时对输入尺寸相对宽容,常见的天文照片分辨率(比如 6000×4000)都能直接喂进去,虽然实际使用中为了显存和速度,一般会切成小块处理。
网络的任务本质上是监督学习里的图像到图像翻译(image-to-image translation)。训练时需要成对的输入输出样本:输入是真实天文照片(含星点),输出是同一张照片去掉星点之后的结果。这里有一个很巧妙的训练策略:在生成训练集时,作者可以通过算法先识别星点位置,再把星点区域的像素替换成周围背景估计值,人工制造出“无星版本”。然后用这一对数据去教网络——看到亮而紧凑的、有一定对称性的小光斑,就把它理解成星点并去除。
但模型真正有价值的点在于它学的不只是“亮度峰值去除”。由于训练数据覆盖了各种不同形态的深空天体(弥漫星云、行星状星云、星系、暗尘带),网络被迫学会区分“致密、点状、按PSF扩散”的恒星和“有内部结构、边缘不规则、亮度分布连续”的云气。它在推理时不会像 Minimum Filter 那样把一坨亮云气也判断成星,这就在语义层面拉开了和传统算法的差距。
为了稳定训练,作者还做了数据增强:随机裁剪、旋转、翻转,让网络对星点在画面任意位置、任意角度、任意密度的情况都保持稳定。这也是为什么你拿一张星点很密、亮暗悬殊的银心区域图进去,它依然能够把星点和暗尘埃剥离得比较干净。如果网络只见过少量样本或单一形态,很容易把暗星云里的亮结误伤成星点,这个在 StarNet 的早期反馈里确实是不少见的问题。
2.2 从 StarNet 到 StarNet++:PyTorch 重建带来什么
目前在使用层面,你可能会遇到两个主要项目:最早的 StarNet(基于 Keras / TensorFlow)和社区维护的 StarNet++(基于 PyTorch)。两者共享同一个核心思想,但工程实现上有几处明显的差异。
- 运行效率:StarNet++ 的 PyTorch 实现推理速度通常更快,显存占用也更友好。实测同一张图,在同样的显卡环境下,StarNet++ 完成推理的时间大概能缩短到原版的一半左右。
- 部署门槛:原版依赖 TensorFlow 的版本和 Python 环境,如果你电脑上还有别的工作流,很容易出现依赖冲突;StarNet++ 只要 PyTorch 装好,权重文件一放,跑起来干净利落。
- 参数细节:两个版本都提供 stride、tile size、overlap 之类的参数,但默认值和命令格式略有差异。
对我来说,StarNet++ 的 GPU 加速能力是让我真正愿意在每次后期都跑一遍去星的主要原因。如果你的主要主力机是 CPU,原版跑一张 4000×6000 的图可能需要半小时甚至更久;而 StarNet++ 在 GPU 上这张图只需要一两分钟,处理节奏完全不一样。
2.3 模型的边界与适用场景
用久了你会发现,StarNet 并不是“万能橡皮擦”。它擅长处理的是常规 RGB 摄影或者窄带合成之后的图像,特别是那些背景已经基本均匀、天体质感自然的情况。但以下场景需要额外注意:
- 严重过曝的星点:模型默认星点是“高斯状光斑”,但如果亮星核心完全饱和、出现大面积溢出,模型会把它理解成面积比较大的“天体”,去星后会有残留。
- 极暗的云气:如果无星云气比天空背景高不了多少,网络可能认为“这里没有值得一提的结构”,输出反而会削弱一部分云气。这类情况需要你在拉伸时注意保留暗部,推荐对线性图先做轻度拉伸(或直接用非线性图)再喂给模型,动态范围太窄的线性图效果反而不好。
- 强人工作品:重磨皮、重降噪之后的图像,星点边缘的人工痕迹会让网络判断出现误差。
所以说,正确的用法不是“点一下就把星点永久删除”,而是把它当作后期流程里的一个分析工具。你通过一次去星把背景和前景暂时分离,就能获得一张可以放心操作的底图,最后再把星点层按自己的审美叠回去。这也是我后面要重点展开的实战流。
3. 跑通 StarNet:环境配置、命令行参数与首张无星图
3.1 三步搭好运行环境
我自己是在 Windows 11 上加一块 NVIDIA GPU 完成主流程的,配置不算复杂,新手照做也能跑通。
第一步是安装 Python 与 GPU 版 PyTorch。建议直接用 Miniconda 创建独立环境,避免污染其他项目的依赖。在命令行里执行:
conda create -n starnet python=3.10 conda activate starnet pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121CUDA 版本需要根据你的显卡驱动来选择,这里 cu121 只是我机器上的例子。装完先验证一下能不能调用 GPU:
import torch print(torch.cuda.is_available())输出 True 再继续;如果是 False,大概率是 PyTorch 版本和显卡驱动不匹配,不要急着往下跑模型。
第二步是拉取 StarNet++ 仓库并下载权重。以 StarNet++ 为例:
git clone https://github.com/AnClark/starnet-plus-plus.git cd starnet-plus-plus # 权重文件从发布页面下载:starnet_plus_plus.pt把权重文件放在仓库根目录或者你自己指定的路径都可以。原版 StarNet 的仓库里也有预训练权重文件,流程类似,只是脚本是 run_starnet.py。
第三步是检查输入图像的格式。我的经验是:喂给 StarNet 的图像最好是 8-bit/16-bit 的 PNG 或 TIFF,尽量不要直接用 FITS 文件。很多同好在 PixInsight 里跑 StarNet 脚本,那是因为脚本内部帮你转了格式;如果从命令行跑,建议先导出成 16-bit PNG 再处理,省掉很多色彩空间和位深方面的兼容问题。
3.2 基本调用命令与参数解读
StarNet++ 的命令行入口大致长这样:
python run_starnet.py --input /path/to/your_image.png --out /path/to/output_dir --no-gpu如果去掉 --no-gpu,它就会尝试用 GPU 推理。支持的常用参数包括控制分块大小的 tile size、控制分块之间重叠率的 overlap、以及控制推理步长的 stride。刚开始跑建议先用默认参数跑一张小图,跑通之后再对正式大图做参数微调。
关于三个参数的直觉理解:
- Stride(步长)是网络在图像上移动窗口的间隔,间隔越大速度越快,但相邻窗口之间的衔接可能变差。
- Tile size(分块大小)决定网络一次看多大区域,越大上下文信息越足、对云气结构的判断越准,但显存占用也越高。
- Overlap(重叠率)是相邻分块之间互相重叠的比例,重叠越大,拼接缝越不明显,但耗时也越长。
我自己的建议是从 stride=1、overlap=32 这类安全参数开始,先把效果和质量放在第一位。等到你确定这张图的目标之后,再逐步调大 stride 换速度。如果你只有 CPU,可以用较小的 tile size 和较大的 stride,但要有心理准备——一张全幅图可能要跑很久,我会建议这种场景直接缩小输出尺寸,不要硬啃原图。
第一次跑的时候不要急着用最终大图。我习惯先裁一块 1000×1000 左右的区域验证模型权重、色彩空间和输出效果,确认没问题之后,再对全图跑一次。这样就算参数不对,浪费的时间也有限。
3.3 第一次跑出来的图像:肉眼验收标准
跑完第一张无星图之后,你需要在屏幕上看几个关键区域来判断效果是否达标:
- 云气边缘:无星图里云气和暗尘埃的边缘应该是连续、柔和的,不能出现一圈一圈的“水波纹”;
- 暗星场:画面没有明显亮星的区域,背景应该保持原有的轻微纹理,不能变成一片死灰或磨砂状;
- 原来星点的位置:跳转到原片里有亮星的位置,无星图里那个区域应该是干净的云气结构,没有暗圈、没有亮斑残留。
如果出现云气边缘被压掉的情况,多半是输入图像动态范围太窄——你把线性图直接喂进去了。StarNet 的训练数据偏向上过一定拉伸、“结构清晰”的图像,建议先做一次自动拉伸(PixInsight 里就是 ScreenTransferFunction + HistogramTransform),再导出 PNG 给它。这个细节我看到很多教程没提,但对效果影响非常大。
如果出现边缘拼接痕迹,最常见的原因就是 overlap 太小。我一般把 overlap 从默认值往上提到 64 或者 128,拼接缝问题基本能解决,代价是推理时间多了不少,但这笔成本值得掏。后面做正式后期时,我会直接把无星图放进图层体系里,而不是拿着一张“去完星的图”就直接输出,因为无星图只是中间产物。
4. “无星图”不是终点:带星/无星双图层后期流
4.1 分离前的图像准备:校准、拉伸与线性数据
要最大限度发挥 StarNet 的价值,我建议先把图像做完基本的校准和色彩校正,再做一次中等强度的拉伸,最后导出成 16-bit PNG。这个流程的原因在于:网络对低信噪比区域的判断能力有限,背景全是噪声的时候,它很难从噪声里分辨“哪些是暗星点”。而轻度拉伸后的图像,星点与云气之间的对比更明确,网络输出的无星图也更稳定。
我自己常用的顺序是:PixInsight 里完成暗场/平场校准 → DynamicCrop 裁边 → DBE 去梯度 → 色彩校准 → 用 HistogramTransform 做一次“温和”拉伸(让直方图峰值挪到约 1/4 处) → 保存 16-bit PNG。这个状态下的图像仍然偏“灰”,但这恰恰是 StarNet 想要的,它不需要你拉得很艳,只需要结构清楚。
有一点容易踩坑:如果你在 DBE 阶段背景已经被拉得很亮、很平,星点边缘的扩散结构可能会被压掉,StarNet 就会漏掉一些暗星。所以 DBE 的目标不是“把背景做到全黑”,而是“把梯度去掉、背景均匀”,这一点和普通后期其实没有区别,只是从工具角度对输入更友好。
4.2 双图层处理的思路与 PixelMath 合并公式
拿到无星图之后,标准的做法就是构建两个图层:无星背景图层和星点图层。
- 无星背景图层:在 PixInsight 里可以继续做反卷积、HDRMultiscaleTransform、CurvesTransform、噪声缩减,因为这些操作不会影响星点,可以拉得比较大胆。
- 星点图层:用原图减去无星图得到。这个图层基本只包含恒星,你可以对它单独做星点点锐化、星点直径调整,甚至用 MorphologicalTransform 缩小星点尺寸,不用担心伤到云气。
在 PixInsight 的 PixelMath 里,做减法的表达式就是:
R = orig_R - starless_R; G = orig_G - starless_G; B = orig_B - starless_B;把原图和星点图层分别存成两个视图,然后在 PixelMath 里引用即可。得到星点图层后,你可以存储为一个独立图像,也可以直接用 PixelMath 的 RGB/K 分量的表达式把两个图层重新合成:
R = starless_R + stars_R; G = starless_G + stars_G; B = starless_B + stars_B;这个合成公式看起来平平无奇,但它真正厉害的地方在于:你可以在合成之前对 starless 层和 stars 层分别做完全不同的拉伸。比如无星背景层可以拉得较狠,把暗云气的细节全部展示出来;星点层可以压低一点亮度、缩小一点半径,让星点不再喧宾夺主。之后再合成,就得到一张“背景细节拉满、星点克制耐看”的作品,这在以前是得靠蒙版和手工刷的,现在变成图层运算。
4.3 从星点图层里进一步提炼信息
星点图层其实就是“恒星信息图”,除了用来叠加回原图,它还有两个非常实用的衍生操作。
第一个是做星点蒙版。在 PixInsight 里你可以直接用星点图层的亮度作为蒙版,配合 RangeMask 等工具,精准地保护星点区域。比如你想对云气做大幅度锐化但不希望星点跟着变粗,就可以把星点图层的反相作为蒙版;想单独给星点加一点收缩,也可以用星点图层本身作为蒙版。
第二个是星点减淡。这个处理对很多有强衍射芒的作品很有效:你可以对星点图层先做一个极小半径的高斯模糊,或者用 StarMask 生成蒙版后做收缩,然后再把它叠加回去。这样亮星被“缩小”成更细的针点,整体画面看起来更精致,却不会出现传统 star shrink 方法常见的“黑圈”问题。
我把这种“先拆层、再分别处理、最后重组”的流程叫“图层化后期”。它让星点和云气彻底解耦,任何针对云气的操作都不会污染星点,任何针对星点的操作也不会破坏云气。后期思路从此变成了“你只管处理两个图层,不用处理一整张图像”。这个模式的工程前提就是 StarNet 能产出一张干净的无星图,所以第一次用的时候,建议把重点放在“怎么才算干净”上,而不是急着追求画面效果。
5. 我踩过的坑与两个进阶思路
5.1 常见问题与排查方向
我在把 StarNet 嵌入日常工作流的第一个月里,遇到过几个比较有代表性的问题,写出来帮你避坑。
- 输出图像偏灰或偏暗:这种情况我一开始以为是模型问题,后来发现是因为输入的 PNG 是线性图,网络输出的无星图动态范围非常窄。对策是在喂图之前先做轻度拉伸,最好用 PixInsight 的 STF 自动拉伸参考一下。
- 大图上出现明显的网格状拼接痕:多半是 tile overlap 太小,或者 tile 尺寸太大导致每块之间的上下文不一致。把 overlap 从 0/16 提到 64~128,问题基本能消失。
- 星点去了但云气细节也被削平:这通常发生在低信噪比区域。对策是不要直接拿原图去星,而是先对原图做一次轻降噪,让云气纹理稍微连贯一点再进模型。
- GPU 占用很高但速度没上来:可能是你把 stride 设成了 1 且 tile size 设得太大,显存被占满后算子开始频繁做内存交换。建议先缩小 tile size,再考虑调 stride。
5.2 进阶思路:反卷积前先做星点隔离
反卷积(Deconvolution)是天文后期里提升细节的利器,但它的一个天然痛点是:星点本身是高密度的“明亮点源”,在反卷积迭代中很容易被放大成周围一圈一圈的 ringing 伪影,还会拉高噪声。过去的标准建议是用星点蒙版保护星点区域,把反卷积局限在云气上。但 StarNet 打破了这一限制。
我的实际操作是:先在星图中用 StarNet 分出无星背景层,然后只对无星背景层做反卷积。因为背景层里没有星点,反卷积算法不会再被点源的强响应干扰,迭代次数可以更大、增益可以更高,云气细节可以拉得非常锐利。反卷积之后再把星点层叠加回来或者以星点层为蒙版恢复星点。这时候你得到的细节锐度和星点形态之间是完全解耦的——这在以前只有用复杂的星点蒙版才能勉强做到,现在只是流程中的普通一步。
这里要提醒一句:反卷积之前一定先把无星层降噪,否则拉高增益时噪声会一起被放大,云气边缘会有颗粒感。我自己通常的顺序是 TGVDenoise 一次,再反卷。
5.3 进阶思路:窄带 HOO/OIII 映射前的星点分离
另一个我非常推荐的用法是把它用在窄带合成里。很多人拍 SHO 或 HOO 合成,后期最大的痛点之一就是星点颜色和发射线颜色混在一起,合成后星点边缘会出现一圈怪异的颜色偏移。因为窄带数据里星点在不同通道的亮度比值本来就和云气不同。
我的做法是:在每个窄带通道做映射(HOO 的 R=Ha, G=OIII, B=OIII)之前,先对各通道的线性图分别用 StarNet 去星,得到三张无星通道。然后用这三张无星通道做颜色映射和合色,这样云气颜色极其干净。合完之后再从原始含星数据上提取星点层,对星点层单独做星点颜色校正(一般就是 PixInsight 的 SCNR 绿色去噪和轻微收缩),最后叠加回合成图。
这么做有一个很好的副产品:星点和云气的颜色互不干扰,星点可以调成统一舒服的白色/轻微暖色,而云气保持透亮的 OIII 青绿色调。如果你一直没有处理过窄带星点色偏,试试这条流程,会明显感觉到作品“通透”了很多。
就我个人的使用体会来说,StarNet 更像是一个“思路转换器”:它的产出不是最终成片,而是让你把星点这些麻烦的客体从主画面里“摘”出来,还给你一个可以自由发挥的空间。后续你可以在无星层上放开手脚做处理,最后再把星点当成一种可控的视觉元素装回去。玩熟了这套流程后,你会对“后期不是把一张图修好看,而是把图层之间的关系处理对”这句话有更深的认同。