Python OpenCV 1 ～画像の読み込み・表示～

# 開発環境・実行検証環境

- OS : Windows10 Pro
- Anaconda : 4.4.0 (64-bit)
- Python : Python 3.6.1 
- opencv : 3.3.0-py36_202

- サンプル画像は、[カラーバー@wikipedia](https://ja.wikipedia.org/wiki/カラーバー)を利用させていただいています。

## 環境構築

- Anaconda Promptから `anaconda search -t conda opencv3` を実行して、Package Typesが「conda」で、Platformsに「win-64」が含まれるものを探す。
- Anaconda Promptを管理者権限で起動し `conda install -c conda-forge opencv` を実行する。-cのオプションは上記で見つかったUSERで。

```
(C:\Program Files\Anaconda3) C:\WINDOWS\system32>conda install -c conda-forge opencv
Fetching package metadata .............
Solving package specifications: .
Package plan for installation in environment C:\Program Files\Anaconda3:
The following NEW packages will be INSTALLED:
    ca-certificates: 2017.11.5-0    conda-forge
    libwebp:         0.5.2-vc14_7   conda-forge [vc14]
    opencv:          3.3.0-py36_202 conda-forge
    vc:              14-0           conda-forge
The following packages will be UPDATED:
    conda:           4.3.21-py36_0              --> 4.3.29-py36_0 conda-forge
    icu:             57.1-vc14_0                --> 58.2-vc14_0   conda-forge [vc14]
    jpeg:            9b-vc14_0                  --> 9b-vc14_2     conda-forge [vc14]
    libpng:          1.6.27-vc14_0              --> 1.6.28-vc14_2 conda-forge [vc14]
    libtiff:         4.0.6-vc14_3               --> 4.0.7-vc14_1  conda-forge [vc14]
    openssl:         1.0.2l-vc14_0              --> 1.0.2n-vc14_0 conda-forge [vc14]
    zlib:            1.2.8-vc14_3               --> 1.2.11-vc14_0 conda-forge [vc14]
The following packages will be SUPERSEDED by a higher-priority channel:
    conda-env:       2.6.0-0                    --> 2.6.0-0       conda-forge
    qt:              5.6.2-vc14_4               --> 5.6.2-vc14_1  conda-forge [vc14]
Proceed ([y]/n)?
```
 
# 画像の読み込みと表示

``` python:test01.py
import cv2
import numpy as np

def showImage(img,windowName):
  cv2.namedWindow(windowName,cv2.WINDOW_NORMAL)
  cv2.imshow(windowName, img)

  # [ESC]キーが押されたらウィンドウを閉じる
  while True:
    if cv2.waitKey(10) == 27: # 27-> [ESC] Key
      break
  cv2.destroyWindow(windowName)
  print('ウィンドウが閉じられました')

if __name__ == '__main__':
  img = cv2.imread('test.png')
  showImage(img,'テスト');
```

- 実行すると次のようにウィンドウが表示されます。ESCキーを押すとウィンドウが閉じてプログラムが先に進み、「ウィンドウが閉じられました」が出力されます。逆に言えば、ESCキーが押されるまでプログラムはwhile無限ループを続けます。

![無題.png](https://qiita-image-store.s3.amazonaws.com/0/152805/271b5c28-dd24-1835-019c-ff05e808b3f3.png)

- `cv2.namedWindow`の第2引数を省略もしくは`cv2.WINDOW_AUTOSIZE`を設定すると、読み込んだ画像の100%サイズにあわせた大きさのウィンドウ（サイズ変更不可能）が表示されます。

- このプログラムでは、ウィンドウの閉じるボタン[X]を押してクローズすると正常にプログラムが続行されません（「ウィンドウが閉じられました」が表示されない）。閉じるボタン[X]に対応するためには`showImage`関数を次のように書き換えます。

``` python:showImage.py
def showImage(img,windowName):

  cv2.namedWindow(windowName,cv2.WINDOW_NORMAL)
  cv2.imshow(windowName, img)
  while cv2.getWindowProperty(windowName,cv2.WND_PROP_VISIBLE) > 0:
    if cv2.waitKey(10) == 27: # 27-> [ESC] Key
      break
  cv2.destroyWindow(windowName)
  print('ウィンドウが閉じられました')
```

# 画像の形式・サイズの取得

- 画像は内部的には「NumPy配列」として扱われます。画像のサイズ（幅・高さ）やチャンネル数（グレースケールなら1チャンネル、RGBカラーなら3チャンネル）は、`img.shape`でタプル形式で取得できます。

``` python:printImgInfo.py
import cv2
import numpy as np

if __name__ == '__main__':
  img = cv2.imread('test.png')

  print('type(img) : {0}'.format(type(img)))
  print('img.shape : {0}'.format(img.shape))
  height = img.shape[0] # 行
  width = img.shape[1]  # 列
  ch = img.shape[2]
  print('幅:{0}px 高さ:{1}px チャンネル数:{2}'.format(width,height,ch))
```

- 実行結果

```
type(img) : <class 'numpy.ndarray'>
img.shape : (480, 640, 3)
幅:640px 高さ:480px チャンネル数:3
```

- imgの型（タイプ）が「numpy.ndarray」であることが確認できます。
- 画像のサイズとチェンネル数はimg.shapeで取得できます。shapeはNumPy配列の次元数・大きさを取得するプロパティで、タプル形式で値が返ってきます（タプルってなに？の方は、とりあえず配列のようなものでOKです）。
- NumPy配列はもともと行列のためのデータ形式なので、データは［行］→［列］の順に格納されます。そのため、行列の**行数に相当**する「画像の高さ」はタプルの0番目、**列数に相当**する「画像の幅」はタプルの1番目、チャンネル数は2番目に格納されます。
- Pythonらしく？` (height, width, ch)=img.shape`の1行でサイズとチャンネル数を取得・格納もできます（括弧は省略可能）。

# 画像の読み込みオプション

- カラー画像として読み込む場合は、'cv2.imread'の第2引数に'cv2.IMREAD_COLOR'を指定します。
- グレースケール画像として読み込む場合は、'cv2.imread'の第2引数に'cv2.IMREAD_COLOR'を指定します。

``` python:imReadOption.py
img1 = cv2.imread('test.png',cv2.IMREAD_COLOR)
print(img1.shape)  #(480, 640, 3)

img2 = cv2.imread('test.png',cv2.IMREAD_GRAYSCALE)
print(img2.shape)  #(480, 640)
```

- グレースケール画像として読み込み表示させた例

![無題2.png](https://qiita-image-store.s3.amazonaws.com/0/152805/51cc5504-1add-cbc4-4902-8d2e106aa95c.png)

# ピクセル値の取得
- カラー画像として読み込むとRGBでなく**BGR**（青・緑・赤）のチャンネル順でNumPy配列に格納されます。
- サンプル画像の右端上部の青色のあたり（X=630, Y=10）のピクセル値を取得してみます。

``` python:getPixelValue1.py
img = cv2.imread('test.png',cv2.IMREAD_COLOR)
print(img.shape)  #(480, 640, 3)
X = 630
Y = 10
print(type(img[Y,X]))  # <class 'numpy.ndarray'>
print(img[Y,X])        # [180  15  15] //BGR
print(type(img[Y][X])) # <class 'numpy.ndarray'>
print(img[Y][X])       # [180  15  15] //BGR
```

- img[Y,X]でも、img[Y][X]でも、NumPy配列形式で値を取得することができます。
- チャンネル値を指定してスカラー値として値を取得したい場合は以下のようにします。

``` python:getPixelValue2.py
img = cv2.imread('test.png',cv2.IMREAD_COLOR)
print(img.shape)  #(480, 640, 3)
X = 630
Y = 10
CH = 0  # BlueCH
print(type(img[Y,X,CH]))      # <class 'numpy.uint8'>
print(img[Y,X,CH])            # 180
print(type(img[Y][X][CH]))    # <class 'numpy.uint8'>
print(img[Y][X][CH])          # 180
print(type(img.item(Y,X,CH))) # <class 'int'> 
print(img.item(Y,X,CH))       # 180
```
- img.item()のほうが推奨されているようです。戻値の型が違うことに注意です。

# ピクセル値の書き込み

## 範囲

- x:20から30ピクセル、y:10から20ピクセルの範囲を白で塗りつぶす。
- `img[10:20,20:30]`で上記範囲を指定可能

``` python:setPixelValue.py
import cv2
import numpy as np

def showImage(img,windowName):

  cv2.namedWindow(windowName,cv2.WINDOW_NORMAL)
  cv2.imshow(windowName, img)
  while cv2.getWindowProperty(windowName,cv2.WND_PROP_VISIBLE) > 0:
    if cv2.waitKey(10) == 27: # 27-> [ESC] Key
      break
  cv2.destroyWindow(windowName)
  print('ウィンドウが閉じられました')

if __name__ == '__main__':
  
  img = cv2.imread('test.png',cv2.IMREAD_COLOR)
  img[10:20,20:30]=255  #[Y-range, X-range]
  showImage(img,'テスト')

```
![無題3.png](https://qiita-image-store.s3.amazonaws.com/0/152805/54b207d6-1586-d324-2eea-3f05ca7dcb7a.png)

- 横100×縦50のランダムイメージを作成し、カラーバー画像の(X,Y)=(10,10)の位置から挿入する。

``` python:setRandImg.py
img = cv2.imread('test.png',cv2.IMREAD_COLOR)
# 0から255の乱数で初期化したサイズ(50,100,3)のNumPy配列の作成 
randImg = np.random.randint(0, 255, (50,100,3)) 
img[10:60,10:110]=randImg
showImage(img,'テスト')
```
![無題4.png](https://qiita-image-store.s3.amazonaws.com/0/152805/45851a13-e4b2-3000-4ed6-ad965fa4c17d.png)

## 任意の1ピクセルの指定チャンネルに書き込み
- 任意のピクセルの特定チャンネルに値をセットする場合は`img.itemset`を利用する。
    - `img.itemset((10, 20, 0), 128)`：X=20,Y=10のBチャンネルに128をセット
    - `img[20,10,0]=128`でも同様に可能。itemsetが推奨されるらしい。

# 参考資料
- [OpenCV-Python チュートリアル文章](http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/index.html)