作成者別アーカイブ: user

【余談#7】 8700マシンから9900Kマシンへ(予定)

【余談#7】 8700マシンから9900Kマシンへ(予定)

1. やりたいこと

過去記事 【余談#6】 3770マシンから8700マシンへ で書いたように、2018年3月に i7 3770 から i7 8700 にメインパソコンの CPUを入れ替えた。

換装後の環境はとても快適で、今のところは何の不満もない。

でも…
2018年11月に発売された i9 9900K なる 8コア 16スレッド CPUに強い興味がある。

現在使っている Z370マザボに載せられるとのことなので、お金が許すならばぜひこれに乗り換えたい。

2.調査&準備

1) Passmarkのベンチ結果

i9 9900K は、なんと E5-2687W V4 を上回るスコアだ!
今使っている i7 8700 と比べても 34%も処理性能がアップしている!

E5-2687W V4 は 2016年に発売された Intel Broadwell世代のハイエンド CPUで、当時これを Dual搭載しているワークステーションは 100万円超えが当たり前だった。

そんなハイエンド CPUと肩を並べるような CPUを家庭用パソコンに搭載できるのだ。
ぜひほしい!

2) いくらで売ってる?

2018年11月現在、品薄状態で入手困難 だ。
Amazon, ドスパラ、ヨドバシなどでは売り切れ状態だ。

ヤフオクを眺めると、転売屋さんたちがちょっと高めで販売している…

残念ながら自分には これを購入するだけの予算が無い
後日 Intelの出荷量が増えて価格が落ち着いたころに、量販店で買おうと考えている。

過去記事 (103) Intel NUCを Subversionサーバーにする。 で書いたように、Intel NUCの購入で今は4万円近くもお小遣いを失ったばかりなのだ…

3) i7 8700用に購入した Z370マザーボードがそのまま使える!

CPU換装時にマザボも変えなければならないことがしばしばある。

だが、i7 8700用に購入したマザボ ASUS TUF Z370-PLUS GAMINGBIOSをアップデートすれば i9 9900K が載せられるとのこと!

こちらの ASUS公式ページにそう書いてある。
 

BIOSのバージョンが 1002 以上であれば i9 9900K を載せられるのだそうな。

インターネットに繋がっていれば BIOS設定画面で簡単に BIOSのアップデートができる。
これをやってみたら BIOSのバージョンが 1410 に更新された。
我が家での受け入れ準備は OKだ!

4) 捕らぬ狸の皮算用

過去記事 【余談#6】 3770マシンから8700マシンへ と同じように、古い CPUをヤフオクで売却して、そのお金を新しい CPUの購入資金に充てたい。

現在使用している i7 8700 の落札相場は 2018年11月9日時点で4万円前後のようだ。

自分が i9 9900Kの購入を決断した時点で、この相場がいくらになっているのか?
毎度のことだが運と勘が試される。

もし 購入に踏み切るならば…
i9 9900K購入: 80,000円
i7 8700売却 : 36,000円 ← ヤフオクで4万円で売却し手数料を引いた金額
----------------
実質購入金額: 44,000円

む!
今買うか?

いや、冷静になろう。

でも…
2018年3月、自分は税込34,673円で i7 8700を購入した。

その後品薄状態になったためか、
2018年11月、同じお店で税込43,980円で販売している。

つまり…
値下がるどころか 9,307円も値上がりしている!

相場師としての勘が試される状況だ。
i9 9900Kは半年後にはもっと高くなってしまっているのか?

見えない時には手を出さないのが相場の鉄則だ。
今は待とう。

5) CPUクーラーと電源も新調?

下記サイトのレポートを見ると、高性能な空冷CPUクーラーを使っていても、Turbo Boost On(clock = 5GHz)で高負荷状態時には CPU温度が 100℃に達し、サーマルスロットリング(=CPU性能を落として温度を下げる機能)が働いているとのこと。安心・安全マージンを取るには、水冷CPUクーラーを新調する必要がありそうだ。

こちら(↓)で詳しくレビューしてくださっている。

また、消費電力が大きいので電源容量にも注意する。
i9 9900Kは TDP = 95W(base clock 3.6GHz)だが、Turbo Boost ONで全コア 5GHz動作時には消費電力が 250Wにまで増えるのだそうな。
グラボやらメモリやらストレージやら、マザボ上に載せているすべてのデバイスの消費電力を足し合わせたら、今うちで使っている 650W電源ではギリギリになるかもしれない…

よって…
・水冷CPUクーラー
・電源(700W~)

も新調しなければならないかもしれない。

+20,000円?
やばい、想定外の出費が増える…

まぁ BIOS設定で Turbo ModeDisable にして定格(CPU clock = 3.6GHz)で使用するならば無問題だ。
でもそれだと i9 9900Kの魅力が大幅ダウンだよなぁ…

3. 購入

今はここでペンディング状態だ… 2018/11/09

i9 9900Kの価格が落ち着き、CPUクーラーと電源を購入する目途が立ったら、この続きを書こうと思う。
2019年4月までには実現したいなぁ…


(105) 独自定義の例外を発生させる。

(105) 独自定義の例外を発生させる。

throw じゃなくて raise と書く。

########################################################################
class MyError(Exception):
    pass

########################################################################
def check_number( n ):
    ret = False
    try:
        if n < 10 :
            raise MyError('%d is too small' % n)
        if n > 100 :
            raise MyError('%d is too big' % n)
        ret = True
    except MyError as e:
        print('<p style="color:#f00">%s</p>' % e.message)
    return ret

実行してみる。

>>> check_number(1)
<p style="color:#f00">1 is too small</p>
False
>>>
>>> check_number(50)
True
>>>
>>> check_number(200)
<p style="color:#f00">200 is too big</p>
False

(104) 「PILで写真に日付を入れる」をWEBサービス化

(104) 「PILで写真に日付を入れる」をWEBサービス化

1. やりたいこと

Pythonは WEBとの親和性が高い。

よって…
先に書いた (102) PILで写真に日付を入れる をWEBブラウザから実行できるようにする。

それだけ…

2. 仕様

1) WEBブラウザから画像ファイルをアップロードする。
2) CGIで Pythonプログラムを起動する。
3) アップロードされた画像ファイルの EXIF情報から撮影日を抽出する。
4) アップロードされた画像ファイルを幅 640pxにリサイズする。
5) アップロードされた画像ファイルの右下に日付を埋め込む。
6) 処理後の画像を WEBブラウザに表示する。
7) 今回は排他制御は考えない。

3. やってみる

出来上がった物はこちら。
https://www.dogrow.net/python/sample/0104/

処理の詳細は (102) PILで写真に日付を入れる に記してある。

index.cgi
サーバーにアップロード後、当該ファイルに実行属性を付与すること。
出力ファイル名はなるべく散らばるように時刻値とプロセスIDを使用している。(※不完全な排他)

#!/usr/local/bin/python
# -*- coding: utf-8 -*-
print 'Content-type: text/html'

import os
import cgi
from datetime import datetime
import mylib

print """
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>サンプル No.104</title>
</head>
<body>
"""

form = cgi.FieldStorage()
if form.has_key('upfile'):     # formから投稿された情報 upfile あり?
    item = form['upfile']
    if item.file:              # upfile はアップロードされたファイル?
        img_path = './%s_%d_%s' % (datetime.now().strftime("%s"), os.getpid(), item.filename)
        mylib.save_img(item, img_path)       # 投稿ファイルを保存
        if True == mylib.exec_put_date(img_path) :   # 投稿画像ファイルに日付を書く
            print('<img src="' + img_path + '">')    # 出来上がった画像を表示
        else:
            os.remove(img_path)   # 失敗したならばアップロードファイルを削除

# 投稿フォームを表示
print """
<form enctype="multipart/form-data" action="./index.cgi" method="post">
  <input type="file" name="upfile">
  <input type="submit" value="実行">
</form>
</body>
</html>
"""

mylib.py

from PIL import Image, ImageDraw, ImageFont

########################################################################
class MyError(Exception):
    pass

########################################################################
def save_img( item, save_path ):
    fout = file(save_path, 'wb')
    while True:
        chunk = item.file.read(1000000)
        if not chunk:
            break
        fout.write(chunk)
    fout.close()

########################################################################
def resize_img( img ):
    w,h = img.size
    w_new = 640
    h_new = h * w_new / w
    imgR = img.resize((w_new, h_new), resample=Image.ANTIALIAS)
    return imgR

########################################################################
def exec_put_date( filepath ):
    ret = False
    try:
        img = Image.open(filepath)
        exif = img._getexif()
        if None == exif :
            raise MyError('No exif')

        if False == 36867 in exif :
            raise MyError('No DateTimeOriginal in exif')

        text_datetime = exif[36867]
        text_date = text_datetime.split()[0]
        text_date = text_date.replace(':','-')

        img = resize_img(img)
        pos_x = img.size[0] - 150
        pos_y = img.size[1] - 30

        obj_draw = ImageDraw.Draw(img)
        obj_font = ImageFont.truetype("./ipagp.ttf", 24)
        obj_draw.text((pos_x+1, pos_y+1), text_date, fill=(0, 0, 0),     font=obj_font)
        obj_draw.text((pos_x,   pos_y),   text_date, fill=(255, 160, 0), font=obj_font)

        img.save(filepath)
        ret = True

    except MyError as e:
        print('<p style="color:#f00">%s</p>' % e.message)

    except IOError:
        print('<p style="color:#f00">Invalid file</p>')

    return ret

できた!


(103) TensorFlow #1 : はじめての TensorFlow

(103) TensorFlow #1 : はじめての TensorFlow

1. やりたいこと

とりあえず触れてみるだけ。
今回は以下の公式ホームページに従ってインストール&チュートリアルをやってみる。

2. 環境

TensorFlowは、現在進行形で日々改良が加えられているようだ。

ということは…
あるとき突然不具合のあるバージョンを入れたがために環境が壊れてしまうかもしれない。

ということで…
以下の仮想マシン上に実験環境を作ることにした。
・VirtualBox : 5.2.18
・Ubuntu : 18.04
環境を更新する前に VMのスナップショットを記録しておけば安心だ。

3. やってみる

(1) インストールする。

仮想マシン上に Ubuntu18をインストールし、ここに TensorFlowをインストールする。
すでに Ubuntuのインストールが終わった状態からの作業手順を記録する。

1) 必要なパッケージを入れる。

sudo apt-get update
sudo apt-get install python-virtualenv python3-pip python3-dev python-launchpadlib
pip install --upgrade pip

2) TensorFlow実験用に Python仮想環境を作る。

mkdir ~/tensorflow
cd ~/tensorflow
virtualenv --system-site-packages -p python3 venv
source ~/tensorflow/venv/bin/activate
python -V    # Pythonのバージョンを確認

3) TensorFlowをインストールする。

pip install --upgrade pip
# pip install --upgrade tensorflow  # これだと import tensorflow でコアダンプする...
pip install tensorflow==1.5

Pythonが動くか試してみる。

import tensorflow as tf
print(tf.__version__)

エラーなく TensorFlowのバージョンが表示されればOK

1.5.0

(2) チュートリアルを動かす。

こちらのページのチュートリアルを実行してみる。

TensorFlowをロードする。

import tensorflow as tf

まずは MNIST手書き文字データをロードし、0~255の画素値を 0~1に正規化する。

# MNIST手書き文字画像データをロード
mnist = tf.keras.datasets.mnist
(x_train, y_train),(x_test, y_test) = mnist.load_data()

# 画素値を 0~1 に正規化
x_train, x_test = x_train / 255.0, x_test / 255.0

モデルを作成

model = tf.keras.models.Sequential([
  tf.keras.layers.Flatten(input_shape=(28, 28)),
  tf.keras.layers.Dense(512, activation=tf.nn.relu),
  tf.keras.layers.Dropout(0.2),
  tf.keras.layers.Dense(10, activation=tf.nn.softmax)
])
model.compile(optimizer='adam',
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])

学習を実行

model.fit(x_train, y_train, epochs=5)

実行中のログはこんな感じで出力される。
5 epochsで正解率(accuracy)が 96.3%から 98.6%まで上昇した。
1 epoch目で 96.3%とはかなり優秀なモデルだ。

テストを実行

model.evaluate(x_test, y_test)

学習に使った画像データで高正解率なのは当然なこと。
学習に使っていない画像データでテストした結果は?

なんと 97.99% と高正解率!

とっても優秀なモデルだな。

4. おまけ

(1) MNIST画像を覗いてみる。

まずは MNIST画像データをロードする。

import tensorflow as tf
mnist = tf.keras.datasets.mnist
(x_train, y_train),(x_test, y_test) = mnist.load_data()

どう格納されている?

x_train.shape
(60000, 28, 28)

x_train[60000][28][28] の三次元配列だった。

すなわち…
学習用の画像データは 28 x 28ピクセルサイズの画像が 60000枚ある。

先頭 1個(x_train[0])を画像化して見てみる。

from PIL import Image
img = Image.fromarray(x_train[0])
img.show()

たぶん 5 だろう。

y_trainの方も見てみる。

y_train.shape
(60000,)

y_train[60000] の一次元配列だった。

先頭から 3個だけ中身を見てみる。

y_train[0:3]
array([5, 0, 4], dtype=uint8)

x_trainの先頭には 5 の画像が入っていた。
y_trainの方には正解データが入っているようだ。
3個目が 4 かを確認してみる。

img = Image.fromarray(x_train[2])
img.show()

たぶん 4 だろう。

とりあえず先頭から 100個だけ PILで PNG画像ファイル化してみた。

import tensorflow as tf
from PIL import Image

mnist = tf.keras.datasets.mnist
(x_train, y_train),(x_test, y_test) = mnist.load_data()

for i in range(0,100):
    img = Image.fromarray(x_train[i])
    img.save("./mnist_%05d.png" % i, 'PNG')

日本人は字がキレイと言われるのが何となくわかるなぁ

5. 備忘録

1) 仮想マシン上での実行は遅い。いずれは物理サーバー上に環境を作ろう。

2) 以下の警告が出た。SIMD命令を有効にするため TensorFlowをソースからコンパイルして作った方がよい。

Your CPU supports instructions that this TensorFlow binary was not compiled to use: SSE4.1 SSE4.2

3) インストールした物の組み合わせによっては正しく動かない場合があるみたい。
 コアダンプする場合もあった。発展途上の物なのでこれは仕方がないか…
 今後、回避策を要検討。
 ・TensorFlowはソースから作る。
 ・その他


(102) PILで写真に日付を入れる

(102) PILで写真に日付を入れる

1. やりたいこと

写真の右下に「1988/10/16」とかプリントされるあれを、写真画像に付けてみたい。

2. 実現手段

1) 日付は、写真画像に埋め込まれている EXIF情報から取得する。
2) 写真画像への日付の表示には PIL(Pillow)を使う。

3. やってみる

こんな画像でやってみる。2018年1月14日に山梨県の山中湖で撮影した写真だ。

Step 1 : EXIF情報を見てみる。

画像ファイルから読みだした EXIF情報を列挙してみるだけのプログラムを書いた。

from PIL import Image
from PIL import ExifTags

img = Image.open("img001.jpg")
exif = img._getexif()

for id, val in exif.items():
    tag = ExifTags.TAGS.get(id)
    print("%d : %s : %s" % (id, tag, val))

実行結果は以下の通り。(長いものは省略した)
この中で日付っぽいものを色付けしてみた。
緑色のコメントは、後から自分が付け足した。

271 : Make : xxxxx
272 : Model : xxxxx
274 : Orientation : 1
282 : XResolution : (72, 1)
283 : YResolution : (72, 1)
296 : ResolutionUnit : 2
305 : Software : xxxxx
306 : DateTime : 2018:01:14 15:11:41 # 画像ファイルの更新日
531 : YCbCrPositioning : 1
33434 : ExposureTime : (1, 934)
33437 : FNumber : (9, 5)
34850 : ExposureProgram : 2
34853 : GPSInfo : xxxxx
34855 : ISOSpeedRatings : 20
34665 : ExifOffset : 206
36864 : ExifVersion : b'0221'
36867 : DateTimeOriginal : 2018:01:14 15:11:41 # 撮影された日時
36868 : DateTimeDigitized : 2018:01:14 15:11:41 # 画像がデジタルデータ化された日時
37121 : ComponentsConfiguration : xxxxx
37377 : ShutterSpeedValue : (8298, 841)
37378 : ApertureValue : (2159, 1273)
37379 : BrightnessValue : (9533, 1036)
37380 : ExposureBiasValue : (0, 1)
37383 : MeteringMode : 5
37385 : Flash : 16
37386 : FocalLength : (399, 100)
37396 : SubjectLocation : (2015, 1511, 2217, 1330)
37500 : MakerNote : xxxxx
37521 : SubsecTimeOriginal : 425
37522 : SubsecTimeDigitized : 425
40960 : FlashPixVersion : b'0100'
40961 : ColorSpace : 65535
40962 : ExifImageWidth : 4032
40963 : ExifImageHeight : 3024
41495 : SensingMethod : 2
41729 : SceneType : xxxxx
41985 : CustomRendered : 2
41986 : ExposureMode : 0
41987 : WhiteBalance : 0
41989 : FocalLengthIn35mmFilm : 28
41990 : SceneCaptureType : 0
42034 : LensSpecification : xxxxx
42035 : LensMake : xxxxx
42036 : LensModel : xxxxx

画像に追加表示したいのは撮影日時なので DateTimeOriginal を使えばよいみたいだ。

Step 2 : 画像ファイルから DateTimeOriginal だけを取得する。

今度は関数にしてみた。

from PIL import Image

# EXIFから撮影日を取得
def get_datetime( img ):
    exif = img._getexif()
    for id, val in exif.items():
        if id == 36867:
            return val
    return ''

# 画像ファイルを開き、撮影日を取得し、結果を表示する。
img = Image.open("img001.jpg")
datetime = get_datetime(img)
print(datetime)

実行結果は以下の通り。

2018:01:14 15:11:41

Step 3 : 画像にテキストを追記する。

実は (99) OpenCV #4 : ガンマ補正で画像を見やすく調整 ですでにやっている。
これも PIL を使って実現している。

from PIL import Image, ImageDraw, ImageFont

# 画像をロード
img = Image.open("img001.jpg")

# 画像にテキスト「Hello!」を追記
obj_draw = ImageDraw.Draw(img)
obj_font = ImageFont.truetype("./ipagp.ttf", 30)
obj_draw.text((10, 10), "Hello!", fill=(255, 160, 0), font=obj_font)

# 画像表示
img.show()

こんな感じになった。
因みにフォントはフリーの IPAフォント を使わせていただいた。

ここで 2点問題がある。
1) 文字が見づらい。
2) 文字の表示位置は右下がよい。

まずは文字を見やすくするため、文字色と輝度差が大きい色で影を付けてみる。

from PIL import Image, ImageDraw, ImageFont

# 画像をロード
img = Image.open("img001.jpg")

# 画像にテキスト「Hello!」を追記
obj_draw = ImageDraw.Draw(img)
obj_font = ImageFont.truetype("./ipagp.ttf", 30)
obj_draw.text((11, 11), "Hello!", fill=(0, 0, 0), font=obj_font)
obj_draw.text((10, 10), "Hello!", fill=(255, 160, 0), font=obj_font)

# 画像表示
img.show()

次に、文字の表示位置を画像の右下にしてみる。

from PIL import Image, ImageDraw, ImageFont

# 画像をロード
img = Image.open("img001.jpg")

# テキスト表示位置を決定
pos_x = img.size[0] - 90
pos_y = img.size[1] - 40

# 画像にテキスト「Hello!」を追記
obj_draw = ImageDraw.Draw(img)
obj_font = ImageFont.truetype("./ipagp.ttf", 30)
obj_draw.text((pos_x+1, pos_y+1), "Hello!", fill=(0, 0, 0),     font=obj_font)
obj_draw.text((pos_x,   pos_y),   "Hello!", fill=(255, 160, 0), font=obj_font)

# 画像表示
img.show()

文字の表示位置は、フォントサイズと表示するテキストの長さに合わせて後で調整しよう。
※自動的に計算すればよいのだが、それは今回のテーマではないので…

Step 4 : 本題! 写真に日付を入れる。

今までの Step 1 ~ Step 3 の合わせ技で実現する。

from PIL import Image, ImageDraw, ImageFont

# EXIFから撮影日を取得
def get_datetime( img ):
    exif = img._getexif()
    for id, val in exif.items():
        if id == 36867:
            return val
    return ''

# 画像をロード
img = Image.open("img001.jpg")

# 画像の撮影日を取得
text_datetime = get_datetime(img)
text_date = text_datetime.split()[0]     # 時刻を切り離す。
text_date = text_date.replace(':','-')   # 年月日の区切り文字を変更

# テキスト表示位置を決定
pos_x = img.size[0] - 150
pos_y = img.size[1] - 30

# 画像にテキストを追記
obj_draw = ImageDraw.Draw(img)
obj_font = ImageFont.truetype("./ipagp.ttf", 24)
obj_draw.text((pos_x+1, pos_y+1), text_date, fill=(0, 0, 0),     font=obj_font)
obj_draw.text((pos_x,   pos_y),   text_date, fill=(255, 160, 0), font=obj_font)

# 画像表示
img.show()

イメージした通りになったかも。
※上記の処理中ではエラー発時を一切考慮していないので、必要に応じて追加実装しようと思う。

4. 応用編

画像の指定、色の指定、エラー処理など、汎用性、冗長性を持たせる。

(1) 画像ファイルを指定出来るようにする。

put_text.py

import argparse
from PIL import Image, ImageDraw, ImageFont

# EXIFから撮影日を取得
def get_datetime( img ):
    exif = img._getexif()
    for id, val in exif.items():
        if id == 36867:
            return val
    return ''

def exec_put_date( filepath ):
    # 画像をロード
    img = Image.open(filepath)

    # 画像の撮影日を取得
    text_datetime = get_datetime(img)
    text_date = text_datetime.split()[0]     # 時刻を切り離す。
    text_date = text_date.replace(':','-')   # 年月日の区切り文字を変更

    # テキスト表示位置を決定
    pos_x = img.size[0] - 150
    pos_y = img.size[1] - 30

    # 画像にテキストを追記
    obj_draw = ImageDraw.Draw(img)
    obj_font = ImageFont.truetype("./ipagp.ttf", 24)
    obj_draw.text((pos_x+1, pos_y+1), text_date, fill=(0, 0, 0),     font=obj_font)
    obj_draw.text((pos_x,   pos_y),   text_date, fill=(255, 160, 0), font=obj_font)

    # 画像表示
    img.show()

def main():
    # コマンドライン引数を取得
    parser = argparse.ArgumentParser()
    parser.add_argument('-f','--filepath',  required=True)
    args = parser.parse_args()
    # 画像に日付を埋め込み
    exec_put_date(args.filepath)

if __name__=="__main__": main()

Shellのコマンドラインから以下のように実行する。
-f オプションで処理対象画像のファイルパスを指定する。

python put_text.py -f img001.jpg

(101) OpenCV #6 : 写真から顔を自動検出

(101) OpenCV #6 : 写真から顔を自動検出

1. やりたいこと

Pythonで OpenCVの第 6回目、今回は OpenCVの Haar Cascades を使って写真から顔検出して遊んでみる。

敢えて「遊んでみる」と書いたのは、原理的な所を学習せずに顔検出機能を使ってみるだけだから…

2. 前準備

(1) 顔の写った写真を用意する。

顔検出に使った写真をこのブログに掲載するので、以下の条件を満たしている必要がある。
・自分や知り合いの顔が写っている写真はダメだ。
・ライセンス上の問題が発生する写真はダメだ。

ということで CC0ライセンス画像を使うことにする。
pixabay

(2) OpenCVで使用する学習済みデータを入手する。

こちらから入手する。
https://github.com/opencv/opencv

この中から以下の2ファイルを使用する。
・opencv/data/haarcascades/haarcascade_eye.xml
・opencv/data/haarcascades/haarcascade_frontalface_default.xml

3. やってみる

基本的にはこちらのチュートリアルのまんま書いて遊んでみる。
Face Detection using Haar Cascades

(1) まずは顔だけを抽出してみる。

import numpy as np
import cv2 as cv

# 顔検出対象の画像をロードし、白黒画像にしておく。
imgS = cv.imread('face.png')
imgG = cv.cvtColor(imgS, cv.COLOR_BGR2GRAY)

# 学習済みデータをロード
face_cascade = cv.CascadeClassifier('haarcascade_frontalface_default.xml')

# 顔検出を実行
faces = face_cascade.detectMultiScale(imgG, 1.3, 5)

# 顔検出箇所を矩形で囲む。
for (x,y,w,h) in faces:
    cv.rectangle(imgS,(x,y),(x+w,y+h),(0,255,255),2)

# 表示
cv.imshow('image',imgS)
cv.waitKey(0)

# 表示消去
cv.destroyAllWindows()

斜め後ろから見ている人を除き 5人を検出できた。

(2) 次に顔の中から目を検出してみる。

import numpy as np
import cv2 as cv

# 顔検出対象の画像をロードし、白黒画像にしておく。
imgS = cv.imread('face.png')
imgG = cv.cvtColor(imgS, cv.COLOR_BGR2GRAY)

# 学習済みデータをロード
face_cascade = cv.CascadeClassifier('haarcascade_frontalface_default.xml')
eye_cascade  = cv.CascadeClassifier('haarcascade_eye.xml')

# 顔検出を実行
faces = face_cascade.detectMultiScale(imgG, 1.3, 5)

# 顔検出箇所を矩形で囲む。
for (x,y,w,h) in faces:
    cv.rectangle(imgS,(x,y),(x+w,y+h),(0,255,255),4)
    # 目を検出
    imgG_face = imgG[y:y+h, x:x+w]
    imgC_face = imgS[y:y+h, x:x+w]
    eyes = eye_cascade.detectMultiScale(imgG_face)
    for (ex,ey,ew,eh) in eyes:
        cv.rectangle(imgC_face,(ex,ey),(ex+ew,ey+eh),(0,128,255),4)

# 表示
cv.imshow('image',imgS)
cv.waitKey(0)

# 表示消去
cv.destroyAllWindows()

口を誤検出しているが概ね良好な結果かも。

4. おまけ

(1) 検出パラメーターを変更してみる。

(scaleFactor, minNeighbors) = (1.1, 3)

(scaleFactor, minNeighbors) = (1.1, 5)

(scaleFactor, minNeighbors) = (1.3, 3)

(scaleFactor, minNeighbors) = (1.3, 5)

(scaleFactor, minNeighbors) = (2.0, 3)

(scaleFactor, minNeighbors) = (2.0, 5)

(2) 検出した顔をファイル保存する。

検出した顔をくり抜いて PNGファイルに保存してみる。

import numpy as np
import cv2 as cv
import os, shutil

imgS = cv.imread('face2.jpg')
imgG = cv.cvtColor(imgS, cv.COLOR_BGR2GRAY)

face_cascade = cv.CascadeClassifier('haarcascade_frontalface_default.xml')
faces = face_cascade.detectMultiScale(imgG, 1.1, 3)

img_out_path = './out'
if os.path.exists(img_out_path):
    shutil.rmtree(img_out_path)
os.mkdir(img_out_path)

i = 1
for (x,y,w,h) in faces:
    cv.rectangle(imgS,(x,y),(x+w,y+h),(0,255,255),4)
    imgC_face = imgS[y:y+h, x:x+w]
    fpath = img_out_path + '/face_%05d.png' % i
    cv.imwrite(fpath, imgC_face)
    i = i + 1

こんな感じで切り抜けた。
当然だが誤検出した物も PNGファイル出力されている。

5. 所感

いつかちゃんとアルゴリズムを学習しよう。
特徴点を抽出し、マッチングし、なのだとは思うが。


(100) OpenCV #5 : 画像カラーのバンド入れ替え(BGR → RGB)

(100) OpenCV #5 : 画像カラーのバンド入れ替え(BGR → RGB)

1. やりたいこと

カラー画像を OpenCVでロードした場合、画像データ配列中の並び順は BGR である。
これを RGB の並び順に変更したい。

2. 本当に BGRなのかを確認

こんな画像を用意した。

この画像を OpenCVでロードし、画像データの中身をバンド別(=チャネル別)に見てみる。

因みに…
OpenCVでロードした画像データは numpy配列に格納されている。

import cv2
img = cv2.imread("band.png")
type(img)

実行結果、以下のように numpy.ndarray と表示された。

<class 'numpy.ndarray'>

よって、ロードした画像データから任意のバンドのデータを抽出するには、numpy配列に対する制御をすればよいのだ。

まずは 1バンド目は?

import cv2
import numpy as np
img = cv2.imread("band.png")
cv2.namedWindow('image', cv2.WINDOW_AUTOSIZE)

# 1バンド目だけを取得&表示
img1 = img[:,:,0]
cv2.imshow('image', img1)
cv2.waitKey()
cv2.destroyAllWindows()

確かに B(Blue)が入っていた。

次に 2バンド目は?

# 2バンド目だけを取得&表示
img2 = img[:,:,1]
cv2.imshow('image', img2)
cv2.waitKey()
cv2.destroyAllWindows()

確かに G(Green)が入っていた。

最後に 3バンド目は?

# 3バンド目だけを取得&表示
img3 = img[:,:,2]
cv2.imshow('image', img3)
cv2.waitKey()
cv2.destroyAllWindows()

確かに R(Red)が入っていた。

cv2.imread()でロードした画像データの中身は、確かに BGR の順に格納されていた。

3. やってみる

(1) OpenCVの関数で BGR → RGB変換

便利な関数が用意されている。

import cv2
img = cv2.imread("band.png")

# OpenCVの関数 cvtColor()でバンド変換する。
imgRGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

(2) numpy配列のスワップで実現

前述の「2. 本当に BGRなのかを確認」で確認した通り、画像データ配列の3次元目には以下のように格納されている。
[][][0] : B (Blue)
[][][1] : G (Green)
[][][2] : R (Red)

よって…
[][][0]と [][][2]を入れ替えてやればよいのだ。

これは numpy配列に対する制御で簡単に書ける。

import cv2
import numpy as np
img = cv2.imread("band.png")

# 画像データの 3次元目を 2→1→0 の順に入れ替える。
imgRGB = img[:,:,[2,1,0]]

試しに PILで RGBカラー画像として表示してみる。

# 表示
from PIL import Image
imgP = Image.fromarray(imgRGB)
imgP.show()


OKだ!


(99) OpenCV #4 : ガンマ補正で画像を見やすく調整

(99) OpenCV #4 : ガンマ補正で画像を見やすく調整

1. やりたいこと

Pythonで OpenCVの第 4回目、今回は OpenCVの Look up table を使って画像のガンマ補正(gamma correction)をやってみる。

2. ガンマ補正とは

ガンマ補正(gamma correction)とは、以下の式で入力値に対する出力値を得ること。
これにより画像の輝度を所望の状態に調整すること。

まずは、入力値(I)と出力値(O)の関係をグラフ上で見てみる。
折角 Pythonを使っているので matplotlib でグラフを表示してみる。

import numpy as np
import matplotlib.pyplot as plt

ary_gamma = np.array([0.1, 0.2, 0.4, 0.67, 1, 1.5, 2.5, 5, 10, 25])

for var_gamma in ary_gamma:
    var_px = np.empty(256, np.uint8)
    for i in range(256):
        var_px[i] = np.clip(pow(i / 255.0, var_gamma) * 255.0, 0, 255)
    plt.plot(var_px, label=str(var_gamma))

plt.legend()
plt.xlabel("INPUT")
plt.ylabel("OUTPUT")
plt.show()

実行すると以下のグラフが表示される。
線ごとに色を指定せずとも自動的に色分けしてくれるので便利だ。

γ = 1 であれば入力値=出力値のリニアな関係になるので画像は何も変わらない。
γ < 1 であれば暗い部分の輝度変化が強調される。
γ > 1 であれば明るい部分の輝度変化が強調される。

3. やってみる

(1) 必要最小限の機能

まずは一切の装飾や利便性などを考慮せず、愚直にガンマ補正機能だけを実行してみる。

import cv2
import numpy as np

# ガンマ値を決める。
gamma = 0.8

# 処理対象の画像をロード
imgS = cv2.imread("img001.png")

# ガンマ値を使って Look up tableを作成
lookUpTable = np.empty((1,256), np.uint8)
for i in range(256):
    lookUpTable[0,i] = np.clip(pow(i / 255.0, gamma) * 255.0, 0, 255)

# Look up tableを使って画像の輝度値を変更
imgA = cv2.LUT(imgS, lookUpTable)

# 表示実行
cv2.namedWindow('image', cv2.WINDOW_AUTOSIZE)
cv2.imshow('image', imgA)
cv2.waitKey()

ガンマ値=0.8で実行したところ、暗い部分が少しだけ見やすくなったか?

(2) 少しだけ機能拡張

上記(1)のシンプル実装プログラムに対して、以下の二つの機能を追加実装した。
追加機能#1 : コマンドラインから、画像ファイルを指定できるようにした。
追加機能#2 : コマンドラインから、ガンマ値を指定できるようにした。

###########################################################
# ガンマ補正プログラム
import argparse
import cv2
import numpy as np

def main():
    # コマンドライン引数「γ値」を取得
    parser = argparse.ArgumentParser()
    parser.add_argument('-g','--gamma', required=True)
    parser.add_argument('-f','--filepath',  required=True)
    args = parser.parse_args()
    # γ補正実行
    exec_gamma_correction( args.filepath, float(args.gamma) )

def exec_gamma_correction( filepath, gamma ):
    # 処理対象の画像をロード
    imgS = cv2.imread(filepath)

    # γ値を使って Look up tableを作成
    lookUpTable = np.empty((1,256), np.uint8)
    for i in range(256):
        lookUpTable[0,i] = np.clip(pow(i / 255.0, gamma) * 255.0, 0, 255)

    # Look up tableを使って画像の輝度値を変更
    imgA = cv2.LUT(imgS, lookUpTable)

    # PILで表示用画像を作成
    from PIL import Image, ImageDraw, ImageFont
    imgA_RGB = cv2.cvtColor(imgA, cv2.COLOR_BGR2RGB)
    imgP = Image.fromarray(imgA_RGB)

    # 画像の左上にγ値の表示を埋め込む
    obj_draw = ImageDraw.Draw(imgP)
    obj_font = ImageFont.truetype("/usr/share/fonts/ipa/ipagp.ttf", 40)
    obj_draw.text((10, 10), "γ = %.1f" % gamma, fill=(255, 255, 255), font=obj_font)

    # 表示実行
    imgP.show()

if __name__=="__main__": main()

Shellから以下の二つのコマンドラインオプションを指定して実行する。
-f : 対象画像ファイルのパス
-g : ガンマ値

python3 gamma_correct.py -f img001.png -g 0.8

以下、いろいろなガンマ値で実行してみた。

でも…
iPhoneの自動補正機能が素晴らしいのか?
ガンマ値=1 が一番きれいに見えるなぁ







4. 参考

ありがとうございます。 m(_ _)m
https://docs.opencv.org/master/d3/dc1/tutorial_basic_linear_transform.html


(98) OpenCV #3 : Canny法でエッジ抽出

(98) OpenCV #3 : Canny法でエッジ抽出

1. やりたいこと

Pythonで OpenCVの第 3回目、今回は OpenCVの Canny関数を使って画像のエッジ抽出をやりたい。

2. やってみる

まずは元画像を読み込み、白黒画像に変換する。

import cv2
from PIL import Image

# 対象画像をロード
img = cv2.imread("img001.jpg")

# カラー画像を白黒画像に変換
imgG = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 画像を表示
pilG = Image.fromarray(imgG)
pilG.show()

次に Canny法(ヒステリシス min:100, max:200)でエッジを抽出してみる。
すなわち、Canny法のパラメーターとして、
・UP方向の場合の閾値は 200
・DOWN方向の場合の閾値は 100
・100~200は ON/OFF混在のモヤモヤ区間
とするのだ。
アルゴリズムの詳細は Canny法によるエッジ検出 を参照のこと。

# Canny法でエッジ抽出
edges = cv2.Canny(imgG,100,200)

# 画像を表示
pilE = Image.fromarray(edges)
pilE.show()

次に Canny法(ヒステリシス min:50, max:100)でエッジを抽出してみる。

次に Canny法(ヒステリシス min:150, max:200)でエッジを抽出してみる。

最後に Canny法(ヒステリシス min:125, max:175)でエッジを抽出してみる。

3. 所感

最適な閾値は対象画像により異なるはず。
どうやって見つけるのかな?

4. 参考

ありがとうございます。 m(_ _)m
http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_imgproc/py_canny/py_canny.html


(97) OpenCV #2 : Sobelフィルターでエッジ抽出

(97) OpenCV #2 : Sobelフィルターでエッジ抽出

1. やりたいこと

Pythonで OpenCVの第 2回目、今回は OpenCVの Sobel関数を使って画像のエッジ抽出をやりたい。

2. やってみる

まずは元画像を読み込み、白黒画像に変換する。

import cv2
from PIL import Image

# 対象画像をロード
img = cv2.imread("img001.jpg")

# カラー画像を白黒画像に変換
imgG = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 画像を表示
pilG = Image.fromarray(imgG)
pilG.show()

次に X方向の勾配を算出する。

# X方向の勾配を算出
sx = cv2.Sobel(imgG, cv2.CV_64F, 1, 0, ksize=3)

# 画像を表示
pltSX = Image.fromarray(sx)
pltSX.show()

次に Y方向の勾配を算出する。

# Y方向の勾配を算出
sy = cv2.Sobel(imgG, cv2.CV_64F, 0, 1, ksize=3)

# 画像を表示
pltSY = Image.fromarray(sy)
pltSY.show()

最後に X,Y両方向を合わせた画像の勾配を算出する。

import numpy as np

# 両方向合わせた勾配を算出
grad = np.sqrt(sx ** 2 + sy ** 2)

# 画像を表示
pltGr = Image.fromarray(grad)
pltGr.show()

3. 所感

ちょっとエッジ抽出には向かない画像だったかな…

4. 参考

ありがとうございます。 m(_ _)m
http://labs.eecs.tottori-u.ac.jp/sd/Member/oyamada/OpenCV/html/py_tutorials/py_imgproc/py_gradients/py_gradients.html