该如何安放你,我的照片

一个自行部署的照片托管解决方案伪横评(真水文)。

使用设备:Celeron N5105(无 AVX 指令集),16G RAM,512G SSD;Arch Linux,容器环境 Podman

个人的需求其实很简单,只是一个相册分类 + 照片时间线预览。要说特殊需求大概就是有大量的 HEIC 图片,毕竟谁的空间也不是大风刮来的,另外最好能够用类 MySQL。 要说人还是闲不住,后面实测了 Photoprism、Photoview 和 Immich,所以不知不觉变成真横评了。对于其他服务,可以看这个非常直观的表格

以下依据为个人 2024/06 的照片库,约 10000 张照片,40GB。

Photoprism

photoprism/photoprism

算是比较早的开源相册方案了,基本功能令人安心,但使用略有不便,且设置界面可设置的项较少。

Photoprism 会直接生成标签
Photoprism 会直接生成标签

  • 数据库支持:MariaDB(MySQL)和 SQLite,比较友好。
  • 部署:方式为容器,需要设置大量环境变量作为配置,而不能在网页上直接修改,较为繁琐。
  • 资源占用:不索引时(不含数据库)占用内存 42MB 左右,索引时约为 220MB;数据库中数据 65MB,缩略图等数据 15GB;容器镜像 1.88GB。
  • 文件管理:支持导入和外部资料库,后者不改变原有文件结构;HEIC 等格式图片会自动转换为 JPEG。
  • 人脸识别:识别不出几个人,水平不大行。
  • 搜索:提前给图片生成标签,可以搜索,也可以自行添加标签。

Photoview

photoview/photoview

官方认可 AUR,好耶!相对来说,手动安装过程是最简单而且符合直觉的。托打包者和开发者的福,在安装 AUR 后,只需要修改一下配置文件里的 MySQL 连接参数(或者直接改成 SQLite),然后启动 systemd 服务即可。功能相对来说简单,不过对我来说够用。人脸识别的准确度很一般,但对我来说也是锦上添花的功能。

AUR 安装后的配置指引

提示:如果不想或不能改变文件所有权,建议使用文件 ACL setfacl -Rdm 命令为 photoview 用户授予循环继承权限。

用下来几天发现了两个主要的问题,一个是一次导入过多照片的时候会卡住(假完成),阈值大约 100 张,另一个是 HEIC 图片的 EXIF 可能不能正确识别(提了 issue)。自从作者不太积极开发这个项目之后,感觉基本停滞了。

虽然这个占用是真的小,但需要声明的是,索引的时候仍然会消耗较多 CPU,除非每次都大半夜索引,否则还是没有想象中那么省性能的。

  • 数据库支持:MySQL 或 PostgreSQL。
  • 部署:有 AUR,有 Systemd 部署文档,但个人建议直接用容器。
  • 资源占用:索引时 250MB,日常 10MB;缩略图等数据占用存储 4.5GB,数据库 20MB,镜像 1.38GB。
  • 文件管理:完全尊重外部文件结构,不会改变。但索引有些小 bug,见上。
  • 人脸识别:聊胜于无吧,精度不大行。
  • 搜索:基于文件名的搜索,别的都没有。

Immich

immich-app/immich

Immich 也是后起之秀了,现在 Star 比前辈 Photoprism 还多。它相对来说就比较复杂了,占用资源也多很多,算上数据库下来五个容器(但看起来不久之后会变成 4 个)。但是相对的,可自定义度也高很多,尤其是机器学习的部分。

上述机器学习指的是人脸识别和图像搜索,均可以远程进行,即在性能强的机器上单独运行 ML 容器,然后通过 API 通信。这对人脸来说十分方便,因为较大的人脸识别模型占用很大,但对于基于 CLIP 的图像搜索来说就是另一回事了:不光在索引时需要机器学习模块,搜索时也需要。所幸 CLIP 搜索的原理为对图片和检索词分别生成向量,然后将两者进行对比,而消耗资源比较多的部分是生成图片向量,所以可以先用较强的远程机器生成图片向量,我 NUC 上的弱鸡 CPU 只用来跑检索。还算可以接受。

  • 数据库支持:PostgreSQL+Redis,需同时运行。
  • 部署:似乎十分复杂,个人建议直接用容器。Docker Compose 好说,Podman 的话会非常麻烦。
  • 资源占用:索引时内存基本上吃完(这是我的 NUC 头一次吃 SWAP),如果使用 NVIDIA 硬件加速机器学习,还会占用约 5-8GB 显存(因为我卡只有 8GB);平时内存占用约 400+200MB;缩略图等数据占用 5.7GB(可使用 WEBP,降低了占用),数据库 563MB,镜像 1.6GB+790MB(若使用 CUDA 则为 4.55GB)。
  • 文件管理:同时支持导入和外部资料库。
  • 人脸识别:用较大模型的时候准确度蛮高的。
  • 搜索:基于 CLIP 的自然语言搜索,遥遥领先,但缺点见上。也可以根据人脸、设备等条件筛选。

结语

很缺资源的选 Photoview,性能足够的选 Immich,嫌 Photoview 还不够强的选 Photoprism。

附录:将 Immich 部署为 Podman Pod

来源

默认以 rootful 方式部署,将文件放在 /etc/containers/systemd 或其子目录下,systemctl daemon reloadsystemctl start immich-server.service 即可。

不要忘记修改下面的目录映射和环境变量。两个目录映射中,一个是内部存储(由 Immich 决定文件结构),一个是外部存储(保留用户的文件结构)。

Pod 声明 immich.pod

conf
[Pod]
PodName=immich
PublishPort=2283:3001

immich.env

conf
# You can find documentation for all the supported env variables at https://immich.app/docs/install/environment-variables

# Connection secret for postgres. You should change it to a random password.
# The two values should match
DB_PASSWORD=ZWEuFZHcEyuJJoR6
POSTGRES_PASSWORD=ZWEuFZHcEyuJJoR6

# The values below this line do not need to be changed
###################################################################################

DB_HOSTNAME=immich_postgres
DB_USERNAME=postgres
POSTGRES_USER=postgres

DB_DATABASE_NAME=immich
POSTGRES_DB=immich

REDIS_HOSTNAME=immich_redis

IMMICH_MACHINE_LEARNING_URL=http://immich_machine_learning:3003

数据库容器 immich-postgres.container

conf
[Container]
Pod=immich.pod
ContainerName=immich_postgres
EnvironmentFile=immich.env
Image=docker.io/tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:90724186f0a3517cf6914295b5ab410db9ce23190a2d9d0b9dd6463e3fa298f0
Volume=pgdata:/var/lib/postgresql/data
HealthCmd=["/usr/bin/pg_isready"]
HealthStartPeriod=30s
HealthInterval=10s
HealthTimeout=5s
HealthRetries=5
Notify=healthy

[Service]
Restart=always

缓存容器 immich-redis.container

conf
[Container]
Pod=immich.pod
ContainerName=immich_redis
Image=docker.io/redis:6.2-alpine@sha256:51d6c56749a4243096327e3fb964a48ed92254357108449cb6e23999c37773c5
HealthCmd=["/usr/local/bin/redis-cli", "ping"]
HealthStartPeriod=30s
HealthInterval=10s
HealthTimeout=5s
HealthRetries=5
Notify=healthy

[Service]
Restart=always

[Install]
WantedBy=multi-user.target

机器学习容器 immich-machine-learning.container(本地,无硬件加速)

conf
[Container]
Pod=immich.pod
ContainerName=immich_machine_learning
EnvironmentFile=immich.env
Environment=HF_ENDPOINT=https://hf-mirror.com
Environment=LOG_LEVEL=debug
Image=ghcr.io/immich-app/immich-machine-learning:release
AutoUpdate=registry
Volume=model-cache:/cache
HealthCmd=["/bin/bash", "-c", "exec 5<>/dev/tcp/127.0.0.1/3003"]
HealthStartPeriod=30s
HealthInterval=10s
HealthTimeout=5s
HealthRetries=5
Notify=healthy

[Service]
Restart=always

机器学习容器 immich-machine-learning.container(远程,CUDA 加速)

conf
[Container]
ContainerName=immich_machine_learning
Image=ghcr.io/immich-app/immich-machine-learning:release-cuda
Volume=model-cache:/cache
HealthCmd=["/bin/bash", "-c", "exec 5<>/dev/tcp/127.0.0.1/3003"]
HealthStartPeriod=30s
AutoUpdate=registry
PublishPort=3003:3003
HealthInterval=10s
HealthTimeout=5s
HealthRetries=5
Notify=healthy
Environment=LOG_LEVEL=debug
Environment=HF_ENDPOINT=https://hf-mirror.com
AddDevice=nvidia.com/gpu=all

[Service]
Restart=always

微服务(如索引等)容器 immich-microservices.container(带有 Intel QSV 支持)

conf
[Unit]
Requires=immich-redis.service immich-database.service
After=immich-redis.service immich-database.service

[Container]
Pod=immich.pod
ContainerName=immich_microservices
EnvironmentFile=immich.env
Exec=start.sh microservices
Image=ghcr.io/immich-app/immich-server:release
Volume=/path/to/internal/storage:/usr/src/app/upload:z
Volume=/etc/localtime:/etc/localtime:ro
Volume=/path/to/external/storage:/usr/src/app/external:z
HealthCmd=["/bin/bash", "-c", "exec 5<>/dev/tcp/127.0.0.1/3002"]
HealthStartPeriod=30s
AddDevice=/dev/dri
HealthInterval=10s
HealthTimeout=5s
HealthRetries=5
Notify=healthy

[Service]
Restart=always

主服务容器 immich-server.container

conf
[Unit]
Requires=immich-redis.service immich-database.service immich-microservices.service
After=immich-redis.service immich-database.service immich-microservices.service

[Container]
Pod=immich.pod
ContainerName=immich_server
EnvironmentFile=immich.env
Exec=start.sh immich
Image=ghcr.io/immich-app/immich-server:release
AutoUpdate=registry
Volume=/path/to/internal/storage:/usr/src/app/upload:z
Volume=/etc/localtime:/etc/localtime:ro
Volume=/path/to/external/storage:/usr/src/app/external:z
HealthCmd=["/bin/bash", "-c", "exec 5<>/dev/tcp/127.0.0.1/3001"]
HealthStartPeriod=30s
HealthInterval=10s
HealthTimeout=30s
HealthRetries=10
Notify=healthy

[Service]
Restart=always

[Install]
WantedBy=default.target

网络上的其他讨论可见 Reddit 帖 1Reddit 帖 2