2014年6月20日金曜日

これなら分かる、最尤推定と事後確率最大化(MAP推定)の違い

最尤推定と事後確率最大化。どっちもガウス分布を仮定して対数を取ると2次式になるのでいままでなんとなく「同じようなもん」だと思っていた。

ここでは、観測された値をy、潜在変数をxとして話を進める。

まずはじめに訴えたいのは、どちらの手法も潜在変数xを求めたいということ。


尤度とは、あるモデル(仮説)を仮定したときの観測結果の確率P(y|x)である。xはいろいろな値が考えられるけど、観測結果を最もよく表すようにxを決定すれば現実に即していると言えよう。つまり、P(y|x)をxについて最大化するのが最尤推定。



次に事後確率最大化を考える。
まず単に事後確率といったらP(x|y)なのかP(y|x)なのかよくわからない。どちらも高校でならう事後確率の形式やし。
ここでは、観測がされた後の確率を事後確率と呼ぶ。観測が条件となる確率。つまりP(x|y)のこと。P(x|y)を最大化するの事後確率(MAP推定)だ。では最尤推定とどうちがうのか??

ベイズの定理を使うと
P(x|y) ∝P(y|x)P(x)
右辺は(尤度)*(事前確率)となっている。
ここで(事前確率)を何かの定数だと考えてP(x|y)を最大化を考えると、上の尤度を最大化する場合と同じになっている!
つまり、事後確率最大化は最尤推定に事前確率というバイアスがかかったものだということだ。事後確率は主観とか言われてこれがベイズと呼ばれるアプローチだ。
事後確率の負の対数を取って最大化すると正則化項として出てくる(よく教科書にでてくるパターン)。つまり主観がある種のブレーキとして働くと考えられる。






2014年6月17日火曜日

PyQt: Paste the clipboard image.

The last time I showed how to copy&paste the clipboard text with Excel.
Today I will show you how to deal with a copied clipboard image.

All you have to do is just like below. Not much different from the text example.
 
self.clip = QtGui.QApplication.clipboard()
qimg = self.clip.image()



You might wannat convert QImage to Numpy array, but I couldn't find the very nice solution.
I will just save the image and cv2.imread. This might be slow, but I can accept for now.
qimg.save("tmp.png")
img = cv2.imread("tmp.png")


To covert from numpy array to QImage, I referred this article.
https://stackoverflow.com/questions/18676888/how-to-configure-color-when-convert-numpy-array-to-qimage
def convert_array_to_qimg(cv_img):
    height, width, bytesPerComponent = cv_img.shape
    bytesPerLine = bytesPerComponent * width;
    #cv2.cvtColor(cv_img, cv2.CV_BGR2RGB)
    return QtGui.QImage(cv_img.data, width, height, bytesPerLine, QtGui.QImage.Format_RGB888)


The overall program looks like below.
from PyQt4 import QtGui, QtCore
import sys
import cv2
import numpy as np

def convert_array_to_qimg(cv_img):
    """
    this is based on https://stackoverflow.com/questions/18676888/how-to-configure-color-when-convert-numpy-array-to-qimage
    """
    height, width, bytesPerComponent = cv_img.shape
    bytesPerLine = bytesPerComponent * width;
    #cv2.cvtColor(cv_img, cv2.CV_BGR2RGB)
    return QtGui.QImage(cv_img.data, width, height, bytesPerLine, QtGui.QImage.Format_RGB888)



class MainWindow(QtGui.QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.image_frame = QtGui.QLabel()
        fname = r"sample08.png"
        img = cv2.imread(fname)
        #img = QtGui.QImage(fname)
        qimg = convert_array_to_qimg(img)
        self.image_frame.setPixmap(QtGui.QPixmap.fromImage(qimg))
        self.clip = QtGui.QApplication.clipboard()
        
        self.setCentralWidget(self.image_frame)
        self.move(30,30)
 
    def keyPressEvent(self, e):
        if (e.modifiers() & QtCore.Qt.ControlModifier):
            #selected = self.table.selectedRanges()
                 
            if e.key() == QtCore.Qt.Key_V:#past
                qimg = self.clip.image()
                self.displayImage(qimg)

            elif e.key() == QtCore.Qt.Key_C: #copy
                pass

    def displayImage(self, qimg):
        self.image_frame.setPixmap(QtGui.QPixmap.fromImage(qimg))


 
def main(args):
    app = QtGui.QApplication(sys.argv)
    form = MainWindow()
    form.show()
     
    sys.exit(app.exec_())
 
if __name__ == "__main__":
    main(sys.argv)






2014年6月3日火曜日

scikit-learnを使って数字認識(2) k-NNを使った学習

scikit-learnを使えば簡単な機械学習ならすぐにできる。
今回は特徴ベクトルとして画素値をもちい、それをk-NearestNeighborsにぶちこむ。
k-meansもためしたけど、こちらはあまりうまく行かなかった。
Pythonだと学習した識別器(オブジェクト)をPickleで保存して簡単に取り出せる。

10種類くらいのフォントを学習させて11種類目でためしたが、正答率は100%だった。今後もっといろんな状況下(画素が少ないサンプルとか)で試していってどこまでいけるか確かめたい。

以下メモ

学習部分
import numpy as np
import cv2
from sklearn.neighbors import KNeighborsClassifier
from sampling import convert_to_binary
"""
1. Read sample image and convert to 1d feature vector
2. pass the feature vectors to kmeans clustering maching
3. pickle the result.
"""

def convert_to_feature_vector(src):
    """
    convert source image to a 1d feature vector
    """
    N =10
    im = cv2.GaussianBlur(src,(5,5),0)
    im = cv2.resize(src, (N, N))
    im = im.reshape(N**2)
    #im = np.array(im>124, dtype=np.int8) #convert to 0 and 1
    return im


if __name__ == "__main__":
    #read jpgs, and resize them as a vector

    dirname = [str(i) for i in range(10)]
    dirname += ["dot"]
    dirname += ["bar"]

    X = [] #sample data
    Y = [] #label
    for dn in dirname:

        #input label(0-11)
        if dn == "bar":
            label = 11
        elif dn == "dot":
            label = 10
        else:
            label = int(dn)


        fnames = os.listdir(dn)
        fnames.remove(".DS_Store")

        for fn in fnames:
            #print os.path.join(dn, fn)
            im = cv2.imread(os.path.join(dn, fn))
            im = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
            im = convert_to_feature_vector(im)
            #print im.reshape(10,10)
            X.append(im)
            Y.append(label)

        #convert to np.array
    X = np.array(X)

    #
    knn = KNeighborsClassifier(n_neighbors=5)
    knn.fit(X, Y)

    #save the classifier as pickle
    import pickle
    with open("knn_trained.dump", "w") as f:
        pickle.dump(knn, f)



予測部分
#coding: utf-8

from sklearn import cluster
import pickle
import cv2
from training_knn import convert_to_feature_vector


if __name__ == "__main__":
    with open("knn_trained.dump") as f:
        knn = pickle.load(f)
        print knn

    import os
    dirname = "test_data"
    fnames = os.listdir(dirname)
    try:
        fnames.remove(".DS_Store")
    except ValueError as e:
        print e


    for fn in fnames:
        im = cv2.imread(os.path.join(dirname, fn))
        im = cv2.cvtColor(im, cv2.COLOR_BGR2GRAY)
        x = convert_to_feature_vector(im)
        y = knn.predict([x])
        print fn, y



scikit-learnを使って数字認識(1) サンプル取得

OCRはフリーのものもあるけど、数値データを起こしたいときには無駄に高機能になってしう。それだけでなくご認識も増える。

手書きでない数値(0-9, "-", ".")を認識するだけならそんなに苦労しないのでは?と思ったのでちょっと実験してみた。

フツウの人は手書き文字認識に興味があるやろけど、
調べてみるとStackoverflowに以下のような投稿があって結構盛り上がっている。
https://stackoverflow.com/questions/9413216/simple-digit-recognition-ocr-in-opencv-python

今回は

  1. 上のリンクの方法で数値を切り取り
  2. scikit-learnを使って学習
  3. 学習データを元に新しい予測
ということを行う。

まず、以下のように切り取られた画像から数値を取得したい。


イメージしている成果物は、WebやPDF上をマウスで矩形選択するとそこの数値をテキストデータに変換するツールだ。
ほぼ上のサイトと同じことをしているが、一つだけ違うところfindContoursで輪郭の取得オプションだ。opencvでは輪郭の取得のみならず、それら輪郭の関係まで返してくれる。今回はcv2.RETR_CCOMPを指定した。(一番外側の輪郭とするとなぜか図の枠が検出されたので)輪郭についてはこのページがわかりやすかった。輪郭の階層を指定することで輪郭の大きさによる分類など無駄な作業が省ける。
下のプログラムを実行するとこうなる。


if __name__ ==  "__main__":
    sample_dir = "fonts"
    sample_file = "sample03.png"
    im = cv2.imread(os.path.join(sample_dir, sample_file))
    im_copy = im.copy()
    gray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
    blur = cv2.GaussianBlur(gray,(5,5),0)
    thresh = cv2.adaptiveThreshold( src=blur, 
                                    maxValue=255, 
                                    adaptiveMethod=cv2.ADAPTIVE_THRESH_MEAN_C,#cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
                                    thresholdType=cv2.THRESH_BINARY,
                                    blockSize=5,
                                    C=3)
    thresh_copy = thresh.copy()  #thresh are destroyed when findCountours

    cv2.imshow('threshold', thresh)
    cv2.waitKey(0)


    contours,hierarchy = cv2.findContours(thresh_copy, cv2.RETR_CCOMP, cv2.CHAIN_APPROX_SIMPLE)
    print "%d contours are found" % len(contours)


    i = 0
    for con, hie in zip(contours, hierarchy[0]):
        if hie[3] != -1: #2:first child, 3:parent
            (x,y,w,h) = cv2.boundingRect(con)
            #roi = thresh[y:y+h,x:x+w]
            roi = im[y:y+h, x:x+w]

            cv2.rectangle(im_copy,(x,y),(x+w,y+h),(0,0,255),2)
            cv2.imshow('rect',im_copy)
            #cv2.imshow("threshold", roi)
            
            key = cv2.waitKey(0)
            if key == 27: # (escape to quit)
                sys.exit()

            c = chr(key)

            if c == ".":
                c = "dot"
            elif c == "-":
                c = "bar"
            elif c == "n": #negative
                c = "negative"

            file_head = sample_file.replace(".png", "")
            if c == "negative":
                fname = os.path.join(c, file_head + "_" + c + ("%02d.png"%i))
                i+=1
            else:
                fname = os.path.join(c, file_head + "_" + c + ".png")

            #write ROI 
            print fname
            #cv2.imwrite(fname, roi)