ユーニックス総合研究所

  • home
  • archives
  • python-pillow-image-putpixel

PythonのPillowのImage.putpixelの使い方: 画像に色を置く

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

PillowのImage.putpixelの使い方

Pythonの画像処理ライブラリであるPillowには画像を処理するImageモジュールがあります。
Imageモジュールは画像内の指定の位置にピクセルを置くことが出来る関数putpixel()を持っています。

Image.putpixel()を使うと画像の好きな位置に好きな色を描くことが出来るようになります。

ピクセルとは何か?

「ピクセル」とは「画素(がそ)」のことで、これは画像を構成する最小単位のことです。
画像はこのピクセルが並んで表現されています。
英語では「pixel」と表現されます。
100pxとか312pxとかについてるpxはこのピクセルのことです。
たとえば「横幅100px」というのは「横に画素が100個並んでいる」ということになります。

Pillowではこの画像のピクセルを自由に編集することが可能です。
その手段の1つが今回タイトルになっているImage.putpixel()になります。

Image.putpixel()の構造

Image.putpixel()は以下の構造を持っています。

Image.putpixel(xy, value)  

xy(第1引数)

第1引数のxyにはピクセルを置く画像上の位置をタプルで指定します。
タプルの第1要素がX座標、第2要素がY座標です。
これは(x, y)というフォーマットになります。

座標が画像のサイズの範囲外であればImage.putpixel()は例外IndexErrorを送出します。

value(第2引数)

第2引数のvalueにはピクセルの色を指定します。
画像のモードがRGBであれば長さ3のタプル、RGBAであれば長さ4のタプルになります。
RGBのフォーマットは(r, g, b), RGBAのフォーマットは(r, g, b, a)になります。
モードがL(8ビット)であればvalueにはタプルではなく整数を渡します。

返り値

返り値はありません。

白いキャンバスに赤い十字線

白いキャンバスに赤い十字線を引いてみたいと思います。

from PIL import Image  

# 横幅500px, 高さ500px, 背景白色の画像を生成  
im = Image.new('RGB', (500, 500), (255, 255, 255))  

# ループで十字線を引く  
for i in range(500):  
    color = (255, 0, 0)  # 赤色  
    im.putpixel((i, 250), color)  # 横に線を引くためのピクセルを置く  
    im.putpixel((250, i), color)  # 縦に線を引くためのピクセルを置く  

# 編集した画像を保存  
im.save('dst/out0.png')  

出力結果。

編集の考え方としては、まず正方形の画像を生成します。↑の例では横幅500px, 高さ500pxの正方形です。
そしてループで、0500までカウントします。そのカウント変数を座標のX座標、Y座標にそれぞれ指定し、横と縦それぞれに一本線を引くようにします。(i, 250)という座標の指定がそれに相当するところですが、250は画像の半分のサイズ(つまり画像の中心座標)になるので、線が画像の中央に描画されるようになります。

colorには、画像のモードがRGBになっているので長さ3のタプルを指定しています。

格子グラデーションを描画する

格子にグラデーションがかかったような画像を生成します。

from PIL import Image  
import math  

# 横幅500px, 高さ500px, 背景白色の画像を生成  
width, height = 500, 500  
im = Image.new('RGB', (width, height), (255, 255, 255))  

# ループでピクセルを描画する  
for x in range(width):  
    for y in range(height):  
        color = (int(math.cos(x) * y), 128, 128)  # 色を生成  
        im.putpixel((x, y), color)  # 色を置く  

# 編集した画像を保存  
im.save('dst/out1.png')  

出力結果。

これはシェーダーに通ずるところがある描画です(というかシェーダーが画像処理に通ずる?)。
画像を生成し、ループでピクセルを描画します。for文を入れ子にして画像の横幅と高さそれぞれの幅だけ回すようにします。こうすることで画像の各ピクセルを1つずつ編集できるようになります。
colorの生成にx, yのカウント変数を使っていますが、ここの演算方法を工夫することで様々な画像を生成できるようになります。
他にもtimeという時間を表現する変数を追加し、内側のループでインクリメントして利用するというのも一般的です。

シェーダーのようにリアルタイムに描写が変わる様子は、画像を連番で作成しない限り見ることは出来ませんが、しかしそれでもなかなか面白いプログラミングと言えます。
このプログラミングが面白いと感じた方はシェーダーを勉強されてみるといいかもしれません。

🦝 < CGの世界へ

テレビノイズの再現

↑の画像にテレビに表れる横線のノイズを描画してみます。

from PIL import Image  
import math  

# 画像を開き、横幅と高さを得る  
im = Image.open('img/a.png').convert('RGB')  
width, height = im.size  

# 横幅と高さの分だけループでピクセルを編集する  
for x in range(width):  
    for y in range(height):  
        r, g, b = im.getpixel((x, y))  # 画像のピクセルを取得  
        color = (int(math.sin(y) * r), g, b)  # 画像のピクセルを編集  
        im.putpixel((x, y), color)  # 画像にピクセルを置く  

# 編集した画像を保存  
im.save('dst/out2.png')  

出力結果。

Image.getpixel()で指定した位置の画像のピクセルを取得できます。
これで取得したピクセルを編集すれば、すでにある画像のピクセルを編集できるということになります。
↑の例では開いた画像のピクセルをループ内で取得し、そのピクセルに格子になるような編集を加えて、再び画像の元の位置にピクセルを置いています。

  • 画像からピクセルを取る
  • 取ったピクセルを編集する
  • 編集したピクセルを元の位置に再配置する

このプロセスを経ることで画像に自由にエフェクトなどを追加することが可能です。

問題

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

  1. 座標
  2. サイズ

Q2: Image.putpixel()の第2引数として適当なものを答えよ

  1. サイズ
  2. 座標

Q3: 元の画像にエフェクトを追加したい場合に適当な処理を答えよ

  1. getpixel()でピクセルを取得し、編集後putpixel()で再配置する
  2. putpixel()でピクセルを取得し、編集後getpixel()で再配置する
  3. putpixel()で画像のサイズを取得し、merge()で合成する

正解はこちら↓

Q1: 1
Q2: 1
Q3: 1