OpenCV 4.3 + Python で Hello World と顔検出

## 概要

- macOS に Homebrew で OpenCV 4.3 をインストール
- OpenCV 4.3 と Python で基本的な処理を実行する
  - Hello World 画像生成
  - 画像ファイルをウィンドウ表示
  - カメラからの映像をリアルタイムにグレースケール化
  - 画像から顔の位置を検出

## 環境

- macOS Catalina
- Python 3.8

```
$ sw_vers
ProductName:	Mac OS X
ProductVersion:	10.15.4
BuildVersion:	19E287
```

```
$ python --version
Python 3.8.2
```

## セットアップ

### Homebrew で OpenCV をインストール

依存関係で NumPy もインストールされる。

```
$ brew install opencv
==> Installing dependencies for opencv: numpy
==> Installing opencv dependency: numpy
```

```
$ brew info opencv
opencv: stable 4.3.0 (bottled)
Open source computer vision library
https://opencv.org/
/usr/local/Cellar/opencv/4.3.0 (771 files, 224.5MB) *
```

```
$ brew info numpy
numpy: stable 1.18.3 (bottled), HEAD
Package for scientific computing with Python
https://www.numpy.org/
/usr/local/Cellar/numpy/1.18.3 (484 files, 15.6MB) *
```

### OpenCV-Python のセットアップ

OpenCV-Python は Python から OpenCV を使えるようにした OpenCV 公式のライブラリ。

[OpenCV: Introduction to OpenCV\-Python Tutorials](https://docs.opencv.org/4.3.0/d0/de3/tutorial_py_intro.html)

> OpenCV-Python is a library of Python bindings designed to solve computer vision problems.

今回の環境では OpenCV-Python は /usr/local/opt/opencv/lib/python3.8/site-packages/cv2 にあった。

```
$ ls -la /usr/local/opt/opencv
lrwxr-xr-x  1 foobar  admin  22  5  4 20:14 /usr/local/opt/opencv -> ../Cellar/opencv/4.3.0
```

```
$ tree /usr/local/opt/opencv/lib/python3.8/site-packages/cv2
/usr/local/opt/opencv/lib/python3.8/site-packages/cv2
├── __init__.py
├── config-3.8.py
├── config.py
├── load_config_py2.py
├── load_config_py3.py
└── python-3.8
    └── cv2.cpython-38-darwin.so

1 directory, 6 files
```

OpenCV-Python を使うため環境変数 PYTHONPATH を設定。

```
$ export PYTHONPATH="/usr/local/opt/opencv/lib/python3.8/site-packages"
$ echo $PYTHONPATH
/usr/local/opt/opencv/lib/python3.8/site-packages
```

[1\. コマンドラインと環境 — Python 3\.8\.3rc1 ドキュメント](https://docs.python.org/ja/3/using/cmdline.html#envvar-PYTHONPATH)

> PYTHONPATH
> モジュールファイルのデフォルトの検索パスを追加します。この環境変数のフォーマットはシェルの PATH と同じで、 os.pathsep (Unix ならコロン、 Windows ならセミコロン) で区切られた1つ以上のディレクトリパスです。存在しないディレクトリは警告なしに無視されます。

### NumPy パッケージのインストール

OpenCV を Python から使う際には NumPy パッケージが必要。

[OpenCV: Introduction to OpenCV\-Python Tutorials](https://docs.opencv.org/4.3.0/d0/de3/tutorial_py_intro.html)

> OpenCV-Python makes use of Numpy, which is a highly optimized library for numerical operations with a MATLAB-style syntax. All the OpenCV array structures are converted to and from Numpy arrays. This also makes it easier to integrate with other libraries that use Numpy such as SciPy and Matplotlib.

pip コマンドで NumPy パッケージをインストール。
今回は Homebrew でインストールした際に依存関係でインストールされた NumPy のバージョンに合わせておく。

```
$ pip install numpy==1.18.3
```

```
$ pip list | grep numpy
numpy      1.18.3
```

### pip を使わないセットアップも可能

今回の環境では Homebrew で OpenCV をインストールした際に /usr/local/lib/python3.8/site-packages 以下に numpy と cv2 がインストールされているため以下のように環境変数を設定しても動作する。

```
$ export PYTHONPATH="/usr/local/lib/python3.8/site-packages"
$ echo $PYTHONPATH
/usr/local/lib/python3.8/site-packages
```

この場合、NumPy パッケージを pip で新たにインストールする必要はない。

## Python から OpenCV が使えるか確認

以下の myenv.py というファイルを用意。

```myenv.py
import sys
import cv2
import numpy
print("sys.path:\n" + "\n".join(sys.path))
print("OpenCV: " + cv2.__version__)
print("NumPy: " + numpy.__version__)
```

パッケージのパスが通っているか、cv2 や numpy が import できていて OpenCV や NumPy のバージョンを表示できるか確認。

```
$ python myenv.py
sys.path:
/Users/foobar/myapp
/usr/local/opt/opencv/lib/python3.8/site-packages/cv2/python-3.8
/usr/local/opt/opencv/lib/python3.8/site-packages
/Users/foobar/.pyenv/versions/3.8.2/lib/python38.zip
/Users/foobar/.pyenv/versions/3.8.2/lib/python3.8
/Users/foobar/.pyenv/versions/3.8.2/lib/python3.8/lib-dynload
/Users/foobar/.pyenv/versions/3.8.2/lib/python3.8/site-packages
OpenCV: 4.3.0
NumPy: 1.18.3
```

## Hello World 画像生成

ソースコード: hello.py

```hello.py
import numpy as np
import cv2 as cv

# 画像データを入れる numpy.ndarray オブジェクト
image = np.zeros((256, 256, 3), np.uint8)

# テキストを描画する
text = "Hello, world"
org = (0, 100)
font_face = cv.FONT_HERSHEY_SIMPLEX
font_scale = 1.0
color = (0, 255, 127) # Blue, Green, Red
thickness = 1
line_type = cv.LINE_8
cv.putText(image, text, org, font_face, font_scale, color, thickness, line_type)

# 画像を出力する
path = "hello-world.png"
cv.imwrite(path, image)
```

実行すると Hello World 画像が出力される。

```
$ python hello.py
```

![hello-world.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139365/8ba65f93-ed7e-c4fd-7ee3-4bafcf6d8ee8.png)

## 画像ファイルをウィンドウに表示

ソースコード: showimage.py

```showimage.py
import sys
import cv2

# コマンドライン引数を読む
if len(sys.argv) < 2:
  print("画像ファイルのパスを指定してください。")
  sys.exit(1)
path = sys.argv[1]

try:
  # ファイルから画像を読み込む
  image = cv2.imread(path)

  # 画像をウィンドウに表示する
  winname = path
  cv2.imshow(winname, image)

  # キーが押されるまで待つ
  cv2.waitKey()

except cv2.error as err:
  print("エラー発生: {0}".format(err))
```

実行すると指定した画像がウィンドウ表示される。

```
$ python showimage.py hello-world.png
```

<img width="256" alt="showimage.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139365/83429617-4a37-f29b-6d24-ec4b7cf80c64.png">

## カメラからの映像をリアルタイムにグレースケール化

公式ドキュメント [OpenCV: Getting Started with Videos](https://docs.opencv.org/4.3.0/dd/d43/tutorial_py_video_display.html) に載っているサンプルコードをほぼそのまま使用する。

ソースコード: videocapture.py

```videocapture.py
import cv2 as cv

cap = cv.VideoCapture(0)
if not cap.isOpened():
  print("Cannot open camera")
  exit(1)

while True:
  # Capture frame-by-frame
  ret, frame = cap.read()
  # if frame is read correctly ret is True
  if not ret:
    print("Can't receive frame (stream end?). Exiting ...")
    break
  # Our operations on the frame come here
  gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
  # Display the resulting frame
  cv.imshow('frame', gray)
  if cv.waitKey(1) == ord('q'):
    break

# When everything done, release the capture
cap.release()
cv.destroyAllWindows()
```

実行するとカメラからの映像をリアルタイムにグレースケール化したものがウィンドウに表示される。

```
$ python videocapture.py
```

<img width="960" alt="videocapture.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139365/6a6594f1-b282-be14-0fd1-b4e840468517.png">

## 画像から顔の位置を検出

ソースコード: facedetect.py

```facedetect.py
import sys
import numpy as np
import cv2 as cv

# コマンドライン引数を読む
if len(sys.argv) < 3:
  print("画像ファイルのパスを指定してください。")
  sys.exit(1)
input_image_path  = sys.argv[1]
output_image_path = sys.argv[2]

# 画像を読み込む
# アルファチャンネル付きに対応するため IMREAD_UNCHANGED を使う
input_image = cv.imread(input_image_path, cv.IMREAD_UNCHANGED)

# Haar 特徴ベースのカスケード分類器による物体検出の準備
# 顔検出用のカスケード分類器を使用
face_cascade_name = "/usr/local/share/opencv4/haarcascades/haarcascade_frontalface_alt.xml"
face_cascade = cv.CascadeClassifier(face_cascade_name)

# 顔を検出
faces = face_cascade.detectMultiScale(input_image)

# 出力画像データを入れるオブジェクト
# 入力画像データを出力画像データにコピー
output_image = np.copy(input_image)

for (x, y, w, h) in faces:

  # 検出した顔の座標を出力
  print("Face: [{} x {} from ({}, {})]".format(w, h, x, y))

  # 顔の位置に楕円を描画
  center = (x + w // 2, y + h // 2)
  size = (w // 2, h // 2)
  angle = 0
  startAngle = 0
  endAngle = 360
  color = (255, 0, 255, 255) # Blue, Green, Red, Alpha
  thickness = 4
  cv.ellipse(output_image, center, size, angle, startAngle, endAngle, color, thickness)

# 画像を出力
cv.imwrite(output_image_path, output_image)
```

入力画像ファイルパスと出力画像ファイルパスを指定して実行する。

いくつかの画像で試す。

[File:Lenna \(test image\)\.png \- Wikipedia](https://en.wikipedia.org/wiki/File:Lenna_(test_image).png)

```
$ python facedetect.py lenna.png lenna-output.jpg
Face: [175 x 175 from (215, 202)]
```

![lenna-output.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139365/39be6cbf-4bb6-2489-b33e-911aab622016.jpeg)

[これでも糖質制限ダイエット中の三銃士を連れてきたよ｜無料の写真素材はフリー素材のぱくたそ](https://www.pakutaso.com/20150920253post-6016.html)

```
$ python facedetect.py 150711109603_TP_V.jpg 150711109603_TP_V-output.jpg
Face: [187 x 187 from (681, 116)]
Face: [133 x 133 from (1115, 151)]
```

![150711109603_TP_V-output.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139365/b9d0bc66-88b4-231f-6be4-06e2f3c242d8.jpeg)

[カルビを焼く自分を想像する自分を想像する自分を想像する看護師 \| 看護師フリー写真素材サイト スキマナース](https://nurse-web.jp/photo/archives/279)

```
$ python facedetect.py cut32_karubi2-1200x801.jpg cut32_karubi2-1200x801-output.jpg
Face: [97 x 97 from (473, 181)]
Face: [148 x 148 from (98, 273)]
Face: [66 x 66 from (714, 108)]
```

![cut32_karubi2-1200x801-output.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139365/ba067789-2e62-5b57-9faf-0481942b97ea.jpeg)

[集合している人たちのイラスト（世界） \| かわいいフリー素材集 いらすとや](https://www.irasutoya.com/2019/10/blog-post_448.html)

```
$ python facedetect.py group_young_world.png group_young_world-output.png
Face: [249 x 249 from (997, 354)]
Face: [292 x 292 from (729, 165)]
Face: [295 x 295 from (431, 177)]
```

![group_young_world-output.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139365/15958c9f-e922-f434-ebc1-ac6f76c18e9a.png)

## 参考資料

- [OpenCV: OpenCV\-Python Tutorials](https://docs.opencv.org/4.3.0/d6/d00/tutorial_py_root.html)
- [OpenCV: Drawing Functions in OpenCV](https://docs.opencv.org/4.3.0/dc/da5/tutorial_py_drawing_functions.html)
- [OpenCV: Getting Started with Videos](https://docs.opencv.org/4.3.0/dd/d43/tutorial_py_video_display.html)
- [OpenCV: Cascade Classifier](https://docs.opencv.org/4.3.0/db/d28/tutorial_cascade_classifier.html)
- [numpy\.zeros — NumPy v1\.18 Manual](https://numpy.org/doc/1.18/reference/generated/numpy.zeros.html)
- [numpy\.copy — NumPy v1\.18 Manual](https://numpy.org/doc/1.18/reference/generated/numpy.copy.html)
- [OpenCV 4\.3 \(C\+\+\) で Hello World と顔検出 \- Qiita](https://qiita.com/niwasawa/items/87feb5f7923bbe2a7e26)