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

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

ただいまの
回答率

90.46%

  • Python

    12384questions

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

  • OpenCV

    1573questions

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

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

解決済

回答 4

投稿

  • 評価
  • クリップ 0
  • VIEW 1,489

van-0215

score 69

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

img_gauss =  cv2.GaussianBlur(self.gray_image(img=img),(1,1),0)
binary_image = cv2.threshold(img_gauss, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)[1]

photo_size = binary_image.shape[0] * binary_image.shape[1]
for j in range(binary_image.shape[0]):
    for k in range(binary_image.shape[1]):
        if binary_image[j][k] == 255:
           cnt = cnt + 1

print (cnt / photo_size)

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

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

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

黒の要素が多い画像

黒の要素が少ない画像

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

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 4

checkベストアンサー

+2

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

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


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

# -*- coding: utf-8 -*-
import argparse
import tkinter as tk
# library
import cv2
from PIL import Image, ImageTk


class ImageData(object):
    def __init__(self, src):
        assert src is not None
        self.__canvas = src.copy()
        self.__grayscale = cv2.cvtColor(self.canvas, cv2.COLOR_BGR2GRAY)

    @property
    def canvas(self):
        return self.__canvas

    @property
    def grayscale(self):
        return self.__grayscale


class Application(tk.Frame):
    def __init__(self, master=None):
        super().__init__(master)
        self.data = None
        self.createWidgets()

    def createWidgets(self):
        controls = dict()
        self.topframe = tk.LabelFrame(self, text='params')
        self.topframe.grid(row=0, column=0)

        controls['ADAPTIVE'] = {'label':'0:MEAN_C / 1:GAUSSIAN_C', 'from_':0, 'to':1, 'length':300, 'orient':tk.HORIZONTAL, 'command':self.__onChanged_ScaleValue}
        self.scale_adaptive = tk.Scale(self.topframe, controls['ADAPTIVE'])
        self.scale_adaptive.set(1)
        self.scale_adaptive.pack()

        controls['THRESHOLDTYPE'] = {'label':'0:BINARY / 1:INV', 'from_':0, 'to':1, 'length':300, 'orient':tk.HORIZONTAL, 'command':self.__onChanged_ScaleValue}
        self.scale_thresholdType = tk.Scale(self.topframe, controls['THRESHOLDTYPE'])
        self.scale_thresholdType.pack()
        # initial stepvalue 3.
        controls['BLOCKSIZE'] = {'label':'blocksize', 'from_':3, 'to':255, 'length':300, 'orient':tk.HORIZONTAL, 'command':self.__onChanged_ScaleValue}
        self.scale_blocksize = tk.Scale(self.topframe, controls['BLOCKSIZE'])
        self.scale_blocksize.set(11)
        self.scale_blocksize.pack()

        controls['C'] = {'label':'c', 'from_':0, 'to':255, 'length':300, 'orient':tk.HORIZONTAL, 'command':self.__onChanged_ScaleValue}
        self.scale_c = tk.Scale(self.topframe, controls['C'])
        self.scale_c.set(2)
        self.scale_c.pack()

        self.lblimage = tk.Label(self)
        self.lblimage.grid(row=1, column=0)
        self.__adaptiveMethod = {0:cv2.ADAPTIVE_THRESH_MEAN_C, 1:cv2.ADAPTIVE_THRESH_GAUSSIAN_C}
        self.__thresholdType = {0:cv2.THRESH_BINARY, 1:cv2.THRESH_BINARY_INV}

    def draw(self):
        adaptiveMethod = self.__adaptiveMethod[self.scale_adaptive.get()]
        thresholdType = self.__thresholdType[self.scale_thresholdType.get()]
        size = self.scale_blocksize.get()
        c = self.scale_c.get()
        # adaptiveThreshold params check
        # blocksize range:Odd numbers{3,5,7,9,…} intial:3
        #   in:0,0  out:NG blocksize of even.
        #   in:2,0  out:NG blocksize of even.
        #   in:3,10 out:NG size * size - c < 0
        #   in:5,25 out:OK
        if size % 2 == 0:
            return
        if (size * size - c) < 0:
            return
        try:
            result = cv2.adaptiveThreshold(self.data.grayscale, 255, adaptiveMethod, thresholdType, size, c)
            self.__changeImage(result)
        except (Exception) as ex:
            print(ex)
            pass

    def loadImage(self, src):
        self.data = ImageData(src)
        self.__changeImage(src)

    def __changeImage(self, src):
        #imgtk = ImageTk.PhotoImage(Image.fromarray(cv2.cvtColor(src, cv2.COLOR_BGR2RGB)))
        imgtk = ImageTk.PhotoImage(Image.fromarray(src))
        self.lblimage.imgtk = imgtk
        self.lblimage.configure(image=imgtk)

    def __onChanged_ScaleValue(self, event):
        self.draw()


def main():
    parser = argparse.ArgumentParser(prog='adaptiveThreshold',
                                     description='AdaptiveThreshold Simulator')
    parser.add_argument('--version', action='version', version='%(prog)s 0.0.3')
    parser.add_argument('--image', '-in', default='c5377162d636f88f9b28215e8e06439b.jpeg')
    parser.add_argument('--delay', '-d', default='100')
    args = parser.parse_args()

    print('args:{0}'.format(args))

    app = Application()
    app.master.title('AdaptiveThreshold Simulator')
    app.loadImage(cv2.imread(args.image))
    app.pack()
    app.mainloop()


if __name__ == "__main__":
    main()

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2018/03/14 22:31

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

    キャンセル

  • 2018/03/15 10:55

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

    キャンセル

+2

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

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

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

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2018/03/14 20:42 編集

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

    キャンセル

  • 2018/03/14 20:57

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

    キャンセル

  • 2018/03/14 21:03 編集

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

    キャンセル

  • 2018/03/15 10:56

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

    キャンセル

+1

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

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2018/03/14 19:10

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

    キャンセル

  • 2018/03/14 19:18

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

    キャンセル

  • 2018/03/14 19:30

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

    キャンセル

  • 2018/03/14 19:39

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

    キャンセル

0

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

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

th, bin_img = cv2.threshold(
    img, 0, 255,
    cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU
)
if th > 200:
    bin_img = np.ones(bin_img.shape) * 255

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

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

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

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

  • Python

    12384questions

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

  • OpenCV

    1573questions

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