GIMPでPython-Fuを使った画像処理③

画像処理シリーズ

GIMPでPython-Fuを使った画像処理① - かすブログ

GIMPでPython-Fuを使った画像処理② - かすブログ

前回のコードから、実際の写真に使えるようにプラグインをアップデート。

今回のフィルタを適用した画像

目次

スクリプト呼び出し時に設定値を変更出来るようにする

設定値をGIMPスクリプト呼び出し画面から調整できるように変更する。 呼び出し画面を実装するような手間は必要なく、スクリプト処理のメソッドを登録する、register関数に引数情報を入力すると 自動的に設定画面が作成された。 ソースコードのprmsの配列要素を追加する。

####################
# スクリプト登録
####################
# メニューバーの My にcross_filterを追加(画像を開くまで実行不可)

prms = [
    (PF_IMAGE, "image",         "Input image", None),
    (PF_INT8,  "kobo_num",      "[filter setting] Kobo Num (half)", 2),
    (PF_INT8,  "length1",       "[filter setting] Motion Blur Length 1", 5),
    (PF_INT8,  "length2",       "[filter setting] Motion Blur Length 2", 10),
    (PF_INT8,  "angle_offset",  "[filter setting] Motion Blur angle offset", 45),
    (PF_INT8,  "clip_color",    "[pre processing] under the value is clip to 0", 230),
    (PF_INT8,  "strength",      "[post processing] cross value alpha blend strength", 200),
    (PF_BOOL,  "merge_off",     "[check] cross Merge OFF Mode", False)
    ]
register(
    "cross_filter",             # name
    "my cross filter",          # blurb (宣伝文句),
    "help: this is my filter",  # help
    "skattun",                  # author
    "(C) 2023 skattun",         # copyright
    "2023/5/5",                 # date
    "cross_filter",             # menupath(not use)
    "RGB*",                     # imagetypes
    prms,                       # params
    [],                         # results
    plugin_main,                # function
    menu = "<Image>/My")        # menu path設定

モーションぼかし前に一定輝度以下の画素を削除する処理を追加

前回の処理のように、自分で指定した高輝度画素だけモーションぼかしする場合は不要だったが、 通常の画像を処理する場合はモーションぼかしをかける必要のない画素はalphaにしておく必要があった。 そのため、一定輝度(clip_color)以下の画素をalphaにする処理を追加する。 crosss_filterのメソッドを前処理・フィルタ本体・後処理ブロックに分けた。

def cross_filter(image, layer, kobo_num, length1, length2, angle_offset, clip_color, strength, merge_off):

    try:
        # undoグループ開始
        pdb.gimp_image_undo_group_start(image)

        ##########
        # 前処理
        ##########

        # レイヤーをコピー
        work_layer = pdb.gimp_layer_copy(layer, 1)  # alphaチャンネルを追加
        pdb.gimp_image_insert_layer(image, work_layer, None, 0)

        # トーンカーブ でclip_color値を0.0に変換
        clip_color_d = clip_color / 255.
        points = spline_to_points([0.0, 0.0, clip_color_d, 0.0])
        print(points)
        pdb.gimp_drawable_curves_explicit(work_layer, HISTOGRAM_VALUE, 256, points)

        # 色を透明度に で黒をalphaに変換
        color = (0.0, 0.0, 0.0)
        pdb.plug_in_colortoalpha(image, work_layer, color)

        ##########
        # main
        ##########
        # クロスフィルタの実体
        cross_filter_core(image, work_layer, kobo_num, length1, length2, angle_offset, strength, merge_off)

        ##########
        # 後処理
        ##########

        # レイヤーを削除
        if not merge_off:
            image.remove_layer(work_layer)

    finally:
        # undoグループ終了
        pdb.gimp_image_undo_group_end(image)

        # 処理終了
        pdb.gimp_progress_end()
トーンカーブで一定輝度(clip_color)以下の値を0にクリップする

まずは不要な画素を0にクリップする。

       clip_color_d = clip_color / 255.
        points = spline_to_points([0.0, 0.0, clip_color_d, 0.0])
        print(points)
        pdb.gimp_drawable_curves_explicit(work_layer, HISTOGRAM_VALUE, 256, points)

前回使ったspline_to_points()メソッドを再利用。(x,y)の順でポイントを引数で与える。 x(input): clip_color_d -> y(output): 0.0 という設定。 トーンカーブVALUEで指定する。名前的にはHISTOGRAM_LUMINANCEがよさそうだが、こちらはRuntimeErrorとなって実行できなかった。

0をalphaに変換

先ほど不要な画素を0にしたので、0を画素値指定でalphaに変換する

        # 色を透明度に で黒をalphaに変換
        color = (0.0, 0.0, 0.0)
        pdb.plug_in_colortoalpha(image, work_layer, color)

ソースコード

以下がプラグインソースコード全体

# -*- coding: utf-8 -*-

####################
# import
####################
# gimpfu用
from gimpfu import *

# ファイル入出力用
import sys

####################
# parameters
####################

# --verboseなら%USER_PROFILE%のパス、
# 通常起動なら.xcfファイルがあるパスに出力される
OUTTXT_PATH = "stdout.txt"
PW = 10
PH = 10

####################
# method
####################


# spline曲線を256点のポイントにして返すメソッド
def spline_to_points(spline):
    points = [ k/255.0 for k in range(256) ]
    x0 = 0.0
    y0 = 0.0
    ix = 0
    x0 = spline.pop(0) # remove initial (0.0,0.0)
    y0 = spline.pop(0)
    while spline:
        x = spline.pop(0)
        y = spline.pop(0)
        while ix < 256:
            xi = ix / 255.
            if xi > x:
                break
            points[ix] = ((x-xi) * y0 + (xi-x0) * y) / (x-x0)
            ix += 1

        x0 = x
        y0 = y
    return points


def cross_filter_core(image, layer, kobo_num, length1, length2, angle_offset, strength, merge_off):

    kobo = kobo_num # 光芒の数の半分、光芒は偶数のみ可能

    mblur_type = 0      # リニアモーション
    mblur_cx = 0        # center x
    mblur_cy = 0        # center y
    # gauss_h = 3         # gauss h
    # gauss_v = 3         # gauss v

    # レイヤーグループを追加
    layer_group = pdb.gimp_layer_group_new(image)
    pdb.gimp_image_insert_layer(image, layer_group, None, 0)

    angle = angle_offset
    angle_dx = 360 / (kobo * 2)
    for n in range(kobo):

        # レイヤーをコピー
        work_layer = pdb.gimp_layer_copy(layer, 1)  # alphaチャンネルを追加
        pdb.gimp_image_insert_layer(image, work_layer, layer_group, 0) # レイヤーグループに追加, positon:0 (top)

        # 角度を更新
        angle += angle_dx

        # ガウスフィルタIIR処理実行
        # pdb.plug_in_gauss(image, work_layer, gauss_h, gauss_v, 0) # 0: IIR

        # モーションぼかし処理実行
        pdb.plug_in_mblur(image, work_layer, mblur_type, length1, angle, mblur_cx, mblur_cy)
        pdb.plug_in_mblur(image, work_layer, mblur_type, length2, angle, mblur_cx, mblur_cy)

        # アルファチャンネル調整
        strength_d = strength / 255.
        points = spline_to_points([0.0, 0.0, 0.2, strength_d])
        pdb.gimp_drawable_curves_explicit(work_layer, HISTOGRAM_ALPHA, 256, points)

    # レイヤーのマージ
    if not merge_off:
        dst_layer = pdb.gimp_image_merge_layer_group(image, layer_group)
        dst_layer.name = "cross_filter"

def cross_filter(image, layer, kobo_num, length1, length2, angle_offset, clip_color, strength, merge_off):

    try:
        # undoグループ開始
        pdb.gimp_image_undo_group_start(image)

        ##########
        # 前処理
        ##########

        # レイヤーをコピー
        work_layer = pdb.gimp_layer_copy(layer, 1)  # alphaチャンネルを追加
        pdb.gimp_image_insert_layer(image, work_layer, None, 0)

        # トーンカーブ でclip_color値を1.0に変換
        clip_color_d = clip_color / 255.
        points = spline_to_points([0.0, 0.0, clip_color_d, 0.0])
        print(points)
        pdb.gimp_drawable_curves_explicit(work_layer, HISTOGRAM_VALUE, 256, points)

        # 色を透明度に で黒をalphaに変換
        color = (0.0, 0.0, 0.0)
        pdb.plug_in_colortoalpha(image, work_layer, color)

        ##########
        # main
        ##########
        # クロスフィルタの実体
        cross_filter_core(image, work_layer, kobo_num, length1, length2, angle_offset, strength, merge_off)

        ##########
        # 後処理
        ##########

        # レイヤーを削除
        if not merge_off:
            image.remove_layer(work_layer)

    finally:
        # undoグループ終了
        pdb.gimp_image_undo_group_end(image)

        # 処理終了
        pdb.gimp_progress_end()


# 引数はregisterで登録するときに指定する。
def plugin_main(image, kobo_num, length1, length2, angle_offset, clip_color, strength, merge_off):

    layer = pdb.gimp_image_get_active_layer(image)

    # cross_filter実行
    cross_filter(image, layer, kobo_num, length1, length2, angle_offset, clip_color, strength, merge_off)



####################
# スクリプト登録
####################
# メニューバーの My にcross_filterを追加(画像を開くまで実行不可)

prms = [
    (PF_IMAGE, "image",         "Input image", None),
    (PF_INT8,  "kobo_num",      "[filter setting] Kobo Num (half)", 2),
    (PF_INT8,  "length1",       "[filter setting] Motion Blur Length 1", 5),
    (PF_INT8,  "length2",       "[filter setting] Motion Blur Length 2", 10),
    (PF_INT8,  "angle_offset",  "[filter setting] Motion Blur angle offset", 45),
    (PF_INT8,  "clip_color",    "[pre processing] under the value is clip to 0", 230),
    (PF_INT8,  "strength",      "[post processing] cross value alpha blend strength", 200),
    (PF_BOOL,  "merge_off",     "[check] cross Merge OFF Mode", False)
    ]
register(
    "cross_filter",             # name
    "my cross filter",          # blurb (宣伝文句),
    "help: this is my filter",  # help
    "skattun",                  # author
    "(C) 2023 skattun",         # copyright
    "2023/5/5",                 # date
    "cross_filter",             # menupath(not use)
    "RGB*",                     # imagetypes
    prms,                       # params
    [],                         # results
    plugin_main,                # function
    menu = "<Image>/My")        # menu path設定

####################
# main
####################
main()

####################
# 100m pic filter setting memo
# length1: 80
# length2: 100
# angle: 45
# clip: 230
# strength: 80

####################

フィルタ実行結果と考察

元画像 / フィルタ適用後

strengthパラメタでalphaブレンド調整できるようにした。 多少弱めても大量のクロスがつく。多すぎる。もちろんフィルタ用の元画像を手動で修正すれば調整できるが、 スクリプトに何かしら対策を追加したいところ。 また、光学的なクロスフィルタで撮った写真と比べるとデジタル処理感が出てしまっている。

光学的なクロスフィルタで撮った写真

手動でブラシで黒塗りをして光源を間引いてフィルタ適用してみた画像がこちら。

元画像 / フィルタ用編集画像 / フィルタ適用後

光線をつけたいところだけ簡単につけれるのはデジタル処理の利点かも。

今日でGWが終わるので、次回以降改善したい内容をメモ。

  • クロスが密集しすぎるので、間引きたい。また太めの光源にもクロスがついていて太くなるのを細くしたい、もっと先細りしたクロスにしたい。
    • 前処理にハイパスフィルタを入れてみる?
    • ランダムで光源をマスクする処理を追加?
  • 光線が単色になってしまう。色むらを付けれないか?
    • クリップするときにLEVELだけでなくR/G/B chそれぞれの値でクリップしたレイヤを作成してフィルタ処理してランダムに合成?

GIMPでPython-Fuを使った画像処理②

前回の続き。

GIMPでPython-Fuを使った画像処理① - かすブログ

今回は実際にクロスフィルタを実装する。 クロスフィルタはレンズフィルタを使った工学的なフィルタのため、 画像処理で実施するクロスフィルタは当然疑似的なものとなる。 GIMPのモーションぼかしフィルタを使って疑似的な光芒を作成した。

クロスフィルタを使った写真の例 光が十字状に輝いているのがクロスフィルタの効果。 頂点の数はカメラの光芒の数に依存する。この写真では4枚。

★参考にしたサイト★

・スプライン曲線のメソッド leware.net

★環境★

★内容★

[GIMP] クロスフィルタ合成用のレイヤを作成

まず最初に、クロスフィルタ合成用のレイヤを作成する。 ここでは黄色の1dotだけ以外を透明にしたレイヤを新規に追加した。 この1dotにフィルタをかける。

[python-fu] クロスフィルタを実装

疑似的だが以下のようにしてクロスフィルタの画像処理を作成した。

  1. 高輝度部分(今回は黄色の1dot)にモーションぼかしフィルタを光線にしたい方向に角度・長さを設定してフィルタをかける
  2. モーションぼかしフィルタ1回だと先細りしないので, 少しlengthを長くしてもう一度同じ方向にモーションぼかしフィルタをかける
  3. GIMPで実行してみるとかなり透明に近くなってしまうので、alphaのトーンカーブを調整して光線が見えるようにする。

フィルタ処理はトーンカーブの調整が少し難易度が高め。フィルタをかけるだけなら簡単に実装できる。 レイヤの扱いも簡単にできるようにapiが用意されていて使い勝手が良かった。 またundoグループという、undo一回で戻すグループを指定できるのも素晴らしい。

# -*- coding: utf-8 -*-

####################
# import
####################
# gimpfu用
from gimpfu import *

# ファイル入出力用
import sys

####################
# parameters
####################

# --verboseなら%USER_PROFILE%のパス、
# 通常起動なら.xcfファイルがあるパスに出力される
OUTTXT_PATH = "stdout.txt"
PW = 10
PH = 10

####################
# method
####################


# spline曲線を256点のポイントにして返すメソッド
def spline_to_points(spline):
    points = [ k/255.0 for k in range(256) ]
    x0 = 0.0
    y0 = 0.0
    ix = 0
    x0 = spline.pop(0) # remove initial (0.0,0.0)
    y0 = spline.pop(0)
    while spline:
        x = spline.pop(0)
        y = spline.pop(0)
        while ix < 256:
            xi = ix / 255.
            if xi > x:
                break
            points[ix] = ((x-xi) * y0 + (xi-x0) * y) / (x-x0)
            ix += 1

        x0 = x
        y0 = y
    return points


def cross_filter_core(image, layer):

    kobo = 4 # 光芒の数の半分、光芒は偶数のみ可能

    mblur_type = 0      # リニアモーション
    mblur_length1 = 5   # length(1回目)
    mblur_length2 = 10  # length(2回目)
    angle_offset  = 0   # angle offset
    mblur_cx = 5        # center x
    mblur_cy = 2        # center y
    gauss_h = 3         # gauss h
    gauss_v = 3         # gauss v

    # レイヤーグループを追加
    layer_group = pdb.gimp_layer_group_new(image)
    pdb.gimp_image_insert_layer(image, layer_group, None, 0)

    angle = angle_offset
    angle_dx = 360 / (kobo * 2)
    for n in range(kobo):

        # レイヤーをコピー
        work_layer = pdb.gimp_layer_copy(layer, 1)  # alphaチャンネルを追加
        pdb.gimp_image_insert_layer(image, work_layer, layer_group, 0) # レイヤーグループに追加

        # 角度を更新
        angle += angle_dx

        # ガウスフィルタIIR処理実行
        # pdb.plug_in_gauss(image, work_layer, gauss_h, gauss_v, 0) # 0: IIR

        # モーションぼかし処理実行
        pdb.plug_in_mblur(image, work_layer, mblur_type, mblur_length1, angle, mblur_cx, mblur_cy)
        pdb.plug_in_mblur(image, work_layer, mblur_type, mblur_length2, angle, mblur_cx, mblur_cy)

        # アルファチャンネル調整
        points = spline_to_points([0.0, 0.0, 0.2, 0.8])
        pdb.gimp_drawable_curves_explicit(work_layer, HISTOGRAM_ALPHA, 256, points)

    # レイヤーのマージ
    dst_layer = pdb.gimp_image_merge_layer_group(image, layer_group)
    dst_layer.name = "cross_filter"

def cross_filter(image, layer):

    try:
        # 処理開始
        gimp.progress_init("Discolouring " + layer.name + "...")

        # undoグループ開始
        pdb.gimp_image_undo_group_start(image)


        # クロスフィルタの実体
        cross_filter_core(image, layer)

    finally:
        # undoグループ終了
        pdb.gimp_image_undo_group_end(image)

        # 処理終了
        pdb.gimp_progress_end()


# 引数はregisterで登録するときに指定する。
def plugin_main(image):

    layer = pdb.gimp_image_get_active_layer(image)

    # cross_filter実行
    cross_filter(image, layer)



####################
# スクリプト登録
####################
# メニューバーの My にcross_filterを追加(画像を開くまで実行不可)

prms = [
    (PF_IMAGE, "image",       "Input image", None)
    # (PF_DRAWABLE, "drawable", "Input drawable", None)
    ]
register(
    "cross_filter",             # name
    "my cross filter",          # blurb (宣伝文句),
    "help: this is my filter",  # help
    "skattun",                  # author
    "(C) 2023 skattun",         # copyright
    "2023/5/5",                 # date
    "cross_filter",             # menupath(not use)
    "RGB*",                     # imagetypes
    prms,                       # params
    [],                         # results
    plugin_main,                # function
    menu = "<Image>/My")        # menu path設定

####################
# main
####################
m

[spline_to_points()] スプライン用のポイントデータ作成について

トーンカーブ調整用に渡す配列データを生成するため、折れ点位置を与えるとトーンカーブ用の配列を生成するメソッドを リンクからもらって実装した。与えた点を通る直線を計算して、直線補完する。

[GIMP] 実行結果

身代わり人形の目元を輝かせることができた。

元画像 / フィルタ用黄色点 / フィルタ適用後

GIMPでPython-Fuを使った画像処理①

GIMPPython-Fuを使った画像処理を試してみる。 まずはスクリプトGIMPに登録して画像を読み込むところまで。 最終的にはオリジナルcross filterをpython-fuで実行できるようにしたい。

参考にしたサイト: qiita.com

www.randynetwork.com

環境:

手順は以下の通り。

1. python-fuのソースコードを置くフォルダのパスを確認

gimpを起動し、メニューバーからアクセス 編集 > 設定 > フォルダー > スクリプト

ここに書いてあるパスにPython-Fuのソースコードを置くと、読み込まれる。 編集して好きなところにすることもできそう。

2. GIMPの起動モードをverboseに切り替え(やらなくても良い)

verboseモードにしておくと、うまくいかなかったときの助けになるかもしれない。 私はwindowsのタスクバーにGIMPのアイコンを登録しているので、 そこのショートカットのプロパティから実行時の引数を渡して設定した。 GIMPアイコンを右クリック > もう一度GIMPを右クリック > プロパティ GIMPプログラムのパスの最後に以下のオプションを追加。(""の外に書く)

--verbose --console-messages

3. お試しスクリプトを作成、python-fu置き場に置く

以下のプログラムを作成した。 ※画像サイズが大きいと処理に時間がかかるため、左上の(10x10 or 画像サイズ)の小さい方の画素のみアクセスしている。

# -*- coding: utf-8 -*-

####################
# import
####################
# gimpfu用
from gimpfu import *

# ファイル入出力用
import sys

####################
# parameters
####################

# --verboseなら%USER_PROFILE%のパス、
# 通常起動なら.xcfファイルがあるパスに出力される
OUTTXT_PATH = "stdout.txt"
PW = 10
PH = 10

####################
# method
####################

# 引数はregisterで登録するときに指定する。
# message: PF_STRING
# image: PF_IMAGE
# drawable: PF_DRAWABLE
def plugin_main(message, image, drawable):

    # verboseモードだとコンソールに出力され、
    # 通常モードだとエラーメッセージボックスとして表示される。
    # messageには呼び出し時にhello worldを渡している。
    gimp.message("Message: " + message)

    # ファイル書き出し用
    sys.stdout = open(OUTTXT_PATH, 'w')

    # ファイルにピクセル値を書き出し
    w = min(PW, drawable.width)
    h = min(PH, drawable.height)
    src_rgn = drawable.get_pixel_rgn(0, 0, w, h, False, False)
    for x in xrange(0, w):
        for y in xrange(0, h):
            p = map(ord, src_rgn[x, y])
            print x, ":", y, " -- ", "".join(map((lambda x: format(x, '02x')), p))


####################
# スクリプト登録
####################
# メニューバーの Filters > My にスクリプトを追加

prms = [
    (PF_STRING, "string", "Text string", 'Hello, world!'),
    (PF_IMAGE, "image",       "Input image", None),
    (PF_DRAWABLE, "drawable", "Input drawable", None)
    ]
register(
    "cross_filter",             # name
    "my cross filter",          # blurb (宣伝文句),
    "help: this is my filter",  # help
    "skattun",                  # author
    "(C) 2023 skattun",         # copyright
    "2023/5/5",                 # date
    "cross_filter",             # menupath(not use)
    "RGB*",                     # imagetypes
    prms,                       # params
    [],                         # results
    plugin_main,                # function
    menu = "<Image>/Filters/My")# menu path設定

####################
# main
####################
main()

登録する場所をFilters以下にすると、画像を開かないとスクリプトを実行できなくなる。以下のパスにするとメインメニューに登録され、 画像を開かなくても実行できるとのこと。(リンク参照)

menu="<Image>/PyPlgin"

このソースコードをhelloworld.pyという名前で保存してスクリプト置き場に保存

4. GIMPを起動して画像を読み込み、実行

お試し用の身代わり人形画像はこちら

google colab で google driveを使う

pythonの開発環境としてgoogle colabを使うことにした。
google colabはgoogle提供の無料で使えるpythonの実行環境で、
gpuが使える、jupyter notebookのように色々メモしながら開発できるなどとても魅力的。
TPUという機械学習特化の計算リソースも設定一つで使えるようだ。

試しに色々コードを書いてしばらくすると、/conent(作業dir)においていたファイルが消えている...!!
ブラウザのセッションが切れると、pythonの変数はもちろん作業dirの中身も消えるようだ。
f:id:skattun:20190430233148p:plain

ソース実行に必要なファイルなど、消えて欲しくないものはgoogle driveに保存しておくのが良さそう。
google colabからgoogle driveを簡単にマウントすることができる。

google colab から google driveのファイル読み書き

# google colab try_google_drive.ipynb

from google.colab import drive
drive.mount('/content/gdrive')

# google drive内のファイル読み込み
with open("gdrive/My Drive/upload_file_1.txt", "r") as f:
  a = f.read()
 
# google drive内のファイル書き込み
with open("gdrive/My Drive/upload_file_1.txt", "a") as f:
  f.write("hoge fuga")

print(a)

作業dirの/content以下にgdriveでmountされる。

ブラウザからあらかじめgoogle driveにアップしておき、colab内で読み書きする分には普通のファイルと同じように使える。
ただし、ファイル作成、directory作成は一手間必要。

colabからgoogle driveのファイル・ディレクトリ作成(ファイルupload)

ファイルの作成は作業dirのファイルをgoogle driveにuploadすることで実行する。

!pip install -U -q PyDrive # colabでpip install実行するコマンド

# google drive 認証
from pydrive.auth import GoogleAuth
from pydrive.drive import GoogleDrive
from google.colab import auth
from oauth2client.client import GoogleCredentials

auth.authenticate_user()
gauth = GoogleAuth()
gauth.credentials = GoogleCredentials.get_application_default()
drive = GoogleDrive(gauth)

##### colab上にlinkが表示され、認証するように促される #####
##### 認証したらauth用のコードをコピペする                  #####

# 作業dirで作ったファイルをupload
with open("upload_file_1.txt", "w") as f:
  f.write("output string 1")

file = drive.CreateFile()
file.SetContentFile("upload_file_1.txt")
file.Upload()

# directory作成
folder_name = 'DirTest'
folder_metadata = {'title' : folder_name, 'mimeType' : 'application/vnd.google-apps.folder'}
folder = drive.CreateFile(folder_metadata)
folder.Upload()

# 作成したdirectoryにファイルをupload
foldertitle = folder['title']
folderid = folder['id']
print('title: %s, id: %s' % (foldertitle, folderid))

#Upload file to folder
file = drive.CreateFile({"parents": [{"kind": "drive#fileLink", "id": folderid}]})
file.SetContentFile("upload_file_1.txt")
file.Upload()

そのうち簡単にできるようになるかな...?

アスペクト比を変えずに画像縮小

deep learningの学習用素材を集めて学習させるために
アスペクト比を変えずに画像サイズを変換した。
opencvにあるresizeと、空いた領域を0で埋めるzeroを組み合わせて実装する。

  • 画像サイズが縦または横のどちらか一つでも指定サイズより大きかったら、Down Scale (openv resize)
  • 画像は左上に詰めて配置し、余った領域は0で埋める
  • Up Scaleはしない

ソースコード

def resize(src_file, dst_file, width, height):
"""
画像ファイルを与えられたサイズにサイズ変換する関数
アス比は固定、左上に寄せる、余った部分はゼロで埋める

Returns
-------
ret_scale : float
拡大/縮小した倍率
"""

src_img = cv2.imread(src_file)
h, w, c = src_img.shape
# dst_img = cv2.resize(src_img, dsize=(width, height))

# アス比固定, padding
scale_w = width / w
scale_h = height / h

ret_scale = 1.0
# Down Convert
if(scale_w < 1.0 or scale_h < 1.0):
if(scale_w < scale_h): 
resize_img = cv2.resize(src_img, dsize=None, fx=scale_w, fy=scale_w, interpolation = cv2.INTER_AREA)
ret_scale = scale_w
else:
resize_img = cv2.resize(src_img, dsize=None, fx=scale_h, fy=scale_h, interpolation = cv2.INTER_AREA)
ret_scale = scale_h
else:
resize_img = src_img

# dst_img 生成
dst_img = np.zeros((height, width, 3), dtype = np.uint8)

# dst_imgにresize_imgを合成
top = 0
left = 0
#dst_img[top:height + top, left:width + left] = resize_img
h, w, c = resize_img.shape
dst_img[0:h, 0:w] = resize_img

cv2.imwrite(dst_file, dst_img)

return ret_scale

実行結果の画像

480x270に変換した。
黒くなっているところが0埋めした場所。
どの画像も固定サイズになり、左上に詰めれていることがわかる。

f:id:skattun:20190414181754j:plainf:id:skattun:20190414181713j:plainf:id:skattun:20190414181657j:plain

MS COCO Dataset

deep learning 学習・検証用の画像を探していたところ、

MS COCO (Microsoft)のdatasetとapiがよくまとまっててとても使いやすそうだった。
データ量も豊富そう。

object detectには使えるが、顔認証のような用途向けでは無い。
(顔データはない)

python用のapimatlab用のapiが公開されている。
慣れてるpythonapiを使ってMS COCO apiの動作確認を実施した。

環境

導入

  • ms coco api のgitをダウンロード
https://github.com/cocodataset/cocoapi.git
  • imageとannotationは別途必要なので、Microsoftの公式サイトからダウンロード

COCO - Common Objects in Context

    • 2014 val images
    • 2014 Train/Val Annotations

これをそれぞれgitからダウンロードしたapiディレクトリの直下(PythonAPI, README.txt等と同階層)に
images
annotations
というディレクトリ名で置く。

  • pipで必要なパッケージをインストール
pip install cython
pip install setuptools
pip install stringio
pip install scikit-image
pip install jupyter

cythonとsetuptoolsはcoco apiのmake時にエラーが出るようだ。
scikit-imageとjupyterはMS COCOのexampleを表示するのに必要
※他にもいるかも...?


|

  • coco api make

coco/PythonAPI 直下でmakeを実行

$coco/PythonAPI/make

Jupiter notebook

jupyterのnotebookでms cocoのデモ用ipynbを実行する。

[coco/PythonAPI$]jupyter notebook

そのままデモの内容を参考にすれば一通りのapiの動作を確認できる。

pycocoDemo

jupyter notebookを実行すると、ブラウザで以下のようなページが表示される。

f:id:skattun:20190408120500p:plain
Jupiter notebook実行

pycocoDemoを選択すると、いくつかのコードブロックに分かれてあらかじめ実行結果が表示されている。
それぞれ画面上部の実行ボタンを押下して動作を確認していくと、一通りの使い方がわかる。

pycocoDemo画面

%matplotlib inline
from pycocotools.coco import COCO
import numpy as np
import skimage.io as io
import matplotlib.pyplot as plt
import pylab
pylab.rcParams['figure.figsize'] = (8.0, 10.0)

dataDir='..'
dataType='val2014'
annFile='{}/annotations/instances_{}.json'.format(dataDir,dataType)

dataDirは親ディレクトリ、dataTypeはダウンロードしたval2014を設定

# initialize COCO api for instance annotations
coco=COCO(annFile)
# display COCO categories and supercategories
cats = coco.loadCats(coco.getCatIds())
nms=[cat['name'] for cat in cats]
print('COCO categories: \n{}\n'.format(' '.join(nms)))

nms = set([cat['supercategory'] for cat in cats])
print('COCO supercategories: \n{}'.format(' '.join(nms)))

カテゴリの一覧を表示する。catはカテゴリのこと。

# get all images containing given categories, select one at random
catIds = coco.getCatIds(catNms=['person','dog','skateboard']);
imgIds = coco.getImgIds(catIds=catIds );
imgIds = coco.getImgIds(imgIds = [324158])
img = coco.loadImgs(imgIds[np.random.randint(0,len(imgIds))])[0]

人と犬とスケートボードが写っている画像をランダムで表示。
catNmsのところのカテゴリを変更すると色々検索できる。

# load and display image
# I = io.imread('%s/images/%s/%s'%(dataDir,dataType,img['file_name']))
# use url to load image
I = io.imread(img['coco_url'])
plt.axis('off')
plt.imshow(I)
plt.show()

img['coco_url'])はprintしたら
http://images.cocodataset.org/val2014/COCO_val2014_000000324158.jpg
とcocodatasetのurlが入っていた。ローカルのimageファイルを指定したい場合は、

I = io.imread(path + img['file_name'])

pathを自分で指定可能。
axis offは座標表示OFFしてるだけ。

# load and display instance annotations
plt.imshow(I); plt.axis('off')
annIds = coco.getAnnIds(imgIds=img['id'], catIds=catIds, iscrowd=None)
anns = coco.loadAnns(annIds)
coco.showAnns(anns)

annがannotationのことで、人、犬、スケボの領域をマークしてくれる。
領域はskeltonという複数点の2Dポリゴンのような形で保持されている。
coco.showAnns(anns)ではこのskeletonの領域がマークされるが、
skeletonとは別にbboxというオブジェクト全体を矩形で囲った領域情報も取れる。

ann["bbox"]

annを読み込んだらbboxキーで取れる。
float情報だが、int()で変換してopencvでroiかけたところ、それっぽい領域が取れるので、
処理過程で小数点が生じただけ?

import cv2
# plt.show()
img = cv2.imread(imgDir + img['file_name'])
cv2.imwrite("test_image.png", img)
i = 0
for ann in anns:
    x,y,w,h = ann["bbox"]
    roi = img[int(y):int(y+h), int(x):int(x+w)]
    cv2.imwrite("test_image_" + str(i) + ".png", roi)
    i += 1

元画像
f:id:skattun:20190408235313p:plain

bboxをroiで切り抜き

f:id:skattun:20190408235438p:plainf:id:skattun:20190408235432p:plainf:id:skattun:20190408235426p:plain

参考サイト

cocodataset.org

qiita.com

pythonで特定のディレクトリから画像ファイルのリストを取得(拡張子で判別)

pythonで特定のディレクトリから画像ファイルのリストを取得する。
画像ファイルかどうかは拡張子で判定、今回は.jpg, .png, .bmpを画像ファイルとする(大文字小文字は区別しない)

# 正規表現でディレクトリから画像ファイルのリストを取得
import os
import re

path = "data/"
pattern = ".*\.(jpg|png|bmp)"
files = [f for f in os.listdir(path) if re.search(pattern, f, re.IGNORECASE)] # 大小文字無視
print(files)

結果
['001.png', '002.png', '003.png']

globでは正規表現が使えないため、少し遠回りする形になった。