PikaPikaLight

主に光モノ工作の忘備録

ドット絵カメラシステムを作った

初めに

昨年末にLEDドット絵の世界に足を踏み入れました。 pikapikalight.hatenadiary.com ドット絵のデータはExcelを駆使してそれなりに効率化できたのですが、それでもなかなか大変な作業でした。
カメラで撮った絵をリアルタイムでドット絵に変換できたら簡単にドット絵が作れて楽しそうと思い、挑戦してみました。

完成品

結論を先に。こんなのを作ることが出来ました。 youtu.be

ハードウェア

カメラはM5StackのUNIT-Vを使いました。以前こいつで色々やっていて、UNIT-VのMicroPythonはWS2812が動かせることを覚えていたのです。 pikapikalight.hatenadiary.com ここで問題が一つ、UNIT-VのGroveコネクタは5V出せるのですが電流はほとんど流せない。とてもじゃないけど16x16=256個のLED点けることはできない。LEDパネルとUNIT-Vの電源を別々にとるしかないけれど、それだと配線がすっきりしない。
考えた末に閃きました。以前作成した「虹を投影する懐中電灯」と同じく、パネルの方に先に電源をつないでそこからコントローラーの方に電源をつなぐことで配線をすっきりさせる手法です。 電源コネクタにはUSB-Cを、UNIT-Vとの接続にはGroveコネクタを使えるように改造しました。

ソフトウェア

恥ずかしながらコードを公開します。
私はこれまでほとんどC言語しか使ったことがなく、このUNIT-Vで初めてPythonにふれました。正直、言語としての良さは理解できてません。 けれどたったこれだけのコードでこれが作れる事実はすごいなと思います。

import sensor, image, time, lcd
from Maix import GPIO
from modules import ws2812
from fpioa_manager import *
fm.register(board_info.CONNEXT_A)
class_ws2812 = ws2812(board_info.CONNEXT_A,256)
while 1:
    try:
        sensor.reset()
        break
    except:
        time.sleep(0.1)
        continue
sensor.set_hmirror(1)
sensor.set_vflip(1)
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.B64X64)
sensor.skip_frames(time = 1000)
sensor.set_contrast(-2)
sensor.set_saturation(2)
sensor.set_auto_gain(True)
sensor.run(1)
r_num = 0
g_num = 0
b_num = 0
for i in range(10):
    a = class_ws2812.set_led(i,(10,10,10))
    a=class_ws2812.display()
    img = sensor.snapshot()
    img2 = img.resize(1, 1)
    print(img2.get_pixel(0, 0))
    r_num = r_num + img2.get_pixel(0,0)[0]
    g_num = g_num + img2.get_pixel(0,0)[1]
    b_num = b_num + img2.get_pixel(0,0)[2]
    time.sleep(0.1)
print(r_num)
print(g_num)
print(b_num)
rgb_ave = (r_num + g_num + b_num) / 3
print(rgb_ave)
r_factor = rgb_ave / r_num
g_factor = rgb_ave / g_num
b_factor = rgb_ave / b_num
print(r_factor)
print(g_factor)
print(b_factor)
sensor.set_auto_gain(False,sensor.get_gain_db())
print(sensor.get_gain_db())
try:
    while(True):
        img = sensor.snapshot()
        img2 = img.resize(16, 16)
        img2 = img2.histeq()
        k = 0
        sc = 5
        for i in range(8):
            for j in range(16):
                rr = img2.get_pixel(i * 2, j)[0] * r_factor
                gg = img2.get_pixel(i * 2, j)[1] * g_factor
                bb = img2.get_pixel(i * 2, j)[2] * b_factor
                rr = int(rr) / sc
                gg = int(gg) / sc
                bb = int(bb) / sc
                rgb = (int(rr),int(gg),int(bb))
                a = class_ws2812.set_led(k,rgb)
                k = k + 1
            for l in range(16):
                rr = img2.get_pixel(i * 2 + 1, 15 - l)[0] * r_factor
                gg = img2.get_pixel(i * 2 + 1, 15 - l)[1] * g_factor
                bb = img2.get_pixel(i * 2 + 1, 15 - l)[2] * b_factor
                rr = int(rr) / sc
                gg = int(gg) / sc
                bb = int(bb) / sc
                rgb = (int(rr),int(gg),int(bb))
                a = class_ws2812.set_led(k,rgb)
                k = k + 1
        a=class_ws2812.display()
        time.sleep(0.1)
except KeyboardInterrupt:
    pass

コードのポイント

sensor.set_framesize(sensor.B64X64)

framesizeを正方形にしておくことで16x16に変換したときに歪まないようになります。

sensor.set_contrast(-2)
sensor.set_saturation(2)

こうしたほうがなんとなく色がくっきりするような気がしたのでこうしてますが、気のせいである可能性大です。

r_num = 0
g_num = 0
b_num = 0
for i in range(10):
    a = class_ws2812.set_led(i,(10,10,10))
    a=class_ws2812.display()
    img = sensor.snapshot()
    img2 = img.resize(1, 1)
    print(img2.get_pixel(0, 0))
    r_num = r_num + img2.get_pixel(0,0)[0]
    g_num = g_num + img2.get_pixel(0,0)[1]
    b_num = b_num + img2.get_pixel(0,0)[2]
    time.sleep(0.1)
print(r_num)
print(g_num)
print(b_num)
rgb_ave = (r_num + g_num + b_num) / 3
print(rgb_ave)
r_factor = rgb_ave / r_num
g_factor = rgb_ave / g_num
b_factor = rgb_ave / b_num

起動時にホワイトバランスを補正するシーケンスです。起動時に白いものにカメラを向けて補正値を算出することで色の再現性を上げられないかと考えたコードです。本当は16x16個分の補正値を出した方がいいんでしょうが、そこまでやってません。 img.resize(1, 1)で1画素データにしてしまってそのRGBの補正値を出しています。この辺、今後の検討課題です。

sensor.set_auto_gain(False,sensor.get_gain_db())

auto_gainをFalseにするのは必須です。これをしないとチカチカしちゃいます。

img2 = img.resize(16, 16)

カメラの画像を16x16にするたった一行の呪文です。

    k = 0
        sc = 5
        for i in range(8):
            for j in range(16):
                rr = img2.get_pixel(i * 2, j)[0] * r_factor
                gg = img2.get_pixel(i * 2, j)[1] * g_factor
                bb = img2.get_pixel(i * 2, j)[2] * b_factor
                rr = int(rr) / sc
                gg = int(gg) / sc
                bb = int(bb) / sc
                rgb = (int(rr),int(gg),int(bb))
                a = class_ws2812.set_led(k,rgb)
                k = k + 1
            for l in range(16):
                rr = img2.get_pixel(i * 2 + 1, 15 - l)[0] * r_factor
                gg = img2.get_pixel(i * 2 + 1, 15 - l)[1] * g_factor
                bb = img2.get_pixel(i * 2 + 1, 15 - l)[2] * b_factor
                rr = int(rr) / sc
                gg = int(gg) / sc
                bb = int(bb) / sc
                rgb = (int(rr),int(gg),int(bb))
                a = class_ws2812.set_led(k,rgb)
                k = k + 1

sc = 5 はRGB値を5分の一に下げて、明るさ=消費電流を下げる処置です。 16個ごとに逆転させているのは使用したLEDパネルがそういう並びのものだったからです。

完成

スナフキンのフィギュアがいい感じに出たときは感動しました。