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

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

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

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

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

4回答

4268閲覧

OpenCVでの画像処理~完全なる二値化~

van-0215

総合スコア89

OpenCV

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

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

0グッド

0クリップ

投稿2018/03/14 09:59

スキャナで読み込んだ画像を白黒に完全に二値化するプログラミングを書いています。

python

1img_gauss = cv2.GaussianBlur(self.gray_image(img=img),(1,1),0) 2binary_image = cv2.threshold(img_gauss, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1] 3 4photo_size = binary_image.shape[0] * binary_image.shape[1] 5for j in range(binary_image.shape[0]): 6 for k in range(binary_image.shape[1]): 7 if binary_image[j][k] == 255: 8 cnt = cnt + 1 9 10print (cnt / photo_size)

上のプログラムを実行した時、読み込んだ画像が真っ白ならば、最後の行のprintで0が出てきてほしいのですが、現状出てきてくれません。
実際には以下のような二値化した画像が出力されてしまいます。(printの出力とは別で画像を出力している)
イメージ説明

これが全てにおいて統一されているのならいいのですが、他の真っ白の画像では以下のようなものが出力されます。
イメージ説明

下はそれぞれの読み込んだ元画像です。

黒の要素が多い画像

黒の要素が少ない画像

真っ白の画像を読み込んだ時に出力結果が0になるような実装の仕方はありますか?

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

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

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

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

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

guest

回答4

0

ベストアンサー

cv2.adaptiveThresholdが使える気がします。

関数の入力パラメータを適宜変更し出力画像サンプルを出力生成するシュミレータぽいものを作成するとデバック時に楽です。
昔自作したのを置いておきます。ご参考までに。


2018/03/15追記
入力ソースがスキャナなので、スキャナの機種によってはドロップアウトカラーの設定が行えるかもしれません。マニュアルに書いてあるか一度確認してみてはどうでしょうか。。

Python

1# -*- coding: utf-8 -*- 2import argparse 3import tkinter as tk 4# library 5import cv2 6from PIL import Image, ImageTk 7 8 9class ImageData(object): 10 def __init__(self, src): 11 assert src is not None 12 self.__canvas = src.copy() 13 self.__grayscale = cv2.cvtColor(self.canvas, cv2.COLOR_BGR2GRAY) 14 15 @property 16 def canvas(self): 17 return self.__canvas 18 19 @property 20 def grayscale(self): 21 return self.__grayscale 22 23 24class Application(tk.Frame): 25 def __init__(self, master=None): 26 super().__init__(master) 27 self.data = None 28 self.createWidgets() 29 30 def createWidgets(self): 31 controls = dict() 32 self.topframe = tk.LabelFrame(self, text='params') 33 self.topframe.grid(row=0, column=0) 34 35 controls['ADAPTIVE'] = {'label':'0:MEAN_C / 1:GAUSSIAN_C', 'from_':0, 'to':1, 'length':300, 'orient':tk.HORIZONTAL, 'command':self.__onChanged_ScaleValue} 36 self.scale_adaptive = tk.Scale(self.topframe, controls['ADAPTIVE']) 37 self.scale_adaptive.set(1) 38 self.scale_adaptive.pack() 39 40 controls['THRESHOLDTYPE'] = {'label':'0:BINARY / 1:INV', 'from_':0, 'to':1, 'length':300, 'orient':tk.HORIZONTAL, 'command':self.__onChanged_ScaleValue} 41 self.scale_thresholdType = tk.Scale(self.topframe, controls['THRESHOLDTYPE']) 42 self.scale_thresholdType.pack() 43 # initial stepvalue 3. 44 controls['BLOCKSIZE'] = {'label':'blocksize', 'from_':3, 'to':255, 'length':300, 'orient':tk.HORIZONTAL, 'command':self.__onChanged_ScaleValue} 45 self.scale_blocksize = tk.Scale(self.topframe, controls['BLOCKSIZE']) 46 self.scale_blocksize.set(11) 47 self.scale_blocksize.pack() 48 49 controls['C'] = {'label':'c', 'from_':0, 'to':255, 'length':300, 'orient':tk.HORIZONTAL, 'command':self.__onChanged_ScaleValue} 50 self.scale_c = tk.Scale(self.topframe, controls['C']) 51 self.scale_c.set(2) 52 self.scale_c.pack() 53 54 self.lblimage = tk.Label(self) 55 self.lblimage.grid(row=1, column=0) 56 self.__adaptiveMethod = {0:cv2.ADAPTIVE_THRESH_MEAN_C, 1:cv2.ADAPTIVE_THRESH_GAUSSIAN_C} 57 self.__thresholdType = {0:cv2.THRESH_BINARY, 1:cv2.THRESH_BINARY_INV} 58 59 def draw(self): 60 adaptiveMethod = self.__adaptiveMethod[self.scale_adaptive.get()] 61 thresholdType = self.__thresholdType[self.scale_thresholdType.get()] 62 size = self.scale_blocksize.get() 63 c = self.scale_c.get() 64 # adaptiveThreshold params check 65 # blocksize range:Odd numbers{3,5,7,9,…} intial:3 66 # in:0,0 out:NG blocksize of even. 67 # in:2,0 out:NG blocksize of even. 68 # in:3,10 out:NG size * size - c < 0 69 # in:5,25 out:OK 70 if size % 2 == 0: 71 return 72 if (size * size - c) < 0: 73 return 74 try: 75 result = cv2.adaptiveThreshold(self.data.grayscale, 255, adaptiveMethod, thresholdType, size, c) 76 self.__changeImage(result) 77 except (Exception) as ex: 78 print(ex) 79 pass 80 81 def loadImage(self, src): 82 self.data = ImageData(src) 83 self.__changeImage(src) 84 85 def __changeImage(self, src): 86 #imgtk = ImageTk.PhotoImage(Image.fromarray(cv2.cvtColor(src, cv2.COLOR_BGR2RGB))) 87 imgtk = ImageTk.PhotoImage(Image.fromarray(src)) 88 self.lblimage.imgtk = imgtk 89 self.lblimage.configure(image=imgtk) 90 91 def __onChanged_ScaleValue(self, event): 92 self.draw() 93 94 95def main(): 96 parser = argparse.ArgumentParser(prog='adaptiveThreshold', 97 description='AdaptiveThreshold Simulator') 98 parser.add_argument('--version', action='version', version='%(prog)s 0.0.3') 99 parser.add_argument('--image', '-in', default='c5377162d636f88f9b28215e8e06439b.jpeg') 100 parser.add_argument('--delay', '-d', default='100') 101 args = parser.parse_args() 102 103 print('args:{0}'.format(args)) 104 105 app = Application() 106 app.master.title('AdaptiveThreshold Simulator') 107 app.loadImage(cv2.imread(args.image)) 108 app.pack() 109 app.mainloop() 110 111 112if __name__ == "__main__": 113 main()

投稿2018/03/14 12:25

編集2018/03/14 23:15
umyu

総合スコア5846

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

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

KSwordOfHaste

2018/03/14 13:31

自分が「こうかな」と思ったこと+αのことを考慮してくれるのがumyuさん回答のadaptiveThresholdかなぁと思いました。
van-0215

2018/03/15 01:55

ありがとうございました。 umyuさんの仰る通りcv2.adaptidThresholdを使用したら理想の結果に近い値が出力されました。
guest

0

大津の二値化を使ってしまうとLouiS0616さんコメントの通り閾値が自動計算されてしまうと思うのですが、白っぽいからといって「それが真っ白」という結論にはできないと思います。

そもそも二値化は「あるパターンがそこにある」という前提で行うものです。そのパターンが画像の特徴「全体的に明るい画像か暗い画像か」にかかわらず「明るいなら明るいなりに、暗いなら暗いなりに」平均的な画素地より明るいか暗いかで二値化するのが大津の二値化の狙いなわけですので、そういう処理の結果をもって実画像から「真っ白」という判断はできないと思います。

cv2.THRESH_OTSUによる二値化ではなく、事前に平均明度を計算し、そこから閾値を定め、あくまでその閾値以上か以下かで二値化するとよいのではないでしょうか?

投稿2018/03/14 11:12

編集2018/03/14 11:14
KSwordOfHaste

総合スコア18394

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

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

LouiS0616

2018/03/14 11:44 編集

その場合、『白い紙の一部に文字が書いてある』ときに真っ白とみなされる懸念がありませんか?
KSwordOfHaste

2018/03/14 11:57

おっしゃるとおりと思います。 それはGaussianBlurでどの程度ぼかすかやどの程度にはっきり書かれたパターンを検出すべきかによって影響を受けると思います。
KSwordOfHaste

2018/03/14 12:03 編集

例えばLouiS0616さん回答にあるように全体的な明度がある値以上(それより暗いものは白い紙ではなかったと判断)かつその平均明度よりある程度低い(白地にグレーの文字などを想定するなら高めの閾値、真っ黒なインクで書かれた文字を識別するならずっと低めの閾値)を設定することになるだろうと思います。スキャナーで取り込んだ印刷原稿なのか一般の写真を解析するかなど入力画像の違いや解析の目的によって閾値の調整には余地がある気がします。
van-0215

2018/03/15 01:56

お二人の発言をみていて、勉強になる点が多かったです。 今回は回答してくださりありがとうございました。
guest

0

2値化の閾値をもっと上げてみたらどうですか?

投稿2018/03/14 10:08

TaroToyotomi

総合スコア1430

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

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

LouiS0616

2018/03/14 10:10

大津の二値化ならば、閾値は動的に決まっているような。
TaroToyotomi

2018/03/14 10:18

あっ!本当だ。見逃してました。すみません。 元画像がjpegのべた画像に大津の2値化を使うのってやりたいことに対して正しいのか分からないです。
TaroToyotomi

2018/03/14 10:30

2値化する前にヒストグラムを計算して、そのボトムとピークの差が狭すぎる場合は、べた画像と判断して2値化せずに真っ白に塗りつぶしちゃえばいいんじゃないですかね。
LouiS0616

2018/03/14 10:39

それも有りな気がしますね。
guest

0

真っ白と言っても、実際はノイズが入っているわけですよね。
これに対して大津の二値化をかけると、白に限りなく近い値が閾値となってしまいます。

強引な感がありますが、閾値がある程度255に近いなら真っ白とみなす、とかですかね。

Python

1th, bin_img = cv2.threshold( 2 img, 0, 255, 3 cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU 4) 5if th > 200: 6 bin_img = np.ones(bin_img.shape) * 255

投稿2018/03/14 10:36

編集2018/03/14 10:37
LouiS0616

総合スコア35660

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問