ユーニックス総合研究所

  • home
  • archives
  • python-pillow-image-effect-spread

PythonのPillowのImage.effect_spread()で画像のピクセルをランダムに拡大する【エフェクト】

  • 作成日: 2020-10-08
  • 更新日: 2023-12-26
  • カテゴリ: Python

PillowのImage.effect_spread()の使い方

Pythonの画像処理ライブラリというとPillowが有名ですが、Pillowには画像を管理するImageモジュールがあります。
このImageモジュール、Image.effect_spread()という画像のピクセルをランダムに拡大する関数を持っています。

これを使うと画像にザラついたエフェクトをかけることができます。

これはモザイク処理とはまた違うエフェクトなんですが、モザイク処理の代替としても使うことが出来ます。
画像のピクセルをランダムに拡大するエフェクトです。
エフェクトが画像のピクセルごとに処理され、結果的にモザイクのような絵になります。
輪郭がぼやけて、画像全体がざらついた質感になり、すりガラスを通して見てるような効果が得られます。

Webアプリなどでサムネイルの画像にこのエフェクトをかけて、マウスオーバーで元の画像を表示するなどのギミックが考えられます。
また、クイズサイトなどで画像のクイズを出す時に、このエフェクトをかければ画像の正体を隠したクイズを出すことができそうです。

Image.effect_spread()の使い方自体は非常にシンプルです。
画像にeffect_spread()を付けてエフェクトを行う関数を呼び出し、引数を1つ与えるだけです。
この手軽さはPillowの魅力の1つでもあります。

PillowにはImage.effect_spread()のようなエフェクトを生成する関数が沢山あります。
関連するエフェクトについては↓のリンクを参照してください。

Image.effect_spread()の構造

Image.effect_spread()は↓のような構造を持っています。
この関数は画像のピクセルをランダムに拡大します。
引数を1つ取り、返り値を1つ返します

Image.effect_spread(distance)  

Image.effect_spread()の引数と返り値について↓で解説します。

distance(第1引数)

拡大するピクセル同士の距離です。
この値を小さくすればするほど目が細かくなり、輪郭のぼやけが弱くなります。
逆にこの値が大きくなればなるほど画像が荒くなり、輪郭がぼやけます。

この引数の値は10ぐらいだとまだ原型の画像を認識できるぼやけ方になります。
100まで行くと原型の画像の輪郭がほんとんどなくなり、認識するのがむずかしくなる程度です。

用途に合わせてこの引数の値を調整することで、希望する粗さのエフェクトがかかった画像が手に入るでしょう。

返り値

返り値はエフェクトを適用したあとの画像(Imageオブジェクト)です。
Imageオブジェクトなので、saveを呼び出すことでエフェクトを掛けた後の画像をファイルに保存することができます。
また、別のエフェクトを連続してかけることも可能です。

今回使用する画像

今回検証に使用する画像は↓です。

RGBAの横幅500px高さ500pxPNG画像です。
ピクセルが目立つドットの画像なので今回の検証にはピッタリかと思います。

ピクセルを拡大する

先ほどの画像のピクセルを拡大してみます。
まずfrom PIL import ImageとやってPillowのImageモジュールをインポートします。
次にImage.open()に画像のパスを指定して画像を開きます。
その画像のImage.effect_spread()を呼び出して、画像のピクセルをランダムに拡大します。
この時にImage.effect_spread()distance引数に適当な値を渡します。
その後、エフェクトを適用した画像をImage.effect_spread()の返り値から得て、その画像をImage.save()でパスを指定して保存します。

from PIL import Image  

im = Image.open('img/a.png')  # 画像を開く  
im = im.effect_spread(10)  # ピクセルをランダムに拡大する  
im.save('dst/out0.png')  # 画像を保存する  

保存したdst/out0.pngを開くと↓のように表示されます。

画像を見るとdistance10ぐらいだた、まだそれほどぼやけ方が強くないのがわかります。
輪郭線はぼやけていますが、目で補正出来る程度ですし、原型のおもかげが強く残っています。
クイズなどの用途ではこのぐらいのエフェクトの画像は使えないでしょう。

ピクセルをさらに拡大する

Image.effect_spread()の第1引数distanceの値を大きくすると画像がさらに荒くなります。
100ぐらいにするともはや原型の輪郭があいまいになってきます。
画像のピクセル(画素)がランダムに配置されるようになります。

from PIL import Image  

im = Image.open('img/a.png')  # 画像を開く  
im = im.effect_spread(100)  # ピクセルをランダムに拡大する  
im.save('dst/out1.png')  # 画像を保存する  

出力結果。

このぐらいのエフェクトがかかった画像だと「この画像はなんだ?」というクイズなどに使えるかもしれませんね。
1つこのImage.effect_spread()を使ってWebサービスを作ってみてはいかがでしょうか。

🦝 < クイズサービスね

ソースコードの解析

Image.effect_spread()のコードを追いかけると最終的にEffects.cモジュールのImagingEffectSpread()関数に辿り着きます。

コードを見るとわかりますが、ランダムなピクセルの座標は↓の式で求められています。

xx = x + (rand() % distance) - distance / 2;  
yy = y + (rand() % distance) - distance / 2;  

元の座標に乱数をdistanceで割った余りを加えて、そこからdistanceの半分を引いています。
ということはdistance0にしたら0除算エラーになるのでは?
ということなんですが、実際に試してみましょう。

from PIL import Image  

im = Image.open('img/a.png')  # 画像を開く  
im = im.effect_spread(0)  # 0除算エラー?  

実行するとプログラムが不正に終了します。
Windows10のコマンドプロンプトでは%ERRORLEVEL%(終了ステータス)に↓のような値が入ります。

-1073741676;  

この0除算エラーは2020年09月08日の段階ではそのままになっています。
Issueを立てておいたのでその内フィックスされるかもしれません。

追記:
Issueを立てた2日後ぐらいにフィックスされました。
Pillowの開発は活発に行われているようです。

問題

Q1: Image.effect_spread()の第1引数として適当なものを答えよ

  1. int
  2. str
  3. list

Q2: Image.effect_spread()の第1引数の値を大きくするとどうなるか答えよ

  1. 画像がクッキリする
  2. 画像が荒くなる
  3. 画像のネガポジが反転する

Q3: Image.effect_spread()の返り値として適当なものを答えよ

  1. list
  2. dict
  3. Imageオブジェクト

問題の正解はこちら↓

Q1: 1
Q2: 2
Q3: 3