目录
介绍
计算机显示屏显示照片库中的照片。这个用Python编写的软件应用程序几乎可以在任何运行的计算机python3和cv2图像库上运行。
背景
数码照片显示应用有许多应用。然而,我有一个退休的老树莓派正在找工作,所以自然而然地,一个修补工程师应该花几周时间编写一些软件,这样灰胡子的老树莓派才能有一个有用的生活。我能说什么,有些人对他们的宠物多愁善感,我想Pi对我来说就像是某种宠物......
我决定使用Python环境,因为这允许我在Windows上开发,但在Linux上运行软件(Raspberian)。
使用代码
代码存储在单个Python源文件中,该文件使用python3解释器从命令行运行。
python3 digitalFotoFrame.py photoframe.ini
有一个命令行参数——一些配置文件的名称,允许配置相框软件的操作。
配置文件
配置文件是一个简单的文本文件,包含以下四行键值格式。这些行可以按任何顺序排列。以#开头的注释行将被忽略。
DELAY=4.5
WAKE=6
SLEEP=22
PATH=/media/HDD
有四个关键字(它们区分大小写,因此请全部使用大写字母!
DELAY=4.5 | 这意味着每张图片显示4.5秒。 |
WAKE=6 | 这将配置唤醒时间。此时,节目在本例中开始0600小时。 |
SLEEP=22 | 这是睡眠时间——此时,节目停止和屏幕空白——在这种情况下,2200小时或晚上10点。 |
PATH=/media/HDD | 这是存储图片的文件夹——这里可以有包含图片的子文件夹。 |
在WAKE和SLEEP小时之间,图片将以随机顺序连续显示。
在SLEEP和WAKE小时(夜间)之间,屏幕将空白,不显示任何图片。
所选PATH可能包含许多文件,分为文件夹和子文件夹。只有具有.jpg或.png的文件才会被考虑显示。其他文件可能在此文件夹中,但它们将被忽略。
软件概述
该软件是为Python 3编写的单个Python源文件。它仅由三个Python函数和一个主程序组成。
在顶级视图中,应用程序将通过读取配置文件(在命令行上传入)进行初始化。从此文件中,提取了四个参数(如上所示)。
在操作级别,PATH将扫描要显示的图片。这些图片以随机顺序显示,在配置文件的DELAY时间内,每张图片都在屏幕上显示。这发生在一天中的WAKE和SLEEP小时之间。每小时一次——如果找到配置文件——参数将被重新读取(可能已更改),并重新扫描照片。
在SLEEP和WAKE小时之间,屏幕是空白的,不显示任何照片。
软件功能
该软件使用cv2库(opencv)进行图像功能,例如读取、调整大小、显示图片。有关此Python库的完整详细信息,请参阅下面的链接。它适用于Windows、MacOS、Linux和Raspberian。此库必须安装在运行应用程序的计算机上。
pip install opencv-python
或:
pip3 install opencv-python
链接到 OPENCV-PYTHON
就Python库而言,其他依赖项都是标准依赖项,因此不需要特殊安装。
顶级程序代码
下面显示了应用程序的顶级Python代码。它基本上尝试查找和读取配置文件。这定义了photoFolder、delay、wakehour和bedtimehour的参数。如果无法成功读取配置文件,则应用程序将无法运行,并且将终止,并显示错误消息。
成功读取这些参数后,将调用应用程序的主要函数(runFotoFrame)来显示文件并处理唤醒和睡眠时间。
#------------------------------------------------------------
# top-level python script
#
# set up some global variables to default values.
# The config file sets these values on startup
#
# later on, these may be read and changed by a control
# file that is looked for every hour or two in the scanned
# folder. If you want to change these while the pix frame
# is running, you can change this file at any time.
#
# command line:
# python3 digitalFotoFrame.py configfile.txt
#-------------------------------------------------------------
if __name__ == "__main__":
#----------------------------------
#---- default parameter values ----
#----------------------------------
photoFolder='/media/HDD' # top level folder for pictures
delay=4 # seconds for each displayed picture
wakehour=7 # 07am = 0700 = 7, start showing pictures
bedtimehour=21 # 10pm = 2100hrs = 21, stop showing pictures
print("---- Digital Foto Frame - Starting ----")
configFileRead=False
configfn="photoframe.ini" # default name to look for hourly in top level folder
# search arg list for
if len(sys.argv)>1:
configfn=sys.argv[1]
print("reading config file: ",configfn)
result=checkForControlFile(configfn,delay,wakehour,bedtimehour,photoFolder)
if result[4]: # set to true if successful read of config file
delay=result[0]
wakehour=result[1]
bedtimehour=result[2]
photoFolder=result[3]
configFileRead=True
print("Config file read: ",delay,wakehour,bedtimehour,photoFolder)
time.sleep(3) # wait just a bit to read messages if any
if not configFileRead:
print("\n--- Unable to read config file ---\n")
else:
# and then, let's get this show on the road
params=[delay,wakehour,bedtimehour,photoFolder,configfn]
runFotoFrame(params)
扫描要显示的照片
该scanForFiles函数用于递归扫描 PATH 文件夹和任何子文件夹以显示图片。它返回要显示的图片的文件名列表。我已经成功地将它用于超过10000个文件的结构。
#-------------------------------------------------------------
#--- scan a folder tree (recursively) for jpg or png files
#-------------------------------------------------------------
def scanForFiles(folder):
pictureFiles=[]
itr=os.scandir(folder)
for entry in itr:
if entry.is_file():
fn=entry.name.lower()
if fn.endswith('.jpg') or fn.endswith('.png'):
pictureFiles.append(entry.path)
if entry.is_dir(): # recurse for sub-folders
x=scanForFiles(entry.path)
pictureFiles.extend(x)
#itr.close()
return pictureFiles
这里没什么特别的——Python os.scandir()标准函数用于扫描所有文件。扩展名为.jpg或.png的文件将添加到列表中。任何文件夹都会递归扫描.jpg或.png文件,这些文件也会添加到列表中。
读取配置文件
配置文件包含操作所需的设置。该checkForControlFile()函数用于尝试从外部文本文件中读取这些设置并返回值列表。
这些值以包含五个元素的列表的形式返回。
- result[0]——延迟(以秒为单位)
- result[1]——唤醒时间(小时)(0..23)
- result[2]——睡眠时间(小时)(0..23)
- result[3]——存储图片的路径
- result[4]——true如果参数读取成功
#-------------------------------------------------------------
#--- Try to open a configuration file and read settings from it
#-------------------------------------------------------------
#-------------------------------------------------------------
def checkForControlFile(controlFn,delay,wakeHour,bedtimeHour,photoFolder):
#
# Sample control file has four lines in KEY=VALUE format
# - delay in seconds
# - wake hour (0..23)
# - sleep hour (0..23)
# - path to find pictures and control file
#
# File is deposited into the top folder where the pictures are stored (PATH)
# File is named instructions.ini
# File has 4 lines
#
# Control file will be read hourly
#
#DELAY=3.5
#WAKE=6
#SLEEP=21
#PATH=/media/pi/photoframe
#
result=[delay,wakeHour,bedtimeHour,photoFolder,False]
readparams=0 # bitwise log of keywords found to verify we had a full
# configuration file with every line in it that we expect
#print(controlFn)
try:
with open(controlFn,'r') as finp:
print(datetime.datetime.now(),'Reading configuration file')
for line in finp:
print(line)
if line.startswith('DELAY='):
x=float(line[6:])
x=max(1.,x)
x=min(60.,x) # limit 1..60
result[0]=x
readparams=readparams | 1
if line.startswith('WAKE='):
x=float(line[5:])
x=max(0.,x)
x=min(10.,x) # limit 0..60
result[1]=int(x)
readparams=readparams | 2
if line.startswith('SLEEP='):
x=float(line[6:])
x=max(0.,x)
x=min(23.,x) # limit 0..60
result[2]=int(x)
readparams=readparams | 4
if line.startswith('PATH='):
result[3]=line[5:-1] # strip off new line at end
readparams=readparams | 8
except:
pass
print('Read configuration file results ',result)
if (readparams == 15):
result[4] = True # read file properly, all 4 bits set = 1111 = 0xf = 15
return result
主画面显示功能
主要的图片显示功能是应用中比较复杂的功能。它处理两种模式(唤醒和显示照片、睡眠和空白屏幕)。它还处理这两种模式之间的转换。
它将定期(大约每小时一次)在指定路径中查找配置文件。如果找到一个,则读取新的配置文件,并扫描文件夹(甚至可能是新文件夹)以查找照片。这意味着,如果要更改照片,可以更改照片文件夹中的配置文件(必须命名为photoframe.ini)。这可以直接完成(通过一些文本编辑器),或者您可以将某些FTP可访问或SMB可访问共享驱动器上的照片文件夹复制到那里,然后在那里复制新图片和新的photoframe.ini文件)。
每小时一次,应用程序将查找photoframe.ini文件并重新扫描——因此您可以更新照片、更改照片文件夹、更改延迟时间、更改唤醒和睡眠时间,这些更改将在下一个小时的顶部得到响应。
准备要显示的图片
OpenCV库允许我们创建一个“全屏”窗口来显示图片。当然,这很好,但是显示器的尺寸(水平和垂直像素)可能与所显示图片的尺寸不匹配。例如,图片的水平尺寸可能比显示器宽,
为了确保所有图片在显示器上尽可能好地显示,我们可能需要调整它们的大小,使它们适合显示器的边界——这可能涉及根据图片放大或缩小。这是由函数底部的代码计算出来的。
调整图片大小后,其至少一个尺寸将与显示器的尺寸相同——因此它将垂直(侧面有一些额外的空间)或水平(顶部/底部有一些额外的空间)填充显示器。
现在,为了确保这些额外的空间显示为黑色像素,我们在侧面或顶部/底部添加了一些边框像素。这称为与图像接壤。在此边界步骤之后,我们将得到一张与显示器尺寸完全相同的图片,因此我们知道,在给定图片和屏幕尺寸的情况下,它以最佳方式显示。
#-------------------------------------------------------------
# main photo frame loop
#
# - Run the photo frame code continuously displaying photos
# from the specified folder.
# - Go dark at bedtimehour and light up again at wakehour
# - Check every hour for a new instructions.ini file with
# parameter updates.
# - Rescan for pictures every hour in case user has deleted
# or added pictures
# - One idea is that an FTP server or SAMBA remote disk mount
# could be used to update the photos to be shown and to
# update the instructions.ini file to change parameters
#-------------------------------------------------------------
def runFotoFrame(params):
# grab our parameters that control operation of the
# digital foto frame
delay=params[0] # delay in seconds to show each picture
wakehour=params[1] # hour (int 24 hr format 0..23) to start showing
bedtimehour=params[2] # hour (in 24 hr format 0..23) to stop showing
photoFolder=params[3] # be real careful when changing this in config file
configfn=params[4] # name of config file to look for in top level folder
# determine if this is a windows OS based system
isWindows=sys.platform.startswith('win') # 'win32' or 'linux2' or 'linux'
# initialize a CV2 frame to cover the entire screen
cv2frame='frame'
cv2.namedWindow(cv2frame, cv2.WINDOW_NORMAL)
cv2.setWindowProperty(cv2frame, cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)
# let's find out what size of display we're working with
tmp=cv2.getWindowImageRect(cv2frame)
wid=float(tmp[2])
hgt=float(tmp[3])
# sometimes the getWindowImageRect returns nonsense like
# width or height of -1 or some other non useful value
if hgt<480. or wid<640.:
hgt=1080. # assume a 9h x 16w form factor
wid=1920.
#print(hgt,wid)
# scan the photoFolder for a list of picture files
pictureFiles=scanForFiles(photoFolder)
random.shuffle(pictureFiles) # randomly shuffle the picture files
print(datetime.datetime.now(),'Scan found',len(pictureFiles),'files')
# initialize for hourly and sleep processing
lastHour=datetime.datetime.now().hour
sleeping=False
done=False
if not isWindows:
os.system("xset s off") # screen blanking off
# and loop forever (until some key is hit)
while not done:
# during waking hours, display pictures
# during sleeping hours, keep display blanked
for fn in pictureFiles:
# let's see if it's time to do hourly tasks
now=datetime.datetime.now()
hour=now.hour
if not isWindows:
os.system("xset dpms force on");
#-- hourly tasks, only done when not sleeping
if hour!=lastHour and not sleeping:
lastHour=hour
if not isWindows:
os.system("xset s off") # screen blanking off
# try to read configuration file instructions.ini
controlFn=os.path.join(photoFolder,configfn)
result=checkForControlFile(controlFn,delay,wakehour,bedtimehour,photoFolder)
if result[4]: # set to true for successful config file read
delay=result[0]
wakehour=result[1]
bedtimehour=result[2]
photoFolder=result[3]
# rescan folder
pictureFiles=scanForFiles(photoFolder)
random.shuffle(pictureFiles)
print(datetime.datetime.now(),'Scan found',len(pictureFiles),'files')
#--- run always, do wake up tasks or sleep tasks
#
# for example wakehour might be 9am and bedtimehour might be 9pm
# wakehour=9 and bedtimehour=21 (12+9)
if hour>=wakehour and hour<bedtimehour:
# we are in wake up time of day
#--- if we were sleeping, then it is time to wake up
if sleeping:
print(datetime.datetime.now(),'Wake up')
if not isWindows:
os.system("xset s off") # screen blanking off
os.system("xset dpms force on");
sleeping=False
#--- display a photo
# handle fault in loading a picture
gotImg=False
try:
print('loading',fn)
img = cv2.imread(fn, 1)
if len(img)>0:
gotImg=True
except:
gotImg=False
if not gotImg:
continue
#-- now, maybe resize image so it shows up well without changing the aspect ratio
# add a border if the aspect ratio is different than the screen
# so we upscale or downscale so it maxes out either the
# horizontal or vertical portion of the screen
# then add a border around it to make sure any left-over
# parts of the screen are blacked out
widratio=wid/img.shape[1]
hgtratio=hgt/img.shape[0]
ratio=min(widratio,hgtratio)
dims=(int(ratio*img.shape[1]),int(ratio*img.shape[0]))
#print(fn,img.shape,ratio,dims[1],dims[0])
imgresized=cv2.resize(img,dims,interpolation = cv2.INTER_AREA)
#print(imgresized.shape)
# now, one dimension (width or height) will be same as screen dim
# and the other may be smaller than the screen dim.
# we're going to use cv.copyMakeBorder to add a border so we
# end up with an image that is exactly screen sized
widborder=max(1,int((wid-imgresized.shape[1])/2))
hgtborder=max(1,int((hgt-imgresized.shape[0])/2))
#print(hgtborder,widborder)
imgbordered=cv2.copyMakeBorder(imgresized,hgtborder,hgtborder,widborder,widborder,cv2.BORDER_CONSTANT)
#print('resized,bordered',imgbordered.shape)
# and now show the image that has been resized and bordered
cv2.imshow(cv2frame, imgbordered)
#--- now we pause while the photo is displayed, we do this
# by waiting for a key stroke.
k = cv2.waitKey(int(delay*1000)) & 0xff
# 255 if no key pressed (-1) or ascii-key-code (13=CR, 27=esc, 65=A, 32=spacebar)
if k!=0xff:
# if a key was pressed, exit the photo frame program
done=True
break
else:
#-- during sleep time we go here
# during sleep time, blank the screen
if not sleeping:
print(datetime.datetime.now(),'Going to sleep')
if not isWindows:
os.system("xset dpms force standby");
sleeping=True
k = cv2.waitKey(300*1000) & 0xff # wait 300 seconds
# 255 if no key pressed (-1) or ascii-key-code (13=CR, 27=esc, 65=A, 32=spacebar)
if k!=0xff:
done=True
# when the photo display session ends,
# we need to clean up the cv2 full-frame window
cv2.destroyWindow(cv2frame)
完整的源代码可在GitHub digitalFotoFrame上找到。
https://www.codeproject.com/Articles/5373372/A-DIY-Digital-Picture-Frame-in-Python