本記事では、下図左のような線図の線を検出して、下図右のようにCADで読み込めるsvgファイルを作成する雛形コードを載せました。
線図の線は、領域ごとに閉じたポリゴンとして作成します。そのため、押し出すことができます。下図は、オープンソフトのFreeCADでsvgを読み込み、Extrudeでソリッド(立体)を作成した例です。
本プログラムでは、OpenCVのcv2.ximgproc.thinningメソッドを使用します。もし使用しない場合、線図の線が枝分かれしてる箇所で2重線となり、領域が分かれてしまいます(下図)。
■ライブラリのインストール
pip install opencv-contrib-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/tubo.png' 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) plt.imshow(gray) # In[3]: # 2値反転 gray_inv = cv2.bitwise_not(gray) plt.imshow(gray_inv) # In[4]: # 上下反転(座標抽出する際に反転するため) gray_flip = cv2.flip(gray_inv, 0) plt.imshow(gray_flip) # In[5]: # 細線化(スケルトン化) skeleton = cv2.ximgproc.thinning(gray_flip, thinningType=cv2.ximgproc.THINNING_GUOHALL) plt.imshow(skeleton) # In[6]: # 輪郭の座標を読み取る contours, hierarchy = cv2.findContours(skeleton, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_NONE) # 取得したデータと階層をプリント出力でチェック print(len(contours)) print(hierarchy) # In[7]: # 必要な輪郭の(x,y)座標データを取得する x_list = [] y_list = [] df_group_list = [] for i in range(len(contours)): # 階層が0の場合に処理する if hierarchy[0][i][-1] == 0: # 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() # In[8]: # 画像の縁の座標を削除する場合 True, 削除しない場合False if False: 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() # In[9]: # 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 False: 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) # パスメソッドで生成する場合(データ間を直線で結ぶ座標を作る) p = "" for i in range(len(points)): if i == 0: p = "M " + str(points[i][0]) + " " + str(points[i][1]) elif i == len( points ) - 1: p += " L " + str(points[i][0]) + " " + str(points[i][1]) + " Z" else: p += " L " + str(points[i][0]) + " " + str(points[i][1]) path = dwg.path(p).stroke(width = 1).fill('none') dwg.add(path) # # ポリゴンメソッドで生成する場合 # 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)
(参考)線を閉じたポリゴン出なくて、pathとして作成したい場合の例は次のリンク先を参照下さい。
以上
<広告>
リンク
リンク