黒線の枠線がある場合、文字と区別するのが難しいですが、色付きのマーカーであれば、その色を目印にして以下の手順で抽出できそうです。
OpenCV でできる基本的な画像処理だけでいけそうです。
手順
「赤色のマーカーで囲われている」という前提で書きます。
-
- 画像を読み込む。
-
- 枠線部分のみを2値化で抽出する。
- 2.1. HSV 色空間に変換する。
- 2.2. 赤色とそれ以外を cv2.inRange() で2値化する。(赤以外の場合は要パラメータ調整)
OpenCV - inRange による範囲指定で2値化する方法について
OpenCV - モルフォロジー演算について
OpenCV - findContours() による輪郭抽出
- 3.2. cv2.contourArea() で輪郭の面積を計算し、誤検出の輪郭を消す。
OpenCV - 輪郭の面積を求める方法について
-
- 輪郭内部を白、それ以外を黒としたマスク画像を作成し、元画像の輪郭内部の領域のみを残す。
OpenCV - マスクを使用した画像の合成方法について
サンプルコード
以下の環境で確認
python
1import cv2
2
3# 画像を読み込む。
4img = cv2.imread("book.png")
5
6# HSV 色空間に変換する。
7hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
8
9# 2値化で赤の枠線を抽出する。
10binary = cv2.inRange(hsv, (170, 0, 0), (180, 255, 255))
11
12# OPENING でノイズを消す。
13kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
14eroded = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)
15
16# 輪郭を抽出する。
17contours, hierarchy = cv2.findContours(eroded, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
18
19# 誤検出の輪郭を消す。
20contours = list(filter(lambda x: cv2.contourArea(x) > 100, contours))
21
22# 検出された輪郭内部を (255, 255, 255) で塗りつぶす。
23mask = np.zeros_like(img)
24cv2.drawContours(mask, contours, -1, color=(255, 255, 255), thickness=-1)
25
26# 元画像でマスクの値が (255, 255, 255) の画素以外を白に置換する。
27img = np.where(mask == 255, img, 255)
28
29# 結果を保存する。
30cv2.imwrite("test.png", img)
入出力
入力画像
2値化後の画像
モルフォロジー変換でノイズを消した後の画像
マスク画像
結果
funa さんのアドバイスを受けて追記
簡便な方法として,drawContoursの直前で凸包に変えてしまえば,とりあえず「一部切れたけども連続はしている(=1つのContourではある)」的なパターンに関しては救えそうな気もしますが…
囲った線が一箇所途切れている場合は、funa さんのアドバイス通りに輪郭を cv2.convexHull() で凸包にすることで対応できたので、参考までに追記しておきます。
python
1import cv2
2
3# 画像を読み込む。
4img = cv2.imread("sample.png")
5
6# HSV 色空間に変換する。
7hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
8
9# 2値化で赤の枠線を抽出する。
10binary = cv2.inRange(hsv, (170, 0, 0), (180, 255, 255))
11
12# OPENING でノイズを消す。
13kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
14eroded = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)
15
16# 輪郭を抽出する。
17contours, hierarchy = cv2.findContours(
18 eroded, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE
19)
20
21# 誤検出の輪郭を消す。
22contours = list(filter(lambda x: cv2.contourArea(x) > 100, contours))
23
24# 輪郭の凸包に置き換える。
25convex_hulls = list(map(cv2.convexHull, contours))
26
27# 検出された輪郭内部を (255, 255, 255) で塗りつぶす。
28mask = np.zeros_like(img)
29cv2.drawContours(mask, convex_hulls, -1, color=(255, 255, 255), thickness=-1)
30
31# 元画像でマスクの値が (255, 255, 255) の画素以外を白に置換する。
32img = np.where(mask == 255, img, 255)
33
34# 結果を保存する。
35cv2.imwrite("test.png", img)
2箇所以上途切れていたりする場合は findContours() の輪郭が分割されてしまうので、統合する処理を入れる必要がありそうです。
実際、取り組んでくうちにいろいろ課題が出てくるとは思いますが、基本的な方針については回答に記載されていると思いますので、あとは宿題とします。
追記
実際のケースに近い画像で試してみたところ、赤ペンやマッキーで囲んだ枠をしっかり認識していました。また赤鉛筆では薄いためか厳しく、
原因は以下です。
線が細いため、2値化した時点で枠線に隙間が空いてしまっています。
これに対して、 findcontours() を行うと以下のようにそれぞれ別の輪郭あると検出されてしまいます。
そのため、複数に分割されてしまっている輪郭を1つの輪郭として統合するような処理が必要となると思います。
こちらの環境のためかエラーメッセージが出る箇所もあったので追記しました。
おそらく、質問者さんの環境の OpenCV はバージョン3 なのだと思います。
findcontours() の返り値が OpenCV 3 はタプル3つだったのが、OpenCV 4 ではタプル2つに変更されました。
OpenCV - findContours() による輪郭抽出