エンジニアリングとお絵描き

お絵描きが趣味のエンジニアが、誰かの為になりそうなことや未来の自分の助けになりそうなことを書き残します.

ガウシアンフィルタ

作業の流れ

ハフ変換を行う前にCanny法というものを使ってエッジ検出を行います。 Canny法ではまずガウシアンフィルタによるぼかしの処理を行います。

よってPythonの練習もかねて、まずガウシアンフィルタを実装しようと思います。

ガウシアンフィルタを使う例

ガウシアンフィルタは趣味のお絵描きでもよく使用している便利な処理です。 以下にガウシアンフィルタがイラスト制作に使われる例を示します。 (プログラム書く前に絵の制作で時間を溶かしたのは内緒...)

f:id:pengin_0_maru:20200529143849p:plainf:id:pengin_0_maru:20200529144038p:plain
ガウスぼかしの使い方

このように、イラストの背景にガウスぼかしをかけることによって主役の人物が見やすくなります。 この処理はお絵かきソフトClipStudioで行いました。

このガウスぼかしに使用するガウシアンフィルタを実装していきます。

ガウシアンフィルタの原理

ガウシアンフィルタではガウス分布というものを利用して、注目画素からの距離に応じて近傍の画素値に重みをかける処理を行っています。以下にガウス分布の式を載せます。

\displaystyle{
g(x,y,σ)=\frac{1}{2πσ^2}exp\left(-\frac{x^2+y^2}{2σ^2} \right)
}

ガウス分布は確率統計とかでよく出てくる正規分布と同じものみたいです。偏差値の計算など、色々なところで使われるみたいです。 このガウス分布によって注目画素の周りの画素の画素値に重みを付けることでぼかしていきます。

プログラム

実際にガウシアンフィルタを行うプログラムです。 グレースケール化は無くても良いのですが勉強の過程で書いてみて、今後も必要なので載せました。

import cv2
import numpy as np

#グレースケール化
def BGR2GRAY(img):
    b = img[:,:,0].copy()
    g = img[:,:,1].copy()
    r = img[:,:,2].copy()

    print(b)

    out =  0.2126*r + 0.7152*g + 0.0722*b

    #整数に変換
    out = out.astype(np.uint8)
    return out

#ガウシアンフィルタ
def gaussian_filter(img,k_range=7,sigma=1.3):
    #入力画像がカラーかグレー化を判定し、グレーなら要素を増やす
    if len(img.shape) == 3:
            H,W,C = img.shape
    else:
            img = np.expand_dims(img,axis=-1)
            H,W,C = img.shape

    #//は切り捨て除算
    pad = k_range//2

    #隅の画素でも重み付けができるように0パディング
    out = np.zeros((H + pad *2,W + pad * 2,C),dtype=np.float)
    #入力画像を0パディング付きで複製
    out[pad: pad + H,pad: pad + W] = img.copy().astype(np.float)

    #カーネルの準備
    K = np.zeros((k_range,k_range),dtype=np.float)
    for x in range(-pad,-pad + k_range):
        for y in range(-pad, -pad + k_range):
            #ここで肝のガウス分布の式が登場
            K[y + pad,x + pad] = np.exp(-(x ** 2 + y ** 2)/(2 * (sigma ** 2)))
    K /= (2*np.pi * sigma * sigma)
    #最後にカーネルの合計で割る
    K /= K.sum()

    tmp = out.copy()

    for y in range(H):
        for x in range(W):
            for c in range(C):
                out[pad + y,pad + x,c] = np.sum(K * tmp[y: y + k_range,x:x + k_range,c])

    #0から255の範囲に収める
    out = np.clip(out,0,255)
    out = out[pad: pad + H,pad:pad + W].astype(np.uint8)

    return out


img = cv2.imread("hough.png").astype(np.float)

out = gaussian_filter(img)

cv2.imshow("result",out)
cv2.waitKey(0)

gaussian_filter内の画像はこのような感じになっているはずです。

f:id:pengin_0_maru:20200530134729p:plain
解説みたいな図

このサイトのコードをガッツリ参考にしました github.com

結果はこんな感じです。

f:id:pengin_0_maru:20200530135724p:plain
出力結果

このガウシアンフィルタ、実はOpenCVのメソッドを使うと10行で実装できてしまいます。

import cv2
import numpy as np
from matplotlib import pyplot as plt

img = cv2.imread('hough.png')

#kernel = np.ones((5,5),np.float32)/25
out = cv2.GaussianBlur(img,(7,7),1.3)
cv2.imshow("result",out)
cv2.waitKey(0)

次はこのガウシアンフィルタを使ってCanny法を実装しようと思います。

作りたいもの

今後趣味として作っていきたいものをまとめました。

  • ハフ変換による直線検出webアプリ
  • ディスプレイ鏡化アプリ
  • 頂点と辺から成るグラフの、頂点位置を自由に変更でき、同型グラフをすぐに理解できるwebアプリ
  • ゴキブリ監視システム

どんどん作りたいものを増やし、作れそうなものからどんどん作っていこうと思います。

ハフ変換を手軽に行うウェブアプリを作りたい

大学の講義でハフ変換という技術を学び、画像処理100本ノックQ44_Q46でハフ変換のプログラムを学びました。

github.com

この時、このハフ変換による直線検出は絵を描く際のパース取りに使えるのではないか閃き、ダウンロードやインストールなどの煩わしい手順は無く、webで簡単にできるハフ変換アプリが作りたいと考えました。

現在の進捗としては、取り合えずPythonが使える環境を整え、簡単なPythonの書き方を勉強しました。(今まで画像処理はC++で行っていた)

現状、100本ノックのハフ変換プログラムを完全に理解したわけでは無いため、次はハフ変換のプログラムを自力で書けるようになろうと思います。 OpenCVにはハフ変換を行う関数が付いているみたいなので、それを使ってしまう誘惑に負けないように、頑張ろうと思います。

OpenPose、gitの導入とブログ散策

今日はOpenPoseにより取得した2次元の骨格データを3次元に推定する技術について調べました。

OpenPoseは既に私用のPCでデモを動かしており、初めて動かした際にはかなり感動しました。 この技術があれば手軽なモーションキャプチャなんて簡単だと思ったのですが、そう上手くはいかないみたいです...。

f:id:pengin_0_maru:20200528224823p:plain
OpenPoseにより関節の位置を取得したデッサン人形

OpenPoseでは画像の関節の位置を取得できるのですが、それはあくまで2次元であり、そのデータを3次元の姿勢に推定しなければモーションキャプチャとして3Dモデルを動かすことはできません。

姿勢推定のコードはGitHubにいくつかあるようで、今日は主に以下のページを読んでいました。

qiita.com

またgitをインストール、コマンド入力によりGiuHubのファイルをDLできるようになりました。(今更...)

今までは学校からの課題に取り組むだけでしたが、これからは色々な方のブログやGitHubのコードを読み、自分もアウトプットしていきたいと思います。

C++でVectorに触れた

画像処理100本ノック、問32番へ取り組んだ際に問題が発生したので記録します。 github.com

double型の複素数を要素に持つ二次元配列を使用したらVisual Studioに怒られました。 スタック領域では16KBまでしか使えないとかなんとか...

docs.microsoft.com

そこで友人の力を借りてVector型について学び、無事解決できました。

怒られたコード

〜
struct fourier_str {
    std::complex<double> coef[height][width];
};
〜

修正したコード

〜
struct fourier_str {
    std::vector<std::vector<std::complex<double> > > coef;
    fourier_str() : coef(height,std:: vector<std::complex<double> >(width)) {}
};
〜

f:id:pengin_0_maru:20200518011835p:plain
Vectorを使った2次元配列的な考え(友人作)

[5月18日追記]

二次元配列を使用して怒られた際には、作成した構造体を引数に持つ関数を幾つか作成するプログラムでしたが、これをすべてmain関数にまとめてしまうと怒られなくなりました...。 謎は深まる...。