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

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

新規登録して質問してみよう
ただいま回答率
85.48%
Google Colaboratory

Google Colaboratoryとは、無償のJupyterノートブック環境。教育や研究機関の機械学習の普及のためのGoogleの研究プロジェクトです。PythonやNumpyといった機械学習で要する大方の環境がすでに構築されており、コードの記述・実行、解析の保存・共有などが可能です。

Google Apps Script

Google Apps ScriptはGoogleの製品と第三者のサービスでタスクを自動化するためのJavaScriptのクラウドのスクリプト言語です。

OpenCV

OpenCV(オープンソースコンピュータービジョン)は、1999年にインテルが開発・公開したオープンソースのコンピュータビジョン向けのクロスプラットフォームライブラリです。

Q&A

2回答

2856閲覧

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

onion1

総合スコア12

Google Colaboratory

Google Colaboratoryとは、無償のJupyterノートブック環境。教育や研究機関の機械学習の普及のためのGoogleの研究プロジェクトです。PythonやNumpyといった機械学習で要する大方の環境がすでに構築されており、コードの記述・実行、解析の保存・共有などが可能です。

Google Apps Script

Google Apps ScriptはGoogleの製品と第三者のサービスでタスクを自動化するためのJavaScriptのクラウドのスクリプト言語です。

OpenCV

OpenCV(オープンソースコンピュータービジョン)は、1999年にインテルが開発・公開したオープンソースのコンピュータビジョン向けのクロスプラットフォームライブラリです。

0グッド

2クリップ

投稿2019/06/24 13:22

編集2019/06/25 12:52

前提・実現したいこと

初心者です。色々検索しても糸口が見えないので質問させていただきます。
画像のうち黒い線で囲んだ部分を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)

気になる質問をクリップする

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

tiitoi

2019/06/24 15:05 編集

黒い線で囲むのは作成するアプリでやるのですよね? そうであれば、マウスかなにかで囲ったときの座標を取得できますか?
onion1

2019/06/24 15:35

わかりにくくて申し訳ありません。紙の一部分を実際に鉛筆の線で囲んだ写真をトリミングしたいです。
tiitoi

2019/06/24 16:03

すこし考えてみましたが、黒の文字に黒の線となると、画像処理では文字を無視して、対象としたい枠線だけを検出するというのがなかなか難しそうです。 枠線は黒以外のなにか色ペンであったりすれば、もう少し可能性はあるのですが。。
onion1

2019/06/24 16:44

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

2019/06/24 17:21

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

2019/06/25 02:14 編集

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

回答2

0

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

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

手順

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

    1. 画像を読み込む。
    1. 枠線部分のみを2値化で抽出する。
  • 2.1. HSV 色空間に変換する。
  • 2.2. 赤色とそれ以外を cv2.inRange() で2値化する。(赤以外の場合は要パラメータ調整)

OpenCV - inRange による範囲指定で2値化する方法について

  • 2.3. ノイズをモルフォロジー変換で消す。

OpenCV - モルフォロジー演算について

    1. 輪郭を抽出する。
  • 3.1. findContours() で輪郭を抽出する。

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

  • 3.2. cv2.contourArea() で輪郭の面積を計算し、誤検出の輪郭を消す。

OpenCV - 輪郭の面積を求める方法について

    1. 輪郭内部を白、それ以外を黒としたマスク画像を作成し、元画像の輪郭内部の領域のみを残す。

OpenCV - マスクを使用した画像の合成方法について

    1. 画像を保存する。

サンプルコード

以下の環境で確認

  • OpenCV 4
  • Python 3.7

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() による輪郭抽出

投稿2019/06/24 17:19

編集2019/06/25 14:31
tiitoi

総合スコア21956

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

fana

2019/06/25 02:03

紙に手でマーキングしたのを撮った絵が対象とのことなので,場合によっては 人手で施したマーキングが雑できっちり閉じていないだとか,2値化関連の処理結果がちょい切れてしまう場合等への配慮が欲しいかも感.
tiitoi

2019/06/25 02:47

コメントありがとうございます。 数ピクセルの隙間なら膨張処理で行けそうですが、それ以上空いていた場合になにかいい方法はありますでしょうか?
fana

2019/06/25 04:24

(マーキングの形状が例示されているような形の範疇ならば) 簡便な方法として,drawContoursの直前で凸包に変えてしまえば,とりあえず「一部切れたけども連続はしている(=1つのContourではある)」的なパターンに関しては救えそうな気もしますが… マーキング領域が複数個に分かれてしまっても【質問への追記・修正、ベストアンサー選択の依頼】のところに少し書いたように「対象画像が文書である」ことを利用できるならば,その構造をヒントにできそうに思います. ぱっと思いつくのは,元々存在している文字群を(縦よりも横方向に大きく)膨張させることで文字同士をくっつけて行毎の塊みたいなのにできれば,マーキングとその中の文字群の包括関係等をヒントにできるかも?とか.
tiitoi

2019/06/25 12:15

> 凸包に変えてしまえば 凸包をとることは思いつきませんでした。参考になります。 アドバイスありがとうございます。 > その構造をヒントにできそうに思います. OCR でやっていることなので、先に文字のブロックを認識してしまうのも手ですね。
onion1

2019/06/25 12:43 編集

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

2019/06/25 14:38 編集

原因については追記しました。コメント欄で funa さんが言及されている事です。 「キレてしまっている線をつなげる」とか「分割された輪郭を後から統合する」とかいくつか対策は考えられると思います。 画像処理は試行錯誤が必要なものなので、いろいろ試して見てください。 1つのQAで質問者さんが想定されるあらゆるケースに対応できるようなコードを提示するのは、すみませんが難しいです。
fana

2019/06/26 01:10

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

0

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

1:二値化する

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

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

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

投稿2019/06/24 16:57

yominet

総合スコア187

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

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

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問