Linux CV 基本環境配置
整理一下關於 Linux 上 CV 開發環境的各種工具的配置。包括各種錯誤的處理,在無 root 權限下安裝到指定目錄,以及多人共用 root 帳戶的情況下重複安裝不同版本且不影響系統中已存在的版本。
從幾個文件目錄開始
bin
放置可執行的二進制程序,可以直接運行;lib
是一些動態鏈接庫,其它程序依賴;include
則是一些 C 的頭文件,在編譯程序的時候,要 include 進來。
/bin
:zsh、ls、rm、mkdir,所有用戶可以使用的與系統相關的程序。/sbin
:shutdown、ifconfig、mount,系統運行直接相關的需要 root 權限的程序/usr/bin
:whereis、tar、clang、g++,系統預裝的程序。/usr/sbin
同樣為需要 root 權限的/usr/local/bin
:ffmpeg、x264,一些自己安裝的第三方程序。/usr/local/sbin
同理
此外一些包管理軟件,例如 Homebrew 安裝在 /usr/local/Cellar/
下,並軟鏈接到 /usr/local/bin
。一些大型的包管理軟件比如 Anaconda 會安裝在 /usr/local/opt
或 ~/opt
下。
環境変量
上述這些 bin 文件夾都是默認在 PATH 中的,所以打開 Terminal 直接敲指令就可以使用,不需要帶程序路徑,如果在其它地方的程序,就需要軟鏈接到這裡(/usr/local/bin
),或者把其它路徑加到 PATH 中,例如 ~/.bashrc
/~/.zshrc
內 export PATH=/Users/kotori/opt/anaconda3/bin:$PATH
。
同樣的,在上述標準的動態鏈接庫 lib 內的 .so
也可以正常被其它需要調用的程序識別到,也可以軟鏈接(到 /usr/local/lib
)或 export LD_LIBRARY_PATH=/mnt/x264-master:$LD_LIBRARY_PATH
。查看已在環境中的 lib 的位置 ldconfig -p | grep lib
。
如果 head file 不在上述標準的 include 文件夾内,那麽 gcc 編譯的時候就要指定一下 -I
。
例如在 macOS 用 Homebrew 時,如果檢測到系統已經安裝了某程序,那麼它就不會把 /usr/local/Cellar/
內的程序鏈接到 /usr/local/bin
,brew info opencv@3
顯示:
1 | opencv@3 is keg-only, which means it was not symlinked into /usr/local, because this is an alternate version of another formula. |
這時候如果其它程序編譯需要 Homebrew 的這個版本的 OpenCV 而不是系統内原有的話,就要指定一下 -I/usr/local/Cellar/opencv@3/3.4.5_6/include
,-L/usr/local/Cellar/opencv@3/3.4.5_6/lib
多版本共存
一般軟件編譯后的 sudo make install
就是直接將編譯得到的可執行文件和 lib 複製到 usr/local
。如果無 root 權限,或是多人共用 root 而系統内已經有一個別人裝好的,但這個版本/配置不符合要求時,可以不進行 sudo make install
,而是統一在:
在有自己賬戶時,可以在 bashrc 中配置 export XXX
,這樣每次登錄時自動把自定義目錄添加到環境變量中。
多人共用 root 時,可以在 bashrc 中配置一個 alias XXX=export XXX
,每次登錄 shell 時敲一下,不影響其他人。
Conda
conda 不僅可以管理 Python 包(安裝在 miniconda3/envs/<env_name>/lib/python3.x/site-packages
),也可以管理其他軟件(bin/lib,例如 Cuda/FFmpeg 等)(安裝在 miniconda3/envs/<env_name>/bin
)。
SSH
ssh-keygen -t rsa -C "<comment>"
生成公鑰私鑰,ssh-copy-id -i ~/.ssh/id_rsa.pub root@10.x.x.x
將 public key 寫入到遠程機器的 ~/.ssh/authorized_key
文件中,或者(通過其它接口)手動複製進去。
Cuda
要安裝以下幾個東西,除了驅動以外其它不如找個 PyTorch 官方 Docker 鏡像直接解決,在宿主機折騰還容易出問題,Driver 在服務器上一般也沒法自己裝:
- 顯卡驅動,也就是
nvidia-smi
會顯示的版本。
https://www.nvidia.com/download/Find.aspx 按 Cuda ToolKit 版本搜索最新,一個 .run
文件(.run
前面一半是個安裝 shell 指令,後面一半是個安裝文件 tar)。
- cuda complier/cuda runtime toolkit
也就是 nvcc -V
會顯示的版本,準確說顯示的是 Runtime API 的版本。我見過 Driver API Version: 9.2, Runtime API Version: 9.0
的機器,一般要求的都是 Runtime API 版本,它是 Driver API 的更高一層抽象,基本全部都是調用的 Runtime API。想要不坑最好還是把 Cuda 更新到很新,在 2020 就是 10.2
(目前 11.x
已經發布),可以看一下 PyTorch 它提供的編譯好的版本中 Cuda 最新的是哪個即可。
在 conda 環境中可以直接 conda install cudatoolkit=10.2
。在外面的話 https://developer.nvidia.com/cuda-downloads 在 Legacy Releases 中,有 run/deb/rpm 包,我喜歡用 run 的。
- cudnn,用於深度學習的加速庫 (CUDA Deep Neural Network library),
cat /usr/local/cuda/include/cudnn.h | grep CUDNN_MAJOR -A 2
可查看版本。
https://developer.nvidia.com/cudnn 要註冊,填一個 survey。有 tar/deb/rpm,它是一個編譯好了的 lib 和一些 head,我喜歡用 tar 然後 cp && chomod
即可。參考 installlinux-tar。下載時選第一個 cuDNN Library for Linux (x86)
即可,如果得到 solitairetheme8
後綴的文件手動改一下後綴名即可。
- 如果有多卡,還需要 nccl,一個多卡之間的通信後端(比如 PyTorch 中的
torch.distributed
中很多方法就是 nccl 的實現)
https://developer.nvidia.com/nccl/nccl-download 同樣要註冊和填一個 survey。只有 Ubuntu/Debian 的 deb 和 RedHat/CentOS 的 rpm,按對應系統選即可。
由於 Cuda 和 前面的 CPU 工具是並行的存在,所以這些 GPU 相關東西都會裝在 /usr/local/cuda
中,調用 Cuda 的軟件也會自動在這個目錄下查找 bin 和 lib 和 include。在 Windows 下的 cuda 文件夾會自動帶上版本號,因此可以多版本共存。在 Linux 下,Cuda 10.1 update2 之後可以指定安裝目錄。
FFmpeg with nvenc
前幾個月聽說現在 NVENC 效果不錯了,配置了一個試試看,大概 6x 的速度,1.4x 的文件體積?對於不極致質量追求的地方應該說相當好用。官網:developer.nvidia.com,trac.ffmpeg.org/wiki/HWAccelIntro
用包管理器的好處就是它真的很貼心,常用的組件通通給配好了。自己編譯的話,每個依賴都要先手動裝好然後在 configure 裡 enable。但是目前包管理器安裝的 FFmpeg 一般都不會帶 GPU 編碼器。
系統環境
driver 版本 Driver Version: 396.44
complier/cuda toolkit 版本 Cuda compilation tools, release 9.0, V9.0.176
這裡有個坑點,nvenc API version
並不知道怎麼查看,而它直接關係到頭文件的版本,我隨便編譯了一個之後運行的時候顯示,這裡才看到是 8.1
1 | [h264_nvenc @ 0x36d8640] Driver does not support the required nvenc API version. Required: 10.0 Found: 8.1 |
然後重新搞了一個,反正也挺快的。。。
Dependencies
Compiler
除了 gcc/g++ 之外,ffmpeg 为了提高效率使用了汇编指令,如 MMX 和 SSE 等,所以先要安裝 yasm,x264 還需要 nasm,否則會報錯 nasm/yasm not found or too old. Use --disable-x86asm for a crippled build.
yasm:Debian 下直接 sudo apt-get install yasm
。
nasm:www.nasm.us,下載後直接運行 ./configure
,尋找編譯器並生成相應 Makefile,然後 make
編譯,之後 make install
裝進 /usr/local/bin
。
x264
我怎麼覺得 libavcodec 就帶了 x264 編解碼啊?但是實踐告訴我 Unknown encoder 'libx264'
就是要單獨搞搞。
總還是會用到 CPU 編碼,所以 git clone https://code.videolan.org/videolan/x264.git
得到源碼後,./configure --enable-static --enable-shared
,然後 make
-> sudo make install
給它編譯安裝。
這會生成 .a
(Archive libraries) 靜態編譯的程序會用到,和 .so
(shared object) 的動態庫。
libmp3lame
一個 mp3 的編碼器,總之 FFmpeg 啥都要自己配好才工作,apt-get install libmp3lame-dev
或者與 x264 一樣的安裝方式。它好像也可以 AAC,我試了下是 OK 的?
Head File
然後 clone 一下頭文件 https://github.com/FFmpeg/nv-codec-headers
,這裡知道系統裡的 API 是 8.1 的(上面那個系統環境版本簡直太「經典」了),所以 git checkout -b sdk/8.1 origin/sdk/8.1
把老版本的拉下來。cd nv-codec-headers && sudo make install && cd –
編譯。
Compile and Command
把 FFmpeg clone 下來,然後配置啟動一下 x264、libmp3lame、nvenc,cuda 不在標準目錄要指定一下 -I
和 -L
./configure --enable-cuda-nvcc --enable-cuvid --enable-nvenc --enable-nonfree --enable-libnpp --enable-gpl --enable-libx264 --enable-libmp3lame --extra-cflags=-I/usr/local/cuda/include --extra-ldflags=-L/usr/local/cuda/lib64
生成 Makefile 後,make -j 10
-> make install
我這裡碰到奇怪情況,make install
後好像沒在環境変量裡,x264 也是。運行會顯示:./ffmpeg: error while loading shared libraries: libx264.so.161: cannot open shared object file: No such file or directory
。ldd ./ffmpeg
看了下,顯示以下:
1 | linux-vdso.so.1 (0x00007fff12dac000) |
/usr/local/lib
裡明明有個 libx264.so
soft link -> libx264.so.161
,不知道它為啥要找帶版本號後綴的。有說 /etc/ld.so.conf
加上 x264 clone 到的目錄然後 sudo ldconfig
。感覺不如直接 export LD_LIBRARY_PATH=/mnt/x264-master:$LD_LIBRARY_PATH
,然後把 FFmpeg 也 export 到 PATH 裡。
command
ffmpeg -hwaccel cuvid -hwaccel_output_format cuda (-c:v h264_cuvid) -i a.avi -i b.mp4 -map 0:0 -map 1:1 -c:v h264_nvenc -c:a copy -y c.mp4
啟用 GPU 編解碼,加上解碼也有不小速度提升,source 不是 h.264 的也有提升(去掉括號內的)。
FFmpeg Static Builds
官網上發現有個 static build,不用 GPU 的話不如直接用這玩意方便,什麼都帶。
一鍵安裝腳本
innerlee/setup 這個 repo 提供了常用軟件的安裝腳本。
配置環境變量
1 | export ZZROOT=${some path}/app |
所有腳本會把 downloads
,解壓到 src
,然後編譯並安裝到 bin
或 lib
。DCMAKE_INSTALL_PREFIX=$ZZROOT
和其它一系列 FLAGS。
make DESTDIR=/home/john install
TroubleShoot
對於 CMake 中需要下載的一些網絡資源,例如儲存在 GitHub 上,以 OpenCV 為例。
在 opencv/3rdparty/ippicv 中會下載 ~60 MB 的 ippicv,在 opencv_contrib/modules/xfeatures2d 中會下載 boostdesc_binboost 和 vgg_generated。這些二進制文件都在 opencv_3rdparty repo 不同的 branch 中,可以看一下 .cmake
文件中的 SHA,例如 set(IPPICV_COMMIT "a56b6ac6f030c312b2dce17430eef13aed9af274")
,再找到對應的 branch(一般就是最新的,檢查一下 iigalanin Update IPPICV binaries (20191018) a56b6ac on Feb 25
)。
1 | set(IPPICV_COMMIT "a56b6ac6f030c312b2dce17430eef13aed9af274") |
拼接 https://raw.githubusercontent.com/opencv/opencv_3rdparty/32e315a5b106a7b89dbed51c28f8120a48b368b4/ippicv/ippicv_2019_lnx_intel64_general_20180723.tgz
以 ippicv 爲例,牆内一般卡在 IPPICV: Download: ippicv_2019_lnx_intel64_general_20180723.tgz
然後超時。
把這些用代理下載好後,放到例如 $ZZROOT/app/3rdparty
中,然後修改對應 cmake 文件中的
1 | ocv_download(FILENAME ${OPENCV_ICV_NAME} |
"https://raw.githubusercontent.com/opencv/opencv_3rdparty/${IPPICV_COMMIT}/ippicv/"
修改為 "file:///data/app/3rdparty "
還要 comment 一下腳本中把 tar 解壓到 src 的步驟。
OpenCV
1 | CMakeFiles/Makefile2:4315: recipe for target 'modules/videoio/CMakeFiles/opencv_videoio.dir/all' failed |
videoio 一般是 FFmpeg 錯誤,沒有配置好 share 或者沒有把路徑添加到環境變量。
Docker
https://docs.docker.com/get-started/
https://docs.docker.com/engine/reference/commandline/docker/
Image
Docker 把所有所需文件(系統環境、應用程序及依賴)打包在一個 Image 二進制文件中,通過 Image 文件可以創建 Container。他們的關係類似於各種語言中 Class 和 Instance 的關係。打包好的 Image 可以被複製到其它機器中運行,獲得完全相同的運行環境。
DockerHub
DockerHub 上有大量已經製作好的 Image 文件,通過 docker pull
docker image pull
命令可以抓取文件,例如:
1 | docker pull nvidia/cuda:9.2-base-ubuntu16.04 |
就得到一個由 NVIDIA
發佈的,包含 ubuntu 16.04 和 cuda 9.2 環境的 Image。
使用 docker image ls
或 docker images
可以列出本機所有 Image 文件,使用 docker image rm [imageName]
或 docker rmi [imageName]
刪除。
1 | REPOSITORY TAG IMAGE ID CREATED SIZE |
Dockerfile
通過 Dockerfile 可以自定義地製作一個符合所需的 Image 文件,一個簡單的 Dockerfile 示例如下:
1 | FROM nvidia/cuda:9.2-base-ubuntu16.04 |
FROM
繼承一個基礎鏡像,RUN
運行一些 shell 指令,所有結果(對文件的改動)都會打包進 Image 中WORKDIR
切換當前目錄,相當於cd
COPY
把本地的一些文件複製打包進 Image 中指定位置
docker build -t <ImageName>:<Tag> .
.
.
= Dockerfile Path,在 tag 前加入私有倉庫的地址(類似 xxxxx.com/user-dev/tag:version
)在之後 push 時會自動 push 到這個地址,否則是 hub.docker.com。
Export/Load Image
docker save nginx:latest > /root/docker-images/nginx.tar
docker load --input /root/docker-images/nginx.tar
Upload to DockerHub
首先需要 login docker login -u=[username] (-p=[Password]) [custom dockerhub address]
,不帶網址則登陸到官方 Hub。
可能需要重新 Tag 一下鏡像,docker tag 287b0bcf82c9 nginx:latest
,可更改名稱和 Tag。
最後 docker push [Image]:[Tag]
。
Container
各種方法得到了一個 Image 後,即可創建若干個 Container。
docker run -itd -v $PWD:/workspace -p 8000:3000 --shm-size=1024m --name torch nvidia/cuda:9.2-base-ubuntu16.04 /bin/bash
-it
容器的 Shell 映射到當前 Shell,在當前 Shell 輸入的命令會傳入容器-d
退出 Shell 後保持後台運行-v
將本機目錄映射到容器內目錄-p
容器內的3000
端口 映射到本機的8000
端口--shm-size
指定共享內存的大小,默認的 64M 在 PyTorch 的 DataLoader 中會報錯RuntimeError: DataLoader worker (pid 1378) is killed by signal: Bus error. It is possible that dataloader's workers are out of shared memory.
--name
指定 container 名字/bin/bash
容器啓動後內部第一個執行的命令,啓動 Bash 使得可以使用 Shell
docker container run
會創建一個新的 Container(如果 Image 不存在會自動嘗試 pull
),一個 Container 也是一個二進制文件,在 Container 内的任何改動都會保存到這個文件中,Container 退出/停止之後
ctrl+d
exit
docker ps
docker ps -a
1 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES |
docker container start 847442df5cc7
docker attach 847442df5cc7
/ docker exec -it 847442df5cc7 /bin/bash
,id/name
ctrl+p+q
docker container rm [containerID]
Jupyter and VSCode
Jupyter 和 VSCode 可以遠程連接到服務器作業,然後保存直接更改到服務器上。Jupyter 是啟動了一個 http 服務,然後在網頁上進行編輯,原生 VSCode 用的是 SSH。不管怎樣都是比 Vim 方便。Jupyter 上傳下載文件方便一點,不需要 SFTP 了。VSCode 帶了 Terminal,且有自動補全,各種都好用。
Jupyter
在網上看了一圈,發現都是莫名奇妙的配置方法,麻煩且,我感覺就多人共用一個 root 還是挺常見的吧,直接命令生成新的配置覆蓋不好吧。。其實它可以指定配置文件啟動。把底下的祖傳配置腳本隨便放到哪,然後 jupyter notebook --allow-root -y --ip=0.0.0.0 --config="<path>/jupyter_notebook_config.py"
就好。其實只要有 --ip=0.0.0.0
都不需要後面的 config 它就會啟動一個可以遠程登陸的 kernel,這個 ip 默認是 localhost,寫 0 自動用本機 IP,端口 8888
。下面的 config 主要是自定義一個端口和打開 Jupyter 之後的工作目錄,以及獲取環境變量中的 password。
1 | import os |
運行之後會顯示下面的,它給出的 IP 和域名估計都不對,就 ifconfig
查一下服務器內網 IP,然後用指定的端口和它生成的 token 登陸即可(系統環境中配置了密碼按照上面的配置腳本會自動使用這個密碼)。
1 | [I 15:10:10.848 NotebookApp] Jupyter Notebook 6.1.1 is running at: |
VSCode
VSCode 裝上 Remote 插件 (ms-vscode-remote.remote-ssh
) 可以 SSH 登陸,但是我發現不少地方會在中間做一層跳轉,也就是 SSH 登陸到的只是一台跳轉機而不是真正的服務器,需要用代理 SSH 穿透。
需要一個 Docker 容器,裡面需要有幾個包 apt-get update && apt-get upgrade && apt-get install build-essential openssh-server -y
這個可以直接加到 Dockerfile 中,進入容器時需要映射需要的 Port,可以用 host 模式全部映射出來。
在容器內:
1 | mkdir /var/run/sshd \ |
然後 vim /etc/ssh/sshd_config
把 Port 22
那一行解除注釋,改成需要的端口([1024-65536] 沒有在使用的),然後把本機的 ssh pubkey 加入到容器的 ~/.ssh/authorized_keys
中。最後在容器外 docker exec -d <ContainerName> /usr/sbin/sshd -D
啟動 SSH Server。
配置一下本地 VSCode 的 Config ~/.ssh/config
1 | Host * |
Code Server
網頁版 VSCode,在真正的服務器啟動 HTTP 服務,然後客戶端通過一個 HTTP 代理連接服務器(通常用跳轉機就會提供這麼一個 Proxy)。VSCode 本身就是基於 electron.js 的,所以搬到 Web 運行很正常。好處是不用配置 SSH,臨時用別的機器也隨便找個瀏覽器就能用。
下載 code-server 二進制版本:code-server/releases,下載完成解壓縮,然後直接運行 bin/code-server
就會啓動服務並在 ~/.config/code-server/config.yaml
生成一個配置文件:
1 | bind-addr: 127.0.0.1:8080 |
將配置文件複製到自己目錄下,修改 bind-addr
為 0.0.0.0:${port}
同 Jupyter 一樣開啓遠程登錄和指定端口。修改 password
或使用 export PASSWORD=${password}
配置環境變量都可以修改密碼,或使用 --auth none
參數啓動,關閉驗證。
安裝插件可以使用 --install-extension ms-python.python
或直接在網頁 VSCode 中正常安裝或下載 .vsix
文件后在左側 Extensions -> Views and More Actions(左上角三個點) -> Install from VSIX…。
之後指定配置文件啓動 bin/code-server --config /data/vscode_config.yaml
。它默認打開 Auto Save,和桌面版有點不一樣,所以默認不顯示 unsaved indicator。
Not serving HTTPS
此外,我看到說 1.40 以上已經自帶 Web 版本了,需要從源碼編譯:让 VSCode 在本地 Run 起来。
Background 運行
nohup {cmd} > {logfile.log} 2>&1 &
即可。結束可先通過端口查詢進程 netstat -nlp | grep <port>
然後 kill
。
也可以使用 screen:screen -S ${codeserver}
新建啓動一個新的 screen 會話,在這啓動 code server,ctrl+a+d
退出可回到原 shell 界面,screen -r ${codeserver}
恢復(進入已存在的)screen。
screen -ls
列出所有正在運行的 screen:
1 | There are screens on: |
screen -X -S {24862} quit
關閉某個 screen。
1 | bind-addr: 127.0.0.1:8080 |
將配置文件複製到自己目錄下,修改 bind-addr
為 0.0.0.0:${port}
同 Jupyter 一樣開啓遠程登錄和指定端口。修改 password
或使用 export PASSWORD=${password}
配置環境變量都可以修改密碼,或使用 --auth none
參數啓動,關閉驗證。
安裝插件可以使用 --install-extension ms-python.python
或直接在網頁 VSCode 中正常安裝或下載 .vsix
文件后在左側 Extensions -> Views and More Actions(左上角三個點) -> Install from VSIX…。
之後指定配置文件啓動 bin/code-server --config /data/vscode_config.yaml
。它默認打開 Auto Save,和桌面版有點不一樣,所以默認不顯示 unsaved indicator。
Not serving HTTPS
此外,我看到說 1.40 以上已經自帶 Web 版本了,需要從源碼編譯:让 VSCode 在本地 Run 起来。
Background 運行
nohup {cmd} > {logfile.log} 2>&1 &
即可。結束可先通過端口查詢進程 netstat -nlp | grep <port>
然後 kill
。
也可以使用 screen:screen -S ${codeserver}
新建啓動一個新的 screen 會話,在這啓動 code server,ctrl+a+d
退出可回到原 shell 界面,screen -r ${codeserver}
恢復(進入已存在的)screen。
screen -ls
列出所有正在運行的 screen:
1 | There are screens on: |
screen -X -S {24862} quit
關閉某個 screen。
附:Linux 自帶的幾個命令
查找移動文件
find -name "*.mp4" -exec mv {} ./src \;
會使用每個查找的結果放在 {}
中單獨執行後面的命令,以 \;
結尾。
不過很多命令可直接使用 *
通配,比如 cp
mv
不需要額外配合使用 find
。
排除掉某些文件 ls ori/ | grep -v exclude | xargs -i cp -r ori/{} dst/
。xargs -i
實現將管道 |
傳遞過來的 stdin 進行處理然後傳遞到命令的參數位置上。
統計目錄下文件數量 ls -l | grep "^-"| wc -l
,配合 ls -lR
ls -lR prefix*
-l
一行一個列出,grep
選取 -
開頭的(文件) 或 d
(目錄),wc -l
統計行數。
不同機器 SSH 複製
本地到遠程 scp -r <local_file/dir> remote_username@remote_ip:remote_file/dir
,遠程到本地將後面兩個參數反過來。
壓縮包
tar -cvf <filename>.tar <dir_path>
,c
打包,x
解包,v
詳細信息,f
指定文件。z
gz 壓縮,j
bz2 (bunzip2) 壓縮,-C <path>
指定解包目錄,如果裡面文件很多可以創建一個目錄給它外面套一层文件夾。
r
追加,find -name "*.mp4" -exec tar -rvf video.tar {} \;
,每個命令單獨執行的,所以要用追加。不過這裡 tar -rvf video.tar *.mp4
比較直接。u
替換,使用指定文件替換包中同名文件。
r, u, c, x
選一種模式,f
必須要且在最後;有 gz 後綴加個 z
,有 bz2 加 j
;v
隨意。
unzip
-d
指定解包目錄,-q
不輸出詳細信息。
Package Management
Miniconda conda config --set auto_activate_base false
,在 ~/.condarc
中配置。
apt list --installed