【OpenCV; Python】findcontours関数のまとめ

OpenCVのPython版のfindcontoursについての詳しい説明をネットで探しても見つけられなかったので、自分でいろいろと試してみたことをメモしたいと思います。間違っていた場合は、ご指摘よろしくお願いします。

#findcontours関数
OpenCVで画像の輪郭抽出を行う場合には、findContoursという関数を使います。第一引数に入力画像、第二引数に抽出モード、第三引数に近似手法を取ります。

```:findcontours関数
image, contours, hierarchy = cv2.findContours(入力画像, 抽出モード, 近似手法)
```

本記事では近似手法については説明しません。統一して```cv2.CHAIN_APPROX_SIMPLE```という近似手法を使っています。

#白黒ブロブの輪郭抽出の違い
ブロブとは「塊」という意味で、黒または白の画素の固まりのことを指します。輪郭抽出において、白ブロブの輪郭と黒ブロブの輪郭では、輪郭の抽出の仕方が異なるらしいので、以下で説明します。

###黒のブロブの輪郭
黒のブロブの輪郭は、ブロブの最外行列の頂点を反時計回りに抽出するようです。
![image](https://qiita-image-store.s3.amazonaws.com/0/99071/4efeb4c8-4b0c-f3d1-5eaa-66ebc1bd7c36.png)


###白のブロブの輪郭
白のブロブの輪郭は、ブロブの最外行列の外側を下図のように時計回りに抽出するようです。
![image](https://qiita-image-store.s3.amazonaws.com/0/99071/6d50177f-8fc5-d119-d9e0-f9fa5c42e68f.png)

###優先順位
輪郭を外側から検出していく場合、黒が一番最初に検出されます。ですので、白い背景に黒のブロブがある場合は、黒のブロブが一番最初に検出されますが、黒の背景にしている場合は、背景の黒が一番最初に検出されてしまうので、注意が必要です。

#気づいたこと
- 画像の最外層行列は輪郭として抽出されない。
![image](https://qiita-image-store.s3.amazonaws.com/0/99071/c2805ce3-93f0-6b05-0911-14ac7dff35d5.png)
画像の最外層行列(上の画像では0行、9行、0列、9列)は輪郭として抽出されませんでした。上の画像で、例えば、抽出モード```RETR_EXTERNAL```で輪郭抽出を行うと、出力は以下のようになりました。

```:出力
[array([[[1, 1]],

       [[1, 8]],

       [[8, 8]],

       [[8, 1]]])]
```


- ブロブの輪郭の検出の順番

ブロブの輪郭を検出する順番は、輪郭抽出の開始点の座標のyの値が大きい順に検出されるようです。yの値が等しい場合は、xの値が大きい方の順番が早くなりました。

#抽出モード
抽出モードには、少なくとも```RETR_EXTERNAL```, ```RETR_LIST```, ```RETR_CCOMP```, ```RETR_TREE```の4つがあります。

###RETR_EXTERNAL
RETR_EXTERNALは、輪郭のうち、最も外側の輪郭のみを抽出するモードです。輪郭の内側に輪郭があっても、抽出しません。従って、白ブロブの輪郭は検出しません。
![image](https://qiita-image-store.s3.amazonaws.com/0/99071/89829f2d-4600-ff4b-6e6c-2c9e193dbded.png)

```:出力
[array([[[7, 7]],

       [[7, 8]],

       [[8, 8]],

       [[8, 7]]]), array([[[1, 1]],

       [[1, 5]],

       [[5, 5]],

       [[5, 1]]])]
```

###RETR_TREE
RETR_TREEは、入れ子構造になった輪郭を完全に表現します。つまり、まず黒ブロブを「・気づいたこと」で説明した順番に検出していき、入れ子構造になっているブロブを見つけるとそのブロブ内のブロブの輪郭を抽出していきます。
![image](https://qiita-image-store.s3.amazonaws.com/0/99071/90a6d9d0-b5fb-f9e2-efb8-7e8aec647528.png)

```:出力
[array([[[7, 7]],

       [[7, 8]],

       [[8, 8]],

       [[8, 7]]]), array([[[1, 1]],

       [[1, 5]],

       [[5, 5]],

       [[5, 1]]]), array([[[1, 2]],

       [[2, 1]],

       [[4, 1]],

       [[5, 2]],

       [[5, 4]],

       [[4, 5]],

       [[2, 5]],

       [[1, 4]]]), array([[[3, 3]]])]
```

###RETR_LIST
RETR_LISTは、白の輪郭、黒の輪郭、内側、外側関係なく、同じ階層で輪郭が取得されます。ですので輪郭の取得する順番は、「・気づいたこと」で説明した順番になります。
![image](https://qiita-image-store.s3.amazonaws.com/0/99071/90ab5fe6-3d19-9fac-c590-de4a17f86354.png)

```:出力
[array([[[7, 7]],

       [[7, 8]],

       [[8, 8]],

       [[8, 7]]]), array([[[3, 3]]]), array([[[1, 2]],

       [[2, 1]],

       [[4, 1]],

       [[5, 2]],

       [[5, 4]],

       [[4, 5]],

       [[2, 5]],

       [[1, 4]]]), array([[[1, 1]],

       [[1, 5]],

       [[5, 5]],

       [[5, 1]]])]
```

###RETR_CCOMP
RETR_CCOMPは、まず黒ブロブの輪郭を全て抽出した後に白ブロブの輪郭を抽出します。順番は、「・気づいたこと」で説明した順番になります。
![image](https://qiita-image-store.s3.amazonaws.com/0/99071/d416762f-4a2a-9129-701b-a36fb7f421bb.png)

```:出力
[array([[[7, 7]],

       [[7, 8]],

       [[8, 8]],

       [[8, 7]]]), array([[[3, 3]]]), array([[[1, 1]],

       [[1, 5]],

       [[5, 5]],

       [[5, 1]]]), array([[[1, 2]],

       [[2, 1]],

       [[4, 1]],

       [[5, 2]],

       [[5, 4]],

       [[4, 5]],

       [[2, 5]],

       [[1, 4]]])]
```