重なった矩形の和集合部分の面積を求める

# Overview

![a1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41925/e63081ff-9353-4c75-98a0-148cb3a5991f.png)

こんな感じで、無作為に重なった矩形の、和集合部分の面積を求めたいときとかあるじゃないですか。

ほら最近流行りのこういうのとか。
（画像はこちらから拝借：https://note.nkmk.me/python-opencv-face-detection-haar-cascade/）
![img](https://raw.githubusercontent.com/nkmk/python-snippets/c4d1af90cd6e0d5885285ec8146fb9ac5f567d44/notebook/data/dst/opencv_face_detect_rectangle.jpg)

これ、補助線引いて手計算する分には気合でなんとかできるんですけど、まともに計算するとかなり大変というか
https://mathcommunication.hatenablog.com/entry/2016/10/11/003213#%E5%B9%B3%E9%9D%A2%E5%9B%B3%E5%BD%A2%E3%81%AB%E5%AF%BE%E3%81%99%E3%82%8B%E5%8F%AF%E6%B8%AC%E6%80%A7%E3%81%A8%E9%9D%A2%E7%A9%8D

NP困難？だかなんだかで凡人には無理なので、

## 方針

直接描いて、ピクセルを数えます。

計算式で算出する場合みたいに綺麗な値にはならないし、解像度で誤差も出るけど、
それが問題になるケースって多分あんまりないんじゃないかと。

具体的には、

### 二値化して

![b1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41925/5e81b313-0dbc-a711-59bf-7046fe21ef09.png)

### 黒（塗りつぶし色）のピクセルを数える

opencv に `countNonZero` っていうまさにそれな関数があるので、コレを使う。
https://docs.opencv.org/2.4/modules/core/doc/operations_on_arrays.html#countnonzero

## コード

じゃ、いきます。

```text:requirements.txt
cycler==0.10.0
kiwisolver==1.2.0
matplotlib==3.2.1
numpy==1.18.4
opencv-python==4.2.0.34
Pillow==7.1.2
pyparsing==2.4.7
python-dateutil==2.8.1
six==1.14.0
```

ランダムなので実行するたびに矩形の位置とサイズ変わります。

```python:a.py
from PIL import Image, ImageDraw
import numpy as np
import cv2

import random
import matplotlib


rects = [
    (random.randrange(200),
        random.randrange(200),
        random.randrange(50, 120),
        random.randrange(50, 120)
    ) for x in range(20)
]

colors = random.sample(matplotlib.colors.cnames.keys(), len(rects))

# 線画用
im1 = Image.new('RGB', (200, 200), (255, 255, 255))
d1 = ImageDraw.Draw(im1)
# 二値変換確認用
im2 = Image.new('RGB', (200, 200), (255, 255, 255))
d2 = ImageDraw.Draw(im2)

# 矩形描画
for r, c in zip(rects, colors):
    print(c, r)
    d1.rectangle(r, outline=c)
    d2.rectangle(r, fill=(0, 0, 0))

im1.save("a1.png")
im2.save("a2.png")

# 二値変換
nim = np.array(im2, dtype=np.uint8)
gim = cv2.cvtColor(nim, cv2.COLOR_RGB2GRAY)
ret, bwim = cv2.threshold(gim, 0, 255, cv2.THRESH_OTSU)

# 二値変換後の画像
cv2.imwrite("b1.png", bwim)

whitePixels = cv2.countNonZero(bwim)
blackPixels = bwim.size - whitePixels

print("-"*50)
print(whitePixels, blackPixels, bwim.size)
print("filled area rate:", blackPixels / bwim.size)
```

## 実行

```bash:bash
$ python a.py
gainsboro (188, 158, 61, 63)
palegreen (174, 182, 118, 53)
chartreuse (75, 34, 82, 81)
olive (170, 10, 93, 111)
mistyrose (183, 65, 78, 52)
indianred (25, 55, 51, 109)
mediumaquamarine (102, 155, 59, 89)
navy (169, 127, 85, 96)
darkviolet (103, 152, 76, 51)
yellowgreen (84, 6, 73, 118)
deeppink (42, 61, 95, 90)
bisque (47, 101, 107, 105)
dimgrey (95, 179, 91, 70)
maroon (146, 182, 88, 62)
whitesmoke (189, 118, 83, 112)
lavender (71, 149, 104, 117)
burlywood (36, 82, 105, 82)
darkslategrey (122, 128, 103, 52)
mediumslateblue (163, 121, 84, 92)
silver (144, 126, 61, 58)
--------------------------------------------------
18580 21420 40000
fill area rate: 0.5355
```

おわり。

## 参考

https://note.nkmk.me/python-pillow-imagedraw/
https://techtech-sorae.com/pythonopencv%E3%81%A7%E4%BA%8C%E5%80%A4%E7%94%BB%E5%83%8F%E3%81%8B%E3%82%89%E7%99%BD%E3%81%A8%E9%BB%92%E3%81%AE%E9%9D%A2%E7%A9%8D%E6%AF%94%E3%82%92%E7%AE%97%E5%87%BA/