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ループの中身を組むイメージです。

SEGSを操作する処理の基本形。SEGSを要素にばらし、変更して再構成

Edit SEG_ELTノードの動作では、基本的にはseg_elt入力に与えたSEGをそのまま出力します。そしてメンバの入力ピンに値を入れると、入力SEGに対してメンバの値を上書きしてSEGを出力します。

入力SEGに対し、crop_regionとcropped_maskを上書きして出力。それら以外のメンバは入力SEGの値をそのまま出力。なお、cropped_imageは前段のRemove Image~で削除されている

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には余白をパディングする処理をかけます。

cropped_regionの計算とcropped_maskの作成をPythonで直接記述。
入力ピンこんなに要らない…

処理結果

前回までの記事を読んでいる人は気づくかと思いますが、処理の流れは以下の通りです。
・顔を含む周辺領域(crop_region)の寸法が512x512になるように元画像を画像拡張する
・画像拡張後、顔領域を含む周辺領域を検出
・その領域を512x512に拡大し、マスクをパディング:これが今回の処理

そのため、今回の処理を行う前の時点でもcrop_regionは512x512に近い寸法になります。画像拡張時の誤差の分しか小さくなっていません。
つまり、修正前の画像はほぼ変化せず、それはimg2imgの結果についても同様だということです。

修正部分のみ掲載。どこが違うのか僕自身も初見ではわからなかった

まとめ

ComfyUI-Impact-Pack独自のデータフォーマットであるSEGSのデータ構造を調査し、操作できるようになりました。

この記事が気に入ったらサポートをしてみませんか?