ezdxfとopenCVで画像認識してCAD化する（ezdxfシリーズ番外編）

# ezdxfとopenCVで画像認識してCAD化する

写真を画像認識してCAD化する業務を副業で納品しました。
とても勉強になりましたので、趣味と実益を兼ねてまとめてみます。

**ezdxf**はPythonで**CADデータを編集・作成**するライブラリ、
**openCV**はPythonのお馴染み**画像編集・認識**ライブラリです。

今回はこの２つのライブラリを連携して遊んでみたいと思います。

## 画像認識の題材

折角なら格好いいものをCAD化したいので、私の好きなゲーム『Guilty Gear』シリーズの武器『**ジャンクヤードドッグMK-Ⅲ**』に挑戦します。
![pekka-hyvarinen-1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/705528/e9490ae1-c41a-e3b0-35aa-d0577aaa280a.jpeg)
『ezdxfを使ってみよう』シリーズは[**格ゲー勢に優しいプログラミング記事**](https://qiita.com/Rai-see/items/294a36b84ae13eb1077f#alignment%E8%BE%9E%E6%9B%B8-%E4%B8%80%E5%BF%9C%E7%BD%AE%E3%81%84%E3%81%A8%E3%81%8D%E3%81%BE%E3%81%99%E3%81%AD%CF%89-)です。

## 目次
[**1.ライブラリのインポート**](#ライブラリのインポート)
[**2.openCVで画像の輪郭を抽出する関数の作成**](#openCVで画像の輪郭を抽出する関数の作成)
[**3.画像を読み込み、上記2の関数を呼び出す**](#画像を読み込み、関数line_make()を呼び出す)
[**4.ezdxfで輪郭線を描画する**](#ezdxfで輪郭線を描画する)

# ライブラリのインポート
``` PYthon:ライブラリのインポート
import cv2
from matplotlib import pyplot as plt
import numpy as np
import ezdxf
``` 
# openCVで画像の輪郭を抽出する関数の作成
この項目では以下の作業をプログラムが行います。
* 画像をグレースケール化して読み込む
* 指定した明暗の境界（グレースケールの色番号の境界）の境界の座標を取得する
 
**指定した明暗の境界**は0～255で指定できます。
真っ黒を「0」、真っ白を「255」とした上で、その間の値を指定します。
↓のようなグラデーションをイメージしてください
![グレースケール.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/705528/51b5da37-49d4-59f8-561d-3fce9d3215aa.jpeg)

下記関数は、0～256の間の整数が格納された変数lightを引数として動きます

``` PYthon:領域を座標化する関数
def line_make(light): 
  # 画像の白黒化・明暗の境界を指定
  img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
  kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (10, 10))
  ret, img_binary = cv2.threshold(img_gray,
                                  light, 255, #グレースケールlight～255の領域を抽出の意
                                  cv2.THRESH_BINARY)
  # 輪郭抽出
  contours, hierarchy = cv2.findContours(img_binary,
                                        cv2.RETR_LIST, 
                                        cv2.CHAIN_APPROX_SIMPLE)
  # 輪郭を元画像に描画
  img_contour = cv2.drawContours(img, contours, -1, (0, 255, 0), 5)
　#抽出した輪郭の座標の抽出・リスト化
  contours_list = []
  for i in contours:
    if len(i) > 1 :
      lista = [j[0].tolist() for j in i]　#座標はarray.numpyの形になっているので、リストにする
      contours_list.append(lista)
  
  #輪郭の座標のリストを返す
  return contours_list 
```
※注意：opencvは座標値をnp.arrayで返します。np.arrayはezdxfで扱えないので、プログラム内で整数のリストに変換する措置を取っています。

# 画像を読み込み、関数【line_make()】を呼び出す
先に関数を書いてしまいましたが、プログラムの実質的なスタートはここからです。

**抽出する明暗の境界値は複数の方が、期待する結果が得やすい**です。
プログラム内のリスト【light_list】を修正してご使用ください。
forループでの使用前提と可読性のために、前項で関数を作成しております。

```Python:輪郭抽出の基幹部分
# 画像の読み込み（cv2.imreadの引数に画像URLを指定）
img = cv2.imread('pekka-hyvarinen-1.jpg', 1)
#多角形として描画する座標のリストをつくります。
lines_list = []
#上記関数line_make()に指定するlight（明暗抽出の下限値）をリスト化します
light_list = [50,100,150]
#上記関数line_make()で座標リストを取得し、lines_listに順次格納します。
for light in light_list:
    lines_list.append(line_make(light))
```
明暗の境界値別の座標値のリストが【lines_list】に格納されました
# ezdxfで輪郭線を描画する
現時点で用意できているものは、『明暗の境界の座標』の点情報のみです。データ内は限りなく線に近い精度で点描されておりますが、点の集合体に過ぎません。

この**点の集合体のリストを繋げてのポリラインを作成するのがezdxf**です。
リストを使ったポリラインの描画について[**こちら**](https://qiita.com/Rai-see/items/eb74fcbac53f6a749355#poliline-%E3%83%9D%E3%83%AA%E3%83%A9%E3%82%A4%E3%83%B3%E9%80%A3%E7%B6%9A%E7%B7%9A%E5%A4%9A%E8%A7%92%E5%BD%A2%E3%81%AE%E6%8F%8F%E7%94%BB)を参照ください。


```Python:ezdxfで座標をつなぎ輪郭化する
doc = ezdxf.new("R2000", setup=True, units = 4)
msp = doc.modelspace()

for contours_list in lines_list:
  for contour_list in contours_list:
    poliline_1 = msp.add_lwpolyline(contour_list,
    format="xy", close=True, #closeは連続線を閉じるかT/F
    dxfattribs={'layer':'mylayer', #レイヤー
                'color': 3, #色
                'linetype':"Continuous", #線種
                'const_width':1 #線幅
                })

doc.saveas('junkyarddog.dxf')

#以下matplotlib確認用（不要なら消してください）
from ezdxf.addons.drawing import matplotlib
from ezdxf.addons.drawing import RenderContext, Frontend
from ezdxf.addons.drawing.matplotlib import MatplotlibBackend
import matplotlib.pyplot as plt

fig = plt.figure()
ax = fig.add_axes([0,0,1,1])
ctx = RenderContext(doc)
out = MatplotlibBackend(ax)
Frontend(ctx, out).draw_layout(msp, finalize=True)
fig.show()

```
# 抽出結果とまとめ
![jkd.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/705528/5f8ac8e8-b171-2a74-e3b7-ab14cc668556.png)
細かい調整については省きますが、『ジャンクヤードドッグ』の輪郭線がポリラインで抽出できました。恰好いいですね～。

フリー素材CADデータの人物も、たちまちバッドガイです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/705528/b007e650-cdce-17ba-01a7-bbb2d93808bb.png)

※注意：
**参考画像とCADデータが上下反転して出力されています。**
これはopencvとCADの座標系の違いです。
y座標が大きくなると上にいくか下に行くかという違いです。
本筋からずれるので処理しておりません。ご了承ください。


https://qiita.com/Rai-see/items/aebf4587f7a9b30e3ab1

https://qiita.com/Rai-see/items/eb74fcbac53f6a749355

https://qiita.com/Rai-see/items/294a36b84ae13eb1077f

https://qiita.com/Rai-see/items/04a5733e77f791cdaa09

https://qiita.com/Rai-see/items/51dcea8f496b58c41989

https://qiita.com/Rai-see/items/713b106a058da50d7d43