【StarVector】ラスター（ビットマップ）画像をベクタライズするAIをローカルで動かす

# はじめに
　ビットマップ画像をベクタライズするには、[Adobe Illustrator](https://www.adobe.com/jp/products/illustrator.html)のベクタライズ機能や、[Vectorizer.AI](https://vectorizer.ai/)などのウェブサービスを用いる手法が考えられます。
 
しかし、前者はアルゴリズムによる生成であるため、思うような結果にならなかったり、使用する色数やパス数などの設定を人間が行ったりする必要があります。

また、後者はAIを使ったサービスであり、試行錯誤の必要なく、ピクセル画像をベクタライズすることができますが、ベクタライズしたデータを保存するには有償であり、またウェブサービスであるため、秘匿性の高いデータをベクタライズするのには向きません。


私も画像生成AIで生成したロゴ画像をベクタライズしたいとき、Illustratorを使用しているのですが、パラメータを変えて試行錯誤しみても、思うような結果にならないことも多く、ローカルで動作させられるシステムを探していました。

デモを動かせましたので、備忘録として残しておきます。

# 方法
基本的には、公式リポジトリの指示に従います。
また、今回仮想環境として``Conda``を使用しています。

::: note warn
今回は、``Conda``と``Cuda Toolkit``がインストールされている前提で行います。インストールされていない場合は、予めインストールを行ってください。
:::

## 動作環境
OS: Ubuntu 24.04 LTS
CPU: AMD Ryzen 9950X
GPU: RTX 3070 (VRAM:8GB)
RAM: DDR5 64GB
CUDA Toolkit: 12.8

今回は以上の環境で行いましたが、``StarVector``を動かすのにはここまでのスペックは必要なさそうです。

## 1.リポジトリのダウンロード
コマンドラインで以下のコマンドを入力し、リポジトリをダウンロードします。
```bash
git clone https://github.com/joanrod/star-vector.git
```

## 2.必要なパッケージのダウンロード
Condaで仮想環境を作成し、必要なパッケージをダウンロードしていきます。

まず、Condaの仮想環境を作成します。
```bash
conda create -n starvector python=3.11.3 -y
```
インストールが完了したら、以下のコマンドで仮想環境をアクティベート。
```bash
conda activate starvector
```

アクティベート後、``pip``のアップデートを行い、必要なパッケージをインストールします。
```bash
cd star-vector # リポジトリがあるディレクトリに移動
pip install --upgrade pip  # enable PEP 660 support
pip install torch torchvision torchaudio
pip install -e .
pip install -e ".[train]"
```

:::note info
公式手順に従って行ったところ、以下のようなエラーが出てしまいましたので、先に手動で``PyTorch（Ver. 2.5.1）``をインストールした後、他のパッケージのインストールを行っています。

```error.bash
  error: subprocess-exited-with-error
  
  × python setup.py egg_info did not run successfully.
  │ exit code: 1
  ╰─> [6 lines of output]
      Traceback (most recent call last):
        File "<string>", line 2, in <module>
        File "<pip-setuptools-caller>", line 34, in <module>
        File "/tmp/pip-install-juu60gm4/flash-attn_aba010691942468d883613b09b4fd867/setup.py", line 22, in <module>
          import torch
      ModuleNotFoundError: No module named 'torch'
      [end of output]
  
  note: This error originates from a subprocess, and is likely not a problem with pip.
error: metadata-generation-failed

× Encountered error while generating package metadata.
╰─> See above for output.
```
:::

## 3. Hugging Faceの設定
### 3-1. アクセストークンの生成
このリポジトリを利用するためには、HuggingFaceのログインが必要です。

:::note warn
※ アカウントを作成されていない方は、以下のリンクよりHugging Faceのアカウント登録を行ってください。
:::

以下のリンクよりHugging Faceへ移動し、ログインを行ってください。

https://huggingface.co/


Hugging Faceのログイン後、左上の``Profile``→``Settings``→``Access Tokens``→左上の``Create new token``と進みます。
![ぷろふぁい.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2676391/84ef436a-9461-4b7a-a4a0-463e57faf325.png)


そうしますと、以下の画面のように設定項目が現れるはずですので、``Token type``を``Read``に設定した後、任意の``Token name``を入力し、``Create token``をクリックします。

![りーど.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2676391/a31b46f0-9550-45d2-98e9-df6314f58207.png)


**この際、生成されたアクセストークンをメモしてください。**

### 3-2. 使用するLLMの利用規約に同意する
このリポジトリでは、ベクター画像への変換に``StarCoderBase-1B``と呼ばれるLLMを用います。

このLLMを使用するには、利用規約の同意が必要となりますので、以下のURLよりモデルページにアクセスし、同意してください。

https://huggingface.co/bigcode/starcoderbase-1b

![このへんに同意ボタン.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2676391/4e2c5486-bf60-45cf-b3ae-5c3f65a787be.png)


### 3-3. huggingface-cliにアクセストークンを入力する
先程のターミナルに戻って、以下のコマンドを入力します。
```bash
huggingface-cli login
```
そうしますと、以下のような表示がされますので、先程メモしたアクセストークンを入力して、``Enter``を押してください。

```bash
$ huggingface-cli login

    _|    _|  _|    _|    _|_|_|    _|_|_|  _|_|_|  _|      _|    _|_|_|      _|_|_|_|    _|_|      _|_|_|  _|_|_|_|
    _|    _|  _|    _|  _|        _|          _|    _|_|    _|  _|            _|        _|    _|  _|        _|
    _|_|_|_|  _|    _|  _|  _|_|  _|  _|_|    _|    _|  _|  _|  _|  _|_|      _|_|_|    _|_|_|_|  _|        _|_|_|
    _|    _|  _|    _|  _|    _|  _|    _|    _|    _|    _|_|  _|    _|      _|        _|    _|  _|        _|
    _|    _|    _|_|      _|_|_|    _|_|_|  _|_|_|  _|      _|    _|_|_|      _|        _|    _|    _|_|_|  _|_|_|_|

    To log in, `huggingface_hub` requires a token generated from https://huggingface.co/settings/tokens .
Enter your token (input will not be visible): 

```
また、このときトークンを``git credential``に追加するか聞かれますが、``No``で答えて結構です。

ここまでできましたら、環境構築は完了です。

お疲れ様でした。

# 動作確認
リポジトリに付属しているサンプルコードを用いて動作確認を行います。

クローンした公式リポジトリ直下のディレクトリに移動し、以下のコマンドを入力してください。
```bash
python scripts/quickstart.py
```

初回起動時は、必要なモデルのダウンロードが行われますので、しばらく待ちます。
モデルのダウンロードが完了すると、推論が行われ、以下のような出力が表示されるはずです。

```bash
$ python scripts/quickstart.py 
You are attempting to use Flash Attention 2.0 with a model not initialized on GPU. Make sure to move the model to GPU after initializing it on CPU with `model.to('cuda')`.
The new embeddings will be initialized from a multivariate normal distribution that has old embeddings' mean and covariance. As described in this article: https://nlp.stanford.edu/~johnhew/vocab-expansion.html. To disable this, use `mean_resizing=False`
trainable params: 0 || all params: 1434095106 || trainable%: 0.0
Loading checkpoint shards: 100%|██████████████████| 2/2 [00:00<00:00,  4.13it/s]
Asking to truncate to max_length but no maximum length is provided and the model has no predefined maximum length. Default to no truncation.
```

このとき、特に問題なく動作したようであれば正しくインストールされています。

# 実際に使用してみる
動作確認に用いたプログラムを少し変更し、以下のようなプログラムに変更します。
``scripts/quickstart.py``を開き、以下のように変更してみます。

```scripts/quickstart.py
from PIL import Image
from starvector.model.starvector_arch import StarVectorForCausalLM
from starvector.data.util import process_and_rasterize_svg
import torch

model_name = "starvector/starvector-1b-im2svg"
# model_name = "starvector/starvector-8b-im2svg"

starvector = StarVectorForCausalLM.from_pretrained(model_name, torch_dtype="auto") # add , torch_dtype="bfloat16"

starvector.cuda()
starvector.eval()

image_pil = Image.open("assets/examples/sample-18.png")
image_pil = image_pil.convert('RGB')
image = starvector.process_images([image_pil])[0].to(torch.float16).cuda()
batch = {"image": image}

raw_svg = starvector.generate_im2svg(batch, max_length=4000, temperature=1.5, length_penalty=-1, repetition_penalty=3.1)[0]
svg, raster_image = process_and_rasterize_svg(raw_svg)

with open('output.svg', mode='w') as f:
    f.write(svg)
```

追加したのは、最後の2行です。

このプログラムでは、``assets/examples/sample-18.png``というピクセル画像を、LLMによる推論でラスタライズし、変数``svg``に格納しているようです。
![sample-18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2676391/53e38484-dcf2-4135-a276-98d47b31b41a.png)


この変数``svg``は文字列になりますので、これをそのまま``output.svg``に書き出すという処理を加えています。

動作確認のときと同じように、再度実行すると``output.svg``というファイルが生成されているはずです。

開いてズームすると、確かにベクター画像になっているようです。

![出力されたベクター画像（拡大）](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2676391/46cfb27b-efb2-4608-9a5d-28a01b80e74c.png)

:::note info
※ QiitaはSVGの表示に対応していないので、拡大した画像でご容赦ください。
:::

## 使用感
動作中VRAMは4.5GBくらいを推移しており、軽量なモデルである印象です。

このあと、自前でDALL-Eで出力されたロゴ画像を出力させてみましたが、イマイチうまく生成されない印象。
また、ファイル形式（実行タイミング？）によっても若干生成結果は変わるようです。

もう少しパラメータ数の多いLLMになればうまく出力できるのかもしれませんが、実用には少し遠い印象。。。

もし、入力する画像のコツなどありましたら、お教えいただけると幸いです。


# （かんたんな）仕組み
ベクター画像、とりわけSVGファイルはテキストファイルとして読み込むことができます。

例えば、先程のSVGファイルをテキストファイルとして開くと、以下のようになっています。
```output.svg
svg version='1.2' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='70.0mm' height='50.0mm' viewBox='0 0 70.0 50.0'>
  <desc>Generated by the Chemistry Development Kit (http://github.com/cdk)</desc>
  <g stroke-linecap='round' stroke-linejoin='round' stroke='#000000' stroke-width='.54' fill='#3050F8'>
    <rect x='.0' y='.0' width='70.0' height='50.0' fill='#FFFFFF' stroke='none'/>
    <g id='mol1' class='mol'>
      <line id='mol1bnd1' class='bond' x1='4.36' y1='28.22' x2='9.72' y2='27.07'/>
      <line id='mol1bnd2' class='bond' x1='13.24' y1='24.51' x2='14.82' y2='19.38'/>
      <g id='mol1bnd3' class='bond'>
        <line x1='14.56' y1='20.13' x2='10.98' y2='16.06'/>
        <line x1='15.62' y1='19.19' x2='12.04' y2='15.12'/>
      </g>
      <line id='mol1bnd4' class='bond' x1='14.82' y1='19.38' x2='22.26' y2='17.81'/>
      <line id='mol1bnd5' class='bond' x1='22.26' y1='17.81' x2='27.26' y2='23.52'/>
      <g id='mol1bnd6' class='bond'>
        <line x1='24.98' y1='30.79' x2='27.26' y2='23.52'/>
        <line x1='23.85' y1='29.53' x2='25.61' y2='23.81'/>
      </g>
      <line id='mol1bnd7' class='bond' x1='24.98' y1='30.79' x2='28.28' y2='34.54'/>
      <line id='mol1bnd8' class='bond' x1='32.25' y1='36.12' x2='37.61' y2='34.97'/>
      <g id='mol1bnd9' class='bond'>
        <line x1='39.62' y1='30.42' x2='37.61' y2='34.97'/>
        <line x1='40.87' y1='30.88' x2='39.25' y2='34.63'/>
      </g>
      <line id='mol1bnd10' class='bond' x1='42.81' y1='27.99' x2='48.23' y2='28.65'/>
      <line id='mol1bnd11' class='bond' x1='48.23' y1='28.65' x2='53.23' y2='22.94'/>
      <g id='mol1bnd12' class='bond'>
        <line x1='53.23' y1='22.94' x2='60.67' y2='24.51'/>
        <line x1='54.36' y1='21.78' x2='60.21' y2='23.0'/>
      </g>
      <line id='mol1bnd13' class='bond' x1='60.67' y1='24.51' x2='65.67' y2='18.8'/>
      <g id='mol1bnd14' class='bond'>
        <line x1='65.67' y1='18.8' x2='63.39' y2='11.53'/>
---以下省略------
```

SVGファイルの正体は、このような線や面を構成する関数が大量に記述されているファイルのことで、これをもとにシステム側が描画を行っています。

したがって、ピクセル画像からベクター画像への変換は、**画像をもとに文字列を生成する**とも言いかえることができます。

今回のリポジトリ（論文）では、このような発想から、大規模言語モデルに画像を読み込ませて文字列（svgファイル）を生成させているようです。

# 参考文献
https://github.com/joanrod/star-vector

https://arxiv.org/abs/2312.11556