优化原因
- 人脸识别本来就是机器学习方向的东西,会非常吃
cpu
- 地图语言版本没有完全汉化,有点不爽
- 逆地理编码经常不准
优化目的
- 调用独显加速,
nvidia
环境 - 更换地图显示
- 构造逆地理编码数据
优化前提
- 已经下载人脸识别模型
buffalo_l
数据,已经可以成功调用并识别出人脸 - 已经安装好
nvidia-driver
- 注册有
maptiler.com
的账号
nvidia
加速
首先配置好 docker
的 cuda
环境,具体步骤可以看手册,这里就直接照搬了,省得各位还要吃力看英文文档。
-
宿主机,安装
nvidia-container-toolkit
,给docker
准备的bash# 添加软件源 curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg \ && curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \ sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \ sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list # 启用实验特性 sed -i -e '/experimental/ s/^#//g' /etc/apt/sources.list.d/nvidia-container-toolkit.list # 更新源 sudo apt-get update # 安装 sudo apt-get install -y nvidia-container-toolkit
要注意的是,某个新的版本之后需要
535
以上版本的nvidia-driver
。还有,如果是ubuntu
系统,记得把ubuntu
的软件静默更新给关了,不然后台更新显卡驱动,到时候会出问题。 -
宿主机,配置
docker
环境bashsudo nvidia-ctk runtime configure --runtime=docker sudo systemctl restart docker
-
docker-compose.yml
,此时docker-compose
需要把nvidia
的环境配置进去ymlimmich-machine-learning: container_name: immich_machine_learning # 南大加速,ghcr.nju.edu.cn替换ghcr.io,注意版本,硬件加速有专门的镜像 image: ghcr.nju.edu.cn/immich-app/immich-machine-learning:gpu volumes: - ${IMMICH_MODEL_CACHE_PATH}:/cache restart: unless-stopped environment: - TZ=Asia/Shanghai runtime: nvidia deploy: resources: reservations: devices: - capabilities: [gpu]
-
验证:执行
docker compose up -d
构建容器,然后在容器内执行nvidia-smi
bashdocker exec -it immich_machine_learning nvidia-smi
输出显卡信息即表示容器可以正常调用显卡,并可以使用
cuda
环境
更换地图底图
按照官网文档操作即可,这里仅作转载,浓缩一下操作步骤
- 登录 cloud.maptiler.com
- 新建地图
NEW MAP
- 选择地图类型,然后自定义
CUSTOMIZE
- 左侧有几个小图标菜单,点击
Settings
,然后在子菜单里,Language
选择Chinese
- 调整地图,然后点击在菜单的
Map origin
右侧十字定位图标即可设置当前地图显示为初始位置 - 点击右上角
Publish
发布 - 发布后在地图页面,有个
Use vector style
,将该链接添加到immich
管理界面的地图主题中即可
immich 逆地理编码优化
- 查源码
源码内,逆地理编码返回的是一个{country, state, city}
,国家、省份、城市三级结构。再看一下country
是admin1CodesASCII.txt
里面拿的,state
是从admin2Codes.txt
拿的,具体坐标是从cities500.txt
拿的。看一下数据库的表,主要字段定义为:
json
{
id: Number.parseInt(lineSplit[0]),
name: lineSplit[1],
alternateNames: lineSplit[3],
latitude: Number.parseFloat(lineSplit[4]),
longitude: Number.parseFloat(lineSplit[5]),
countryCode: lineSplit[8],
admin1Code: lineSplit[10],
admin2Code: lineSplit[11],
modificationDate: lineSplit[18],
admin1Name: admin1Map.get(`${lineSplit[8]}.${lineSplit[10]}`),
admin2Name: admin2Map.get(`${lineSplit[8]}.${lineSplit[10]}.${lineSplit[11]}`),
}
这里的 lineSplit
就是 cities500.txt
的行,这时候再看 cities500
的字段定义:
txt
geonameid : integer id of record in geonames database
name : name of geographical point (utf8) varchar(200)
asciiname : name of geographical point in plain ascii characters, varchar(200)
alternatenames : alternatenames, comma separated, ascii names automatically transliterated, convenience attribute from alternatename table, varchar(10000)
latitude : latitude in decimal degrees (wgs84)
longitude : longitude in decimal degrees (wgs84)
feature class : see http://www.geonames.org/export/codes.html, char(1)
feature code : see http://www.geonames.org/export/codes.html, varchar(10)
country code : ISO-3166 2-letter country code, 2 characters
cc2 : alternate country codes, comma separated, ISO-3166 2-letter country code, 200 characters
admin1 code : fipscode (subject to change to iso code), see exceptions below, see file admin1Codes.txt for display names of this code; varchar(20)
admin2 code : code for the second administrative division, a county in the US, see file admin2Codes.txt; varchar(80)
admin3 code : code for third level administrative division, varchar(20)
admin4 code : code for fourth level administrative division, varchar(20)
population : bigint (8 byte int)
elevation : in meters, integer
dem : digital elevation model, srtm3 or gtopo30, average elevation of 3''x3'' (ca 90mx90m) or 30''x30'' (ca 900mx900m) area in meters, integer. srtm processed by cgiar/ciat.
timezone : the iana timezone id (see file timeZone.txt) varchar(40)
modification date : date of last modification in yyyy-MM-dd format
所以,cities500
这里只用了 geonameid
、name
、alternatenames
、latitude
、longitude
、country code
、admin1 code
、admin2 code
、modification date
,搜了一下,alternatenames
甚至都没用到,所以无所谓,留空也行。还有两个参数:admin1Map
和 admin2Map
,源码是分别拿 admin1CodesASCII.txt
和 admin2Codes.txt
的第一列当 key
,第二列当 value
组成这两个数据,这两个文件后两列的内容也不用关心。好,数据分析完成,开始构造!
-
geonameid
这里直接就用咱国家自己的行政区划的代码就行了,4
级行政结构到镇/街道
一级,讲道理,我们用到这个就行了。行政区划代码1-2
位为省级代码,3-4
位为市级代码,5-6
位为县级代码,7-9
位为镇级代码,我们就以区划代码为110101001000
的北京市东城区东华门街道
作为例子,构造我们第一条cities500
数据。 -
admin1CodesASCII.txt
因为cities500
需要国家和省份代码,而这个文件记录的就是国家和省份的代码,按照我们的行政区划来,北京市
的代码就是CN.11
,第一列是代码,第二列是名称北京市
,第三第四列内容随便。 -
admin2Codes.txt
按照原来的结构,也是有四列,第一列除了城市代码,还包括了国家和省份代码,这里因为北京市
是直辖市,下辖没有市一级的单位,因此2-3
位固定为01
,代码变成CN.11.01
,第2
列可以留空或者也直接填写北京市
,三四列内容随便。 -
cities500.txt
主要的内容来啦!这个文件主要的字段,分别在第1/2/4/5/6/9/11/12/19
列,因为我们只细化到镇/街道
一级,第1
列就是对应的代码110101001
,第2
列,就是immich
照片详情里显示的city
,这里要细化,我们直接填北京市东城区东华门街道
,第4
列随意,我直接留空,第5
、6
列填坐标,WGS84
格式,去百度高德或者天地图那里都能抓,第9
、11
列需要跟admin1CodesASCII.txt
里的对应,分别填CN
和11
,第12
列就是城市代码,填区划代码的第5-6
位,第19
列随便填个时间,格式为yyyy-MM-dd
。 -
三个文件结构预览
admin1CodesASCII.txt
bash# | 实际为 tab,这里只是体现具体结构 CN.11 | 北京市 | xxx | xxx
admin2Codes.txt
bash# | 实际为 tab,这里只是体现具体结构,因为北京是直辖市,第二列为空,也可以再写一次北京市 CN.11.01 | 北京市 | xxx | xxx
cities500.txt
txt# | 实际为 tab,这里只是体现具体结构 110101001|东城区东华门街道|dongchengqu|东华门街道|39.917901|116.390731|xxx|xxx|CN|xxx|11|01|xxx|xxx|xxx|xxx|xxx|xxx|2024-12-10
-
i18n-iso-countries
这个是用在项目里实现国际化的库,在逆地理编码查询之后显示的国家名由这个提供,而项目默认返回的是en
版本,所以要想逆地理编码的显示全部显示中文,还需要更改里面的en.json
文件,把CN
对应的值改为中国
,MO
对应改为澳门
,HK
对应改为香港
,TW
对应改为台湾
。 -
关于部分数据的说明
- 我国行政区划代码为
12
位,每一级行政区域对应的代码,只代表本级的名称,低位全为0
,我们级别只到镇一级,村/居委会
一级的末尾3
位并不需要,因此省略,所以geonameid
使用9
位的代码。 村/居委会
一级全国有 60多万 ,数据量很大,而且照片又不是每个村都有,没必要细化到这一级,镇级4万多
个点足够用了。- 只使用镇级,还有一个原因是因为经纬度数据是由高德地图/百度地图/天地图 的
api
抓取,而api
每天调用是限制次数的,数据量太大需要很长时间去抓。 - 还有个只用到镇级最重要的原因,区划代码
12
位,但是数据库里geonameid
的类型是4
个字节的integer
,最大值只有2147483647
,会报错! geoNames
的城市代码和我国的行政区划代码不一致,甚至有冲突,所以还是建议新建一个数据文件,只放所需要地点的经纬度即可,毕竟都能自建相册服务器还查到我这篇文章去研究优化了,想必也没有到处旅游的爱好,比如我,只放了我去过的几个省市的经纬度数据。cities500.txt
的第2
列最终会变成一个个集合相册的名称,过于细化只会导致地点分类那里变得凌乱cities500.txt
的经纬度数据使用的坐标系是GPS
原始的WGS84
,高德使用GCJ02
,百度使用BD09
,天地图使用CGCS2000
,其中只有CGCS2000
和WGS84
是最接近的,一般用途上可以直接使用,另外两个都得做变换。geoNames
的文件对应在容器里的/build/geodata
文件夹内,i18n-iso-countries
对应路径为/usr/src/app/node_modules/i18n-iso-countries
,因此做修改时需要将这两个文件夹从容器里映射出来。- 凡是修改了
geoNames
内文件的内容,改一下目录下的geodata-date.txt
里的时间,服务启动的时候代码会检测这个时间跟上次导入数据的时间是否一致,不一致则重新导入,更改的时间没有要求,只要不一样就行。 - 关于
cities500
,别问,我不会提供现成的文件,带经纬度的地理位置信息是有点敏感的东西,需要的话可以用这个项目数据自己抓,在规则里玩才没人管
- 我国行政区划代码为
后续计划
本来也想给项目贡献代码,然而一是没时间,二是对 nestjs
和 svelte
不熟,也没写过 flutter
,目前只好另辟蹊径先改着凑活用一用。