plant-raspberrypi3のブログ

ラズベリーパイ3とPythonに挑戦して、植物を愛でたり画像を加工したりします。最近はラズパイでBotツイートにチャレンジしてます。

scikit-imageのお勉強 番外編 〜台形補正にチャレンジ!~

先日から行なっているscikit-imageの機能を網羅していくシリーズ。

transformモジュールについては以前も取り上げました。

plant-raspberrypi3.hatenablog.com

今回は、このとき取り上げることができなかった台形補正にチャレンジしてみました。

Scikit-imageでの台形補正のやり方については、以下のページを参考にしました。

Computer vision in geoscience: recover seismic data from images – part 2mycarta.wordpress.com

環境

Jupyter lab上で操作。

準備

台形補正の効果がわかりやすいように、以下のサンプル画像を使用します。

%matplotlib inline
import matplotlib.pyplot as plt
from skimage import data,transform
import numpy as np

f = 'brick.png'
loaded_img = data.load(f)
plt.imshow(loaded_img)
plt.show()

f:id:plant-raspberrypi3:20181016141313j:plain

サンプル画像の呼び出し方は以前の記事をご参照ください。

plant-raspberrypi3.hatenablog.com

次に、台形の四隅の座標を調べます。調べる順番は「左上->左下->右下->右上」の反時計回り。

座標はImageJで手作業で調べました。

四隅の座標は以下のようなNumPyアレイとして、cornerCoordsに代入しておきます。

#[縦, 横]の順番
cornerCoords = np.array([[83,79],[446,31],[443,482],[76,450]])

plt.imshow(loaded_img)
plt.scatter(cornerCoords[:,1],cornerCoords[:,0], s=60, 
 color=['magenta', 'cyan', 'yellow', 'red'])
plt.show()

f:id:plant-raspberrypi3:20181016141416j:plain

手順その1

変換に使うための長方形の枠(?)を四隅の座標を元にして準備します。

w1 = np.sqrt(((cornerCoords[0, 1]-cornerCoords[3, 1])**2)
  +((cornerCoords[0, 0]-cornerCoords[3, 0])**2))
w2 = np.sqrt(((cornerCoords[1, 1]-cornerCoords[2, 1])**2)
  +((cornerCoords[1, 0]-cornerCoords[2, 0])**2))

h1 = np.sqrt(((cornerCoords[0, 1]-cornerCoords[1, 1])**2)
  +((cornerCoords[0, 0]-cornerCoords[1, 0])**2))
h2 = np.sqrt(((cornerCoords[3, 1]-cornerCoords[2, 1])**2)
  +((cornerCoords[3, 0]-cornerCoords[2, 0])**2))

w = max(int(w1), int(w2))
h = max(int(h1), int(h2))

dst = np.array([
  [0, 0],
  [h-1, 0],
  [h-1, w-1],
  [0, w-1]], dtype = 'float32')

手順その2

その1で求めたdstの値と四隅の座標のX,Yをひっくり返しておきます。(必要らしい)

dst[:,[0,1]] = dst[:,[1,0]]
cornerCoords[:,[0,1]] = cornerCoords[:,[1,0]]

手順その3

1. transform.ProjectiveTransformクラスのインスタンス
tform = transform.ProjectiveTransform()
2. 手順その2で準備したdstと四隅の座標をestimate関数に適用
tform.estimate(dst,cornerCoords)
3. アフィン変換でも使用したtransform.warp関数で台数補正を画像に適用
warped =transform.warp(loaded_img, tform, output_shape=(h-1,w-1))

plt.imshow(warped)
plt.show()

f:id:plant-raspberrypi3:20181016142151j:plain

できました!

以上です。

scikit-imageのお勉強 第6回 filtersモジュール・前編 ~平滑化、エッジファインダ~

かなりご無沙汰していたscikit-imageの便利機能の備忘録シリーズの続き。

これまでの回はコチラ

今回は第6回です。filtersモジュールの前編です。平滑化・エッジファインダについてメモしていきます。

平滑化

  • filters.gaussian Gaussianフィルタ

エッジファインダ

  • filters.sobel Sobelフィルタ
  • filters.scharr Scharrフィルタ
  • filters.prewitt Prewittフィルタ
  • filters.roberts Robertsフィルタ
  • filters.laplace Laplacianフィルタ

準備

詳細を読む

前回までと同じようにサンプル画像のロケットを使用します。

カラー画像とグレースケール画像の両方を準備。

%matplotlib inline
import matplotlib.pyplot as plt
from skimage import filters, color, data

def image_show(img,title,record=False):
    plt.imshow(img)
    plt.xticks([])
    plt.yticks([])
    plt.title(title)
 # record=Trueの時タイトルと同名の画像ファイルを保存
    if record:
        plt.savefig(f"scimage_filters/{title}.png",dpi=150)
    plt.show()

rocket = data.rocket()
image_show(rocket,"Raw",record=True)
gray_rocket = color.rgb2gray(rocket)
image_show(gray_rocket,"Gray",record=True)

f:id:plant-raspberrypi3:20181007115723p:plain:w350

f:id:plant-raspberrypi3:20181007115734p:plain:w350

平滑化

Gaussianフィルタは平滑化フィルタです。いわゆる「ぼかし」処理。

詳細を読む

原理についてはこちらのページが詳しいです。

algorithm.joho.info

ガウシアンフィルタは、画像の平滑化に使われるフィルタの1つです。考え方は簡単で、「注目画素からの距離に応じて近傍の画素値に重みをかける」ということをガウス分布を利用して行います。それにより、自然な平滑化をおこなうことができます。

Gaussianフィルタを使ってみる

詳細を読む

グレースケール画像の場合はデフォルトでOKですが、カラー画像に適用したい場合はmultichannel=Trueを設定します。

sigmaの値が大きいほど、ぼかし具合が強くなります。

image_show(rocket[300:400,250:400],"Raw_magnified",record=True)

sigmas = range(0,5,2)

for s in sigmas:
    gaussian = filters.gaussian(rocket,sigma=s,multichannel=True)
    image_show(gaussian[300:400,250:400],f"sigma={s}",record=True)

f:id:plant-raspberrypi3:20181007115811p:plain:w350

f:id:plant-raspberrypi3:20181007115822p:plain:w350

f:id:plant-raspberrypi3:20181007115832p:plain:w350

f:id:plant-raspberrypi3:20181007115839p:plain:w350

エッジファインダ

Sobel、Scharr、Prewitt、Roberts、Laplacianフィルタはエッジをみつけるためのフィルタ群です。

詳細を読む

エッジについては以下のページの説明がわかりやすいです。

エッジ検出

画像のエッジ(edge)とは、画素値が急峻に変化する点です。画素値の変化の大きさを「勾配(Gradient)」や「エッジの強さ」と言います。エッジは物体の輪郭を示す場合が多く、物体認識において重要な要素と言えます。

各フィルタの原理については以下のページを参照。

Sobel, Scharr, Prewitt, Robertsフィルタを使ってみる

詳細を読む

グレースケール画像を使用します。

filter_names = ("filters.sobel","filters.scharr","filters.prewitt",
           "filters.roberts")

filter_tuple = (filters.sobel,filters.scharr,filters.prewitt,
           filters.roberts)

image_show(gray_rocket[300:400,250:400],"Raw_gray_magnified",record=True)

for f,name in zip(filter_tuple,filter_names):
    filtered_image = f(gray_rocket)
    image_show(filtered_image[300:400,250:400],f"{name}_magnified",record=True)

f:id:plant-raspberrypi3:20181007120607p:plain:w350

f:id:plant-raspberrypi3:20181007120621p:plain:w350

f:id:plant-raspberrypi3:20181007120629p:plain:w350

f:id:plant-raspberrypi3:20181007120640p:plain:w350

f:id:plant-raspberrypi3:20181007120657p:plain:w350

Sobel、Scharr、Prewittフィルタはほとんど同じように見えますが、Robertsフィルタだけ結果が若干違います。

Robertsフィルタは他の3つと原理が異なるので(コチラ参照)、当然の結果と言えそうです。

Laplacianフィルタを使ってみる

詳細を読む

こちらもグレースケール画像を使用。

image_show(gray_rocket[300:400,250:400],"Raw_gray_magnified",record=True)

laplace_img = filters.laplace(gray_rocket)
image_show(laplace_img[300:400,250:400],f"laplace_magnified",record=True)

f:id:plant-raspberrypi3:20181007121340p:plain:w350

f:id:plant-raspberrypi3:20181007121356p:plain:w350

Laplacianフィルタは2次微分に相当します。

Laplacianフィルタでエッジ抽出する - [物理のかぎしっぽ]

Laplacianフィルタの場合、ほとんどの画素値が0付近になり、特に変化の大きいところだけがプラス、マイナスの大きい値として現れます。

画素値のヒストグラムを描くと以下のようになります。

plt.hist(laplace_img.flatten())
plt.savefig("scimage_filters/laplace_hist.png",dpi=150)
plt.show()

f:id:plant-raspberrypi3:20181007122402p:plain:w350

以上です。

MacOS High SierraでPython環境構築(再)

High Sierraの入った新しいMacBook ProPython環境を構築し直したので、まとめました。

同じようなネタでだいぶ前にも記事にしましたが(コチラ)、拙いところが多かったので、こちらの方がベターです。

また、下記サイトも参考にさせていただきました。感謝!

qiita.com

手順

  1. homebrewをインストール
  2. pyenvをインストール
  3. Anacondaをインストール
  4. Jupyter labをインストール

1. homebrewをインストール

詳細を展開

Macのための優秀なパッケージマネジャであるHomebrewをインストールします。ターミナルで以下のコードを入力します。

$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

ちょっと時間がかかります(5分くらい?)。

Homebrewのページは以下。

brew.sh

ちなみに、事前にXcodeを入れる必要はなかったようです。

2. pyenvをインストール

詳細を展開

次に、brewコマンドでpyenvをインストールします。

$ brew install pyenv

その後、パスを通すために~/.bash_profileを設定します。

まず、nanoエディタで~/.bash_profile を開きます。

$ nano ~/.bash_profile

下記の三行を追加します。

export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init -)"

Control+oで保存し、Control+xで終了します。

下記コマンドで設定を有効にします。

$ source ~/.bash_profile

lsコマンドなどで、パスが壊れていないか確認しておくと安心。

3. Anacondaをインストール

詳細を展開

anacondaの最新バージョンを確認します。

$ pyenv install -l | grep anaconda3

  anaconda3-2.0.0
  anaconda3-2.0.1
  anaconda3-2.1.0
  anaconda3-2.2.0
  anaconda3-2.3.0
  anaconda3-2.4.0
  anaconda3-2.4.1
  anaconda3-2.5.0
  anaconda3-4.0.0
  anaconda3-4.1.0
  anaconda3-4.1.1
  anaconda3-4.2.0
  anaconda3-4.3.0
  anaconda3-4.3.1
  anaconda3-4.4.0
  anaconda3-5.0.0
  anaconda3-5.0.1
  anaconda3-5.1.0
  anaconda3-5.2.0

Anacondaをインストールします。ここもちょっと時間がかかるかも(10分くらい)。

$ pyenv install anaconda3-5.2.0

pyenvをリフレッシュ。

$ pyenv rehash

新しいAnacondaをローカルで動かすために、新しいディレクトリを作成して移動し、そちらでアクチベートします。

$ mkdir python3_env
$ cd python3_env
$ pyenv local anaconda3-5.2.0

これでpython3_envディレクトリにいるときにはanaconda3-5.2.9が使えるはずです。

ちゃんと適用されているかは、Pythonを起動することで確かめることができます。

$ python

Python 2.7.10 (default, Oct  6 2017, 22:29:07) 
[GCC 4.2.1 Compatible Apple LLVM 9.0.0 (clang-900.0.31)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
$ python

Python 3.6.5 |Anaconda, Inc.| (default, Apr 26 2018, 08:42:37) 
[GCC 4.2.1 Compatible Clang 4.0.1 (tags/RELEASE_401/final)] on darwin
Type "help", "copyright", "credits" or "license" for more information.

または、python3_envディレクトリでcondaコマンドを試してみることでも確認できます。

$ conda -V

conda 4.5.4

こうなっていればOKです。

4. Jupyter labをインストール

詳細を展開

jupyterlabをインストールする際に、msgpackが必要と言われるためインストールしておきます。

$ brew install msgpack
$ pip install msgpack

その後、pipをupgradeし、jupyterlabをインストールします。

$ pip install --upgrade pip
$ pip install jupyterlab

Jupyter Labを開いてみます。

$ jupyter lab

f:id:plant-raspberrypi3:20181002121651p:plain

以上です!

おまけ

複数アカウントでシェアしているPCで行う場合

Homebrewのインストールについて注意が必要です。 以下のページ参照。

qiita.com

$ mkdir homebrew
$ curl -L https://github.com/Homebrew/homebrew/tarball/master | tar xz --strip 1 -C homebrew
$ nano ~/.bash_profile
# export PATH="$HOME/homebrew/bin:$PATH"
$ source ~/.bash_profile

scikit-imageのお勉強 第5回 exposureモジュール ~露出補正~

scikit-imageの便利機能の備忘録シリーズ。

これまでの回はコチラ。

第1回 dataモジュールとioモジュール ~画像の入出力~

第2回 colorモジュール ~色空間~

第3回 drawモジュール ~図形の描画~

第4回 transformモジュール ~拡大縮小・回転・アフィン変換~

今回は第5回です。exposureモジュールについてメモしていきます。

exposureモジュール

露出補正についての機能を含むモジュールです。

公式ページ:Module: exposure — skimage v0.14.0 docs

以下の関数とその効果をざっと紹介します。

  • exposure.equalize_hist
  • exposure.equalize_adapthist
  • exposure.rescale_intensity
  • exposure.adjust_gamma
  • exposure.adjust_sigmoid
  • exposure.adjust_log

まずは準備。

%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np

from skimage import exposure, data, color

def image_show(img):
    try:
        h,w,c = img.shape
        plt.figure(figsize=[float(w/100),float(h/100)])
        plt.title(f'w={w}, h={h}')
        plt.imshow(img)
    except ValueError:
        h,w = img.shape
        plt.figure(figsize=[float(w/100),float(h/100)])
        plt.title(f'w={w}, h={h}')
        plt.imshow(img,cmap='gray')
    plt.xticks([])
    plt.yticks([])
    plt.show()

rocket = data.rocket()
image_show(rocket)
rocket_g = color.rgb2gray(rocket)
image_show(rocket_g)

・元画像

f:id:plant-raspberrypi3:20180905122655p:plain

・元画像(グレー)

f:id:plant-raspberrypi3:20180905122714p:plain

はじめの3つを実行

exposure.equalize_histexposure.equalize_adapthistはカラー画像で実行するとエラーがでました。

・code

image_show(rocket_g)

hist_img = exposure.equalize_hist(rocket_g)
image_show(hist_img)

hist_img = exposure.equalize_adapthist(rocket_g,kernel_size=50)
image_show(hist_img)

hist_img = exposure.rescale_intensity(rocket_g, in_range=(0, 0.5))
image_show(hist_img)

hist_img = exposure.rescale_intensity(rocket_g, out_range=(0, 0.5))
image_show(hist_img)

image_show(rocket)

hist_img = exposure.rescale_intensity(rocket, in_range=(0, 127))
image_show(hist_img)

hist_img = exposure.rescale_intensity(rocket, out_range=(0, 127))
image_show(hist_img)

・元画像(グレー)

f:id:plant-raspberrypi3:20180905123051p:plain

・exposure.equalize_hist

f:id:plant-raspberrypi3:20180905123057p:plain

・exposure.equalize_adapthist

f:id:plant-raspberrypi3:20180905123106p:plain

・exposure.rescale_intensity(in_range=(0, 0.5))

f:id:plant-raspberrypi3:20180905123115p:plain

・exposure.rescale_intensity(out_range=(0, 0.5))

f:id:plant-raspberrypi3:20180905123123p:plain

・元画像

f:id:plant-raspberrypi3:20180905123134p:plain

・exposure.rescale_intensity(in_range=(0, 0.5))

f:id:plant-raspberrypi3:20180905123142p:plain

・exposure.rescale_intensity(out_range=(0, 0.5))

f:id:plant-raspberrypi3:20180905123151p:plain

exposure.adjust_gamma

gammaの値を色々変えて効果の違いを見てみます。

・code

image_show(rocket)

for i in np.arange(1.2,1.7,0.2):
    print(f'gamma={i:.1f}')
    hist_img = exposure.adjust_gamma(rocket,gamma=i)
    image_show(hist_img)

・元画像

f:id:plant-raspberrypi3:20180905123421p:plain

・gamma = 1.2

f:id:plant-raspberrypi3:20180905123504p:plain

・gamma = 1.4

f:id:plant-raspberrypi3:20180905123511p:plain

・gamma = 1.6

f:id:plant-raspberrypi3:20180905123520p:plain

exposure.adjust_sigmoid

cutoffとgainをいじります。

・code

image_show(rocket)

for i in np.arange(2,9,4):
    print(f'cutoff=0,gain={i:.1f}')
    hist_img = exposure.adjust_sigmoid(rocket,cutoff=0,gain=i)
    image_show(hist_img)
    print(f'cutoff=0.5,gain={i:.1f}')
    hist_img = exposure.adjust_sigmoid(rocket,cutoff=0.5,gain=i)
    image_show(hist_img)
    print(f'cutoff=1.5,gain={i:.1f}')
    hist_img = exposure.adjust_sigmoid(rocket,cutoff=1.0,gain=i)
    image_show(hist_img)

・元画像

f:id:plant-raspberrypi3:20180905123741p:plain

・cutoff=0,gain=2.0

f:id:plant-raspberrypi3:20180905123746p:plain

・cutoff=0.5,gain=2.0

f:id:plant-raspberrypi3:20180905123751p:plain

・cutoff=1.5,gain=2.0

f:id:plant-raspberrypi3:20180905123804p:plain

・cutoff=0,gain=6.0

f:id:plant-raspberrypi3:20180905123818p:plain

・cutoff=0.5,gain=6.0

f:id:plant-raspberrypi3:20180905124040p:plain

・cutoff=1.5,gain=6.0

f:id:plant-raspberrypi3:20180905124047p:plain

exposure.adjust_log

gainを調整してみます。

image_show(rocket)

for i in np.arange(0.2,2,0.4):
    print(f'gain={i:.1f}')
    hist_img = exposure.adjust_log(rocket,gain=i)
    image_show(hist_img)

・元画像

f:id:plant-raspberrypi3:20180905124229p:plain

・gain=0.2

f:id:plant-raspberrypi3:20180905124235p:plain

・gain=0.6

f:id:plant-raspberrypi3:20180905124241p:plain

・gain=1.0

f:id:plant-raspberrypi3:20180905124249p:plain

・gain=1.4

f:id:plant-raspberrypi3:20180905124257p:plain

・gain=1.8

f:id:plant-raspberrypi3:20180905124303p:plain

各々の関数の雰囲気を探ってみました。

解説なしでスミマセン。

Twythonでつぶやいてみた [Twitter Bot]

scikit-imageのメモシリーズ途中ですが、息抜きで、PythonTwitterbotを作ってみました、というお話し。

QiitaのRSSがめちゃ簡単に取得できることを知り、「ラズパイ」タグのRSSをつぶやかせることにしました。

qiita.com

RSSの取得

feedparserを使います。

インストールは普通にpipでOK。

ついでにtwythonもインストール。

$ pip install feedparser twython

早速、やってみます。

このあたりのウェブページを参考にさせていただきます。

qiita.com

import feedparser
import time

RSS_URL = "https://qiita.com/tags/%E3%83%A9%E3%82%BA%E3%83%91%E3%82%A4/feed" #%E3%83%A9%E3%82%BA%E3%83%91%E3%82%A4=ラズパイ

qiita_raspi_rss = feedparser.parse(RSS_URL)

for entry in qiita_raspi_rss.entries[:10]:
    title = entry.title
    link  = entry.link
    print(f'{title}: {link}\n')

#出力結果
ラズパイ to VNCviewer: https://qiita.com/third_it_1027/items/ce1cf6fff981c25316cc

Aoide DAC II 備忘録: https://qiita.com/zkusaka/items/90141267b39d2e5eeb18

初心者がRaspberry Pi 3 Model B+を買ってSSH、RDP接続するまでを雑にメモ: https://qiita.com/flecott/items/20e4f1ef0ff1bdbc867a

【Raspberry Pi 3B/3B+】ラズパイをオーバークロック(OC)しよう: https://qiita.com/BearcubThaw/items/4aa0a7d373b597dfea21

【Raspberry Pi 3B/3B+】よく使うLinuxコマンド: https://qiita.com/BearcubThaw/items/b6041e57fee4e530b408

【Raspberry Pi 3B/3B+】ラズパイケースを改造して小型ファンを設置しよう: https://qiita.com/BearcubThaw/items/81b70017e5f7d5662ec5

周辺機器不要でラズパイ のセットアップ: https://qiita.com/yukoh/items/94788a5da05f626cf6f5

ラズパイでKubernetesを動かして見たら、驚いた!: https://qiita.com/MahoTakara/items/2b39e06f077927bafa2c

Raspberry Pi3を5インチのディスプレイで使いたい: https://qiita.com/uichi/items/655cb431cba260315250

Raspberry Pi3を使ってネットワークカメラを構築してみる: https://qiita.com/uichi/items/6094eb9fa633192e71e0

できました!

TwythonでTwitter Bot

完全にこちらを参考にさせていただきました。

deviceplus.jp

  1. ツイッターのアカウント作成
  2. ツイッターディベロッパーに登録(電話番号認証)
  3. Applicationを作成
  4. customer key, customer secret, access token, access token secretの4つを取得
  5. twythonでつぶやく

ツイッターディベロッパーへの登録はコチラからどうぞ。

apps.twitter.com

理由や使い方などを英語で延々と書かされるのがちょっと大変でした。

では、登録できたところで早速つぶやいてみます。

from twython import Twython

twitter = Twython(
    consumer_key,
    consumer_secret,
    access_token,
    access_token_secret
)

message = "こんにちは。これはテストです。"
twitter.update_status(status=message)

RSSをつぶやかせるには、messageのところに、rssで取得した情報を代入します。

for entry in qiita_raspi_rss.entries:
    title = entry.title
    link  = entry.link
    message = f'Qiita ラズパイRSS 「{title}」: {link}'
    twitter.update_status(status=message)
    time.sleep(30)

ツイッターに負荷をかけないように、30秒間スリープいれてみました。

新しく作ったツイッターページはこちら。

twitter.com

なかなか良い感じ。

今後は実際にRaspberry pi3など駆使して、定期的につぶやくようにしてみたいです。

scikit-imageのお勉強 第4回 transformモジュール ~拡大縮小・回転・アフィン変換~

scikit-imageの便利機能の備忘録シリーズ。

これまでの回はコチラ。

第1回 dataモジュールとioモジュール ~画像の入出力~

第2回 colorモジュール ~色空間~

第3回 drawモジュール ~図形の描画~

今回は第4回です。transformモジュールについてメモしていきます。

transformモジュール

回転などの変換に関するモジュールです。

公式ページ:Module: transform — skimage v0.14.0 docs

よく使いそうな機能は以下の4種類。

リサイズ・リスケール

  • transform.resize
  • transform.rescale

回転

  • transform.rotate

スワール(渦巻き化)

  • transform.swirl

アフィン変換

  • transform.AffineTransform + transform.warp

他に画像ピラミッド、Hough変換(特徴抽出の一種)やradon変換(断層画像の変換)なども利用できます。 この中で、今回は画像ピラミッドのみ取り上げます。

画像ピラミッド

  • transform.pyramid_reduce
  • transform.pyramid_expand
  • transform.pyramid_gaussian

いつも通り、テスト画像をdataモジュールで読み込んでおきます。
今回、拡大縮小が見やすいようにimage_show関数をちょっと改変しました。

・code 

%matplotlib inline
import matplotlib.pyplot as plt

from skimage import transform, data

def image_show(img):
    h,w,c = img.shape
    plt.figure(figsize=[float(w/100),float(h/100)])
    plt.title(f'w={w}, h={h}')
    plt.imshow(img)
    plt.xticks([])
    plt.yticks([])
    plt.show()

rocket = data.rocket()
image_show(rocket)

f:id:plant-raspberrypi3:20180904144416p:plain

リサイズ・リスケール

transform.resizeで指定の画像サイズにリサイズできます。

第1引数:画像データ
第2引数:リサイズ後の画像サイズ(タプル)
第3以降の引数:mode, anti_aliasingなど

・code

# 元画像表示
image_show(rocket)

# 画像の縮小
# mode='reflect'、anti_aliasing=True にする
resize_img = transform.resize(rocket,(200,500),mode='reflect',anti_aliasing=True)

image_show(resize_img)

# 画像の拡大
# modeはどれでも適用可
resize_img = transform.resize(rocket,(800,800),mode='constant',anti_aliasing=True)

image_show(resize_img)

・元画像

f:id:plant-raspberrypi3:20180904144537p:plain

・縮小画像

f:id:plant-raspberrypi3:20180904144543p:plain

・拡大画像

f:id:plant-raspberrypi3:20180904144556p:plain


transform.rescaleは特定の倍率に画像を拡大・縮小するための関数です。

第1引数:画像データ
第2引数:倍率(拡大なら>1、縮小なら<1)
第3以降の引数:mode, anti_aliasingなど

・code

# 元画像表示
image_show(rocket)

# 画像の縮小
# mode='reflect'、anti_aliasing=Trueにする
# multichannelは明示(True/False)
rescale_img = transform.rescale(rocket,0.2,mode='reflect',anti_aliasing=True,multichannel=True)

image_show(rescale_img)

# 画像の拡大
# modeはどれでも適用可
# multichannelは明示(True/False)
rescale_img = transform.rescale(rocket,1.5,mode='constant',anti_aliasing=True,multichannel=True)

image_show(rescale_img)

・元画像

f:id:plant-raspberrypi3:20180904144638p:plain

・縮小画像

f:id:plant-raspberrypi3:20180904144646p:plain

・拡大画像

f:id:plant-raspberrypi3:20180904144654p:plain

回転

画像の回転はtransform.rotateで行います。回転の際にリサイズするかどうかを選べます。

第1引数:画像データ
第2引数:角度(時計回り方向)
第3以降の引数:resizeなど

・code

# 元画像の表示
image_show(rocket)

# リサイズなしの回転
rotate_img = transform.rotate(rocket,90,resize=False)

image_show(rotate_img)

# リサイズありの回転
rotate_img = transform.rotate(rocket,90,resize=True)

image_show(rotate_img)

・元画像

f:id:plant-raspberrypi3:20180904144721p:plain

・リサイズなしの回転

f:id:plant-raspberrypi3:20180904144732p:plain

・リサイズありの回転

f:id:plant-raspberrypi3:20180904144744p:plain

スワール(渦巻き化)

画像に渦巻き加工を入れる関数としてtransform.swirlというものがあります。

第1引数:画像データ
第2以降の引数:mode, strengthなど

strengthを変えることで渦の強さを調節できます。

・code

for i in range(1,11,3):
    print(f'strength={i}')
    swirl_img = transform.swirl(rocket,mode='reflect',strength=i)
    image_show(swirl_img)

・元画像

f:id:plant-raspberrypi3:20180904144537p:plain

・strength=1

f:id:plant-raspberrypi3:20180904144903p:plain

・strength=4

f:id:plant-raspberrypi3:20180904144910p:plain

・strength=7

f:id:plant-raspberrypi3:20180904144916p:plain

・strength=10

f:id:plant-raspberrypi3:20180904144923p:plain

アフィン変換

アフィン変換をマスターすると拡大縮小・平行移動・反転・回転・剪断変形などなんでもできて便利です。

zellij.hatenablog.com

「アフィン変換とは平行移動と線形変換を組み合わせた変換」のこと。(中略) 具体的には、線形変換(拡大縮小、剪断、回転)、平行移動があり、これらの組み合わせで表現される。

scikit-imageでのアフィン変換のやり方は、ここまで説明してきた変換とは手順が違うので注意が必要です。 

1. transform.AffineTransformクラスのインスタンスを作成
2. transform.warpで画像に上記インスタンスの効果を適用

  

それぞれのクラス・関数の引数は以下の通りです。

  • transform.AffineTransformの引数

scale:拡大縮小(縦・横のタプルで与える、マイナスで反転)
translation:平行移動(縦・横のタプルで与える)
rotation:回転
shear:剪断変形

反転・回転・剪断変形は画像が画面からはみ出ることが多いので、適宜translationを組み合わせて使います。

  • transform.warpの引数

第1引数:画像データ
第2引数: transform.AffineTransformクラス等のインスタンス

・code

# 元画像の表示
image_show(rocket)

# 拡大縮小のみ
at = transform.AffineTransform(scale=(1.2,1.2))
warp_img = transform.warp(rocket,at)
image_show(warp_img)

# 平行移動のみ
at = transform.AffineTransform(translation=(-30,-50))
warp_img = transform.warp(rocket,at)
image_show(warp_img)

#反転と平行移動
at = transform.AffineTransform(scale=(-1,1),translation=(640, 0))
warp_img = transform.warp(rocket,at)
image_show(warp_img)

# 回転と平行移動
at = transform.AffineTransform(rotation=0.5,translation=(300,-100))
warp_img = transform.warp(rocket,at)
image_show(warp_img)

# 剪断変形と拡大縮小、平行移動
at = transform.AffineTransform(shear=1,scale=(1.5,1.5),translation=(30,30))
warp_img = transform.warp(rocket,at)
image_show(warp_img)

・元画像

f:id:plant-raspberrypi3:20180904145046p:plain

・縮小

f:id:plant-raspberrypi3:20180904145052p:plain

・平行移動

f:id:plant-raspberrypi3:20180904145110p:plain

・反転と平行移動

f:id:plant-raspberrypi3:20180905104616p:plain

・回転と平行移動

f:id:plant-raspberrypi3:20180904145117p:plain

・剪断変形と拡大縮小、平行移動

f:id:plant-raspberrypi3:20180904145128p:plain

おまけ:画像ピラミッド

画像ピラミッドに関してはOpenCVのTutorialページがわかりやすいです。

画像ピラミッド — OpenCV-Python Tutorials 1 documentation

例えば画像中で何かを探すとき(例えば顔)、画像中にどのような大きさで現れるか分かりません。そのような状況では様々な解像度の画像を用意し、全画像に対して物体検出を試みます。これらの異なる解像度を持つ画像の集合を画像ピラミッド(最大解像度の画像を下に、最小解像度の画像を上に積むとピラミッドのようになるからです)と呼びます。

 
画像ピラミッドの関数には、transform.pyramid_reducetransform.pyramid_expandやジェネレーターとして機能するtransform.pyramid_gaussianなどがあります。

・code

#pyramidシリーズ

image_show(rocket)

pyramid_img = transform.pyramid_reduce(rocket,downscale=5,multichannel=True)

image_show(pyramid_img)

pyramid_img = transform.pyramid_expand(rocket,upscale=1.5,multichannel=True)

image_show(pyramid_img)

・元画像

f:id:plant-raspberrypi3:20180904145249p:plain

・transform.pyramid_reduce

f:id:plant-raspberrypi3:20180904145304p:plain

・transform.pyramid_expand

f:id:plant-raspberrypi3:20180904145313p:plain

これらは普通の拡大縮小とあまり変わらないですね。


次のようにジェネレーターを使うと画像ピラミッドっぽさが出ます。

・code

pyramid_gen = transform.pyramid_gaussian(rocket,downscale=2,multichannel=True)

for i in range(5):
    pyramid_img = next(pyramid_gen)

    image_show(pyramid_img)

・1回目

f:id:plant-raspberrypi3:20180904145408p:plain

・2回目

f:id:plant-raspberrypi3:20180904145414p:plain

・3回目

f:id:plant-raspberrypi3:20180904145421p:plain

・4回目

f:id:plant-raspberrypi3:20180904145429p:plain

・5回目

f:id:plant-raspberrypi3:20180904145437p:plain

今回はここまで。

2018/9/10訂正

アフィン変換の説明のところで引数に誤植があったため修正しました。

scikit-imageのお勉強 第3回 drawモジュール ~図形の描画~

scikit-imageの便利機能の備忘録シリーズ。

これまでの回はコチラ。

第1回 dataモジュールとioモジュール ~画像の入出力~

第2回 colorモジュール ~色空間~

今回は第3回です。drawモジュールについてメモしていきます。

drawモジュールでできること

様々な図形を描画し、画像に上書きできます。

公式ページ: Module: draw — skimage v0.14.0 docs

次のような図形が描けます。

直線

  • draw.line
  • draw.line_aa

ベジェ曲線

  • draw.bezier_curve

多角形

  • draw.polygon
  • draw.polygon_perimeter

四角形

  • draw.rectangle

楕円

  • draw.ellipse
  • draw.ellipse_perimeter

正円

  • draw.circle
  • draw.circle_perimeter
  • draw.circle_perimeter_aa

手順としては以下の通り。

  1. draw.xxx関数を使って描画したい座標群rr, ccを指定
  2. NumPyの座標指定の機能を使って、rr,cc座標群を塗りつぶす

直線

直線を描く関数はdraw.linedraw.line_aaの2種類あります。

いずれも引数は以下の通り。

第1引数:始点の座標(縦)
第2引数:始点の座標(横)
第3引数:終点の座標(縦)
第4引数:終点の座標(横)

draw.line_aa()のほうは自然な感じに描けますが、valというパラメーターが増えます。

まず前回と同じように、テスト画像をdataモジュールで読み込みます。

・code 

%matplotlib inline
import matplotlib.pyplot as plt
from skimage import draw, data

def image_show(img):
    plt.imshow(img)
    plt.xticks([])
    plt.yticks([])
    plt.show()

rocket = data.rocket()
image_show(rocket)

・元画像

f:id:plant-raspberrypi3:20180903162440p:plain

直線を描いてみます。

・code 

rr,cc = draw.line(51,101,301,601) 
print(rr[:10]) #10番目までの座標群(縦)
print(cc[:10]) #10番目までの座標群(横)
rocket2 = data.rocket()
rocket2[rr,cc] = 255
image_show(rocket2)
image_show(rocket2[50:100,125:175])

#出力結果
[51 52 52 53 53 54 54 55 55 56]  #10番目までの座標群(縦)
[101 102 103 104 105 106 107 108 109 110]   #10番目までの座標群(横)

・出力画像

f:id:plant-raspberrypi3:20180903163857p:plain

・出力画像(拡大)

f:id:plant-raspberrypi3:20180903163905p:plain

draw.line_aaも試してみます。

・code 

rr,cc,val = draw.line_aa(51,101,301,601) #valも計算される
rocket2 = data.rocket()
for i in range(3):
    rocket2[rr,cc,i] = val*255 #各色チャネルに対して塗りつぶし処理する必要がある
image_show(rocket2)
image_show(rocket2[50:100,125:175])

・出力画像

f:id:plant-raspberrypi3:20180903163929p:plain

・出力画像(拡大)

f:id:plant-raspberrypi3:20180903163938p:plain

ベジェ曲線

draw.bezier_curveを使うとベジェ曲線も描けちゃいます。
ただし、思うような曲線を描くためには試行錯誤が必要かもしれません。

第1引数:始点の座標(縦)
第2引数:始点の座標(横)
第3引数:中間点の座標(縦)
第4引数:中間点の座標(横)
第5引数:終点の座標(縦)
第6引数:終点の座標(横)
第7引数:重み

引数が多くてややこしいです。

・code 

for i in range(1,5):
    rr,cc = draw.bezier_curve(51,101,301,301,301,601,i)
    rocket2 = data.rocket()
    rocket2[rr,cc] = 255
    image_show(rocket2)

・重み=1

f:id:plant-raspberrypi3:20180903164422p:plain

・重み=2

f:id:plant-raspberrypi3:20180903164438p:plain

・重み=3

f:id:plant-raspberrypi3:20180903164450p:plain

・重み=4

f:id:plant-raspberrypi3:20180903164501p:plain

多角形

draw.polygon(塗りつぶし)またはdraw.polygon_perimeter(輪郭)で描けます。

第1引数:頂点の座標群(縦・Numpyアレイで与える)
第2引数:頂点の座標群(横・Numpyアレイで与える)

・code 

import numpy as np

rr,cc = draw.polygon(np.array([101,301,201,101,51]),np.array([101,201,401,401,301]))
rocket2 = data.rocket()
rocket2[rr,cc] = 255
image_show(rocket2)

・出力画像

f:id:plant-raspberrypi3:20180903164651p:plain

・code 

rr,cc = draw.polygon_perimeter(np.array([101,301,201,101,51]),np.array([101,201,401,401,301]))
rocket2 = data.rocket()
rocket2[rr,cc] = 255
image_show(rocket2)

・出力画像

f:id:plant-raspberrypi3:20180903164736p:plain

四角形

scikit-imageのversion0.14.xで足されたようです。

draw.rectangleで描きます。

第1引数:始点の座標([縦、横])
第2以降の引数(end=):終点の座標([縦、横])

・code 

rr,cc = draw.rectangle([101,101],end=[201,301])
rocket2 = data.rocket()
rocket2[rr,cc] = 255
image_show(rocket2)

・出力画像

f:id:plant-raspberrypi3:20180903165858p:plain

楕円

draw.ellipse(塗りつぶし)またはdraw.ellipse_perimeter(輪郭)で描けます。

第1引数:中心の座標(縦)
第2引数:中心の座標(横)
第3引数:長軸・短軸方向の径(縦)
第4引数:長軸・短軸方向の径(横)

・code 

rr,cc = draw.ellipse(251,251,50,150)
rocket2 = data.rocket()
rocket2[rr,cc] = 255
image_show(rocket2)

・出力画像

f:id:plant-raspberrypi3:20180903164847p:plain

・code 

rr,cc = draw.ellipse_perimeter(251,251,50,150)
rocket2 = data.rocket()
rocket2[rr,cc] = 255
image_show(rocket2)

・出力画像

f:id:plant-raspberrypi3:20180903165341p:plain

正円

正円を描く方法はdraw.circledraw.circle_perimeterdraw.circle_perimeter_aaの3種類です。

第1引数:中心の座標(縦)
第2引数:中心の座標(横)
第3引数:直径(縦)

・code 

rr,cc = draw.circle(251,251,50)
rocket2 = data.rocket()
rocket2[rr,cc] = 255
image_show(rocket2)

・出力画像

f:id:plant-raspberrypi3:20180903165532p:plain

・code 

rr,cc = draw.circle_perimeter(251,251,50)
rocket2 = data.rocket()
rocket2[rr,cc] = 255
image_show(rocket2)
image_show(rocket2[190:240,190:240])

・出力画像

f:id:plant-raspberrypi3:20180903165543p:plain

・出力画像(拡大)

f:id:plant-raspberrypi3:20180903165556p:plain

・code 

rr,cc,val = draw.circle_perimeter_aa(251,251,50)
rocket2 = data.rocket()
for i in range(3):
    rocket2[rr,cc,i] = val*255  #各色チャネルに対して塗りつぶし処理する必要がある
image_show(rocket2)
image_show(rocket2[190:240,190:240])

・出力画像

f:id:plant-raspberrypi3:20180903165620p:plain

・出力画像(拡大)

f:id:plant-raspberrypi3:20180903165629p:plain

今回は以上です。