画像処理はじめました。

AI/AR/VRという近未来的な言葉に惹かれ、その技術分野に参戦するために立ち上げたブログです。日々の格闘結果を記録に残してゆこうと思います。

1-2 「セピア・カラー処理」

モチベーション

経年劣化により退色したノスタルジックな画像を作成したい場合に使用します。

やり方

カラー画像を読み込み、読み込んだ画像のB/G/R値に対して平均値を算出します。
算出した平均値を各色チャンネルごとに係数を掛けてB/G/Rへ代入すれば実現できます。

なお、B/G/Rの平均値は、カラー画像からグレー画像を作成するときにも使用します。

プログラム

カラー画像をPILで取得し、Numpy配列化した後、RGBの色情報に対して平均値を算出します。
スライスを用いて平均値に係数を掛けた値を色情報として使用します。

#!/usr/bin/python
# -*- coding: utf-8 -*-

# セピアカラー処理

from PIL import Image 
import numpy as np
from matplotlib import pylab as plt

# 画像の読み込み
im = np.array(Image.open('input.bmp'))
print(im.shape, im.dtype)
# ((640L, 960L, 3L), dtype('uint8'))


# 画像の表示
plt.title("before")
plt.imshow(im)
plt.show()


# B/G/R各チャンネルの平均値を計算
s = (0.33 * im[:,:,0] + 0.33 * im[:,:,1] + 0.33 * im[:,:,2])

# 出力画像を保存する配列を作成 
im_out = im.copy()


# グレー画像(B/G/Rの平均値を各色へ代入する)
im_out[:,:,0] = s  # Red
im_out[:,:,1] = s  # Green
im_out[:,:,2] = s  # Blue

print(im_out.shape, im_out.dtype)


# 画像の表示
plt.title("after gray")
plt.imshow(im_out)
plt.show()

# セピアカラーヘ変換
im_out[:,:,0] = s           # Red
im_out[:,:,1] = 0.8 * s     # Green
im_out[:,:,2] = 0.55 * s    # Blue

print(im_out.shape, im_out.dtype)


# 画像の表示
plt.title("after sepia")
plt.imshow(im_out)
plt.show()


#保存
Image.fromarray(im_out).save('output.bmp')

実行結果

f:id:genetaka1810:20191027175302p:plain

1-1 「B/G/Rの入れ替え」

モチベーション

画像の色合いを変更して、どんな雰囲気になるのか確認してみたい。
例えば、以下のような用途がありそうです。

  • 花の色
    • 赤い花だけど、青色の花だったらどうかな?
  • 看板の文字の色
    • 看板の文字の色を赤にしてみるとどうかな?
    • 背景が黄色だとどんな雰囲気になるかな?

やり方

カラー画像を読み込み、読み込んだ画像のB/G/Rを入れ替えれば実現できます。
この方法は、画像の色バランスを維持したまま色を入れ替えるので、画像変換後も自然な画像として得られます。

プログラム

カラー画像をPILで取得し、Numpy配列化した後、RGBの色情報をスライスを用いて入れ替えます。入れ替える色は、青色→緑色、 緑色→赤色、 赤色→青色 としました。

#!/usr/bin/python
# -*- coding: utf-8 -*-

# カラー画像のR,G,Bの色情報を入れ替える。

from PIL import Image 
import numpy as np
from matplotlib import pylab as plt

# 画像の読み込み
im = np.array(Image.open('input.bmp'))
print(im.shape, im.dtype)

# ((968L, 1296L, 3L), dtype('uint8'))


# 画像の表示
plt.imshow(im)
plt.show()

# 色の入れ替え
im_swap = im.copy()
im_swap[:,:,1] = im_swap[:,:,2] # Blue -> GReen
im_swap[:,:,2] = im_swap[:,:,0] # Red -> Blue
im_swap[:,:,0] = im[:,:,1]      # Green -> Red

print(im_swap.shape, im_swap.dtype)


# 画像の表示
plt.imshow(im_swap)
plt.show()


#保存
Image.fromarray(im_swap).save('output.bmp')

実行結果

f:id:genetaka1810:20191026151717p:plain

参考

別な画像でも試してみました。
赤を青に、緑を赤に入れ替えてます。

元画像の雰囲気を損なわずに、色合いのみ変更できている様子がわかるかと思います。

f:id:genetaka1810:20191026152512p:plain

0-5 「Python カラー値R/G/Bの入れ替え」

PILで取得した画像データをNumpy配列へ代入し、R,G,B各色を入れ替える実験をしました。

#!/usr/bin/python
# -*- coding: utf-8 -*-

# カラー画像のR,G,B色情報を入れ替える。

from PIL import Image 
import numpy as np
from matplotlib import pylab as plt

# 画像の読み込み
im = np.array(Image.open('input2x3.bmp'))
print(im)


# imの中身
# [[[255 0 0]
# [ 0 255 0]
# [ 0 0 255]]
#
# [[ 0 0 0]
# [128 128 128]
# [255 255 255]]]


# 画像の表示
plt.imshow(im)
plt.show()

# 色の入れ替え
#青を緑、赤を青、緑を赤の色情報へ入れ替える
im_swap = im.copy()
im_swap[:,:,1] = im_swap[:,:,2]    # Blue -> Green
im_swap[:,:,2] = im_swap[:,:,0]    # Red -> Blue
im_swap[:,:,0] = im[:,:,1]              # Green -> Red

print(im_swap)

# imの中身
#[[[  0   0 255]
#  [255   0   0]
#  [  0 255   0]]
#
# [[  0   0   0]
#  [128 128 128]
#  [255 255 255]]]
#


# 画像の表示
plt.imshow(im_swap)
plt.show()


#保存
Image.fromarray(im_swap).save('imswap_save.bmp')

実行結果

f:id:genetaka1810:20191025192153p:plain


分かりやすくするために色情報を変換する前後で並べてみました。
記載されている数値は(Red, Blue, Green)の色情報です。

f:id:genetaka1810:20191025192353p:plain


ピクセル座標について、色情報を変換する前後の色値を左上のピクセルから記述すると、次のようになります。

(色変換前)Red=255, Green= 0, Blue=0 -> (色変換後)Red= 0, Green= 0, Blue=255
(色変換前)Red= 0, Green=255, Blue=0 -> (色変換後)Red=255, Green= 0, Blue= 0
(色変換前)Red= 0, Green= 0, Blue=255 -> (色変換後)Red= 0, Green=255, Blue= 0

(色変換前)Red= 0, Green= 0, Blue=0 -> (色変換後)Red= 0, Green= 0, Blue= 0
(色変換前)Red=128, Green=128, Blue=128 -> (色変換後)Red=128, Green=128, Blue=128
(色変換前)Red=255, Green=255, Blue=255 -> (色変換後)Red=255, Green=255, Blue=255

赤→青、緑→赤、青→緑へと入れ替えられていることが確認できます。
白色、灰色、黒色は、それぞれRGBの色が同じ割合で構成されているので、色情報の変換前後で色合いは変化しません。


色の入れ替えについてプログラムの処理内容を確認します。


># 色の入れ替え
>im_swap = im.copy()
>im_swap[:,:,1] = im_swap[:,:,2] # Blue -> GReen
>im_swap[:,:,2] = im_swap[:,:,0] # Red -> Blue
>im_swap[:,:,0] = im[:,:,1] # Green -> Red

im.copy()で画像配列を、im_swapへコピーしています。
配列の複数要素にはスライスを用いてアクセスしています。

スライスとは、例えば
im_swap[:, :, 1]
と指定すると、以下のすべての配列を表す指定方法で、: 記号を用います。

im_swap[0, 0, 1]
im_swap[0, 1, 1]
im_swap[0, 2, 1]
im_swap[1, 0, 1]
im_swap[1, 1, 1]
im_swap[1, 2, 1]


プログラム上の im_swap[:,:,1] = im_swap[:,:,2] は、読み込んだ画像のすべてのピクセルについて、青色の情報を緑色に入れる処理を行っているということになります。


参考に、それぞれの配列をスライスを使用して表示すると以下のようになります。
f:id:genetaka1810:20191025194950p:plain

0-4 「Python Numpy R,G,B 色分割」

PILで取得した画像データをNumpy配列へ代入し、R,G,B各色について、色分割して表示する実験をしました。

#!/usr/bin/python
# -*- coding: utf-8 -*-

# カラー画像から、R,G,Bの色情報を取り出す。

from PIL import Image 
import numpy as np
from matplotlib import pylab as plt

# 画像の読み込み
im = np.array(Image.open('input2x3.bmp'))
print(im)

# imの中身は以下
# [[[255 0 0]
# [ 0 255 0]
# [ 0 0 255]]
#
# [[ 0 0 0]
# [128 128 128]
# [255 255 255]]]

# 画像の表示
plt.imshow(im)
plt.show()



# 赤のみ抽出し表示
img_red = im.copy()
img_red[:, :, 1] = 0 # 緑ゼロ
img_red[:, :, 2] = 0 # 青ゼロ
print(img_red)

# im_redの中身は以下
# Green,Blueの色は、0となっている
# [[[255 0 0]
# [ 0 0 0]
# [ 0 0 0]]
#
# [[ 0 0 0]
# [128 0 0]
# [255 0 0]]]

# 画像の表示
plt.imshow(img_red)
plt.show()



# 緑のみ抽出し表示
img_green = im.copy()
img_green[:, :, 0] = 0 # 赤ゼロ
img_green[:, :, 2] = 0 # 青ゼロ
print(img_green)


# im_greenの中身は以下
# Red,Blueの色は、0となっている
# [[[ 0 0 0]
# [ 0 255 0]
# [ 0 0 0]]
#
# [[ 0 0 0]
# [ 0 128 0]
# [ 0 255 0]]]

# 画像の表示
plt.imshow(img_green)
plt.show()



# 青のみ抽出し表示
img_blue = im.copy()
img_blue[:, :, 0] = 0 # 赤ゼロ
img_blue[:, :, 1] = 0 # 緑ゼロ
print(img_blue)


# im_blueの中身は以下
# Red,Greenの色は、0となっている
# [[[ 0 0 0]
# [ 0 0 0]
# [ 0 0 255]]
#
# [[ 0 0 0]
# [ 0 0 128]
# [ 0 0 255]]]


# 画像の表示
plt.imshow(img_blue)
plt.show()

#保存
Image.fromarray(img_red).save('red_save.jpg')
Image.fromarray(img_green).save('green_save.jpg')
Image.fromarray(img_blue).save('blue_save.jpg')

実行結果

f:id:genetaka1810:20191025000358p:plain

プログラムの実行結果を確認していきます。


# 画像の読み込み
>im = np.array(Image.open('input2x3.bmp'))
>print(im)
> [[[255 0 0] (ピクセル座標(0.0), RGB=(255, 0, 0) )
> [ 0 255 0] (ピクセル座標(0.1), RGB=(0, 255, 0) )
> [ 0 0 255]] (ピクセル座標(0.2), RGB=(0, 0, 255) )
>
> [[ 0 0 0] (ピクセル座標(1.0), RGB=(0, 0, 0) )
> [128 128 128] (ピクセル座標(1.1), RGB=(128, 128, 128) )
> [255 255 255]]] (ピクセル座標(1.2), RGB=(255, 255, 255) )

ピクセル座標の値に対して、各色が割り当てられています。

# 赤のみ抽出し表示
>img_red = im.copy()
>img_red[:, :, 1] = 0 # 緑ゼロ
>img_red[:, :, 2] = 0 # 青ゼロ
>print(img_red)

> [[[255 0 0]
> [ 0 0 0]
> [ 0 0 0]]
>
> [[ 0 0 0]
> [128 0 0]
> [255 0 0]]]

Green,Blueの色は、0となっていることが確認できます。

同様に、緑のみ抽出し表示した場合は、Red,Blueの色は、0となっていることがわかります。
> [[[ 0 0 0]
> [ 0 255 0]
> [ 0 0 0]]
>
> [[ 0 0 0]
> [ 0 128 0]
> [ 0 255 0]]]

青のみ抽出し表示た場合も同様の結果となり、Red,Greenの色は、0となります。
> [[[ 0 0 0]
> [ 0 0 0]
> [ 0 0 255]]
>
> [[ 0 0 0]
> [ 0 0 128]
> [ 0 0 255]]]

0-3 「Python Numpyによる画像表現(グレー画像)」

PILで取得した画像データをNumpy配列へ代入した際、Numpy配列の内容がどのようになるのか調べました。
比較のためにカラー画像と、カラー画像をグレースケールへ変換したデータを記載します。

#!/usr/bin/python
# -*- coding: utf-8 -*-

# Numpyによる画像表現(グレースケール)

from PIL import Image 
import numpy as np
from matplotlib import pylab as plt

# 画像の読み込み (カラー画像)
im = np.array(Image.open('input2x3.bmp'))
print(im.shape, im.dtype)

#画像の表示
plt.imshow(im)
plt.show()


# 画像の読み込み (グレースケール)
im = np.array(Image.open('input2x3.bmp').convert('L'))
print(im.shape, im.dtype)

# 上記を実行すると次のように表示される
# ((2L, 3L, 3L), dtype('uint8'))
# ((2L, 3L), dtype('uint8'))
# 最初のタプルは行、列、色数を表し、続く文字列は要素のデータ型をあらわす。
# uint8 は、符号なし整数8ビットという意味。
# 2番目のimでは、'L'を指定してグレースケールに変換しており、その場合、
# 色情報がなく、shapeのタプルは2つの値のみとなる。

print(im)
#[[ 76 149  29]
# [  0 128 255]]


#画像の表示
plt.gray() # 表示カラーマップをグレースケールとする(*)
plt.imshow(im)
plt.show()

# (*) YIQフォーマットのY画像?
# Y = 0.298912 x R + 0.586611 x G + 0.114478 x Bで計算される。
# RGB(255,0,0) の場合 Y= 76.22256 + 0 + 0
# RGB(0,255,0) の場合 Y= 0 + 149.585805 + 0
# RGB(0,0,255) の場合 Y= 0 + 0 + 29.19189
#
# RGB(128,128,128) の場合 Y= 38.260736 + 75.086208 + 14.653184 = 128.000128
# RGB(255,255,255) の場合 Y= 76.22256 + 149.585805 + 29.19189 = 255.000255


#保存
Image.fromarray(im).save('input2x3_save.bmp')


実行結果
f:id:genetaka1810:20191023220039p:plain


プログラムの実行結果を確認していきます。


# 画像の読み込み (カラー画像)
>im = np.array(Image.open('input2x3.bmp'))
>print(im.shape, im.dtype)
>(2L, 3L, 3L), dtype('uint8')

print文で表示された (2L, 3L, 3L), dtype('uint8') の意味は、読み込ませた画像ファイルが高さ2ピクセル、幅3ピクセルのカラーBMP形式ですので、(2L, 3L, 3L)のタプルは、順に、画像の高さ(行)、画像の幅(列)、色数を表していることがわかります。
また、続く文字列 uint8 (符号なし整数8ビット)は各要素のデータ型をあらわします。

# 画像の読み込み (グレースケール)
>im = np.array(Image.open('input2x3.bmp').convert('L'))
>print(im.shape, im.dtype)
>(2L, 3L), dtype('uint8')

グレースケールでは、(2L, 3L), dtype('uint8') と表示され、色情報がなく、shapeのタプルは2つの値となることが確認できます。
なお、カラー画像からグレースケールへの変換は、PILのImage openの時に、'L'を指定してます。


次に実際にnumpy配列へ代入した画像データをprint(im) 文により確認してみます。
すると、以下の様な結果が得られました。

[[ 76 149 29] <- im[0]
[ 0 128 255]] <- im[1]


これらは、画像の各ピクセル座標に対する輝度情報が表示された結果です。
例えば、 im[0,0] は、ピクセル座標 (0,0) の輝度値 76を示しています。
同様に im[0,1] は、ピクセル座標 (0,1)の輝度値 149を示しています。


カラー画像の読み込み時に、'L'を指定しグレースケールへ変換しているわけですが、カラー画像の場合、ピクセル座標(0,0)のカラー値は、(R,G,B) = (255, 0, 0)となっています。このカラー画素値をグレースケールへ変換すると、76 という値に変換されています。


(補足:要確認)
カラー画像からグレースケールへ変換したときのカラー値は、YIQフォーマットのY画像を表しているのではないかと思われます。
Y画像は、RGBのカラー画像から、人間の目の明るさ感度に特化して作成する画像で、次の計算式により求まります。

Y = 0.298912 x R + 0.586611 x G + 0.114478 x B

(R,G,B) = (255, 0, 0)の場合、 Y= 76.22256 + 0 + 0 と計算され、'L'指定で作成したグレースケールの値 76 とほぼ一致します。
同様に、ピクセル座標(0,1)の カラー値は、RGB(0,255,0)なので、 Y= 0 + 149.585805 + 0 となり、ピクセル座標(0,2)の カラー値は、RGB(0,0,255)の場合、 Y= 0 + 0 + 29.19189 と計算されます。

もう少し調べてみて、何かわかったら記事を更新します。

0-2 「Python Numpyによる画像表現(カラー画像)」

内容

PILで取得した画像データをNumpy配列へ代入した際、Numpy配列の内容がどのようになるのか調べました。

やり方

ペイントツールで作成した画像サイズ縦2ピクセル、横3ピクセルのビットマップカラー画像(input2x3.bmp)をPILを使用して読み込み、numpy配列化します。配列の中身を print文で表示し、ビットマップ画像データがどのようにnumpy配列に保存されているか確認していきます。

プログラム

#!/usr/bin/python
# -*- coding: utf-8 -*-

# Numpyによる画像表現

from PIL import Image 
import numpy as np
from matplotlib import pylab as plt

## 画像の読み込み (カラー画像)
im = np.array(Image.open('input2x3.bmp'))
print(im.shape, im.dtype)
# ((2L, 3L, 3L), dtype('uint8'))
# 行、列、色数に続いて、続く文字列は要素のデータ型をあらわす。
# uint8 は、符号なし整数8ビットという意味。
# したがって、 2x3サイズ(縦x横)のカラー画像の意味

## 配列の中身を確認
print(im)
#[[[255   0   0]    <- 赤色 (Red=255, Green=0, Blue=0)
#  [  0 255   0]    <- 緑色 (Red=0, Green=255, Blue=0)
#  [  0   0 255]]   <- 青色 (Red=0, Green=0, Blue=255)
#
# [[  0   0   0]    <- 黒色 (Red=0, Green=0, Blue=0)
#  [128 128 128]    <- 灰色 (Red=128, Green=128, Blue=128)
#  [255 255 255]]]  <- 白色 (Red=255, Green=255, Blue=255)

## 座標i,jの色チャンネルkの値は、 im = [i,j,k]で取得できる。
print(im[0,0])
# [255   0   0]

print(im[0,0,0]) # R
print(im[0,0,1]) # G
print(im[0,0,2]) # B
# 255
# 0
# 0

# 画像の表示
plt.imshow(im)
plt.show()

#保存
Image.fromarray(im).save('input2x3_save.bmp')

実行結果
f:id:genetaka1810:20191022183803p:plain


プログラムの実行結果を確認していきます。

>im = np.array(Image.open('input2x3.bmp'))
>print(im.shape, im.dtype)
>(2L, 3L, 3L), dtype('uint8')

print文で表示された (2L, 3L, 3L), dtype('uint8') の意味は、読み込ませた画像ファイルが高さ2ピクセル、幅3ピクセルのカラーBMP形式ですので、(2L, 3L, 3L)のタプルは、順に、画像の高さ(行)、画像の幅(列)、色数を表していることがわかります。
また、続く文字列 uint8 (符号なし整数8ビット)は各要素のデータ型をあらわします。


次に実際にnumpy配列へ代入した画像データをprint(im) 文により確認してみます。
すると、以下の様な結果が得られました。

[[[255 0 0] <- ピクセル座標(0,0)の色情報
[ 0 255 0] <- ピクセル座標(0,1)の色情報
[ 0 0 255]] <- ピクセル座標(0,2)の色情報

[[ 0 0 0]    <- ピクセル座標(1,1)の色情報
[128 128 128]  <- ピクセル座標(1,1)の色情報 
[255 255 255]]] <- ピクセル座標(1,1)の色情報

これらは、画像の各ピクセル座標に対する色情報が表示された結果です。例えば、 im[0,0] は、ピクセル座標 (0,0) の色情報を表し、R,G,Bの色情報は、[255 0 0] と取得でき、これは 赤:255, 緑:0, 青:0 の色であることを意味しています。
同様に im[0,1] は、ピクセル座標 (0,1)の色情報を示しており、 赤:0, 緑:255, 青:0 の色を持つことがわかります。


ピクセル座標と色の関係を改めて図示しておきます。

f:id:genetaka1810:20191022183431p:plain

im[1,0] は黒色なので、RGBがすべて0となり、im[1,2]は、白色なのでRGBがすべて255となっています。
つまり、ピクセル座標 ( i, j )、色チャンネル k の値は、 im = [i, j, k]で取得できることがわかると思います。

補足

numpy配列を用いて画像を作成してみました。

補足プログラム

#!/usr/bin/python
# -*- coding: utf-8 -*-
# Numpyによる画像表現

from PIL import Image 
import numpy as np
from matplotlib import pylab as plt

# 2x3サイズのテスト画像作成 (カラー画像)
im=np.zeros((2, 3, 3), np.uint8)

im[0,0,0]=255   # R
im[0,1,1]=255   # G
im[0,2,2]=255   # B

im[1,0,:]=0     # Black
im[1,1,:]=128   # Glay
im[1,2,:]=255   # white

print(im.shape, im.dtype)
# ((2L, 3L, 3L), dtype('uint8'))
# 行、列、色数に続いて、続く文字列は要素のデータ型をあらわす。
# uint8 は、符号なし整数8ビットという意味。
# したがって、 2x3サイズ(縦x横)のカラー画像


## 配列の中身を確認
print(im)
#[[[255   0   0]    <- 赤色 (Red=255, Green=0, Blue=0)
#  [  0 255   0]    <- 緑色 (Red=0, Green=255, Blue=0)
#  [  0   0 255]]   <- 青色 (Red=0, Green=0, Blue=255)
#
# [[  0   0   0]    <- 黒色 (Red=0, Green=0, Blue=0)
#  [128 128 128]    <- 灰色 (Red=128, Green=128, Blue=128)
#  [255 255 255]]]  <- 白色 (Red=255, Green=255, Blue=255)

## 座標i,jの色チャンネルkの値は、 im = [i,j,k]で取得できる。
print(im[0,0])
# [255   0   0]

print(im[0,0,0]) # R
print(im[0,0,1]) # G
print(im[0,0,2]) # B
# 255
# 0
# 0


# 画像の表示
plt.imshow(im)
plt.show()

#保存
Image.fromarray(im).save('output.bmp')

0-1 「Python 画像の表示と保存」

PILとNumpyを使用して画像の表示と保存を行います。
画像ファイルは、「実践 コンピュータビジョン」の著者のページからダウンロードしたものを使用しています。

#!/usr/bin/python
# -*- coding: utf-8 -*-

# 画像の表示と保存

from PIL import Image 
import numpy as np
from matplotlib import pylab as plt

# 画像の読み込み
im = np.array(Image.open('alcatraz1.jpg'))

# 画像の情報を表示
print(im.dtype) #データ型 uint8
print(im.ndim)  #次元数 3
print(im.shape) #サイズ(高さx幅x色数) (1296L, 1936L, 3L)

# 画像の表示
plt.imshow(im)
plt.show()

#保存
Image.fromarray(im).save('alcatraz1_save.jpg')

実行結果
f:id:genetaka1810:20191021163717p:plain

print文で表示される値(文字列)には、以下の意味があります。
 im.dtype:データ型 uint8は、画素データが8ビットの整数型であることを意味しています。
 im.ndim:画像ファイルの次元数を確認でき、3とはRGBのカラーデータであることを示しています。
 im.shape:画像ファイルのサイズが確認できます。(1296L, 1936L, 3L)とは、高さ1296ピクセル、幅1936ピクセル、色数3のカラー画像であることがわかります。