RaspberryPiで個人最適化フォトフレームの実装

#動機
ラズパイでフォトフレームを実装してみましょう。
ラズパイでフォトフレームは~~n番煎じ~~使い古されたテーマですが、従来のものには不満がありました。

というのも、写真の厳選が面倒臭いことです。
例えば友人から送ってもらった写真やカメラの写真などをフォトフレームで表示させる時

・厳選して入れると大変、あるいは枚数が少なく、すぐに見飽きる
・逆に全部突っ込むとミス写真も取り込まれる、恥ずかしい写真が表示される事故が起きる

というジレンマがあります。
この問題を解決すべく、写真の表示を最適化したフォトフレームを作成します。

使用者は好きな時に
・その写真をスキップ
・その写真の表示頻度を下げる
・その写真を二度と表示しない

という操作を行うことができます。こうして調整された表示頻度を保存、更新します。
つまりとりあえず全部の写真を入れて、使っていくうちに調整してやろう、というコンセプトです。

全体のソースコードはページ下に掲載します。
<img width="300" alt="qiita.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/548580/39e16153-d153-1ae9-2be5-21bbdd7f1789.jpeg">


#用意

・Raspberry Pi
今回は家にあったラズパイ3　model　Bを使用。別にどの世代でも可。

・3.5インチディスプレイ
写真表示用に使います。Amazonで2000円くらいのを買ったらドライバが古く使えず...
他社製のドライバを入れると画面だけは映るようになったもののタッチパネルがご臨終に。

#仕様
ただ写真を表示するだけなら、


```
fbi image.jpg
```

だけで事足ります。
しかし細かい仕様の実現や、外部との連携などの拡張性を考え、今回は全てpythonで記述することにします。
画像を扱うならOpenCVなどがメジャーですが、今回はゲーム用モジュールであるpygameを利用していきます。

今回の仕様としては
①DropBoxから自動で画像を取得（未実装）
②スライドショー形式で画像を表示
③画像のスキップや表示頻度を下げる、二度と表示させないなどをボタンで操作
④そうして調整された頻度を保存、更新
とします。主に③のためにpygameを選択しています。

#画像の自動取得
作成中
#画像の表示
画像を表示するためには、ウィンドウの作成からする必要があります。

```py

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)`で消します。
　
　
　
　
　
　

```python

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()`で画面を更新、表示されます。
以上の操作で画像は表示できます。では次にスライドショーを実装していきましょう。

#スライドショーの実装
スライドショーとはつまり、
・時間を計測する
・時間が経過したら新しい画像を読み込み、表示

という処理なわけです。
そこで、画像が表示された際にその時間を保存します。

```python

start_time = pygame.time.get_ticks()
```

そしてwhile(1)のループを回しながら、

```py
time_since_start = pygame.time.get_ticks() - start_time
if time_since_start>10000:
     break 
```
とすることで実現できます。
`pygame.time.get_ticks()`によって時間を取得でき、単位はmsなので、この例では10秒ごとに切り替わります。（実際には写真のロード時間が加算されます。）

#画像のスキップ機能
10秒を待たずして切り替えられるようにします。
具体的にはボタンを押すと次の画像を表示させる機能です。
今回は手軽な物理ボタンがなかったため、キーボードを接続し利用します。

また、なんとこのままでは終了させる機能ができません。
そこでESCを押したときにはプログラムを終了させるようにもします。


```py
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キーが押されたならスキップフラグを立て、次の画像へと移ります。


#画像の表示頻度の変更
さて、これでスライドショーができましたが、表示頻度を減らしたい写真や、二度と見たくない写真の処理を追加します。
[画像の表示](#画像の表示)ではパスを指定して読み込んでいました。その読み込みの部分を重み付きランダムに変更します。

```py
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`にはそれぞれの画像の重みが整数値で入っています。
この２つを`random.choices`にわたすことで、その重みに従って`photonum`の中から選択してくれます。
そして選択された画像ファイルが読み込まれます。

あとはこの`weight`を操作してやるだけです。

今回はテキストファイルに改行区切りで`weight`を保存します。
また、重みは初期値を整数値10とします。

```py
data = open('data.txt')
weight =[]
for line in data:
   weight.append(int(line))
data.close()
```

これで一行ずつ`data.txt`から重みを読み取り、`weight`に格納します。

これで、先程出てきたKEYDOWNを用いて、

```py
if event.key == K_x:
     flugloop =False
     weight[filenum-1] = 0
```
とすればxが押された時に重みを0にし、その写真は二度と表示されないようになります。
同じようにデクリメントすればその写真が表示される頻度を減らせます。

さて、このlistは当然終了したら失われます。そこで`data.txt`に書き込むことで保存しましょう。
ESCキーの挙動に付け加えます。

```py
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()
```

こうすることで次回起動時も設定が引き継がれます。

#ソースコード全体
<details><summary>サンプルコード</summary><div>

```py

#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):#画像を１枚表示させる
        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() 
   
```
 </div><details/>



#今後の拡張性
DropBoxからの写真自動追加の実装。
物理ボタンの設置。
ウェブカメラを使って、よく注視する写真の重みをあげてよく表示させる、などをやると、表示頻度の重み調整が更に楽になりそう。