John(筋肉)の備忘録的な何か

備忘録的な何かであり、進捗そのものでもあるよ!!怖い人は見ないで!

HOG特徴量を用いたSVM検出器を作ってみた

Johnです。機械学習関連の記事を貪るように読む春休みを過ごしています。

今回はSVM検出器で自分好みの顔含む2次絵を選別できないかと頑張ってみたpart1という感じです。

openCVやselectivesearchなんかのおかげで簡単に(精度は悪いけど)作れたので自慢したいと思います。

SVMを作るコード

# coding:utf-8

import os
import numpy as np
import cv2

# HOGをとるためのパラメータ
win_size = (60, 60)
block_size = (16, 16)
block_stride = (4, 4)
cell_size = (4, 4)
bins = 9
hog = cv2.HOGDescriptor(win_size, block_size, block_stride, cell_size, bins)

# HOGを計算し、データとラベルのリストを返す関数
def calc_hog():
    train_and_labels = []
    for file in os.listdir("./dataset/pos/"):
        img = cv2.imread(f"./dataset/pos/{file}", 0)
        img = cv2.resize(img, win_size)
        train_and_labels.append((hog.compute(img), 0))

    for file in os.listdir("./dataset/neg/"):
        img = cv2.imread(f"./dataset/neg/{file}", 0)
        img = cv2.resize(img, win_size)
        train_and_labels.append((hog.compute(img), 1))
    
    # 順番をランダムにする処理
    # もっといい方法がありそう
    np.random.shuffle(train_and_labels)
    train = []
    label = []
    for train_and_label in train_and_labels:
        train.append(train_and_label[0])
        label.append(train_and_label[1])

    return np.array(train), np.array(label)

if __name__ == "__main__":
    train, label = calc_hog()

    # SVMを新規作成
    svm = cv2.ml.SVM_create()
    # カーネル関数設定
    svm.setKernel(cv2.ml.SVM_LINEAR)
    # パラメータCを決定
    svm.setType(cv2.ml.SVM_C_SVC)
    svm.setC(0.5)
    # 訓練
    svm.train(train, cv2.ml.ROW_SAMPLE, label)
    svm.save('train.xml')

汚いコードですが、いまは書き直すのが億劫なのでにゃんです。

データは自分好みの二次絵顔検出ということで、顔だけトリミングした画像70枚ほどと、それ以外の画像50枚ほどでしました。 CNNとかに比べると圧倒的に少ないですが、調べた感じSVMはこれくらいでまぁ行けるそう。

学習時間はCPUで2秒とかだった気がする。

これによりtrain.xmlというSVMが作れるよ。

SVMを試すコード

# coding:utf-8

import os
import time
import numpy as np
import cv2
from selectivesearch import selective_search as ss

start_time = time.time()

# HOGをとるためのパラメータ
win_size = (60, 60)
block_size = (16, 16)
block_stride = (4, 4)
cell_size = (4, 4)
bins = 9
hog = cv2.HOGDescriptor(win_size, block_size, block_stride, cell_size, bins)

# 検出したい画像を選択
target_path = "/mnt/c/Users/user/Pictures/dataset_favorite/"
target = os.listdir(target_path)[0]
print(target)

# 作成したSVMをロード
svm = cv2.ml.SVM_load("train.xml")

# HOG用とselectivesearch用に2種類用意
img_bgr = cv2.imread(target_path + target)
img = cv2.cvtColor(img_bgr.copy(), cv2.COLOR_BGR2GRAY)

img_load_time = time.time()
print(f"img_load:{img_load_time - start_time}")

# selectivesearchを実行
# img_lblには範囲で区切られている画像データが入っている
# regionsはselectivesearchで得られるrectの情報とか
img_lbl, regions = ss(img_bgr, scale=500, sigma=0.9, min_size=1000)

ss_time = time.time()
print(f"ss:{ss_time - img_load_time}")

hog_list = []
hog_list_rect = []
almosit_regions = len(regions)

# HOGの結果をリストにまとめる
for index, i in enumerate(regions):
    x, y, w, h = i["rect"]
    # rectの範囲が小さいものを排除
    if h < win_size[1] or w < win_size[0]:
        continue
    print(f"hog: {index + 1}/{almosit_regions}")
    img_temp = cv2.resize(img[y:y+h, x:x+w].copy(), win_size)
    hog_list.append(hog.compute(img_temp))
    hog_list_rect.append((x, y, w, h))

# SVMにHOGを渡し、検出する
result = svm.predict(np.array(hog_list))[1].ravel()

hog_and_svm_time = time.time()
print(f"hog_and_svm:{hog_and_svm_time - ss_time}")

print(result)

# 0、つまりは検出できたもののrectを保管
recog_face_rect = [hog_list_rect[index] for index, i in enumerate(result) if i == 0.]

# 赤色
color = (0, 0, 255)

# 元画像の検出できた先にrectを描画
if recog_face_rect:
    for i in recog_face_rect:
        x, y, w, h = i
        cv2.rectangle(img_bgr, (x,y), (x+w, y+h), color, thickness=8)
    cv2.imwrite(f"./ppp.jpg", img_bgr)

end_time = time.time()
print(f"end:{end_time - hog_and_svm_time}")

print(f"total:{end_time - start_time}")

とりあえず作ったって感じで読みにくいでしょうがこれで指定した1つの画像に対して検出をかけます。

出力した画像は著作権的にアウトなので載せれはしませんが、精度は悪かったです。lbpcascade_animeface.xmlとぼんやり比べて精度わりぃ~~ってだけですけど。

まとめ

  • アニキャラの顔ならlbpcascade_animeface.xmlを使った方がよい
  • selective_search、めっちゃいい
  • データをもっと丁寧に作れば精度全然変わりそう(データでかなり手を抜いた)

Lifegameを実装した!

こんにちは。John(None)です。 休みの間手持ち無沙汰だったのでLifegameを実装しました。

Lifegameはググってください。 numpyだけで動作するので遊んでみてね!!

コード

# coding:utf-8

import os
import time
import numpy as np

stage_x = 5
stage_y = 10


class Lifegame:
    def __init__(self):
        #self.game = np.zeros([stage_y + 2, stage_x + 2])

        # 壁ループなし
        game_yet = np.random.randint(0, 2, size=(stage_y + 2, stage_x + 2))

        game_format_x_format =[[0]]
        game_format_x_format.extend([[1]] * stage_y)
        game_format_x_format.append([0])

        game_format_x = np.array(list(map((lambda i: i * (stage_x + 2)), game_format_x_format)))

        game_format_y = [0]
        game_format_y.extend([1] * stage_x)
        game_format_y.append(0)

        game_format = game_format_x * game_format_y
        self.game = game_yet * game_format

        #self.game = np.random.randint(0, 2, size=(stage_y + 2, stage_x + 2))

        # 代入(=)だとarrayのポインタを代入する扱いになるのでnp.arrayでコピーする
        self.next = np.array(self.game)
        while True:
            os.system("clear")
            self.print_game()
            time.sleep(0)
            self.all_do_next()
            self.game = np.array(self.next)

    def pixcel_do(self, x, y):
        x_y_around = np.sum(list(map((lambda i: i[x-1] + i[x] + i[x+1]), (self.game[y-1:y+2]))))
        if not self.game[y][x]:
            if x_y_around == 3:
                self.next[y][x] = 1
        else:
            if x_y_around - 1 <= 1:
                self.next[y][x] = 0
            elif x_y_around - 1 >= 4:
                self.next[y][x] = 0
            else:
                self.next[y][x] = 1

    def all_do_next(self):
        for y in range(1, stage_y + 1):
            for x in range(1, stage_x + 1):
                self.pixcel_do(x, y)

    def print_game(self):
        print("_" * stage_x)
        ans = ""
        for y in range(1, stage_y + 1):
            for x in range(1, stage_x + 1):
                if self.game[y][x]:
                    ans += "人"
                else:
                    ans += "  "
            ans += "\n"
        print(ans)

if __name__ == "__main__":
    Lifegame()

「&」コマンドと標準入力の優先度がふと気になった。

どうもJohn(テスト爆死)です。 テストが今日で終わり、連続活動時間が43時間を超えつつありますが進捗をなにか生み出したい機運だった次第です。

なにすんのか

今回はふと「&」コマンドとPythonの標準出力の優先度を知りたくなった感じです。ただただふと思っただけのことです。

実験するべくこんなプログラムを用意しました。

input_echo.py

# coding:utf-8

import sys

while True:
        print("1")
        mes = input()
        print("2")
        print(mes)
        print("3")
        if mes == "pyexit":
                sys.exit()

実験方法はいたって簡単。 python3 input_echo.py &を実行するだけ。

結果

$ python3 input_echo.py &

$ 1

となりました。まぁここまでは想定通り。

次にaとか入力してみます。

$ 1
a
a: command not found

[3]+  Stopped python3 input_echo.py

ほげ~~。強制終了ぽよ~~。

まとめ

「&」コマンドつけないとちゃんと実行されるのでpythonの標準入力はバックグラウンドでは働かないんだなぁ。。としみじみ思いました。

次はマルチプロセシングとかやった時のinput()の処理を見てみたい。

では寝たいと思います。

どうやらここで寝落ちしてたみたいです。 ブログ書き終わって投稿せずに寝るマンしてました。

ラズパイをスリープしないようにする

こんにちは、John(ほげ)です。 テスト期間なのですが、最近の発見を記録しておきたいと思います。

現在学校で電光掲示板というかデジタルサイネージを作っているのですが、うっかりラズパイのスリープ設定をいじるのを忘れて設置してしまっていました。 そこで得た情報をまとめておこうと思います。

尚、RaspberryPi3 modelBを使用しています。

なにしたか

はじめ別の方法で試したのですがうまくいかなかったのでどれが正しいとかはわかりませんが、以下すべて実行すればどうにかなります多分。

/etc/xdg/lxsession/LXDE-pi/autostart

"/etc/xdg/lxsession/LXDE-pi/autostart"に

@xset s 0 0

@xset s noblank

@xset s noexpose

@xset dpms 0 0 0

@xset -dpms

と追記するだけ。

RaspberryPi3はここではなく"/etc/xdg/lxsession/LXDE/autostart"(LXDE-piではなくLXDE)に同じように書き込むべきという意見もあるようだった。自分は両方に書き込んだ。

/etc/lightdm/lightdm.conf

"/etc/lightdm/lightdm.conf"に

[SeatDefaults]

xserver-command=X -s 0 -dpms

と追記する。

これはどのRaspberryPiのバージョンによる違いはなさそう。

最後に

こまごましているけど、こういうことで積極的にブログを書いていきたい。ほげ~~。

rootのシェルをvimにするゲーム。

どうもJohnです。先日先輩のいるやんさんに面白いゲームを教えていただいたので、それの攻略をやろうかと思います。

どういうゲームか

タイトルの通り、sudo suvimが起動されちゃう状況に設定。それを解決するゲームです。なんかCTFっぽい。

vimの部分をbash等にもう一度戻すとクリアな訳ですが、編集するにはrootである必要があり、sudoでコマンドを実行しようとするとvimが起動されてしまい、コマンドが実行できないという感じのゲーム。

プレイ方法

sudo chsh -s /usr/bin/vim rootを実行する。これでおk。ゲームスタートです。是非やってみて。

攻略法

以下攻略法です。

chshコマンドについて調べてみたところ、どうやら/etc/passwdが設定ファイルのようです。

スタート段階でcat /etc/passwdを実行すると、一番上の行に

root:x:0:0:root:/root:/usr/bin/vim

とあるはずです。

ここまでわかればVimmerの方はわかるんでしょうが、自分は何分Emacs派というほどではありませんが、Vimをあまり使わないのでどうやって開くかも調べました。

手順としてはこう。

  1. sudo suvimをroot権限で起動する。
  2. /etc/passwdと入力。
  3. カーソルが/etc/passwdの上にある状態でCtrl-W gf
  4. すると/etc/passwdが開ける。
  5. いい感じにrootのところを編集。
  6. クリア!

という感じです。

最後に

どうやらrootの方を変更するゲームはEasyモードらしい。 HardモードでUserの方も変更するゲームもあるらしい。またやる。

今更ながらKosenセキュリティコンテストに参加したよ!

お久しぶりです。最近忙しくてダメだったJohnです。めっちゃ久しぶりの更新。。

今回タイトルの通り、Kosenセキュリティコンテストにチーム「:(){:|:&};:」として参加してきました。write up的なのを書かなくちゃいけないそうですがそこまで問題解けていないのがつらいです。

まず結果。。

f:id:julia-bardera-jb:20171029001125p:plain

やったぜ6位!

完全趣味チームとして参加していた割にはいい感じの結果となりました。

Write up的なの。

全然解いてないのがばれるのが恥ずかしいので、「はぁ、好き。。」ってなった問題を一つ。

Crypto 100 「解凍して解凍せよ」

皆大好き画像処理!というわけで簡単な画像処理です。 今回、画像処理系が全然なかったのが悲しいくらいでした。

以下問題。

f:id:julia-bardera-jb:20171029001727p:plain

問題文は「ファイルからフラグを読み取れ!」。 そしてzipファイルが渡されました。

zipファイルは上げられなかったので、その中身の画像をどうぞ。

masks.png

f:id:julia-bardera-jb:20171029002029p:plain

xor.png

f:id:julia-bardera-jb:20171029002026p:plain

ファイル名がもうあれなので、そんな感じにするべく、わざわざPythonを書きました。

# coding:utf-8
from PIL import Image, ImageDraw
import numpy as np

image1 = Image.open("./xor.png", "r")
image2 = Image.open("./masks.png", "r")
x_range = image1.convert("RGB").size[0]
y_range = image1.convert("RGB").size[1]

target = Image.new("RGB", ((x_range, y_range)), (255, 255, 255))
white_canvas = ImageDraw.Draw(target)

np_image1 = np.array(image1)
np_image2 = np.array(image2)
y = 0
while y < y_range:
    x = 0
    while x < x_range:
        if np_image1[x][y][0] == np_image2[x][y][0]:
            white_canvas.point((x, y), (0, 255, 255))
        else:
            white_canvas.point((x, y), (np_image1[x][y][0], np_image1[x][y][1], np_image1[x][y][2]))
        x += 1
    y += 1

target.save("./ans.png", "PNG", quality=100, optimize=True)

必死だったので綺麗さとか求められなかった。 訳すると、画像をXORして、True?まぁ、1を返したところだけシアンブルーにするというプログラム。

結果がこれ。

ans.png

f:id:julia-bardera-jb:20171029002834p:plain

xとy、ミスった。

しかし、ここでJohnはチームメイトのいるやんさんにInkscape使えばと助言されます。

そしてそしてその結果。 f:id:julia-bardera-jb:20171029003401p:plain

Flagは「SCKOSEN{simple_visual_cryptography}」でした。

最後に。

楽しかった! そしてそれ以上にこれ。 f:id:julia-bardera-jb:20171029003814p:plain 明石高専のチームが2トップという瞬間。 うれしかったですね。

校内でもCTFを開こうと色々模索中です。

Python、Pillowで画像の最頻値を取る!

どうも、Johnです。 テスト期間なんですが、進捗出したい欲がすごかった上、インターンエントリーシートに「自分ブログやってまっせ!」と言ってしまったので書きたいと思います。

何やったのか

今回画像の最頻値を取るのですが、実はブログ始めたての頃に一度やっています。

john-bardera.hatenablog.com

このときはPython始めたてで何もわかんなかったので、入力画像3枚でしかできませんでしたが、今回は何枚でも出来るようにしました。

ソース(適当)

numpy使いてぇ、ってなって書き始めたのにそうでもなくなって悲しい。 あと英語できないのでネーミング勘弁して。

# coding:utf-8
import statistics as st
from PIL import Image, ImageDraw
import numpy as np

def stmode(matrix):
    #最頻値が存在しない時、1番はじめの画像に準ずる
    try:
        mode=st.mode(matrix)
    except Exception:#statistics.StatisticsError
        mode=matrix[0]
    finally:
        res=(int(mode[:3]), int(mode[3:6]), int(mode[6:]))
        return res

images=[]
almost_images=13
num=0
while num < almost_images:
    image=Image.open("./tikuma/"+str(num)+".JPG", "r")
    #imagesに各画像のRGB値が入れられる
    images.append(np.array(image))
    if not num:
        x_range=image.convert("RGB").size[0]
        y_range=image.convert("RGB").size[1]
    num+=1

#白いキャンバス作成
target=Image.new("RGB", ((x_range, y_range)), (255, 255, 255))
white_canvas=ImageDraw.Draw(target)

y=0
while y < y_range:
    x=0
    while x < x_range:
        before_mode=[]
        num=0
        while num < almost_images:
            #各画像の[x][y]の要素(例,[1,14,514])を9桁の文字列(例,"001014514")に変換する
            before_mode.append(str(images[num][y][x][0]).zfill(3)+str(images[num][y][x][1]).zfill(3)+str(images[num][y][x][2]).zfill(3))
            num+=1
        #その最頻値を出し、白いキャンバスに色を置いて行く
        white_canvas.point((x, y), stmode(before_mode))
        x+=1
    y+=1

target.save("./tikuma/ans/ans"+str(almost_images)+".jpg", "JPEG", quality=100, optimize=True)

入力に使った画像

可愛いでしょ?

☆とGET、帰ボタンが違うので、これの除去を目標としました。

こんな感じに0~13.JPGとして使ってます。 f:id:julia-bardera-jb:20170730001714j:plain

結果

入力3枚 f:id:julia-bardera-jb:20170730000505j:plain 入力5枚 f:id:julia-bardera-jb:20170730000554j:plain 入力7枚 f:id:julia-bardera-jb:20170730000604j:plain 入力9枚 f:id:julia-bardera-jb:20170730000617j:plain 入力11枚 f:id:julia-bardera-jb:20170730000626j:plain 入力13枚 f:id:julia-bardera-jb:20170730000636j:plain

ちなみに処理にかかった時間は入力3枚~7枚までは2空3分。 11枚や13枚になると4分30秒と言ったところでした。

まとめ

艦これ楽しい!!!!!

まとめ改

GETと帰ボタンが枚数多くするごとにずれていっていたが、おそらく入力誤差であろう。もしかしたらこのぐらいの小さい変化であったら、平均を取った方がきれいになるかもね。

次からはちゃんとnumpy使うもん!