ゲーム開発
Unity
UnrealEngine
C++
Blender
ゲーム数学
ゲームAI
グラフィックス
サウンド
アニメーション
GBDK
制作日記
IT関連
ツール開発
フロントエンド関連
サーバサイド関連
WordPress関連
ソフトウェア設計
おすすめ技術書
音楽
DTM
楽器・機材
ピアノ
ラーメン日記
四コマ漫画
その他
おすすめアイテム
おもしろコラム
  • ゲーム開発
    • Unity
    • UnrealEngine
    • C++
    • Blender
    • ゲーム数学
    • ゲームAI
    • グラフィックス
    • サウンド
    • アニメーション
    • GBDK
    • 制作日記
  • IT関連
    • ツール開発
    • フロントエンド関連
    • サーバサイド関連
    • WordPress関連
    • ソフトウェア設計
    • おすすめ技術書
  • 音楽
    • DTM
    • 楽器・機材
    • ピアノ
  • ラーメン日記
    • 四コマ漫画
      • その他
        • おすすめアイテム
        • おもしろコラム
      1. ホーム
      2. 20210217_01

      【Python】Pillowを使ってピクセル操作!画像フィルタをかけてみる

      ツール開発Python画像編集pillow
      2021-02-17

      マイケル
      マイケル
      みなさんこんにちは!
      マイケルです!
      エレキベア
      エレキベア
      クマ〜〜〜〜〜〜
      マイケル
      マイケル
      今日はPythonの Pillow というライブラリを使って
      いろんな画像フィルタをかけてみるよ!
      エレキベア
      エレキベア
      画像フィルタって何クマ??
      マイケル
      マイケル
      たとえばこのエレキベアを…
      マイケル
      マイケル
      グレーにしたり
      マイケル
      マイケル
      モザイクをかけたり
      マイケル
      マイケル
      いろいろな効果をかけることなんだ!
      エレキベア
      エレキベア
      (人の画像で遊ぶなクマ・・・)
      なるほどクマ
      ペイントソフトとかでかけれるやつ クマね
      マイケル
      マイケル
      Photoshop等ペイントソフトでかけるフィルタも、
      内部で変換する処理を行なっている からね!
      マイケル
      マイケル
      今回はその画像フィルタの中でも、簡単でスタンダードな

      ・ネガポジ反転
      ・グレースケール
      ・エッジ検出
      ・モザイク

      の処理を実装していくよ!
      エレキベア
      エレキベア
      それは楽しみクマ〜〜〜〜

      参考書籍

      マイケル
      マイケル
      今回、画像フィルタの実装をするにあたって
      参考にした参考書はこちらになります!

      グラフィックスプログラミング入門

      マイケル
      マイケル
      この本ではJavaScriptでの実装ですが、
      数学理論等、グラフィックス描画の基礎から書かれているので
      読んでるだけでも勉強になります!
      エレキベア
      エレキベア
      表紙もかわいいクマね
      読んでみるクマ
      マイケル
      マイケル
      画像フィルタの話はおまけで載っているレベルだったけど、
      目から鱗の内容でした!

      ピクセルとは

      マイケル
      マイケル
      画像フィルタの実装に入る前に、ピクセルについて理解しておこう!
      エレキベア
      エレキベア
      ピクセルって何クマ??
      マイケル
      マイケル
      説明しよう!
      ピクセルとは
      • コンピュータで画像を扱う際の、色の最小単位 のことである!
      エレキベア
      エレキベア
      色の最小単位クマ?
      マイケル
      マイケル
      その通り!
      例えば下のイルカの画像では、青や赤といった色をピクセル単位で表示しているんだ!

      ↑イルカの画像(20px * 20px)
      エレキベア
      エレキベア
      (イルカ・・・・???)
      マイケル
      マイケル
      よくみる画像サイズの「px」は、言い換えれば縦横のピクセル数のことなんだね
      エレキベア
      エレキベア
      なるほどクマ
      画像は全てピクセル単位で色を表示しているクマね
      マイケル
      マイケル
      今回行う画像フィルタは、ピクセル単位で色を取得して変換 という流れで処理を行うよ!
      ピクセルの概念は基本になるので覚えておきましょう!
      エレキベア
      エレキベア
      楽しみクマ〜〜〜〜

      画像フィルタの実装

      マイケル
      マイケル
      それじゃ早速実装してみよう!
      今回画像フィルタを行う対象は、ラーメン豚山のラーメン画像です!

      ↑ラーメン豚山はとてもおいしい
      エレキベア
      エレキベア
      (なんでこれを選んだクマ・・・)

      全体の処理

      マイケル
      マイケル
      フィルタ4種類を全て実装したソースは以下になります!
      image_filter()メソッドにフィルタの種類を渡して変換する 処理にしています!
      from PIL import Image
      import math
      
      # 画像フィルタ種類
      INVERT_FILTER = "INVERT_FILTER"
      GRAYSCALE_FILTER = "GRAYSCALE_FILTER"
      LAPLACIAN_FILTER = "LAPLACIAN_FILTER"
      MOSAIC_FILTER = "MOSAIC_FILTER"
      
      
      # 画像フィルタ処理
      def image_filter(image, filter_type):
          # 画像のRGBA,サイズを取得
          rgba = image.convert('RGBA')
          size = rgba.size
          # 返却用画像生成
          filter_image = Image.new('RGBA', size)
      
          ###################
          # 画像フィルタ処理
          # (指定タイプで分岐)
          ###################
          # *** ネガポジ反転 ***
          if filter_type == INVERT_FILTER:
              for x in range(size[0]):
                  for y in range(size[1]):
                      # RGBAの取得
                      r, g, b, a = rgba.getpixel((x, y))
                      # 255から引いて反転
                      r = 255 - r
                      g = 255 - g
                      b = 255 - b
                      filter_image.putpixel((x, y), (r, g, b, a))
          # *** グレースケール ***
          elif filter_type == GRAYSCALE_FILTER:
              for x in range(size[0]):
                  for y in range(size[1]):
                      # RGBAの取得
                      r, g, b, a = rgba.getpixel((x, y))
                      # RGBの平均値を設定
                      luminance = int((r + g + b) / 3)
                      r = luminance
                      g = luminance
                      b = luminance
                      filter_image.putpixel((x, y), (r, g, b, a))
          # *** ラプラシアンフィルタ(エッジ検出) ***
          elif filter_type == LAPLACIAN_FILTER:
              for x in range(size[0]):
                  for y in range(size[1]):
                      # RGBAの取得
                      r, g, b, a = rgba.getpixel((x, y))
                      # 上下左右のRGBA取得
                      t_r, t_g, t_b, t_a = rgba.getpixel((max(x - 1, 0), y))
                      b_r, b_g, b_b, b_a = rgba.getpixel((min(x + 1, size[0] - 1), y))
                      l_r, l_g, l_b, l_a = rgba.getpixel((x, max(y - 1, 0)))
                      r_r, r_g, r_b, r_a = rgba.getpixel((x, min(y + 1, size[1] - 1)))
                      # 上下左右の色を加算し、中心の色*4 を除算する
                      # (差分がない場合は0になる)
                      r = t_r + b_r + l_r + r_r - (r * 4)
                      g = t_g + b_g + l_g + r_g - (g * 4)
                      b = t_b + b_b + l_b + r_b - (b * 4)
                      filter_image.putpixel((x, y), (r, g, b, a))
          # *** モザイク ***
          elif filter_type == MOSAIC_FILTER:
              # ブロックサイズを定義
              block_size = 8
              for x in range(size[0]):
                  for y in range(size[1]):
                      # blockSizeで切り捨て、インデックスを求める
                      set_x = math.floor(x / block_size) * block_size
                      set_y = math.floor(y / block_size) * block_size
                      # 求めたインデックスのRGBAを設定
                      r, g, b, a = rgba.getpixel((set_x, set_y))
                      filter_image.putpixel((x, y), (r, g, b, a))
          # *** その他 ***
          else:
              # そのまま返却
              filter_image = image
          # フィルタ結果を返却
          return filter_image
      
      
      # 画像読込
      read_image = Image.open("【画像パス】")
      # 画像フィルタ処理
      # ※第2引数にフィルタのタイプを指定
      disp_image = image_filter(read_image, MOSAIC_FILTER)
      # フィルタ結果表示
      disp_image.show()
      
      エレキベア
      エレキベア
      「画像フィルタ処理」の部分で変換しているクマね
      マイケル
      マイケル
      その通り!
      一つ一つ内容を見ていこう!

      ネガポジ反転

      マイケル
      マイケル
      まずはネガポジ反転
      名前の通り色を反転させるフィルタで、かけた結果は以下のようになるよ!

      ↑ネガポジ反転後のラーメン
      エレキベア
      エレキベア
      (まずそうクマ・・・。)
      マイケル
      マイケル
      処理の流れとしては、

      1. 画像からRGBAとサイズ(縦横のピクセル数)を取得
      2. 「255からRGB値を引いて設定し直す」処理をピクセル分繰り返す

      となっています!
      
      ・・・略・・・
      
      # 画像フィルタ処理
      def image_filter(image, filter_type):
          # 画像のRGBA,サイズを取得
          rgba = image.convert('RGBA')
          size = rgba.size
          # 返却用画像生成
          filter_image = Image.new('RGBA', size)
      
          ###################
          # 画像フィルタ処理
          # (指定タイプで分岐)
          ###################
          # *** ネガポジ反転 ***
          if filter_type == INVERT_FILTER:
              for x in range(size[0]):
                  for y in range(size[1]):
                      # RGBAの取得
                      r, g, b, a = rgba.getpixel((x, y))
                      # 255から引いて反転
                      r = 255 - r
                      g = 255 - g
                      b = 255 - b
                      filter_image.putpixel((x, y), (r, g, b, a))
      
      
      ・・・略・・・
      
      
      マイケル
      マイケル
      RGB値は0〜255で表されるから255から引くことで反転できる、ということだね!

      ↑RGB値は0〜255で表すことができる

      エレキベア
      エレキベア
      ピクセル一つ一つに対して反転処理を行うクマね

      グレースケール

      マイケル
      マイケル
      そして2つ目はグレースケール
      これも名前の通り、モノトーン色のみにするフィルタだね!

      ↑グレースケール後のラーメン
      エレキベア
      エレキベア
      (なんか悲しいクマ・・・。)
      マイケル
      マイケル
      ラーメンが泣いている・・・!
      マイケル
      マイケル
      どういう時に白黒になるかというと、
      実はRGB値が全て同じ値の時に白黒になるんだ!

      ↑RGB値が同一の場合に白黒になる

      エレキベア
      エレキベア
      それは知らなかったクマ
      マイケル
      マイケル
      これを利用して、RGBの合計値を3で割ることで平均値を設定してあげます!
      
      ・・・略・・・
      
      # 画像フィルタ処理
      def image_filter(image, filter_type):
          # 画像のRGBA,サイズを取得
          rgba = image.convert('RGBA')
          size = rgba.size
          # 返却用画像生成
          filter_image = Image.new('RGBA', size)
      
          ###################
          # 画像フィルタ処理
          # (指定タイプで分岐)
          ###################
      
      ・・・略・・・
      
          # *** グレースケール ***
          elif filter_type == GRAYSCALE_FILTER:
              for x in range(size[0]):
                  for y in range(size[1]):
                      # RGBAの取得
                      r, g, b, a = rgba.getpixel((x, y))
                      # RGBの平均値を設定
                      luminance = int((r + g + b) / 3)
                      r = luminance
                      g = luminance
                      b = luminance
                      filter_image.putpixel((x, y), (r, g, b, a))
      
      ・・・略・・・
      
      
      マイケル
      マイケル
      これだけでグレースケール化することができます!
      エレキベア
      エレキベア
      こんな簡単にできるクマね
      感動クマ・・・。

      エッジ検出

      マイケル
      マイケル
      そして3つ目はエッジ検出
      これはラプラシアンフィルターを使用して色の差分が大きい箇所のみ目立たせることで処理できるよ!

      ↑エッジ検出後のラーメン
      エレキベア
      エレキベア
      これは難しそうクマ・・・。
      どうやるクマ??
      マイケル
      マイケル
      これは対象ピクセルの色と周りのピクセルの色に差分があるかをチェックすることで処理するよ!
      マイケル
      マイケル
      上の図のように、周りのピクセル(上下左右)の色の合計から中央ピクセルの色を4倍した値を引いた差分を設定するんだ!
      その結果、差分が大きいほど白色に近い値になるから強調されるということだね!
      
      ・・・略・・・
      
      # 画像フィルタ処理
      def image_filter(image, filter_type):
          # 画像のRGBA,サイズを取得
          rgba = image.convert('RGBA')
          size = rgba.size
          # 返却用画像生成
          filter_image = Image.new('RGBA', size)
      
          ###################
          # 画像フィルタ処理
          # (指定タイプで分岐)
          ###################
      
      ・・・略・・・
      
          # *** ラプラシアンフィルタ(エッジ検出) ***
          elif filter_type == LAPLACIAN_FILTER:
              for x in range(size[0]):
                  for y in range(size[1]):
                      # RGBAの取得
                      r, g, b, a = rgba.getpixel((x, y))
                      # 上下左右のRGBA取得
                      t_r, t_g, t_b, t_a = rgba.getpixel((max(x - 1, 0), y))
                      b_r, b_g, b_b, b_a = rgba.getpixel((min(x + 1, size[0] - 1), y))
                      l_r, l_g, l_b, l_a = rgba.getpixel((x, max(y - 1, 0)))
                      r_r, r_g, r_b, r_a = rgba.getpixel((x, min(y + 1, size[1] - 1)))
                      # 上下左右の色を加算し、中心の色*4 を除算する
                      # (差分がない場合は0になる)
                      r = t_r + b_r + l_r + r_r - (r * 4)
                      g = t_g + b_g + l_g + r_g - (g * 4)
                      b = t_b + b_b + l_b + r_b - (b * 4)
                      filter_image.putpixel((x, y), (r, g, b, a))
      
      
      ・・・略・・・
      
      
      エレキベア
      エレキベア
      なるほどクマ・・・!
      こんな方法でエッジ検出していたクマね
      マイケル
      マイケル
      ちなみに上下左右の4ピクセルのチェックを行う方法は4近傍法といって、
      斜めも含めた8ピクセルをチェックする8近傍法という方法もあるよ!
      エレキベア
      エレキベア
      処理の負荷と精度の考慮が必要ということクマね

      モザイク

      マイケル
      マイケル
      そして最後はモザイク
      これは分かりやすくモザイクです!

      ↑モザイク後のラーメン
      エレキベア
      エレキベア
      モザイククマ
      マイケル
      マイケル
      これは、モザイクにするブロック数をあらかじめ決めておいて、
      そのブロック範囲を全て同じ色で塗りつぶす
      ことで実現できるよ!
      
      ・・・略・・・
      
      # 画像フィルタ処理
      def image_filter(image, filter_type):
          # 画像のRGBA,サイズを取得
          rgba = image.convert('RGBA')
          size = rgba.size
          # 返却用画像生成
          filter_image = Image.new('RGBA', size)
      
          ###################
          # 画像フィルタ処理
          # (指定タイプで分岐)
          ###################
      
      ・・・略・・・
      
          # *** モザイク ***
          elif filter_type == MOSAIC_FILTER:
              # ブロックサイズを定義
              block_size = 8
              for x in range(size[0]):
                  for y in range(size[1]):
                      # blockSizeで切り捨て、インデックスを求める
                      set_x = math.floor(x / block_size) * block_size
                      set_y = math.floor(y / block_size) * block_size
                      # 求めたインデックスのRGBAを設定
                      r, g, b, a = rgba.getpixel((set_x, set_y))
                      filter_image.putpixel((x, y), (r, g, b, a))
      
      ・・・略・・・
      
      
      エレキベア
      エレキベア
      これは割と分かりやすいクマね
      マイケル
      マイケル
      ブロック数で割った結果をインデックスとして扱う、というシンプルな処理だね!
      モザイクはよく使いそうだから覚えておいて損はなさそうだ!

      おわりに

      マイケル
      マイケル
      というわけで4種類の画像フィルタの実装について紹介しました!
      どうだったかな?
      エレキベア
      エレキベア
      いつも使うフィルタの実装内容が分かって楽しかったクマ〜〜〜〜
      マイケル
      マイケル
      画像フィルタはまだまだたくさんある!
      興味をもったらぜひ他のフィルタの実装方法も調べてみましょう!
      マイケル
      マイケル
      それでは今日はこの辺で!
      アデュー!!
      エレキベア
      エレキベア
      クマ〜〜〜〜〜〜〜〜〜

      【Python】Pillowを使ってピクセル操作!画像フィルタをかけてみる 〜完〜


      ツール開発Python画像編集pillow
      2021-02-17

      関連記事
      【Unity】Timeline × Excelでスライドショーを効率よく制作する
      2024-10-31
      【Node.js】廃止されたAmazonアソシエイト画像リンクをAmazon Product Advertising API経由で復活させる
      2024-01-08
      【Electron × Vue3】カテゴリ情報のCSVデータを操作するツールを作る
      2023-12-31
      【Electron × Vue3】画像をリサイズして任意の場所に保存するツールを作る
      2023-12-31
      【Electron × Vue3】Electron × Vue3 × TypeScript × Vite でツール開発環境を整える
      2023-12-31
      【Flutter3】Googleスプレッドシートと連携した英単語学習アプリを作る
      2022-12-11
      【Python】Pythonスクリプトをexe、app化する【cx_Breeze】
      2021-08-29
      【Python】ぷよぷよ風の文字連結パズルゲームを作る【あけおめパズル】
      2021-01-03