図面の中にある特定の記号を抽出してマーキングするという課題があったので、練習がてらに電気工事士の試験問題にあった図面でコンセントなどの抽出を試してみた。

# はじめに

世の中にあるお仕事の中に「図面の中に描かれている特定の記号の数を数える」という作業があるらしく、これを機械学習で解決できないだろうかという相談があった。
それはそんなに難しくなくできるだろうなぁ〜なんて思ったら意外とはまったので、手順を整理してメモしておく。

# 必要なもの

- 図面のデータ  
  電気工事士の過去問にいくつか図面があったのでそれを使用。  
  データはJPEG形式
- Python  
  3系を使用
- OpenCV

ちなみにMacを使用して行いました。他の環境ではコマンド名などが若干異なるかもしれません。

環境構築については、いつも手前味噌ではありますが以下の記事をご参照下さい。
また、バージョンについても適宜修正してご利用下さい。

[Macで深層学習の環境をさくっと作る手順 with TensorFlow and OpenCV](https://qiita.com/mix_dvd/items/b49651cf1181a986506c)

# 概要

以下のような図面があります。

![2016_1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114240/daac9cba-1a08-7f56-8bfb-02bc0e0f9d5a.jpeg)

この中から以下の記号を抽出できるようにしてみます。

- ![001_0.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114240/081f8bd8-07c1-c517-84f0-a74015da069f.jpeg)
- ![002.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114240/dabecd50-5a90-4ecd-4ad5-b4d2b9f2e020.jpeg)
- ![006.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114240/df80ef78-0926-2910-d0b8-471076904854.jpeg)

# 準備

## 抽出したい画像

まずは、以下のように抽出したい記号だけが入った画像を用意します。

![スクリーンショット 2019-08-29 12.51.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114240/cd9ef232-b231-6731-06d1-b8efff91f2ab.png)

ここでは画像のサイズを幅50、高さ50としました。

## 抽出しない画像

次に、抽出したい記号が入っていない画像を用意します。
これには特に制約がないので、適当に記号のない範囲を選んで切り出したものを数個用意します。
以下のように適当に切り出したものを10数個作成しました。

![005.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114240/ae55d683-a30a-dba0-1b70-b74c4af8dee8.jpeg)

## フォルダ構成

以下のフォルダを作成します。

- pos  
  抽出したい記号の画像を入れるフォルダ
- neg  
  抽出したい記号の入っていない画像を入れるフォルダ
- vec  
  途中で生成するベクトルデータを保存するフォルダ
- work  
  作業用に使用するフォルダ
- cascade  
  学習結果を保存するフォルダ
- sample  
  図面の画像を保存するフォルダ
- detected  
  抽出結果の画像を保存するフォルダ

# ベクトルデータの作成

作成した記号の画像から20個ずつのベクトルデータを作成し、それを結合して一つのベクトルデータを作成します。

ベクトルデータの結合には以下のライブラリを使用します。
mergevec.pyというファイルをダウンロードして同じフォルダに入れておいて下さい。

https://github.com/wulfebw/mergevec

```py
from glob import glob
import subprocess
from mergevec import merge_vec_files

n = 20

files = glob("pos/*.jpg")

for i in range(len(files)):
    
    file = files[i]
    vec = "work/{}.vec".format(i)
    cmd = ["opencv_createsamples", "-vec", vec, "-img", file, "-num", str(n), "-bgcolor", "255"]
    res = subprocess.call(cmd)

merge_vec_files("work", "vec/pos.vec")
```

# 抽出するものが入っていない画像の一覧を作成

以下のコマンドでテキストファイルを作成します。

```sh
ls neg | Xargs -I {} echo neg/{} > neglist.txt
```

# 抽出する記号の学習

OpenCVのopencv_traincascadeというコマンドを使って記号を学習させます。

```sh
opencv_traincascade -data cascade -vec vec/pos.vec -bg neglist.txt -numPos 190 -featureType LBP
```

それなりに時間がかかりますが、学習が終わるとcascadeフォルダにcascade.xmlというファイルが作成されます。

# 記号の抽出

学習結果を利用して、記号の抽出を実施します。

```py
from glob import glob

import matplotlib.pyplot as plt
import cv2

def marking(img, cascadeFileName):
    
    cascade_path = cascadeFileName
    cascade = cv2.CascadeClassifier(cascade_path)
    
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    marker = cascade.detectMultiScale(gray)
    
    for (x,y,w,h) in marker:
        
        penned_img = cv2.rectangle(img,(x,y),(x+w,y+h),(255,0,0),2)
        
    return penned_img

imgFileNames = glob("sample/*.jpg")

for filename in imgFileNames:
    
    print(filename)
    img = cv2.imread(filename)
    cascadeFileName = "cascade/cascade.xml"

    img = marking(img, cascadeFileName)
    cv2.imwrite("detected/{}".format(filename.split("/")[-1]), img)
```

上記スクリプトを実行するとsampleフォルダの中にある画像から記号を抽出して四角で囲み、detectedフォルダに保存します。

実行結果。

![2016_1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114240/f031c68a-afe0-2e42-7e10-039bd0932ba8.jpeg)

誤検出や未検出も見受けられますが、とりあえず最初のステップは完成！

後は抽出精度を向上させ、誤検出したものは別途TensorFlowなどで正しいものとそうでないものを識別させれば良いかなと思います。

...ということで、できた！！