#動機
ラズパイでフォトフレームを実装してみましょう。
ラズパイでフォトフレームはn番煎じ使い古されたテーマですが、従来のものには不満がありました。
というのも、写真の厳選が面倒臭いことです。
例えば友人から送ってもらった写真やカメラの写真などをフォトフレームで表示させる時
・厳選して入れると大変、あるいは枚数が少なく、すぐに見飽きる
・逆に全部突っ込むとミス写真も取り込まれる、恥ずかしい写真が表示される事故が起きる
というジレンマがあります。
この問題を解決すべく、写真の表示を最適化したフォトフレームを作成します。
使用者は好きな時に
・その写真をスキップ
・その写真の表示頻度を下げる
・その写真を二度と表示しない
という操作を行うことができます。こうして調整された表示頻度を保存、更新します。
つまりとりあえず全部の写真を入れて、使っていくうちに調整してやろう、というコンセプトです。
#用意
・Raspberry Pi
今回は家にあったラズパイ3 model Bを使用。別にどの世代でも可。
・3.5インチディスプレイ
写真表示用に使います。Amazonで2000円くらいのを買ったらドライバが古く使えず...
他社製のドライバを入れると画面だけは映るようになったもののタッチパネルがご臨終に。
#仕様
ただ写真を表示するだけなら、
fbi image.jpg
だけで事足ります。
しかし細かい仕様の実現や、外部との連携などの拡張性を考え、今回は全てpythonで記述することにします。
画像を扱うならOpenCVなどがメジャーですが、今回はゲーム用モジュールであるpygameを利用していきます。
今回の仕様としては
①DropBoxから自動で画像を取得(未実装)
②スライドショー形式で画像を表示
③画像のスキップや表示頻度を下げる、二度と表示させないなどをボタンで操作
④そうして調整された頻度を保存、更新
とします。主に③のためにpygameを選択しています。
#画像の自動取得
作成中
#画像の表示
画像を表示するためには、ウィンドウの作成からする必要があります。
import pygame
from pygame.locals import *
import sys
pygame.init()
screen =pygame.display.set_mode((400,300))
pygame.mouse.set_visible(False)
pygame.display.set_caption("Photo")
pygame.init()
で初期化を行い、
display.set_mode()
で画面のサイズを指定します。
(なお引数としてFULLSCREEN
を入れることでフルスクリーンになりますが、フルスクリーンでバグると終了が大変になるのでデバッグ中はやらない方がよさそうです。)
フォトフレームとしてマウスカーソルがあるのは不格好なのでpygame.mouse.set_visible(False)
で消します。
img =pygame.image.load("file.jpg")
img = pygame.transform.scale(img1,(400,300))
rectimg = img.get_rect()
screen.fill((0,0,0))
screen.blit(img,rect_img)
pygame.display.update()
ここが画像の描写部分です。
image.load
で画像を一枚読み込んできます。
screen.fill
でRGB背景色を指定します。今回は黒です。
screen.blit
で画像を貼り付け、最後のdisplay.update()
で画面を更新、表示されます。
以上の操作で画像は表示できます。では次にスライドショーを実装していきましょう。
#スライドショーの実装
スライドショーとはつまり、
・時間を計測する
・時間が経過したら新しい画像を読み込み、表示
という処理なわけです。
そこで、画像が表示された際にその時間を保存します。
start_time = pygame.time.get_ticks()
そしてwhile(1)のループを回しながら、
time_since_start = pygame.time.get_ticks() - start_time
if time_since_start>10000:
break
とすることで実現できます。
pygame.time.get_ticks()
によって時間を取得でき、単位はmsなので、この例では10秒ごとに切り替わります。(実際には写真のロード時間が加算されます。)
#画像のスキップ機能
10秒を待たずして切り替えられるようにします。
具体的にはボタンを押すと次の画像を表示させる機能です。
今回は手軽な物理ボタンがなかったため、キーボードを接続し利用します。
また、なんとこのままでは終了させる機能ができません。
そこでESCを押したときにはプログラムを終了させるようにもします。
for event in pygame.event.get():
if event.type == KEYDOWN:
if event.key == K_ESCAPE:
pygame.quit()
sys.exit()
if event.key == K_z:
flugloop = False
まず発生したイベントをループで取り出していき、それがキーを押されたものだったら処理を実行します。
K_ESCAPE がESCキー、K_zがzキーです。
このように、K_aなどで任意のキーが押された時の挙動を実装できます。
ESCキーならif文の中で即終了処理、zキーが押されたならスキップフラグを立て、次の画像へと移ります。
#画像の表示頻度の変更
さて、これでスライドショーができましたが、表示頻度を減らしたい写真や、二度と見たくない写真の処理を追加します。
画像の表示ではパスを指定して読み込んでいました。その読み込みの部分を重み付きランダムに変更します。
photonum = list(range(1,len(w)+1))
filenum = random.choices(photonum,weight)
filename = str(filenum[0]) + ".jpg"
pygame.image.load(filename)
あらかじめ画像の名前を1.jpg, 2.jpg等にしておきます。
ここで、二行目random.choices
が重み付けランダムで選ぶ関数です。
photonum
には1~枚数分の番号が入ったlistで
weight
にはそれぞれの画像の重みが整数値で入っています。
この2つをrandom.choices
にわたすことで、その重みに従ってphotonum
の中から選択してくれます。
そして選択された画像ファイルが読み込まれます。
あとはこのweight
を操作してやるだけです。
今回はテキストファイルに改行区切りでweight
を保存します。
また、重みは初期値を整数値10とします。
data = open('data.txt')
weight =[]
for line in data:
weight.append(int(line))
data.close()
これで一行ずつdata.txt
から重みを読み取り、weight
に格納します。
これで、先程出てきたKEYDOWNを用いて、
if event.key == K_x:
flugloop =False
weight[filenum-1] = 0
とすればxが押された時に重みを0にし、その写真は二度と表示されないようになります。
同じようにデクリメントすればその写真が表示される頻度を減らせます。
さて、このlistは当然終了したら失われます。そこでdata.txt
に書き込むことで保存しましょう。
ESCキーの挙動に付け加えます。
if event.key == K_ESCAPE
f = open("data.txt",'w')
for i in weight:
f.write(str(i))
f.write("\n")
f.close()
pygame.quit()
sys.exit()
こうすることで次回起動時も設定が引き継がれます。
#ソースコード全体
サンプルコード
#photo.py
import pygame
from pygame.locals import *
import sys
import random
def get_image(w=[]):
photonum = list(range(1,len(w)+1))
filenum = random.choices(photonum,w) #画像をランダムで取り出し
filename = str(filenum[0]) + ".jpg"
return pygame.image.load(filename).convert_alpha(),filenum[0] #戻り値は画像とそのID
def main():
data = open('data.txt') #重み付けの読み込み
weight =[]
for line in data:
weight.append(int(line))
data.close()
while(1):#画像を1枚表示させる
pygame.init()
screen =pygame.display.set_mode((400,300),FULLSCREEN)
pygame.mouse.set_visible(0)
pygame.display.set_caption("test")
x=get_image(weight)
img1 = x[0]
filenum = x[1]
img2 = pygame.transform.scale(img1,(400,300))
rect_img2 = img2.get_rect()
screen.fill((0,0,0))
screen.blit(img2,rect_img2) #画像の表示
pygame.display.update() #画面の更新
start_time = pygame.time.get_ticks() #時間計測開始
flugloop = True
while(1): #操作を受け付けるためのループ
time_since_start = pygame.time.get_ticks() - start_time #時間の計測
if time_since_start>10000: #10秒たったらbreak,次の画像表示へ
break
if flugloop ==False: #フラグがたっていればbreak,次の画像表示へ
break
for event in pygame.event.get(): #操作の受付け
if event.type == QUIT:
pygame.quit()
sys.exit()
if event.type == KEYDOWN:
if event.key == K_ESCAPE: #escが押されたら
f = open("data.txt",'w') #表示頻度を保存
for i in weight:
f.write(str(i))
f.write("\n")
f.close()
pygame.quit() #プログラム終了
sys.exit()
if event.key == K_SPACE: #スペースキーが押されたら重みを減らして次の画像へ
flugloop = False
if weight[filenum-1] >3:
weight[filenum-1] -=1
if event.key == K_x: #xキーが押されたら重みを0に、二度と表示しない
flugloop =False
weight[filenum-1] = 0
if event.key == K_z:
flugloop =False
main()
#今後の拡張性
DropBoxからの写真自動追加の実装。
物理ボタンの設置。
ウェブカメラを使って、よく注視する写真の重みをあげてよく表示させる、などをやると、表示頻度の重み調整が更に楽になりそう。