石油王研究課|CIFLAB

CIFLAB石油王研究課の活動ブログです。

辞書型の要素に関数をいれると便利だった

プログラミングをしているときに、たくさん条件分岐したいっていう時ないですか?僕はあります。

それで例えばこんな感じにifとelifで繋げるとするじゃないですか

def f_a():
    return 1
def f_b():
    return 2
def f_c():
    return 3
def f_d():
    return 4
def f_e():
    return 5
def f_f():
    return 6

tmp_str = 'a'

if tmp_str == 'a':
    print(f_a())
elif tmp_str == 'b':
    print(f_b())
elif tmp_str == 'c':
    print(f_c())
elif tmp_str == 'd':
    print(f_d())
elif tmp_str == 'f':
    print(f_f())

だらだらとしていて、見苦しいなーっと思いました。それでふと辞書にすればスッキリするんじゃないかと思って関数を要素にすればいいじゃんと気づきました。
関数ポインタを使ってこんな感じに書き直してみました。

def f_a():
    return 1
def f_b():
    return 2
def f_c():
    return 3
def f_d():
    return 4
def f_e():
    return 5
def f_f():
    return 6

tmp_str = 'a'
func_tmp = {'a':f_a, 'b':f_b, 'c':f_c, 'd':f_d, 'e':f_e, 'f':f_f}
print(func_tmp[tmp_str]())

結構スッキリした気がします。ただあまりこの書き方見ない気がするので、もしかすると良くないのかもしれない…そもそも凄い数の条件分岐がある時点でだめなんだろうなと思ったりした。

python + opencvで個人認識 その4

GW最終日、明日から海の日までしばらく祝日がないのでテンションが下がっています。テンションが下がりつつ、個人認識の続きを書きました。

今回は前回学習したモデルを用いて、自分の顔を認識させたいと思います。そして何番煎じか分かりませんが、認識させたとき自分の顔を笑い男の画像と置き換えたいと思います。

流れとしては以下を実装しました。

  1. USBカメラから画像を読み込む
  2. カスケード分類器を用いて、顔の候補領域を取り出す
  3. 候補領域を学習した識別器にかけ、本人か識別する
  4. 本人ならば笑い男の画像を合成する

画像の読み込みはその1で書いてあるため飛ばします。

顔の候補領域はopencvにあるカスケード分類器を用います。カスケード分類器に関しては以下のサイトを参考にしました。

http://opencv.jp/opencv-2.2/c/objdetect_cascade_classification.html

カスケード分類器に関するコードはこちらになります。デフォルトで用意されている特徴分類器を用います。
今回は正面顔と横顔用の特徴分類器を用いました。

    def cascade_init(self):
        profilecascade_path = 'haarcascade_profileface.xmlのパス'
        frontalcascade_path = 'haarcascade_frontalface_alt2.xmlのパス'

        self.profile_cascade = cv2.CascadeClassifier(profilecascade_path)
        self.frontal_cascade = cv2.CascadeClassifier(frontalcascade_path)

    def execute_cascade(self, frame):
        profile_facerect = self.profile_cascade.detectMultiScale(frame,
        scaleFactor=1.1, minNeighbors=2, minSize=(1, 1))

        frontal_facerect = self.frontal_cascade.detectMultiScale(frame,
        scaleFactor=1.1, minNeighbors=2, minSize=(1, 1))

        return profile_facerect, frontal_facerect

次に候補領域から取り出した画像をcnnで自分の顔かどうかを識別させます。
CNNの設定でsimple_predictで画像一枚を入力として、識別結果を返すように設定していました。また今回は自分の顔と認識した中で最も大きい領域を自分の顔の領域としました。これは自分の顔周辺をいくつか候補として出した場合、自分の顔と識別する結果が複数得られるためです。

    def cnn_init(self):
        TRAIN_BATCH = 100
        TEST_BATCH = 90
        self.cnn_net = TensorCNN(TRAIN_BATCH, TEST_BATCH, self.IMG_WIDTH, self.IMG_HEIGHT)
        self.cnn_net.net_init()

    def load_cnnmodel(self, path):
        self.cnn_net.load_model(path)
    
    def rect_cnn(self, imgs, rects):
        get_area = lambda tmprect: (tmprect[2] - tmprect[0]) * (tmprect[3] - tmprect[1])

        myimg_rect = []
        rect_area = []
        for img,rect in zip(imgs,rects):
            res = self.predict_cnn(img)
            print(res)
            if np.argmax(res) == self.MYIMG_LABEL:
                self.exit_prob += 1
                myimg_rect.append(rect)
                rect_area.append(get_area(rect))

        return myimg_rect, rect_area

    def predict_cnn(self, img):
        input_img = cv2.resize(img,(self.IMG_WIDTH, self.IMG_HEIGHT))
        input_img = np.reshape(input_img,(1,self.IMG_HEIGHT,self.IMG_WIDTH,self.CH))
        predict_dict = {self.cnn_net.predict_input: input_img,
        self.cnn_net.keep_prob: 1.0}
        return self.cnn_net.sess.run(self.cnn_net.simple_predict, feed_dict=predict_dict)

最後に自分の顔画像があればそれを笑い男の画像に取り替えます。透過領域があるpng画像からマスクを生成して、合成しました。

    def combine_img(self, frame, img, x,y):
        height_over_check = lambda x: self.HEIGHT if x > self.HEIGHT else x
        width_over_check = lambda x: self.WIDTH if x > self.WIDTH else x

        img = cv2.resize(img, (int(self.WIDTH/1.2), int(self.HEIGHT/1.2)))
        height,width = img.shape[:2]
        
        ex = width_over_check(x+width)
        ey = height_over_check(y+height)

        mask = img[:,:,3]   #これでアルファチャンネルのみの行列が抽出。
        mask = cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR)
        mask = mask/255.0 

        img = img[:,:,:3]
        frame_float = frame.astype(np.float64)

        frame_float[y:ey, x:ex] *= 1 - mask[:(ey-y),:(ex-x)]
        frame_float[y:ey, x:ex] += img[:(ey-y),:(ex-x)] * mask[:(ey-y),:(ex-x)]

        return frame_float

これらを組み合わせると、自分の顔画像が見つかったときに笑い男の画像を貼り付けます。

f:id:fortis3m:20180506213809p:plain

ただ、今回過学習しているためか変な領域を自分の顔と認識したり、カメラからの距離に応じて認識精度が変わってしまいました。
対策としてはシンプルに画像量を増やすか、顔認識で優秀なモデルを借りて、追加で自分の顔を認識させるとかが考えられます。

また顔候補領域が顔の向きによっては出にくい、処理が重くてカクカクするなどの問題もありました。

やっぱり、顔認識とかの論文を読んだ方が良さそうだなというのが素直な感想です。
一応以下に全コードをあげておきます。

import cv2
import time
import numpy as np

from cnn import TensorCNN

class FaceRecognition:
    def __init__(self, size):
        self.IMG_HEIGHT = 64
        self.IMG_WIDTH = 64
        self.CH = 3
        self.MARGIN = 0

        self.MYIMG_LABEL = 1
        
        self.WIDTH = size[0]
        self.HEIGHT = size[1]

        self.exit_prob = 0
        self.judge = 0

    def cnn_init(self):
        TRAIN_BATCH = 100
        TEST_BATCH = 90
        self.cnn_net = TensorCNN(TRAIN_BATCH, TEST_BATCH, self.IMG_WIDTH, self.IMG_HEIGHT)
        self.cnn_net.net_init()

        self.cnn_recoglog = []

    def load_cnnmodel(self, path):
        self.cnn_net.load_model(path)
        print('CNN model downloaded is success')
    
    def rect_cnn(self, imgs, rects):
        get_area = lambda tmprect: (tmprect[2] - tmprect[0]) * (tmprect[3] - tmprect[1])

        myimg_rect = []
        rect_area = []
        for img,rect in zip(imgs,rects):
            res = self.predict_cnn(img)
            print(res)
            if np.argmax(res) == self.MYIMG_LABEL:
                self.exit_prob += 1
                myimg_rect.append(rect)
                rect_area.append(get_area(rect))

        return myimg_rect, rect_area

    def predict_cnn(self, img):
        input_img = cv2.resize(img,(self.IMG_WIDTH, self.IMG_HEIGHT))
        input_img = np.reshape(input_img,(1,self.IMG_HEIGHT,self.IMG_WIDTH,self.CH))
        predict_dict = {self.cnn_net.predict_input: input_img,
        self.cnn_net.keep_prob: 1.0}
        return self.cnn_net.sess.run(self.cnn_net.simple_predict, feed_dict=predict_dict)

    #profile 横顔
    #frontalface 正面顔
    def cascade_init(self):
        profilecascade_path = 'haarcascade_profileface.xmlへのパス'
        frontalcascade_path = 'haarcascade_frontalface_alt2.xmlへのパス'

        self.profile_cascade = cv2.CascadeClassifier(profilecascade_path)
        self.frontal_cascade = cv2.CascadeClassifier(frontalcascade_path)

        self.pro_cascade_log = []
        self.front_cascade_log = []

        print('Cascade init succeded...')

    def execute_cascade(self, frame):
        profile_facerect = self.profile_cascade.detectMultiScale(frame,
        scaleFactor=1.1, minNeighbors=2, minSize=(20, 20))

        frontal_facerect = self.frontal_cascade.detectMultiScale(frame,
        scaleFactor=1.1, minNeighbors=2, minSize=(20, 20))

        return profile_facerect, frontal_facerect
        #return frontal_facerect

    def set_cascade_log(self, pro_facerect, fro_facerect):
        #if len(pro_facerect) > 0:
        #    self.pro_cascade_log = [rect for rect in pro_facerect]

        if len(fro_facerect) > 0:
            self.front_cascade_log = [rect for rect in fro_facerect]
    
    def set_cnn_rectlog(self, rects, areas):
        self.judge += 1
        if len(rects) > 0:
            self.exit_prob += 1
            self.cnn_recoglog = []
            index = np.argmax(areas)
            self.cnn_recoglog = rects[index]

    #候補領域周辺から取得する
    def sample_img(self, frame):
        img_list = []
        rect_list = []

        if len(self.pro_cascade_log) > 0:
            for rect in self.pro_cascade_log:
                img_list.append(self.rect2img(rect, frame))
                rect_list.append(rect)

        if len(self.front_cascade_log) > 0:
            for rect in self.front_cascade_log:
                img_list.append(self.rect2img(rect, frame))
                rect_list.append(rect)

        if len(self.cnn_recoglog) > 0:
            img_list.append(self.rect2img(self.cnn_recoglog, frame))
            rect_list.append(rect)        
        return img_list,rect_list

    def rect2img(self,rect,frame):
        zero_check = lambda x: 0 if x < 0 else x
        height_over_check = lambda x: self.HEIGHT if x > self.HEIGHT else x
        width_over_check = lambda x: self.WIDTH if x > self.WIDTH else x

        sx = zero_check(rect[0] - self.MARGIN)
        sy = zero_check(rect[1] - self.MARGIN)

        ex = width_over_check(rect[0] + rect[2] + self.MARGIN)
        ey = height_over_check(rect[1] + rect[3] + self.MARGIN)

        return frame[sy:ey,sx:ex]

    def combine_img(self, frame, img, x,y):
        zero_check = lambda x: 0 if x < 0 else x
        height_over_check = lambda x: self.HEIGHT if x > self.HEIGHT else x
        width_over_check = lambda x: self.WIDTH if x > self.WIDTH else x

        bias = 200
        x = zero_check(x-bias)
        y = zero_check(y-bias)

        img = cv2.resize(img, (int(self.WIDTH/1.2), int(self.HEIGHT/1.2)))
        height,width = img.shape[:2]
        
        ex = width_over_check(x+width)
        ey = height_over_check(y+height)

        mask = img[:,:,3]   #これでアルファチャンネルのみの行列が抽出。
        mask = cv2.cvtColor(mask, cv2.COLOR_GRAY2BGR)  #maskを3色分にした。0 ->(B0, G0, R0)になる。
        mask = mask/255.0   #正規化

        img = img[:,:,:3]
        frame_float = frame.astype(np.float64)

        frame_float[y:ey, x:ex] *= 1 - mask[:(ey-y),:(ex-x)]
        frame_float[y:ey, x:ex] += img[:(ey-y),:(ex-x)] * mask[:(ey-y),:(ex-x)]

        return frame_float

    def capture_camera(self,size=None):
        cap_flag = False
        cv2.namedWindow("Get_faceimg")
        cap = cv2.VideoCapture(0)

        #-1をつけることでαチャネルを読み込む
        waraiotoko = cv2.imread('warai_flat.png',-1)
        check_rate = 0
                
        while True:
            # retは画像を取得成功フラグ
            ret, frame = cap.read()
            frame = cv2.resize(frame, (self.WIDTH,self.HEIGHT))

            check_rate += 1
            #毎フレーム処理すると重いので適当なタイミングで検出処理を行う
            if check_rate > 3:
                image_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
                pro_facerect,fro_facerect = self.execute_cascade(image_gray)
                self.set_cascade_log(pro_facerect,fro_facerect)
                check_rate = 0

            imgs,rects = self.sample_img(frame)
            rects, areas = self.rect_cnn(imgs, rects)
            
            self.set_cnn_rectlog(rects, areas)

            if len(self.cnn_recoglog) > 0 and self.exit_prob/self.judge > 0.8:
                frame = self.combine_img(frame,waraiotoko,
                self.cnn_recoglog[0],self.cnn_recoglog[1])

            frame= frame.astype(np.uint8)
            cv2.imshow('camera capture', frame)

            k = cv2.waitKey(10) # 10msec待つ
            if k == 27: # ESCキーで終了
                break
            if k == 0x73:
               cv2.imwrite('result/' + 'res' + '.png',frame)

        # キャプチャを解放する
        cap.release()
        cv2.destroyAllWindows()

if __name__ == '__main__':
    face_recog = FaceRecognition(size = (800,600))
    face_recog.cascade_init()
    face_recog.cnn_init()
    face_recog.load_cnnmodel('models/')
    face_recog.capture_camera()
    

python + opencvで個人認識 その3

前回、前々回で学習データの取得を行いました。実は前回、前々回の方法だと大量のデータを取ってくるのには向いていないんですよね。自画像の方はムービーにしてひたすら撮りまくれば増やせるんですが、スクレイピングの方が一度に20枚しか取れませんでした。google画像検索で何か対策されているっぽいんですよね。スクレイピングを改良してデータを増やすのもいいと思ったんですが、折角学習させるということで今回はデータを水増しさせます。

確かその1で水増しはしないと言っていましたが、あれは嘘です。なので今回はデータを水増しさせて、それで学習を行い自分の顔を認識できるのかを確認していきたいと思います。

今回水増し処理として、ガウス分布に基づくノイズ、ごま塩ノイズ、平滑化処理、アフィン変換を行いました。

水増しを行う処理はopencvで以下のように書きました。

import cv2
import numpy as np

class DataPadder:    
    def padding_gammmanoise(self, img, mean=0, sigma=15):
        src = img
        row,col,ch= src.shape
        gauss = np.random.normal(mean,sigma,(row,col,ch))
        gauss = gauss.reshape(row,col,ch)
        gauss_img = src + gauss

        return gauss_img

    def padding_saltpappernoise(self, img, s_vs_p=0.5, amount=0.004):
        src = img
        row,col,ch = src.shape
        sp_img = src.copy()

        # 塩モード
        num_salt = np.ceil(amount * src.size * s_vs_p)
        coords = [np.random.randint(0, i-1 , int(num_salt)) for i in src.shape]
        sp_img[coords[:-1]] = (255,255,255)

        # 胡椒モード
        num_pepper = np.ceil(amount* src.size * (1. - s_vs_p))
        coords = [np.random.randint(0, i-1 , int(num_pepper)) for i in src.shape]
        sp_img[coords[:-1]] = (0,0,0)

        return sp_img

    def padding_smoothing(self, img, mask):
        src = img
        blur_img = cv2.blur(src, mask)
        return blur_img

    def padding_affine(self, img, rotate, scale=1.0):
        src = img
        row,col,ch = src.shape
        sp_img = src.copy()

        center = (row/2,col/2)

        matrix = cv2.getRotationMatrix2D(center, rotate, scale)
        affine = cv2.warpAffine(sp_img, matrix, (row,col))
        return affine

それでこれを用いて画像を水増しします。こんな風に

from datapadder import DataPadder
from dataread import DataRead

import cv2
import time

dr = DataRead()
padder = DataPadder()

dr.write_pathlabel('dataset','path.txt')
imgdatas = dr.read_imgdata('path.txt',(64,64),2)

myface_img = []
face_img = []

rotates = [45,90,135,180,225,270,315]

#ノイズ、ごま塩、回転(45,90,135,180,225,270,315)
for img,label in imgdatas:
    if label[1] == 1:
        myface_img.append(padder.padding_gammmanoise(img))
        myface_img.append(padder.padding_saltpappernoise(img))
        myface_img.append(padder.padding_smoothing(img, (5,5)))
        for rotate in rotates:
            myface_img.append(padder.padding_affine(img, rotate))            
    else:
        face_img.append(padder.padding_gammmanoise(img))        
        face_img.append(padder.padding_saltpappernoise(img))
        face_img.append(padder.padding_smoothing(img, (5,5)))
        for rotate in rotates:
            face_img.append(padder.padding_affine(img, rotate))

now = int(time.time())

for i,img in enumerate(myface_img):
    cv2.imwrite('paddimg/myface/' + str(now) + str(i) + '.png',img)

for i,img in enumerate(face_img):
    cv2.imwrite('paddimg/face/' + str(now)  + str(i) + '.png',img)

そうすると、こんな感じの画像たちから
f:id:fortis3m:20180430153524p:plain

こんなのができます。
f:id:fortis3m:20180430153711p:plain
いきなりインド人が現れていますが、写ってないだけで元の画像にちゃんといます。

さて、これでようやく学習を行うためのデータが集まりました。待ちに待った学習を行っていきます。

今回はTensorflowでCNNを学習させます。

二層のcnnと全結合層をtensorflowで実装しました。以下、cnnの実装です。

import tensorflow as tf
import numpy as np

class TensorCNN:
    def __init__(self, train_batch, test_batch, width, height):
        # Set model parameters
        self.trainbatch_size = train_batch
        self.testbatch_size = test_batch

        self.learning_rate = 0.01
        self.image_width = width
        self.image_height = height

        self.target_size = 2

        self.num_channels = 3 # greyscale = 1 channel

        self.conv1_features = 20
        self.conv1_ksize = 4
        self.conv2_features = 30
        self.conv2_ksize = 4

        self.max_pool_size1 = 2 # NxN window for 1st max pool layer
        self.max_pool_size2 = 2 # NxN window for 2nd max pool layer
        self.fully_connected_size1 = 100
    
    def save_model(self,path):        
        saver = tf.train.Saver()
        saver.save(self.sess, path)
        
    def load_model(self,path):
        saver = tf.train.Saver()
        saver.restore(self.sess,path)

    def net_init(self):
        
        self.sess = tf.Session()
        with tf.variable_scope("net") as scope:
            self.val_init()
            self.weight_init()
            self.model_init()
            self.loss_init()
            self.predict_init()
            self.optimizer_init()        
        
        # Initialize Variables
        init = tf.global_variables_initializer()
        self.sess.run(init)

    def end(self):
        self.sess.close()
        
    def train_run(self,batch_x,batch_t):
        train_dict = {self.x_input:batch_x,self.y_target:batch_t,self.keep_prob:0.5}
        self.sess.run(self.train_step,feed_dict=train_dict)
        temp_train_loss, temp_train_preds = self.sess.run([self.loss, self.prediction], feed_dict=train_dict)
        temp_train_acc = self.get_accuracy(temp_train_preds, batch_t)
        
        return temp_train_acc,temp_train_loss
    
    def test_run(self,batch_testx,batch_testt):
        test_dict = {self.eval_input: batch_testx, self.eval_target: batch_testt,self.keep_prob:1.0}
        temptest_loss,temptest_preds = self.sess.run([self.test_loss,self.test_prediction], feed_dict=test_dict)
        temp_test_acc = self.get_accuracy(temptest_preds, batch_testt)    
        
        return temp_test_acc,temptest_loss    
    
    def val_init(self):
        x_input_shape = (self.trainbatch_size, self.image_width, self.image_height, self.num_channels)
        self.x_input = tf.placeholder(tf.float32, x_input_shape)
        self.y_target = tf.placeholder(tf.int32, (self.trainbatch_size,self.target_size))

        eval_input_shape = (self.testbatch_size, self.image_width, self.image_height, self.num_channels)
        self.eval_input = tf.placeholder(tf.float32, shape=eval_input_shape)
        self.eval_target = tf.placeholder(tf.int32, shape=(self.testbatch_size, self.target_size))

        predict_input_shape = (1, self.image_width, self.image_height, self.num_channels)
        self.predict_input = tf.placeholder(tf.float32, shape=predict_input_shape)
        self.predict_target = tf.placeholder(tf.int32, shape=(1,self.target_size))
    
    def weight_init(self):
        # Convolutional layer variables
        conv1_initval = np.sqrt(1/self.image_height * self.image_width)

        self.conv1_weight = tf.Variable(tf.truncated_normal([
        self.conv1_ksize,self.conv1_ksize,
        self.num_channels, self.conv1_features],
        stddev=conv1_initval, dtype=tf.float32))

        self.conv1_bias = tf.Variable(tf.zeros(
            [self.conv1_features], dtype=tf.float32))

        num_units = ((self.image_height - self.conv1_ksize)/2) ** 2
        conv2_initval = np.sqrt(1/num_units)
        
        self.conv2_weight = tf.Variable(tf.truncated_normal(
            [self.conv2_ksize,self.conv2_ksize,
            self.conv1_features, self.conv2_features],
            stddev=conv2_initval, dtype=tf.float32))

        self.conv2_bias = tf.Variable(
            tf.zeros([self.conv2_features], dtype=tf.float32))

        # fully connected variables
        resulting_width = self.image_width // (self.max_pool_size1 + self.max_pool_size2)

        resulting_height = self.image_height // (self.max_pool_size1 + self.max_pool_size2)

        full1_input_size = resulting_width * resulting_height * self.conv2_features

        full1_initval = np.sqrt( 1/((full1_input_size)/2 + 1) )

        self.full1_weight = tf.Variable(tf.truncated_normal(
            [full1_input_size, self.fully_connected_size1],
            stddev=full1_initval, dtype=tf.float32))
        
        self.full1_bias = tf.Variable(tf.truncated_normal(
            [self.fully_connected_size1], stddev=0.1, dtype=tf.float32))
        
        self.full2_weight = tf.Variable(tf.truncated_normal(
            [self.fully_connected_size1, self.target_size],
            stddev=np.sqrt(1/self.fully_connected_size1), dtype=tf.float32))

        self.full2_bias = tf.Variable(
            tf.truncated_normal([self.target_size],
            stddev=0.1, dtype=tf.float32))

        #dropout
        self.keep_prob = tf.placeholder(tf.float32)
    
    # Initialize Model Operations
    def my_conv_net(self,input_data):
        # First Conv-ReLU-MaxPool Layer
        conv1 = tf.nn.conv2d(input_data, self.conv1_weight, strides=[1, 1, 1, 1], padding='SAME')
        relu1 = tf.nn.relu(tf.nn.bias_add(conv1, self.conv1_bias))
        
        max_pool1 = tf.nn.max_pool(relu1,
        ksize=[1, self.max_pool_size1, self.max_pool_size1, 1],
        strides=[1, 2, 2, 1], padding='SAME')

        # Second Conv-ReLU-MaxPool Layer
        conv2 = tf.nn.conv2d(
            max_pool1, self.conv2_weight,
            strides=[1, 1, 1, 1], padding='SAME')
        relu2 = tf.nn.relu(tf.nn.bias_add(conv2, self.conv2_bias))
        max_pool2 = tf.nn.max_pool(
            relu2, ksize=[1, self.max_pool_size2, self.max_pool_size2, 1],
            strides=[1, 2, 2, 1], padding='SAME')

        self.max_pool2 = max_pool2
        
        # Transform Output into a 1xN layer for next fully connected layer
        final_conv_shape = max_pool2.get_shape().as_list()
        final_shape = final_conv_shape[1] * final_conv_shape[2] * final_conv_shape[3]
        flat_output = tf.reshape(max_pool2, [final_conv_shape[0], final_shape])

        # First Fully Connected Layer
        fully_connected1 = tf.nn.relu(tf.add(tf.matmul(flat_output, self.full1_weight), self.full1_bias))
        fully_connected1 = tf.nn.dropout(fully_connected1,self.keep_prob)

        # Second Fully Connected Layer
        final_model_output = tf.add(
            tf.matmul(fully_connected1, self.full2_weight), self.full2_bias)
        
        self.fc2 = final_model_output
        
        return(final_model_output)
    
    def model_init(self):
        self.model_output = self.my_conv_net(self.x_input)
        self.test_model_output = self.my_conv_net(self.eval_input)
        self.predict_model_output = self.my_conv_net(self.predict_input)
    
    def loss_init(self):
        # Declare Loss Function (softmax cross entropy)
        self.loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=self.model_output, labels=self.y_target))
        self.test_loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=self.test_model_output, labels=self.eval_target))
        
    def predict_init(self):
        # Create a prediction function
        self.prediction = tf.nn.softmax(self.model_output)
        self.test_prediction = tf.nn.softmax(self.test_model_output)
        self.simple_predict = tf.nn.softmax(self.predict_model_output)

    # Create accuracy function
    def get_accuracy(self,logits, targets):
        batch_predictions = np.argmax(logits, axis=1)
        num_correct = np.sum(np.equal(batch_predictions, np.argmax(targets, axis=1)))
        return(100. * num_correct/batch_predictions.shape[0])
    
    def optimizer_init(self):
        my_optimizer = tf.train.MomentumOptimizer(self.learning_rate, 0.9)
        self.train_step = my_optimizer.minimize(self.loss)

このcnnのクラスを用いて学習させた結果を下図に載せます。それぞれ、正答率と損失関数になります。

f:id:fortis3m:20180505165254p:plain
f:id:fortis3m:20180505165304p:plain

収束しているみたいです。ただ、元々の画像の量が少なかったので実際に使ってみて個人認識ができるのかどうか気になるところです。

一応学習させ、プロットしたときのコードを載せておきます。

import matplotlib.pyplot as plt
import numpy as np

from cnn import TensorCNN
from dataread import DataRead

IMG_HEIGHT = 64
IMG_WIDTH = 64
CH_NUM = 3
TARGET_SIZE = 2
TRAIN_BATCH = 100
TEST_BATCH = 90
EPOCHS = 30

dr = DataRead()
dr.write_pathlabel('dataset','path.txt')
dr.import_data('path.txt',(IMG_HEIGHT,IMG_WIDTH),TARGET_SIZE)

data = dr.get_data()

TRAIN_DATA_SIZE = int(len(data) * 0.8)

TRAIN_DATA_SET = data[:TRAIN_DATA_SIZE]
TEST_DATA_SET = data[TRAIN_DATA_SIZE:]

cnn_net = TensorCNN(TRAIN_BATCH, TEST_BATCH, IMG_WIDTH, IMG_HEIGHT)
cnn_net.net_init()

def deivide_datasets(data_sets):
    data_set = np.array(data_sets)

    img_data_set = data_set[:int(len(data_set)), :1].flatten()
    label_data_set = data_set[:int(len(data_set)), 1:].flatten()

    image_ndarray = np.empty((0, IMG_HEIGHT, IMG_WIDTH, CH_NUM))
    label_ndarray = np.empty((0,TARGET_SIZE))

    for (img, label) in zip(img_data_set, label_data_set) :
        image_ndarray = np.append(image_ndarray,
        np.reshape(img, [1, IMG_HEIGHT, IMG_WIDTH, CH_NUM]), axis=0)

        label_ndarray = np.append(label_ndarray,
        np.reshape(label, (1, TARGET_SIZE)), axis=0)

    return image_ndarray, label_ndarray

def get_batch_dataset(img_dataset, label_dataset, indexes):
    img_len = IMG_HEIGHT * IMG_WIDTH * CH_NUM
    img_ndarray = np.empty((0, IMG_HEIGHT, IMG_WIDTH, CH_NUM))
    label_ndarray = np.empty((0, TARGET_SIZE))

    for index in indexes:
        img_ndarray = np.append(img_ndarray,
        np.reshape(img_dataset[index], [1, IMG_HEIGHT, IMG_WIDTH, CH_NUM]),
        axis=0)
        label_ndarray = np.append(label_ndarray,
        np.reshape(label_dataset[index], [1,TARGET_SIZE]), axis=0)
    
    return img_ndarray, label_ndarray

train_imgdata, train_label = deivide_datasets(TRAIN_DATA_SET)
test_imgdata, test_label = deivide_datasets(TEST_DATA_SET)


epochlogs = []

for i in range(EPOCHS):
    epochres = []
    
    #ミニバッチ学習
    train_perm = np.random.permutation(len(train_imgdata))
    test_perm = np.random.permutation(len(test_imgdata))

    train_accuracy = 0
    train_loss = 0
    test_accuracy = 0
    test_loss_ = 0
    cnt = 0
    
    for j in range(0,(len(train_imgdata) - TRAIN_BATCH),TRAIN_BATCH):
        batch_x, batch_t = get_batch_dataset(train_imgdata, train_label,
        train_perm[j:j+TRAIN_BATCH])   

        temp_train_acc,temp_train_loss = cnn_net.train_run(batch_x,batch_t)       
        
        train_loss += temp_train_loss
        train_accuracy += temp_train_acc
        cnt += 1

    print('%d epoch Accuracy = %.2f%% Loss = %.3e'%(i,train_accuracy/cnt,train_loss/cnt))    
    epochres.append(train_accuracy/cnt)
    epochres.append(train_loss/cnt)

    cnt = 0
    for j in range(0,len(test_imgdata) - TEST_BATCH,TEST_BATCH):
        batch_testx, batch_testt = get_batch_dataset(test_imgdata, test_label,
        test_perm[j:j+TEST_BATCH])
        
        temp_test_acc,temp_test_loss = cnn_net.test_run(batch_testx,batch_testt)
        
        test_loss_ += temp_test_loss
        test_accuracy += temp_test_acc
        cnt += 1
    print('%d epoch TestAccuracy = %.2f%% TestLoss = %.3e'%(i,test_accuracy/cnt,test_loss_/cnt))
    epochres.append(test_accuracy/cnt)
    epochres.append(test_loss_/cnt)
    
    epochlogs.append(epochres)

#cnn_net.save_model('models/')

import matplotlib.ticker as ticker
trainresult = np.array(epochlogs)
x_epochs = np.array(np.arange(len(epochlogs)))

plt.plot(x_epochs,trainresult[:,0],label='main',color='b')
plt.plot(x_epochs,trainresult[:,2],label='validation',color='g')
plt.ylabel('accuracy[%]')
plt.xlabel('epochs')
plt.gca().xaxis.set_major_locator(ticker.MaxNLocator(integer=True))
plt.legend()
plt.show()

plt.plot(trainresult[:,1],label='main',color='b')
plt.plot(trainresult[:,3],label='validation',color='g')
plt.ylabel('loss')
plt.xlabel('epochs')
plt.gca().xaxis.set_major_locator(ticker.MaxNLocator(integer=True))
plt.legend()
plt.show()

次回は保存した学習モデルを使って、カメラの映像から個人認識をしてみたいと思います。

espをpythonで動かすのが一瞬だったお話

組み込みでpython使えたら楽なんだけどなーっと思ってたらmicropythonという代物があるらしい。(何を今更という人がたくさんいると思う)

何とespとstmマイコン系列で動くらしい、まじかよとなった。実際に本当であり、何と数分で動かせて感動しました。

ちなみにファームウェアと動かせるファームウェアはmicropythonの公式にあるっぽい。
micropython.org

早速動かしてみようと思った。ちなみに私はEsp32のdevkitを用いました。
akizukidenshi.com

環境はwindowsでいけます、ということはMaclinuxでもいけます。

まずファームウェアを先ほどのサイトから拾ってきます。次にファームを書き込むためにesptoolをインストールします。

pip install esptool

接続して、windowsの人はデバイスマネージャーからespのシリアルの設定をボーレートを115200に設定しましょう。
(これしなくてもいいかも)

それでダウンロードしてきたファームウェアのバイナリを書き込みます。

esptool --chip esp32 --port COM6 --baud 115200 write_flash -z 0x1000 esp32-20180423-v1.9.3-552-gb5ee3b2f.bin

ではteratermでシリアル通信しましょう。ボーレートは115200です。そうすると対話モードが立ち上がりpythonが叩けます、すげーー

f:id:fortis3m:20180423211941p:plain

ちなみにmicropythonのドキュメントがこちらになります。使えるライブラリとかも書いてあります、結構標準ライブラリ使えるんですね。
http://docs.micropython.org/en/latest/esp8266/esp8266/tutorial/pins.html

micropythonすごかったのですが、まだあまり理解していないのでまずはドキュメントとかを読んでいきたいと思います。

python + opencvで個人認識 その2

前回は自分の顔をopencvで保存しました。

今回はcnnを使う前にデータセットの自作とスクレイピングで画像を集めたいと思います。

データセットの自作ではtensorflow等で取り扱えるような形にしたいと思います。今回はデータの水増し等はやらないですが、そのうち水増しのコードも書きたいと思います。

スクレイピングでは自分の顔以外の適当な画像データを集めます。学習データが自分の顔だけより、他の画像データを持ってくることでcnnの汎化性能が上がる気がしたからです。まあ、個人認識の論文とか読んでないので分からないですので間違ってたら教えてくれると嬉しいです。

まずはデータセットの自作からです。こちらのサイトを参考にしました。
qiita.com

各クラスのディレクトリを収納した親ディレクトリのパスとクラス数と画像のサイズを指定するとデータセットを作成します。

以下ソースコードになります。

import cv2
import os

import random
import numpy as np

class DataRead:

    def write_pathlabel(self, path,pathfile):
        dir_list = os.listdir(path)

        with open(pathfile,"w") as file:
            for y,dir_name in enumerate(dir_list):
                files = os.listdir(path+"\\"+dir_name)
                f_list = [path + "\\" + dir_name + "\\" + f + " " + str(y) + "\n" for f in files if os.path.isfile(os.path.join(path+"\\"+dir_name,f))]
                file.writelines(f_list)
    
    def read_data(self, pathfile):
        with open(pathfile,"r") as file:
            self.PATH_AND_LABEL = [(line.rstrip()).split() for line in file]
        random.shuffle(self.PATH_AND_LABEL)
       
    def import_data(self, pathfile, imgshapes, numclass):
        self.read_data(pathfile)
        self.DATA_SET = []

        for path_label in self.PATH_AND_LABEL:
            if len(path_label) !=2:
                continue
            img = cv2.imread(path_label[0])
            img = cv2.resize(img,imgshapes)
            img = img.flatten().astype(np.float32)/255.0
            label_ary = np.zeros(numclass, dtype = 'float64')
            label_ary[int(path_label[1])] = 1

            self.DATA_SET.append([img,label_ary])
    
    def get_data(self):
        return self.DATA_SET

if __name__ == "__main__":
    dr = DataRead()
    dr.write_pathlabel('test','path.txt')
    dr.import_data('path.txt',(64,64),2)
    print(dr.get_data())

次にスクレイピングで画像を取得していきます。BeautifulSoup4とrequestsを用いています。
ページ内のimgタグを取得してきて、requestsを用いて画像をダウンロードします。

以下スクレイピングソースコードになります。

import requests
import urllib
import os

from bs4 import BeautifulSoup

class Scraper:

    def get_imglinks(self,url):
        self.imgs = []
        res = requests.get(url)
        content = res.content
        soup = BeautifulSoup(content, 'html.parser')

        links = soup.find_all("img")
        img_links = [link.get("src") for link in links]

        return img_links
    
    def download(self,keyword,urls):
        if os.path.exists(keyword) == False:
            os.mkdir(keyword)

        for i,url in enumerate(urls):
            fn,ext = os.path.splitext(url)
            res = requests.get(url, allow_redirects=False)
            if res.status_code != 200:
                print(res.status_code)
                continue
            
            if 'image' not in res.headers["content-type"]:
                print(res.headers["content-type"])
                continue
            with open(keyword + "/" + str(i) + ext, "wb" ) as f:
                f.write(res.content)

if __name__ == "__main__":
    scraper = Scraper()
    links = scraper.get_imglinks('https://www.google.co.jp/search?q=rwby&source=lnms&tbm=isch&sa=X&ved=0ahUKEwivruumkM3aAhXDqJQKHZixBLUQ_AUICygC&biw=1812&bih=954')
    scraper.download('rwby',links)

これでデータセットの作成と学習用のデータを集められるようになったので次回こそ学習させる予定です。

Django2.0 ファイルアップロードについて

Django2.0でファイルアップロードの仕方で少しだけ詰まったのでメモしておきます。

まず最初にディレクトリ構成はこんな感じです。

├─.vscode
├─apps
│ ├─media
│ │ └─thumbnail
│ ├─products
│ │ └─migrations
│ ├─static
│ │ └─vendor
│ │ └─bootstrap
│ │ ├─css
│ │ ├─img
│ │ └─js
│ └─templates
│ ├─accounts
│ └─products
├─config
└─docs

今回はmediaの中のthumbnailに画像を保存することを目的としました。

まずmodel.pyから

from django.db import models

class Product(models.Model):
    title = models.CharField(max_length=140)
    thumbnail = models.FileField(upload_to='thumbnail/', blank=True, null=True)
    sentence = models.TextField()

次にform.py

class ProductForm(forms.ModelForm):
    class Meta:
        model = Product
        fields = ("title","thumbnail","sentence")

views.py

request.FILESというディレク処理の中にファイルのデータが入っているので、フォームへデータを受け渡します。
https://docs.djangoproject.com/ja/2.0/topics/http/file-uploads/

from django.shortcuts import render, redirect

from django.views.generic import CreateView

from .models import Product
from .forms import ProductForm

from django.urls import reverse, reverse_lazy

class ProductCreateView(CreateView):
    template_name = "products/product_create_form.html"
    form_class = ProductForm
    model = Product
    success_url = reverse_lazy('products')

    def post(self, request, *args, **kwargs):
        form = self.form_class(request.POST, request.FILES)
        if form.is_valid():
            print('VALID')
            form.save()
            return redirect(reverse('products'))
        print('Not valid')
        return redirect(reverse('post'))

そしてフォームのproduct_create_form.htmlですが、enctype="multipart/form-data"を入れ忘れないようにしましょう。
私はこれを入れ忘れてかれこれ3時間くらい格闘する羽目になりました...
mugenup-tech.hatenadiary.com

{% extends "base.html" %}

{%block body%}

    <h1>新規作成</h1>
    <form action="" method="post" enctype="multipart/form-data" data-ajax="false">
        {% csrf_token %}
        <table class="table">
            {{ form }}
        </table>

        <button class="btn btn-primary" type="submit">送信</button>    
    </form>
{% endblock %}

そしてsettings.pyに以下を追加します。

MEDIA_ROOT = os.path.join(BASE_DIR,'carpediem','media')
MEDIA_URL = '/carpediem/media/'

これでファイルをアップロードできるようになりました。

python+opencvで個人認識 その1

個人認識したい

ちょっと個人認識したいなと思ったので、何はともあれ学習データを集めるところから始める。

使ったものは

取りあえず自分を認識させたいので自分の顔画像を集めるところから始める。
最初は自宅の背景を

f:id:fortis3m:20180415171357p:plain

良い感じに消して顔写真を撮りまくろうと思った。

元々撮影した背景と各フレームごとのデータを引き算してあげればいい感じになるのではと思って

def delete_background(frame,bcg):
    src_img = frame - bcg
    src_img[src_img <= 100] = 0

    print(src_img)
    return src_img

こんな関数を作った。ただ、

f:id:fortis3m:20180415171614p:plain

こんな感じになってしまい、綺麗に消せなかった。

だから結局は適当な範囲を抽出して画像を保存することにした。

以下ソースコード

import cv2
import time
import numpy as np

def delete_background(frame,bcg):
    src_img = frame - bcg
    src_img[src_img <= 100] = 0

    print(src_img)
    return src_img

def capture_camera(mirror=False, size=None):

    img_no = 1

    cv2.namedWindow("Get_faceimg")
    cap = cv2.VideoCapture(0)

    background = cv2.imread('img/bcg.png',cv2.IMREAD_COLOR)

    while True:

        now = int(time.time())

        # retは画像を取得成功フラグ
        ret, frame = cap.read()

        # 鏡のように映るか否か
        if mirror is True:
            frame = frame[:,::-1]

        # フレームをリサイズ
        # sizeは例えば(800, 600)
        if size is not None and len(size) == 2:
            frame = cv2.resize(frame, size)
        
        width = frame.shape[1]
        height = frame.shape[0]

        rect_startx = int(width/4)
        rect_endx = int(3*width/4)

        rect_starty = int(height/8)
        rect_endy = int(7*height/8)

        cv2.rectangle(frame, (rect_startx-1, rect_starty-1), (rect_endx+1, rect_endy+1), (0, 0, 255), 1)

        #frame = frame - background
        #frame = delete_background(frame,background)
        # フレームを表示する
        cv2.imshow('camera capture', frame)
        cv2.imshow('save capture', frame[rect_starty:rect_endy,rect_startx:rect_endx])

        k = cv2.waitKey(10) # 10msec待つ
        if k == 27: # ESCキーで終了
            break
        elif k == 0x73:
            cv2.imwrite('faceimg/' + str(now) + '.png',frame[rect_starty:rect_endy,rect_startx:rect_endx])
        elif k == 32:
            delete_background(frame,background)
            #cv2.imwrite('img/bcg.png',frame)

    # キャプチャを解放する
    cap.release()
    cv2.destroyAllWindows()

if __name__ == '__main__':
    capture_camera(size = (800,600))

背景を消すところはrgbじゃなくてhsvで処理すべきだったかもしれない。久しぶりに画像を扱うので色々とセオリーを忘れている感がいなめない。

次辺りは適当に画像データを集めて、今回の顔写真を使って識別器を作っていきたい。svmか流行りのcnnのどちらかを使おうと思っている。