質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.72%

鉛筆で囲んだ部分をOpenCVでトリミングしたい(できればGoogle Colab、GASで自動化も)

受付中

回答 2

投稿 編集

  • 評価
  • クリップ 2
  • VIEW 817

onion1

score 6

前提・実現したいこと

初心者です。色々検索しても糸口が見えないので質問させていただきます。
画像のうち黒い線で囲んだ部分をOpenCVを用いて切り抜きたいのですが、輪郭抽出のサンプルコードをいくつか行っても適切な結果となりません。
サンプルコード
下の画像でいえば「吾輩は猫である。名前はまだ無い。」の部分のみが残るようにしたいです。

全体像としては、Google ColabでGoogle Driveから切り抜き前の画像を取得し、OpenCVで必要な文字がある部分だけを切り取り、その画像をGoogleDriveにアップロード、Google Apps ScriptによってOCRし、そのテキストをGoogle検索、得たタイトルやスニペットをGmailで送信するという一連の動作を自動化しようと考えています。またこれによりスマートグラスで簡単に紙媒体に書かれた事柄を検索できるようにしたい。

追記

最初に挙げた画像が例としてわかりにくかったので、tiitoiさんの回答をより実際に近い画像で試させていただきました。赤鉛筆のような薄いものだと厳しいようです。

イメージ説明
イメージ説明
イメージ説明
イメージ説明
イメージ説明

また、Google Colabという環境のためかエラーメッセージが出たので、[サイト]を参考にしたところうまくいきました。(https://amazarashi.me/archives/752)

エラーメッセージ

ValueError                                Traceback (most recent call last)
<ipython-input-70-37b431b140a1> in <module>()
     15 
     16 # 輪郭を抽出する。
---> 17 contours, hierarchy = cv2.findContours(eroded, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
     18 
     19 # 誤検出の輪郭を消す。

ValueError: too many values to unpack (expected 2)

試したこと

# 輪郭を抽出する。
image,contours, hierarchy = cv2.findContours(eroded, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • onion1

    2019/06/25 01:44

    なるほど。ありがとうございます。
    色ペンでもトリミングできれば嬉しいです。

    キャンセル

  • tiitoi

    2019/06/25 02:21

    色ペンで赤など黒と区別しやすい色が好ましいです。
    回答に色ペンの場合のやり方を書きました。

    キャンセル

  • fana

    2019/06/25 11:12 編集

    提示画像みたいな絵が処理対象である場合,
    https://teratail.com/questions/158121#reply-237261
    のような,行の位置判定処理とかもヒントにできそうな予感.
    (人手によるマーカが隣の行まで差し掛かるとか,画像処理で複数に分かれちゃうとかいう場合)

    キャンセル

回答 2

+2

黒線の枠線がある場合、文字と区別するのが難しいですが、色付きのマーカーであれば、その色を目印にして以下の手順で抽出できそうです。

OpenCV でできる基本的な画像処理だけでいけそうです。

手順

「赤色のマーカーで囲われている」という前提で書きます。

サンプルコード

以下の環境で確認

  • OpenCV 4
  • Python 3.7
import cv2

# 画像を読み込む。
img = cv2.imread("book.png")

# HSV 色空間に変換する。
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

# 2値化で赤の枠線を抽出する。
binary = cv2.inRange(hsv, (170, 0, 0), (180, 255, 255))

# OPENING でノイズを消す。
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
eroded = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)

# 輪郭を抽出する。
contours, hierarchy = cv2.findContours(eroded, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

# 誤検出の輪郭を消す。
contours = list(filter(lambda x: cv2.contourArea(x) > 100, contours))

# 検出された輪郭内部を (255, 255, 255) で塗りつぶす。
mask = np.zeros_like(img)
cv2.drawContours(mask, contours, -1, color=(255, 255, 255), thickness=-1)

# 元画像でマスクの値が (255, 255, 255) の画素以外を白に置換する。
img = np.where(mask == 255, img, 255)

# 結果を保存する。
cv2.imwrite("test.png", img)

入出力

イメージ説明

入力画像

イメージ説明

2値化後の画像

イメージ説明

モルフォロジー変換でノイズを消した後の画像

イメージ説明

マスク画像

イメージ説明

結果

funa さんのアドバイスを受けて追記

簡便な方法として,drawContoursの直前で凸包に変えてしまえば,とりあえず「一部切れたけども連続はしている(=1つのContourではある)」的なパターンに関しては救えそうな気もしますが…

囲った線が一箇所途切れている場合は、funa さんのアドバイス通りに輪郭を cv2.convexHull() で凸包にすることで対応できたので、参考までに追記しておきます。

import cv2

# 画像を読み込む。
img = cv2.imread("sample.png")

# HSV 色空間に変換する。
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)

# 2値化で赤の枠線を抽出する。
binary = cv2.inRange(hsv, (170, 0, 0), (180, 255, 255))

# OPENING でノイズを消す。
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
eroded = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)

# 輪郭を抽出する。
contours, hierarchy = cv2.findContours(
    eroded, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE
)

# 誤検出の輪郭を消す。
contours = list(filter(lambda x: cv2.contourArea(x) > 100, contours))

# 輪郭の凸包に置き換える。
convex_hulls = list(map(cv2.convexHull, contours))

# 検出された輪郭内部を (255, 255, 255) で塗りつぶす。
mask = np.zeros_like(img)
cv2.drawContours(mask, convex_hulls, -1, color=(255, 255, 255), thickness=-1)

# 元画像でマスクの値が (255, 255, 255) の画素以外を白に置換する。
img = np.where(mask == 255, img, 255)

# 結果を保存する。
cv2.imwrite("test.png", img)

2箇所以上途切れていたりする場合は findContours() の輪郭が分割されてしまうので、統合する処理を入れる必要がありそうです。

イメージ説明

実際、取り組んでくうちにいろいろ課題が出てくるとは思いますが、基本的な方針については回答に記載されていると思いますので、あとは宿題とします。

追記

実際のケースに近い画像で試してみたところ、赤ペンやマッキーで囲んだ枠をしっかり認識していました。また赤鉛筆では薄いためか厳しく、

原因は以下です。

線が細いため、2値化した時点で枠線に隙間が空いてしまっています。

イメージ説明

これに対して、 findcontours() を行うと以下のようにそれぞれ別の輪郭あると検出されてしまいます。
そのため、複数に分割されてしまっている輪郭を1つの輪郭として統合するような処理が必要となると思います。

イメージ説明

こちらの環境のためかエラーメッセージが出る箇所もあったので追記しました。

おそらく、質問者さんの環境の OpenCV はバージョン3 なのだと思います。
findcontours() の返り値が OpenCV 3 はタプル3つだったのが、OpenCV 4 ではタプル2つに変更されました。

OpenCV - findContours() による輪郭抽出

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/06/25 21:40 編集

    大変親切でわかりやすい回答ありがとうございます!実際のケースに近い画像で試してみたところ、赤ペンやマッキーで囲んだ枠をしっかり認識していました。また赤鉛筆では薄いためか厳しく、こちらの環境のためかエラーメッセージが出る箇所もあったので追記しました。

    キャンセル

  • 2019/06/25 23:35 編集

    原因については追記しました。コメント欄で funa さんが言及されている事です。
    「キレてしまっている線をつなげる」とか「分割された輪郭を後から統合する」とかいくつか対策は考えられると思います。

    画像処理は試行錯誤が必要なものなので、いろいろ試して見てください。
    1つのQAで質問者さんが想定されるあらゆるケースに対応できるようなコードを提示するのは、すみませんが難しいです。

    キャンセル

  • 2019/06/26 10:10

    画像の難易度が一気に上がってますねw

    キャンセル

0

枠がある程度大きい場合に限定されますが
(たとえば、句点のみをギリギリ枠で囲った場合などは無理)

1:二値化する

2:白色に対してラベリングする。

3:ラベリングした結果を面積サイズでソートする。
一番大きいのは「下地」であるが、2番目は枠で囲まれた白エリアになる。

こんな感じでどうでしょうか?

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.72%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る