接上篇ComfyUI的批处理(上):如何实现多Prompt自动生图?,今天正经来聊聊For循环。
For循环节点其实挺抽象的,不少会用的都说不清楚。

但好在它抽象的不是For循环的编程概念——For循环的编程概念就是一个带次数的循环——而是怎样去理解这个节点运作逻辑。
只要搞清楚节点上的每一个连接点代表什么、怎么输入的、怎么输出的、怎么循环的,并不难懂。
有朋友跟我说,他跟别人解释For循环,先像下面这样一连,组成一个最简单的循环,然后就开始想尽办法使用各种奇妙比喻,什么传送带、丢沙包……但是再怎么具象比喻,还是自己都觉得抽象,听的人也不一定全部绕得过来。

我觉得如果真要足够好懂,还是得用笨法子,拆开了把每一个连接点都解释清楚,整个循环自然也就解释清楚了。
[以下内容比较细,可酌情跳跃观看]
首先,必须要搞清楚一个问题:怎么样就算形成循环了?
答案非常简单:在循环起始和循环结束节点之间围出一个圈,就形成循环了。
不管是像上面的截图,从起始节点的值出发,连接到结束节点闭环:

还是从起始节点的索引出发,连接到结束节点闭环:

有了循环圈,都可以形成循环。
当然这种最简单的循环,起终点之间没有接入任何有效内容,只是单纯地转圈圈,没啥实际应用意义。但是,我们必须先搞清楚的几个东西,已经都有了。
其实一共就5项,其中两项还不太需要解释。
流:起始节点输出的流一定要连接到结束节点输入的流,这样才能形成循环;
总量:就是需要循环的次数,数字填写几就循环几次。
于是现在只剩下3项:

(节点的翻译有点不全,initial_value=初始值,value=值,别看分别是中英文,其实是一个意思。当初始值1/值1连接点被使用时,节点会自动出现初始值2/值2。暂时我们还不会用到initial_value2和value2,在用到它们之前,接下来我提到的「初始值」均默认指「初始值1」,「值」均默认指「值1」。)
正式开始之前,我先介绍两个节点:

一个叫「展示任何」,可以把输入的东西展示出来,然后原样输出;另一个叫「延迟执行」,就是执行到这个节点,就停顿多少秒,然后继续执行。
这两个节点,对学习陌生的工作流,非常有用。它们都是原样输入原样输出,除了它们能提供展示/延迟功能外,你可以把它们等效成连接线,只当它们不存在。
我们先来搭一个从值出发连接到结束节点的工作流,然后对索引、值、初始值,分别添加3个「展示任何」节点来观察它们的内容, 中间加一个2秒的延迟防止运行太快看不清楚。就像下面这样:

总量设置成5次,运行。
从运行里面可以观察到:
- 「索引」的值从0开始,在每次运行完一个循环后,都会自动加1。循环总量是5次,「索引」的值就是从0到4。
- 5轮循环,「值」和「初始值」都是null(空)。同时,结束节点右边的「值」输出整个工作流运行完才显示null。
下面再保持展示和延迟节点,再来搭一个从索引出发连接到结束节点的工作流,像下面这样:

同样运行5次。
这次可以观察到:
- 「索引」的值依旧是从0开始,在每次运行完一个循环后,都会自动加1。循环总量是5次,「索引」的值就是从0到4。
- 结束节点的「初始值」随索引的变化而变化,整个工作流运行完后,结束节点右边的「值」输出了「索引」的最后值4。
- 起始节点「值」一开始是null,但从第2次循环起,被自动赋值为上一次循环的「索引」值,也就是同一轮循环中结束节点的「初始值」。
两相对比,「索引」这一项我们已经可以搞明白了,它索引的就是循环的次数,只是索引值从0开始。第1次循环索引值为0,第2次循环索引值为1,以此类推。

同时,可以得到一个小知识点,For循环-结束节点右侧端点的「值」,在整个循环结束后才会输出;以及,起始节点的「值」,似乎是随结束节点的「初始值」变化。
我们继续,还是同样的工作流,我们在左边加一个「整数常量」节点,连接到初始值,为它赋一个初始值。
按照ComfyUI的基础逻辑,起始节点「整数常量」只会执行1次,加上前面的小知识点,最后结束节点的输出也只在结束时输出1次。所以真正参与循环的部分是下图绿框的部分,绿框外的都不参与循环。

分别以初始值为0和为3运行一遍。
当赋的初始值为0时,后面的值一直为0;当赋的初始值为3时,后面的值一直为3。

所以现在对于起始节点,我们可以得出结论:
当工作流开始运行时,如果没有为起始节点的初始值赋值,那这里输出的值就是null(空);如果我们提前赋值了初始值,则输出的值就是初始值。
那么,结束节点的初始值又是什么呢?

我们在前面的工作流中再插入一个「简易运算」节点。

简单说明一下,这个节点实现的是,把前面传递过来的值赋给a,然后执行运算a+2,再输出到后面的节点。

执行一下。
我用一个简单的表格记录下执行结果:

结束节点的「初始值」,确实就是参与下一轮循环的「值」,也可以说等同于下一轮起始节点的「初始值」。
再去掉工作流中多余的展示和等待。

这个循环背后赋值的逻辑就很清晰明了了:
- 如有初始值输入,则起始节点以这个值参与第1次循环,否则以空值参与;
- 第1次循环的结果,作为第2次循环的初始值参与第2次循环,以此类推;
- 直到执行完设置好的循环次数(总量),结束节点输出最终的结果。
如果还有初始值2/值2,也是同样的逻辑,两条线并行,各自进行循环。

现在,5个基本项都已经清楚了,循环的流向逻辑应该也就没有什么问题了。
现在我们已经理解For循环的基本运作逻辑了,但光知道原理还不太够,紧跟着来了一个新问题:怎样应用?
我们在ComfyUI里搭建循环,可不是为了让它闲得没事在开始和结束中间反复横跳的,也不是简简单单让它进行数字运算的。我们的诉求是通过For循环实现批量生成/处理图片和视频才对。
那到底如何让一段需要重复的流程循环起来呢?
答案就是:想办法把它接进这个圈里,让起始节点的输出,「流经」你要循环的流程,最终汇入结束节点。

乍一看,挺简单的,像画电路图连接元件一样,把要循环的部分插进去呗。
但真正动手搭建的时候,问题就来了。
如下图,下面蓝色分组的部分是一个再简单不过的SD1.5基础生图流程:

Checkpoint加载器也好,CLIP文本编码器也好,Latent也好,好像没有地方能跟For循环的起始节点连接起来。接不进去啊,那咋办呢?
所以我在上一篇文章说,Prompt line是一个很方便的快捷节点,它自己内置了循环,完全不需要我们考虑怎样触发循环。但自己写For循环,这个问题还是需要考虑一下的。
不卖关子了,直接说答案,通常可以采取的方法是:数字连接。
现在我们要重复的流程是一个生图流程,所以最终输出的展示值一定得有一个图像用来查看生成结果,我们在最右侧的「值1」输出接上一个「预览图像」。而这个图片它应该来自于SD1.5生图流程的输出,所以我们把下面「VAE解码」的「图像」连接到「初始值1」。

现在循环的圈和需要被循环的部分还是没有建立真正的闭环连接。
怎么连接呢?我们看到起始节点还有两个可以对外输出的端口「索引」、「值1」,但实际上「值1」并不可用,因为「值1」在循环中会被「初始值1」赋值,所以它的值类型必须是图片,而不能是数字了。不过「索引」本身就会自动输出0、1、2……的递增数字,而且我们要循环生图,肯定是需要输出不一样的图片的,这个数字应该随着循环次数发生变化,选择「索引」再合适不过了。
再看下面SD1.5生图流程的部分,有哪个数字是允许发生变化但不影响生图稳定和效果的呢?不难找,「随机种」。我们把索引连到随机种上。
现在成功闭环了,点击运行,循环生成了5张图片:
成功实现循环。
不过如果你细心的话,应该能察觉到两个小问题。
第一个问题,如果直接把索引连接到随机种,那生图的种子就会从0、1、2、3……按顺序递增,完全不随机了。
但我们可以通过一个简单的公式计算补救一下。
新建一个随机Seed节点,生成随机种;然后新建一个简易运算节点,索引赋值为a,随机种的值赋值到b,对它们求和a+b,然后再把a+b的和赋值到生图流程K采样器的随机种,这样就又恢复随机性了。

另外一个问题,预览图像现在只能输出最后一次循环的结果,没办法快速查看到这一批生成的所有图片,很不方便。
解决这个问题,就需要用到一个新节点「任何批次组合」。

作用是把any_1和any_2进行组合,后面我们具体解释。
连接方法很简单,把它串在被循环的流程和结束节点的初始值1(或其他数字)输入之间,然后再把另一个输入节点和For循环的值1(或其他对应数字)相连:
我们先来回忆一下前面说的For循环赋值的逻辑。

假设第1次循环生成的图片是a,由于图像的输出连接(结束节点的)初始值1,则初始值1也被赋值图片a;

来到第2次循环,虽然这个工作流中(起始节点的)值1不向后输出,但它还是在自顾自地发生变化。因为上一次循环中初始值1为图片a,所以这次循环的值1就被赋值图片a。进入到下面的生图流程,生成了一张图片b,同样的,初始值1也变成图片b。

继续第3次循环,同样的,值1变成初始值1的图片b,生成图片c,初始值1变成图片c。

最后,循环结束,值1输出图片c。
发现问题没有?初始值1每次都被覆盖掉了,所以循环结束后输出的只是最后一次的结果。
「任何批次组合」解决的就是这个被覆盖掉的问题。

它把前面不向后输出的(起始节点的)值1利用了起来,暂存了上一轮的初始值1,再跟这一轮的生成结果合并到一起,赋值给这一轮的初始值1。

循环结束,值1输出最后一次的初始值1,也就是[图片a,图片b,图片c]。
到这儿,For循环的基本应用方法也就掌握了。
上篇提到过,除了多prompt生图,使用For循环还能实现文件、图片的批处理。比如说,批量放大。
刚好我这里有一些柯达EKTAR H35半格胶卷相机扫出来的照片,裁剪成单张还不到1300*900,按像素计只有可怜的100万像素出头,可以拿来做一下演示。
单纯对于真实照片的放大,我一直非常推荐Philip Hofmann发布的一系列放大模型(https://github.com/Phhofm/models),之前就发文章介绍过其中之一:ComfyUI | 如何AI放大照片不模糊?推荐一个冷门的图片放大模型,这些模型基本不涉及大幅重绘,所以可以避免AI编辑造成的一些负面修改。例如,前面文章中我们用Qwen编辑模型处理过天坛的穹顶,当时瓦片的线条纹理发生了诡异形变,这种情况在使用这些放大模型时就不会发生。
这次我选择使用Philip Hofmann的放大模型4xBHI_dat2_otf_nn,它在4倍高清放大的同时又不会进行过度的降噪处理,就算从1100万像素放大到1.8亿像素,100%缩放查看时仍然有一定细节,还很好地保留了胶片的颗粒质感。

由于并没有进行重绘,工作流也非常简单:

好,现在开始思考:怎么样设计循环,让这个流程能够批量自动处理一个文件夹的图片?
首先我们需要一个能够从文件夹路径加载图像的节点,最好带有索引功能,能实现一张一张加载。这样的加载节点其实不少,像一般整合包都会带的Inspire-Pack和KJNodes都有。

我一般习惯使用这个「加载图像(路径)」。
使用它替换掉原来的单张图像加载节点,然后把循环起始节点的索引跟起始索引相连,这样就可以从0、1、2、3……按顺序自动加载了。因为每一张图片我们都要单独放大,所以图像加载上限填1,代表每次加载1张。

循环次数设置与文件夹内图片数量一致,再添加「任何批次组合」节点,实现循环结束后一次性预览全部放大后的图片。

就是这么简单。

当然也可以做一些其它的花样,比如最近Comfy官方更新了一批图片编辑模板,有几个我觉得挺有意思的,像是这个:

基于Qwen-Image-Edit模型,上传一张图片,可以获得8个角度的视角。
我比较喜欢里面俯视视角,有点像低空无人机的角度,很有意思。
那我就可以对这个工作稍作调整,加入For循环,对一整批图片批量生成俯视角度的新图;同时可以把原图的加载接入值2的循环,这样就能跟生成结果形成对比:

这个工作流的视角变换基于Prompt,如果你愿意的话,也可以再嵌套一个提示词的循环对每张图输出任意你想要的视角(咳,注意文明)或者对图片内容进行固定元素的增减与修改,实现多组输出。
For循环跟不同的节点结合可以有不同的玩法,比如上篇文章说的LLM生成多提示词,加入For循环再配合Excel写入写出节点,对反推和编辑都可以留出极大余地;比如说配合SAM等智能分割节点,就可以实现批量抠图编辑和去水印等功能;比如说配合一些图像处理和逻辑节点,可以实现图片的自动分割拼接,一键追色等等……
掌握了For循环的基本逻辑和应用,这些都等着你去自由探索。
发表回复