Python × OpenCVで軽めに画像処理


[ZOZOテクノロジーズ #1 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/zozo_tech)の記事です。
昨日は @e_tyubo さんによる「[Windowsなチームの中でMacを使うときに必要な知識](https://qiita.com/e_tyubo/items/09c7144b90c70b164ca6)」でした。自分も複数OSを使い分ける機会が多いので、とても参考になりました。
折角「開発合宿にドローンを持ってきてしまうお茶目なエンジニア」と紹介して頂いているのですが、今回は業務寄りのお堅い内容を書きたいと思います笑

自分が所属しているスマートファクトリーチームでは、服の生産工程で発生する業務改善や自動化を担当しています。
その中には画像処理系の案件も多く、ジャンルも業務直結の内容から研究寄りの内容まで様々です。

この様に多様な要望に基づいた開発を行なっていると、「そもそも効率化、自動化が必要か」「すぐに実現できることなのか」「ビジネス面でメリットがあるか」を判断し、開発の優先度付けをする必要があります。

ただ、元々自動化事例やデータが少ないアパレル業界ということもあり、やってみなければ分からないといったケースも珍しくありません。
その為、１００％じゃなくても良いのである程度指標となる結果があると便利です。

今回は上記の様な場面で、プロトタイピングで成果をパッと見せる様に使っている処理を紹介します。
あまり業務に寄りすぎると内容的に出しづらかったりするので、本記事では基本的な処理にしか触れていません。


# 画像の入出力

自分は画像処理を実験的に試す場合、ワークスペースの中に「dataフォルダ」と「resultフォルダ」を作り、これらのフォルダの中で入出力を行なっています。
処理したい画像だけフォルダに入れて処理する方が個人的には楽なので、この様な形を取っています。

## ソースコード

```Python

import os.path
import datetime

import cv2

# 定数
# dataフォルダ
DATA_PATH = "data"
# resultフォルダ
RESULT_PATH = "result"


def main():
    # カレントディレクトリを取得
    current_path = os.getcwd()

    # dataディレクトリを取得
    data_path = os.path.join(current_path, DATA_PATH)
    # dataディレクトリを取得
    result_path = os.path.join(current_path, RESULT_PATH)
    # ディレクトリ直下のファイル一覧を取得
    data_list = os.listdir(data_path)

    for file in data_list:

        # ファイルの拡張子を取得
        file_name, ext = os.path.splitext(file)

        # 拡張子がpng、jpegの場合
        if ext == u'.png' or ext == u'.jpg' or ext == u'.jpeg':
            # 画像を読み込む
            input_path = os.path.join(data_path, file)
            image = cv2.imread(cv2.samples.findFile(input_path))
            
            # 何らかの処理をここに書く

            # ファイル名が被らない様に時間を使って命名
            output_path = os.path.join(result_path, create_time_path(file_name, "拡張子"))
            # 画像を保存
            cv2.imwrite(output_path, "出力画像")

    return

# 時刻込みのユニークのファイルパスを出力する
def create_time_path(file_name, ext):
    # 現在時刻を取得
    time = datetime.datetime.now()
    # パスを作成
    path = file_name + time.strftime("%Y%m%d%H%M%S") + ext

    return path


if __name__ == '__main__':
    main()

```

# 平滑化処理 ガウシアンフィルタ

ガウシアンフィルタとは、注目画素からどれだけ離れてるかによって、重み値をつけるということを、ガウス分布を利用して行っています。標準偏差の値が大きいほど平滑化の効果も大きくなります。

ノイズがのっている画像を本処理する前に、下ごしらえとしてかけたりします。
カーネルサイズは自分が処理したい画像の性質に合わせて調節してみてください。

## ソースコード

```Python
# ガウシアンフィルタ　カーネルサイズ(奇数)
GAUSSIAN_KERNEL = (5, 5)

# ガウシアンフィルタ
def exc_gaussian_blur(image):
    # ガウシアンフィルタ適用
    gaussian_image = cv2.GaussianBlur(image, GAUSSIAN_KERNEL, 0)

    return gaussian_image
```

## 実行結果
### 元画像
![Lennagrey.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308887/564927eb-42c8-5555-a2f2-b93c82936e62.png)

### 処理後画像
![ガウシアン.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308887/675a3333-d680-2369-e2c6-82378f37156d.png)

# エッジ検出処理 Canny
簡単に内容を説明すると、「①ノイズ低減と微分」「②勾配の最大位置の検出」「③しきい値処理」の流れで処理がされています。
設定値によって出力結果は変わってきますので、自分が処理したい画像の性質に合わせて調節してみてください。

## ソースコード
```Python
# canny エッジ検出 閾値 最小
CANNY_MIN = 100
# canny エッジ検出 閾値 最大
CANNY_MAX = 150

# エッジ画像生成関数
def exc_canny_blur(image):
    # Cannyフィルタ適用
    edgeImg = cv2.Canny(image, CANNY_MIN, CANNY_MAX)

    return edgeImg
```

## 実行結果

### 元画像
![Lennagrey.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308887/5a578cf3-db60-5d40-b5ab-e7dbea5489d0.png)

### 処理後画像
![キャニー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308887/ffe2fe62-a909-4f26-41fd-7c0c16890c1f.png)

# 膨張処理
画像の背景や穴に接する対象の画素に、画素を一回り加える処理。
主にノイズ削減として、収縮処理と共に使うことが多いです。

## ソースコード
```Python
# 膨張、縮小フィルタ　カーネルサイズ
MAX_MIN_KERNEL = (10, 10)
# 膨張、縮小フィルタ 実行回数
MAX_MIN_ITERATION = 10

# 膨張処理関数
def exc_dilate(image):
    # 膨張フィルタ適用
    dstImg = cv2.dilate(image, MAX_MIN_KERNEL, MAX_MIN_ITERATION)

    return dstImg
```

## 実行結果
### 元画像
![Lennagrey.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308887/9a5b5ba9-7891-e455-561b-1f15c1a9380b.png)
### 処理後画像
![膨張.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308887/08c40284-1fce-2953-3e5f-912f287ceb56.png)

### 元画像と処理後画像の差分画像
![ori-膨張.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308887/b2486b2d-a9d5-73ab-74bd-fa981424ec5d.png)

# 収縮処理
画像の背景や穴に接する対象の画素に、画素を一回りはぎとる処理。
主にノイズ削減として、収縮処理と共に使うことが多いです。

## ソースコード
```Python
# 膨張、縮小フィルタ　カーネルサイズ
MAX_MIN_KERNEL = (10, 10)
# 膨張、縮小フィルタ 実行回数
MAX_MIN_ITERATION = 10

# 収縮処理関数
def exc_erode(image):
    # 収縮フィルタ適用
    dstImg = cv2.erode(image, MAX_MIN_KERNEL, MAX_MIN_ITERATION)

    return dstImg
```

## 実行結果
### 元画像
![Lennagrey.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308887/c75fe1f9-f7e0-727d-5f6c-5ede26012dbb.png)
### 処理後画像
![収縮.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308887/63587705-13b0-4041-2ea7-b8ded75a417b.png)

### 処理後画像と元画像の差分画像
![縮小-ori.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308887/5de4811b-6e5e-6620-d877-08b38f008b26.png)

# アフィン変換
任意の線形変換と平行移動を組み合わせた変換のことを指します。
自分は画像の回転を行う際によく使っています。

## ソースコード

```Python
# アフィン変換
def exc_affine(dx, dy, image):
    # アフィン変換(平行移動)
    affine_mat = np.float32([[1, 0, dx], [0, 1, dy]])
    height, width = image.shape[:2]
    image_affine = cv2.warpAffine(image, affine_mat, (width, height))

    return image_affine


# 画像の回転
def exc_rotation(angle, scale, image):
    # 中心位置取得
    center = tuple(np.array([image.shape[1] * 0.5, image.shape[0] * 0.5]))
    # 回転変換行列の算出
    affine_mat = cv2.getRotationMatrix2D(center, angle, scale)
    # アフィン変換(回転)
    height, width = image.shape[:2]
    rotation_image = cv2.warpAffine(image, affine_mat, (width, height), cv2.INTER_CUBIC)

    return rotation_image
```

## 実行結果
### 元画像
![Lennagrey.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308887/52085166-790f-05a3-b6bd-05c8b0aa0a14.png)

### 処理後画像 平行移動
縦横100移動の場合。
![アフィン_100_100.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308887/f25c59f1-71b5-859a-728a-30ea9d6b9a30.png)

### 処理後画像 回転
![回転.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308887/c56f2898-cdf1-f79a-00ee-ec37efc1435a.png)

### 処理後画像 スケーリング
スケーリングのステータスを10に設定。
![スケール10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308887/c64c509f-f527-4581-b322-a1d96e7e452a.png)

# おわりに
上記で紹介した処理は、対象の画像によって組み合わせを変えて使っています。組み合わせを変えることで様々なシーンに対応できるので、是非自分用にカスタマイズしてみてください。

[ZOZOテクノロジーズ #1 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/zozo_tech) 明日は @niba1122 さんによる「Rustを使って計算する」です。