ComfyUI-Impact-PackのSEGSとは何か
一連の連投は今回で終わりの予定
前々回の記事の「検出した顔をimg2imgで修正する際、512x512にサイズ指定して顔を切り抜く」を試した際に調べた、SEGSに関する情報が主な内容です。
SEGSに関する記事
ComfyUI-Impact-Packのノードで使用されている、Impact Pack独自のデータフォーマットです。情報は少ないのですが、まとまった記事がいくつかあります。
しかしSEGSのフォーマット自体を調べた記事が見つからなかったので、自分で調べました。
SEGSはImpact Packのソースコードで定義されていますが、そのままだと記述方法が分かりづらいのでまとめました。
SEGSのデータ構造
データ構造はソースコードから推測しました。構造が明示されていない箇所があるので、一部間違っているかもしれません。ご了承ください。
SEGSの実装はタプル。
( (w, h), [SEG, ...] )
[0] : タプル (h, w) 各値は画像の寸法、画像全体の寸法を表す
[1] : SEGデータの配列
SEGデータの実装は名前付きタプル(namedtuple)。これをクラスのように扱う。
メンバ:
cropped_image : torch.TensorまたはNumpy配列、crop_region上の画像
cropped_mask : torch.TensorまたはNumpy配列、crop_region上のマスク
confidence : 検出領域の信頼度
crop_region : タプル (x1, y1, x2, y2) 各値は元画像における座標、切り抜き領域の対角位置
bbox : タプル (bx1, by1, bx2, by2) 各値は元画像における座標、検出領域の対角位置(BoundingBox)
label : 検出結果の種類("face", "hand" など)
control_net_wrapper : ControlNetノードオブジェクトへの参照
具体的には、以下のようにメンバにアクセスします。
# foo_SEGS というSEGSオブジェクトがあるとする
# 画像全体の寸法を取得
foo_width, foo_height = foo_SEGS[0]
print( f"width{foo_width}, height{foo_height}" )
# SEGデータを取得し、その中のbboxメンバを読む
seg_array = foo_SEGS[1]
for seg in seg_array :
bBox = seg.bbox
print( f"bx1{bBox[0]}, by1{bBox[1]}" ) # bx1, by1 の値を表示
注意点として、切り抜いた画像データはcropped_imageとして保持していますが、切り抜く前の画像全体のデータは含まれていません。プレビュー等で全体画像が欲しい場合は別途与える必要があります。
SEGSの定義について
SEGはソース内で以下のように定義されています。
https://github.com/ltdrdata/ComfyUI-Impact-Pack/blob/Main/modules/impact/core.py#L37
SEG = namedtuple("SEG",
['cropped_image', 'cropped_mask', 'confidence', 'crop_region', 'bbox', 'label', 'control_net_wrapper'],
defaults=[None])
namedtupleはPythonのデータ型です。「名前付きタプル」の通り、タプルの要素を名前で参照できるというものです。
しかしSEGの定義だけでは各メンバのフォーマットが分かりません。そのうえSEGS全体の定義は書かれていませんでした。
そこでSEGSを操作するノードを参考にデータ構造を推測しました。以下の記述などを参考にしました。
https://github.com/ltdrdata/ComfyUI-Impact-Pack/blob/Main/modules/impact/core.py#L825
"""
core.pyから説明用に抜粋し、コメント追加
"""
# 与えられたsegsを拡縮した新しいsegsを返す
def segs_scale_match(segs, target_shape):
h = segs[0][0]
w = segs[0][1]
...
new_segs = []
for seg in segs[1]:
cropped_image = seg.cropped_image
cropped_mask = seg.cropped_mask
x1, y1, x2, y2 = seg.crop_region
bx1, by1, bx2, by2 = seg.bbox
"""
crop_region, bboxの構造はタプル
"""
crop_region = int(x1*rw), int(y1*rw), int(x2*rh), int(y2*rh)
bbox = int(bx1*rw), int(by1*rw), int(bx2*rh), int(by2*rh)
...
new_seg = SEG(cropped_image, cropped_mask, seg.confidence, crop_region, bbox, seg.label, seg.control_net_wrapper)
new_segs.append(new_seg)
"""
これがSEGSの全体構造
"""
return (th, tw), new_segs
SEGSを操作するノード
SEGSはImpact Pack独自のフォーマットなので、SEGSを使って画像処理を行うノードのほかに、SEGSを操作するノードも実装されています。特にSEGSをばらすノードと再構成するノードを使い、1個のSEGデータを処理するノードを組む、というのがSEGSを操作する処理の基本形となります。
Decompose ノードの SEG_ELT 出力に対しては、要素1個に対するノードを組みます。forループの中身を組むイメージです。
Edit SEG_ELTノードの動作では、基本的にはseg_elt入力に与えたSEGをそのまま出力します。そしてメンバの入力ピンに値を入れると、入力SEGに対してメンバの値を上書きしてSEGを出力します。
SEG操作の例として、cropped_regionとcropped_maskを書き換える処理のノードを挙げます。(「サイズ指定して顔を切り抜く」処理の一部)
上の結線の場合、Edit SEG_ELT単体ではcropped_imageがそのまま残る処理になるのですが、Decompose前のRemove Image from SEGSでcropped_imageを削除しているため、出力SEGSにはcropped_imageは残りません。
なおcropped_imageを削除している場合、img2img時に改めて全体画像から切り抜かれるため、この段階ではなくても構いません。SEGSPreviewで困りますが、その際にはfallback_imageに全体画像を指定します。
SEGSをPythonで直接扱う
SEGSを操作するノードはあるものの、込み入ったことをしようとするとノードが煩雑になります。
ComfyUIにはPythonを直接記述して実行できるカスタムノードがあります。複雑な処理はこれを使ってPythonで記述する方がむしろ楽です。
今回はASTERRというカスタムノードを使いました。WAS Node Suiteの作者が開発したものです。入力・出力ともに任意の型を扱えるのが便利です。
これを使ってcropped_regionの計算とcropped_maskの作成を行いました。
特にcropped_regionを変更すると、それに合わせてcropped_maskもずらして再作成する必要があります。
今回はcropped_regionの寸法を顔画像検出結果から512x512に大きくするため、cropped_maskには余白をパディングする処理をかけます。
処理結果
前回までの記事を読んでいる人は気づくかと思いますが、処理の流れは以下の通りです。
・顔を含む周辺領域(crop_region)の寸法が512x512になるように元画像を画像拡張する
・画像拡張後、顔領域を含む周辺領域を検出
・その領域を512x512に拡大し、マスクをパディング:これが今回の処理
そのため、今回の処理を行う前の時点でもcrop_regionは512x512に近い寸法になります。画像拡張時の誤差の分しか小さくなっていません。
つまり、修正前の画像はほぼ変化せず、それはimg2imgの結果についても同様だということです。
まとめ
ComfyUI-Impact-Pack独自のデータフォーマットであるSEGSのデータ構造を調査し、操作できるようになりました。
この記事が気に入ったらサポートをしてみませんか?