Python 画像ファイルjpg/pngをsvgへ変換する「OpenCV × svgwrite」

'22/05/01更新:CADで読み込めるsvgを作成する雛形コードを載せました。

 画像ファイルjpgやpng等は、ドット(画素)絵でラスタ形式と呼びます。そして、画像ファイルsvg(Scalable Vector Graphics)をベクタ形式と呼び、曲線でも解像度に依らずに滑らかに表示できます。その仕組みは、xmlベースで線を式の計算によって表示するためです。画像ファイルをsvgファイルの書式フォーマットへ変換できれば、CADソフトで読み込むことが可能になります。

 例えば、下図のように目玉焼きのような図があります。図中の白身の輪郭を手動でなぞって描くには困難です。

 そこで、OpenCVを用いて下図のように画像認識の処理により境界を検出します。

 同時に、下記のように境界座標を取得できます。

 上記のような(x, y)座標データがあれば、svgwriteのpolygonメソッドにてsvgファイルを作成できます。下図は、そのsvg画像ファイルをメモ帳で開いた例です。数値が並んでいます。

 そして、作成したsvgファイルは、CADソフトで読み込めるようになります。下図は「FreeCAD」で読み込んだ例です。

以上のようにすることで、複雑な2次元画像であっても、CADソフトで読み込み、下図のように押し出したりして3次元構造を作成できるようになります。

 

■「OpenCV」と「svgwrite」のインストール

ライブラリのインストールは次のようにします。

pip install opencv-python
pip install svgwrite

■本プログラム

#!/usr/bin/env python
# coding: utf-8

# In[1]:


import os
import cv2
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import svgwrite

# 画像ファイルのパス
#file_path = './pictures/magatama.png'
file_path = './pictures/medama_yaki_color.jpg'
file_name = os.path.basename(file_path)
fname, ext = os.path.splitext(file_name)
print(fname, ext)

# 保存フォルダパス
save_dir = './save_dir/'

# 画像をOpenCVで読み込む
img = cv2.imread(file_path)
plt.imshow(img)


# In[2]:


# グレースケールに変換する
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#cv2.imwrite(f'{save_dir}{fname}_01_gray_src{ext}', gray)
plt.imshow(gray)


# In[3]:


# 2値化フィルターによる輪郭の強調
contour = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 9, 5)
#cv2.imwrite(f'{save_dir}{fname}_02_contour{ext}', contour)
plt.imshow(contour)


# In[4]:


# 輪郭の座標を読み取る
contours, hierarchy = cv2.findContours(contour, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE)

# 取得したデータと階層をプリント出力でチェック
print(len(contours))
print(hierarchy)


# In[5]:


# 必要な輪郭の(x,y)座標データを取得する
x_list = []
y_list = []
df_group_list = []

for i in range(len(contours)):
    # 階層が-1の場合に処理する
    if hierarchy[0][i][-1] == -1: 
        
        # numpyの多重配列になっているため、一旦展開する。
        buf_np = contours[i].flatten() 
        #print(buf_np)
        
        # (x, y)座標の取得
        for i, elem in enumerate(buf_np):
            if i%2 == 0: # 偶数の場合はx座標
                x_list.append(elem)
            else: # 奇数の場合はy座標
                y_list.append(elem * (-1))
        
        # pandasデータフレームへ変換
        mylist = list(zip(x_list, y_list))
        df_buf = pd.DataFrame(mylist, columns = ['x', 'y'])
        
        # データフレームをリストへ格納
        df_group_list.append(df_buf)
        
        # リストを空にして、次のオブジェクトの取得へ
        x_list.clear()
        y_list.clear()
        
print('df_group_list', len(df_group_list))
#print(df_group_list)

# 抽出した輪郭をチェック
for i, df_buf in enumerate(df_group_list, start=1):
    plt.scatter(df_buf['x'], df_buf['y'], s = 0.5)
    plt.xlabel('x')
    plt.ylabel('y')
    plt.grid()
#plt.savefig(f'{save_dir}{fname}_03_scatter.jpg')
#plt.close()
plt.show()


# In[6]:


# 画像の縁の座標を削除する場合 True, 削除しない場合False
if True:
    for i, df_buf in enumerate(df_group_list):
        if not df_buf[(df_buf['x'] == 0) & (df_buf['y'] == 0)].empty:
            #print(i, df_buf)
            df_group_list.pop(i)

# 抽出した輪郭をチェック
DF_name_list = []
DF_list = []
for i, df_buf in enumerate(df_group_list, start=1):
    plt.scatter(df_buf['x'], df_buf['y'], s = 0.5)
    plt.xlabel('x')
    plt.ylabel('y')
    plt.grid()
    
    # 列名にid番号を付与して、DF_listリストへ格納
    DF_name_list.extend(['id_'+ '{0:03}'.format(i) + '_x', 'id_'+ '{0:03}'.format(i) + '_y'])
    DF_list.append(df_buf)

# データフレームを結合
DF = pd.concat(DF_list, axis=1)

# カラム名をリネーム
DF.columns = DF_name_list

# 座標をcsvファイルへ出力する
#DF.to_csv(f'{save_dir}04_{fname}.csv', index=False)

# 座標をexcelファイルへ出力する。openpyxlのインストールが必要。
DF.to_excel(f'{save_dir}{fname}_04.xlsx', index=False)

# 画像でも出力
#plt.savefig(f'{save_dir}{fname}_04_scatter_fix.jpg')
#plt.close()
plt.show()


# In[7]:


# SVGを作成する

# .svgファイルを出力するファイルパス
outfile_name = f'{save_dir}{fname}_05_Draw.svg'

# インスタンス生成
dwg = svgwrite.Drawing(outfile_name,
                       profile = 'tiny', #'tiny', 'full'
                      )

for num, df_buf in enumerate(df_group_list, start=1):
    print(num, 'before', len(df_buf))
    
    # データを間引く場合はTrueを設定
    if True:
        df_buf = df_buf[::10]
        #print(num, df_buf, color)
    print(num, 'after', len(df_buf))
    
    # データ数が少ない場合はノイズとして飛ばす
    if len(df_buf) < 10:
        continue

    # 座標データをpandasデータフレームから、リストへ変換
    points = df_buf.to_numpy().tolist()
    #print(points)
    
    # ポリゴンメソッドで生成する場合
    dwg.add(dwg.polygon(points = points,
                        #stroke = color,
                        stroke_width = 0,
                        fill = 'none', #'none', 'red', color
                        id = 'id_' + '{0:03}'.format(num),
                       ))

dwg.save()
del dwg
print('save', outfile_name)

下記コードも参考下さい。

hk29.hatenablog.jp

以上

<広告>