LoginSignup
53
37

More than 3 years have passed since last update.

画像を読み込んでExcel上でセルの背景色で描画するプログラム作ってみた

Last updated at Posted at 2020-11-26

はじめに

画像を読み込んで Excel 上でセルの背景色で描画するプログラムを作ってみました。

きっかけは、研究室の先輩の Tweet でした。

VBAで画像読み込んで、エクセルのセルの色でその画像を再現するっていうクソプログラムを思いついたから誰か作って

画像そのものをいじるプログラムを組んだ経験あんまりないけど、興味があったので挑戦してみることにしました。

Excel の背景で描画なんてできたらおもしろいですよね:blush:

技術選定

VBA はバイト先で触ったことあるけど時代は Python かなと思ったので、Python とそのライブラリ Openpyxl で開発を進めることにしました。

使ったもの

  • Python
    • Openpyxl
    • NumPy
    • PIL
  • VSCode
  • Ubuntu-20.04
  • Excel

おおまかに開発

実装していきます。

ライブラリのインポート

今回使うライブラリはこれ!

import numpy as np
import openpyxl
import math
import sys
from PIL import Image
from openpyxl.styles import PatternFill

画像ファイルの読み込み

まずは画像ファイルを読み込みましょう。

サンプルとして以下の画像を使います。(友達が描いてくれた私のアイコンです:heart:

# ファイルのインポート
input_file = 'icon.jpg'
im = np.array(Image.open(input_file))

im に画像の情報を読み込みました。

im.shape から画像サイズを取得できます。

print(im.shape)

# (482, 482, 3)

今回の場合は $482$x$482$ であることがわかりました。
$3$ は RGB の $3$ 色のことです。

# 画像のサイズを取得
IMG_HEIGHT = im.shape[0]
IMG_WIDTH = im.shape[1]
COLOR_VARIETY = im.shape[2]

わかりやすいよう変数に保存しておきましょう。

Excel ファイルの作成

出力用 Excel ファイルを作ります。
Openpyxl の出番です:blush:

# ワークブックを作成
wb = openpyxl.Workbook()
ws = wb.worksheets[0]

以降、シート ws に書き込みしていきます。

書き込み

画像データの色の情報を $1$ ピクセルずつ ws のセルの背景色に移植していきます。

for i in range(IMG_HEIGHT):
    for j in range(IMG_WIDTH):
        RGB = [0,0,0]
        for color in range(COLOR_VARIETY):
            RGB[color] = im[i][j][color]

        fill = PatternFill(patternType='solid', fgColor=RGBtoColorCode(RGB))
        ws.cell(row=i+1, column=j+1).fill = fill

上記プログラムで使っている関数 RGBtoColorCode() は、RGB の値を $16$ 進数カラーコードに変換して返す関数です。
関数 PatternFill() の引数 fgColor の値は $16$ 進数カラーコードである必要があるためです。
カラーコード詳細はこちら: 16進数カラーコードの意味と変換計算式

関数は以下の通り定義しました。

# RGBの数値が1桁の場合冒頭に0をつけて2桁にして返す
def addZero(strHexNum='0xAA'):
    if len(strHexNum) == 3:
        return '0x0' + strHexNum[2]
    elif len(strHexNum) == 4:
        return strHexNum
    else:
        print('error!')
        sys.exit()

# RGBをカラーコードに変換
def RGBtoColorCode(RGB=[0, 0, 0]):
    color_code = ''
    for color in range(COLOR_VARIETY):
        color_code += str(addZero(hex(RGB[color])))
    color_code = color_code.replace('0x', '') # 0xrr0xgg0xbb を rrggbb の形にする
    return color_code

出力

最後に、Excel ファイルとして出力します。
Opnepyxl で開いた workbook は忘れず close しましょう:star:
(先日 close せずに何度もプログラムを実行したところ、次回 PC 起動時にエクセルが $100$ 枚ぐらい開いて地獄を見ました)

# ファイルのエクスポート
output_file = input_file.replace('.jpg', '.xlsx') # 元画像名と同名の .xlsx ファイルとして出力
wb.save('result/' + output_file)
wb.close()

改良

以上で一応ちゃんと出力できるようになります。

しかしお察しの通り、出力された Excel を開くと・・・

はいっ
stretched_output_image.png

めっちゃ横に伸びています。

これは、Excel のセルがデフォルトで横長であるためです。

思いつく対処方法は $2$ 通りあります。

  • セルの横幅を縮めて正方形にする。
  • 元画像を横方向に圧縮して横長のセルで辻褄を合わせる。

どちらもできそうですが、今回のプロジェクトの面白さは「普段使っている Excel 上に絵が描かれていること」だと思ったので、後者を採用します。
書き込み処理が減るので実行時間も少し短くなりますね。

セルの縦横比はデフォルトでおよそ $1:3$ なので、元画像で横方向に並んだ $3$ ピクセルずつの色の平均をとって、セルの背景色に設定していきます。

# 出力改良版
N = 3
for i in range(IMG_HEIGHT):
    for j in range(math.floor(IMG_WIDTH / N)):
        # 横に並んだ N ピクセルの平均 RGB をセルの背景色とする
        ave_RGB = [0, 0, 0]

        for n in range(N):
            for color in range(COLOR_VARIETY):
                ave_RGB[color] += im[i][N * j + n][color]

        for color in range(COLOR_VARIETY):
            ave_RGB[color] = round(ave_RGB[color] / N)

        fill = PatternFill(patternType='solid', fgColor=RGBtoColorCode(ave_RGB))
        ws.cell(row=i+1, column=j+1).fill = fill

これで、出力された Excel は:arrow_down:のようになります。

good_output_image.png

おまけ (プログレスバーの実装)

今回の主旨とズレますが、おまけでプログレスバーを実装しました。
画像処理や Excel の扱いって実行に時間がかかるので、「本当に進んでる??」って心配になりがち。
そんな悩みを解決してくれます。

最後の for ループを以下のように書き換えます。

# ゲージ
GAUGE_WIDTH = 30
now_pacentage = 0

print('0', end='')
for g in range(GAUGE_WIDTH):
    print(' ', end='')
print('100%')

print(' ', end='')

# 出力
N = 3
for i in range(IMG_HEIGHT):
    for j in range(math.floor(IMG_WIDTH / N)):
        # 横に並んだ N ピクセルの平均 RGB をセルの背景色とする
        ave_RGB = [0, 0, 0]

        for n in range(N):
            for color in range(COLOR_VARIETY):
                ave_RGB[color] += im[i][N * j + n][color]

        for color in range(COLOR_VARIETY):
            ave_RGB[color] = round(ave_RGB[color] / N)

        fill = PatternFill(patternType='solid', fgColor=RGBtoColorCode(ave_RGB))
        ws.cell(row=i+1, column=j+1).fill = fill

    # ゲージを増やす
    if math.ceil(i / IMG_HEIGHT * GAUGE_WIDTH) > now_pacentage:
        print("■", end="", flush=True)
        now_pacentage = math.ceil(i / IMG_HEIGHT * GAUGE_WIDTH)

print('')

結果、:arrow_down:のようにゲージが進んでいくようになります。

progress_bar.png

おわりに

プログラムの完成形を GitHub にアップしました。
記事よりも綺麗にまとまっていると思うのでよかったら見てみてください!:arrow_down:

ExcelDrawing

今回みたいな基本的な機能を作ると、こんなことできたらいいなあが広がりますね。

ちなみに今、今回学んだノウハウを応用して機械学習とか組み合わせてあつまれ どうぶつの森 のマイデザイン作成支援ツールを開発しています。

こんな感じにできそうっ

mickey_midesign.png
mickey_midesign_acnh.jpg

お楽しみに:sparkles:

53
37
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
53
37