2024/04/21

Basic Linux commands

Commands

systemd and sysvinit

systemd 是 Linux 作業系統之下的一套中央化系統及設定管理程式(init),包括有常駐程式、函式庫以及應用軟體, 因為使用了 cgroup 與 fanotify 等 Linux 組件以實現其特性,所以只適用於 Linux。 有不少 Linux distribution 已將預設 init 程式從 SysVinit 改為使用 systemd, 但是目前仍然也有使用 SysVinit 或者是其它 init 程式的 Linux distribution。

Linux kernel 開機後,第一個執行的程式為 /sbin/init,如果使用 systemd 的系統,那麼這個檔案會是指向 systemd 的連結。 如果要檢查目前系統使用的 init 程式:

ps 1

檢查 systemd 是否有在使用:

stat /sbin/init

也可以使用 readlink 檢查:

readlink /sbin/init

如果是使用 systemd 的系統,Linux 可以選擇從 CLI 或者是 GUI 開機,如果要知道目前的選項,使用下面的命令:

sudo systemctl get-default

如果只是要 run-time 改變,改為 CLI:

sudo systemctl isolate multi-user.target

改為 GUI:

sudo systemctl isolate graphical.target

如果是要修改預設值從 CLI 開機:

sudo systemctl set-default multi-user.target

修改預設值從 GUI 開機:

sudo systemctl set-default graphical.target

如果預設為 GUI 環境,使用 ALT + CTRL + F1 ~ F6 可以切換到 console,如果要切回來, 使用指令 w 列出目前登入的使用者與 ttyx 的使用情況,按 ALT + Fx (x=1, 2, 3.., etc) 切回來。


如果是 sysvinit 系統,修改 /etc/inittab 檔案(修改預設值從 CLI 開機,那麼將 5 改為 3):

id:5:initdefault:

Shell

Shell 是使用者與 Linux kernel 溝通的橋樑,使用者輸入命令來執行各種各樣的任務。

UNIX 的 shell 有兩大主流,一個是 Bourne shell, 為貝爾實驗室 Stephen Bourne 在 1977 年在 Version 7 Unix 中針對大學與學院發布; 一個是 C shell,為 Bill Joy 在 BSD 系統上開發。

目前大多數的 Linux distribution 預設的 Shell 為 GNU Bash。 Bash (Bourne Again SHell) 是在 1987 年由布萊恩·福克斯為了 GNU 計劃而編寫,是 Bourne shell 的後繼相容版本與開放原始碼版本。 1989 年釋出第一個正式版本,原先是計劃用在 GNU 作業系統上,但能執行於大多數類 Unix 系統的作業系統之上。

下面是我的個人設定(使用家目錄下的 .bashrc),用來設定 Prompt 與一些命令的彩色化設定。

# Prompt
export PS1="\[\e[1;36m\]\u\[\e[0m\]@\h:\[\e[38;5;214m\]\w\[\e[0m\]> "

# Bash setting
export HISTCONTROL=ignoreboth

# alias command
alias ls="ls --color=auto"
alias diff="diff --color"
alias grep="grep --color=auto"

# Colors in Man Pages
export GROFF_NO_SGR=1         # For Konsole and Gnome-terminal
man() {
    LESS_TERMCAP_mb=$'\e[01;31m' \
    LESS_TERMCAP_md=$'\e[01;31m' \
    LESS_TERMCAP_me=$'\e[0m' \
    LESS_TERMCAP_se=$'\e[0m' \
    LESS_TERMCAP_so=$'\e[01;44;33m' \
    LESS_TERMCAP_ue=$'\e[0m' \
    LESS_TERMCAP_us=$'\e[01;32m' \
    command man "$@"
}

Tcsh 是一個向下相容 c shell 的 Unix shell。 它本質上是為 c shell 增加命令補完,命令編輯等其他功能。 Tcsh 目前是 FreeBSD 和其延伸發行版的預設 shell。

下面是我的個人設定(使用家目錄下的 .tcshrc),用來設定 Prompt 與一些命令的彩色化設定。

# Prompt
set promptchars = "%#"
set _orange="%{\033[38;5;214m%}"
set _cyan="%{\033[1;36m%}"
set _white="%{\033[0m%}"
set prompt="${_cyan}%n${_white}@%m:${_orange}%~${_white}%# "

unset _orange
unset _cyan
unset _white

# Tcsh setting
set histdup=erase

# alias command
alias ls="ls --color=auto"
alias diff="diff --color"
alias grep="grep --color=auto"

# Colors in Man Pages
set GROFF_NO_SGR=1         # For Konsole and Gnome-terminal
setenv LESS_TERMCAP_mb $'\e[01;31m'
setenv LESS_TERMCAP_md $'\e[01;31m'
setenv LESS_TERMCAP_me $'\e[0m'
setenv LESS_TERMCAP_se $'\e[0m'
setenv LESS_TERMCAP_so $'\e[01;44;33m'
setenv LESS_TERMCAP_ue $'\e[0m'
setenv LESS_TERMCAP_us $'\e[01;32m'

bash 可以使用 history 列出之前使用過的命令。
history -c 可以清除記錄的命令。

如果要將工作放入背景執行,在命令的最後加入 & 即可。
如果是要將工作暫停放入背景中,使用 CTRL + Z
承上,那要怎麼知道目前背景有哪些工作? 使用 jobs 可以列出來(-l 還會列出 PID 的號碼,-r:列出正在背景工具的程序, -s:則是列出正在背景中暫停的工作)。
承上,觀察上面的列表,在前面會有代表的號碼。可以使用 fg 或者是 fg %jobnumber 來將背景的工作恢復為前景運行中。
如果需要刪除背景的工作,可以使用 kill,方式為 kill %jobnumber
Bash 也有支援將工作送到背景的指令,使用 bg 或者是 bg %jobnumber

上面描述的都是 bash 產出的子程序,所以 logout 之後背景程序一樣會消失。 如果需要將工作放到系統背景,可以使用 nohup

File system

Linux 採用索引式檔案系統 (indexed allocation),通常會將這兩部份的資料分別存放在不同的區塊,權限與屬性放置到 inode 中, 至於實際資料則放置到 data block 區塊中。 另外,還有一個超級區塊 (superblock) 會記錄整個檔案系統的整體資訊,包括 inode 與 block 的總量、使用量、剩餘量等。

每個 inode 與 block 都有編號,至於這三個資料的意義可以簡略說明如下:

  • superblock:記錄此 filesystem 的整體資訊,包括 inode/block 的總量、使用量、剩餘量, 以及檔案系統的格式與相關資訊等;
  • inode:記錄檔案的屬性,一個檔案佔用一個inode,同時記錄此檔案的資料所在的 block 號碼;
  • block:實際記錄檔案的內容,若檔案太大時,會佔用多個 block 。

由於每個 inode 與 block 都有編號,而每個檔案都會佔用一個 inode ,inode 內則有檔案資料放置的 block 號碼。 因此,如果能夠找到檔案的 inode 的話,那麼自然就會知道這個檔案所放置資料的 block 號碼, 也就能夠讀出該檔案的實際資料。

File Permission

Every file in Unix has the following attributes −

  • Owner permissions − The owner's permissions determine what actions the owner of the file can perform on the file.

  • Group permissions − The group's permissions determine what actions a user, who is a member of the group that a file belongs to, can perform on the file.

  • Other (world) permissions − The permissions for others indicate what action all other users can perform on the file.

The permissions of a file are the first line of defense in the security of a Unix system. The basic building blocks of Unix permissions are the read, write, and execute permissions.

UNIX 系統使用 chmod 修改 permission。
下面是一個使用的例子(要注意 Unix permission 採用八進位計算):

chmod 644 index.html

如果要加上執行權限:

chmod +x index.html

如果要刪除執行權限:

chmod -x index.html

如果要更改擁有者或者是檔案的群組,使用 chown 進行修改。

Superuser

Linux 系統最高權限的管理者帳號為 root(user id 為 0 的帳號),也就是超級使用者(superuser)帳號。 因為 root 帳號權限很高,通常不建議直接使用 root 登入系統進行管理系統的動作。

su 指令可以讓一般使用者取得 root 權限,取得 root 權限的使用者就如同 root 一樣可以對系統進行各種管理動作。 另外,su 除了可以讓一般使用者取得 root 權限之外,也可以取得其他的帳號權限。下面是一個例子:

su danilo

sudo 指令類似 su,也是用來取得 root 或是其他帳號的權限,一般使用者可以在指令前加上 sudo 來以 root 身分執行任何指令。 系統會提示使用者提供 root 密碼。如果驗證成功,便會以 root 身分執行指令。

id -un 指令會列印目前使用者的登入名稱:

sudo id -un

sudo 可以使用 -u 指定要切換的使用者,-g 指定要切換的群組。

sudo 的主要規則組態檔案為 /etc/sudoers(如果是 openSUSE Tumbleweed,那麼使用 /usr//etc/sudoers)。 如果檔案格式錯誤,使用者可能無法順桘進入系統,因此強烈建議使用 visudo 來進行編輯。 visudo 可以防止發生編輯衝突,並會在儲存修改內容之前檢查語法錯誤。

Locale

Locale 設定由三個部分所組成:語言代碼 (Language Code)、地域 (Territory)、編碼 (Encoding)。 Locale 的設定名稱就是由這三個一起組成。

可以執行 locale 查詢目前的設定。

  1. LC_CTYPE: 字元分類及處理方式。
  2. LC_COLLATE: 字元順序與字串比較。
  3. LC_MESSAGES: 程式中用何種語言來顯示訊息。
  4. LC_MONETARY: 貨幣顯式格式。
  5. LC_NUMERIC: 數字顯式格式。
  6. LC_TIME: 日期與時間的顯式格式。
  7. LC_ALL: 一次設定以上所有的類別。
  8. LANG: 也可用來一次設定所有的 locale 環境,優先權低於 LC_ALL。

如果要列出目前支援的語系,使用下列的指令:

locale -a

Linux distribution 各個設定檔案(以及設定檔案位置)可能不同。

語系設定可以分為二個部份,系統預設的語系與桌面環境的設定。openSUSE 系統的設定在 /etc/locale.conf
如果在 KDE 環境下,KDE 會參考家目錄下的 .config/plasma-localerc 設定,因此桌面環境的設定可以不同於系統的設定。

如果要修改系統語系,可以嘗試下列的指令:

localectl set-locale LANG=en_US.utf8

如果想使用修改設定檔的方式,下面是修改 /etc/locale.conf 設定主語系 LANG 變數的例子:

LANG=zh_TW.UTF-8

系統所採用的 locale 設定優先性是 LC_ 環境變數高於 LANG 環境變數。LC_ALL 的優先性則是所有類別中最高的, 不過一般不建議設定 LC_ALL。

How long the system is up

使用 uptime 可以列出系統已經運作的時間。

Get the machine architecture

使用 arch 或者是 uname -m 可以列出系統的硬體架構。

Get the name of the host

使用 hostname 或者是 uname -n 可以列出目前的主機名稱。

Print the user name

使用 whoami 可以取得目前的使用者名稱。

List Current Logged In Users

使用 w 或者是 who command。

Date

使用 date 可以列出目前的時間。

如果只是要印出日期,可以使用:

date +%F

Calendar

使用 cal 可以列出這個月的月曆。

Output strings

如果需要顯示訊息,可以使用 echo
如果需要輸出格式化的訊息,可以使用 printf

printf "%s is %d years old.\n" Orange 19

Display result and output to file

如果需要顯示程式的結果並且儲存到檔案,可以使用 tee。

下面是一個例子:

ls -al | tee result

如果你只想要將結果儲存到檔案但是不顯示到 stdout,

下面是一個例子:

ls -al | tee result > /dev/null

Display file content

如果需要顯示檔案的內容,可以使用 cat。

下面是一個例子:

cat result

Display binary file content

如果使用 cat 觀察一些非文字檔案會顯示亂碼。 這時候可以嘗試使用 od 這個命令來顯示非文字檔檔案的內容,有以下的選項可以使用:

  • -A:選擇以何種進位表示位址偏移,-Ax 為十六進位,-An 表示不顯示位址偏移。
  • -a:具名字元,例如換行字元顯示為 nl。
  • -b:以八進制格式顯示。
  • -c:以 ASCII 字符格式顯示,換行字元會顯示為 \n。
  • -d:以有符號十進制格式顯示。
  • -x:以十六進制格式顯示。

文字檔案換行符號轉換

Linux/Unix-like 系列的作業系統文字檔案的換行字元為 \n; 與 Dos (以及 Windows 平台)的換行字元為 \r\n 不同。

可以使用 dos2unixunix2dos 嘗試換行符號轉換。

語系編碼轉換

可以使用 iconv 嘗試編碼轉換。

iconv -f big5 -t utf8 input_file -o output_file

Count the number of lines, words, and bytes in the file

如果需要計算檔案的行數,字數或者是大小,可以使用 wc。

其中 -l 為計算行數, -w 為計算 word 數, -c 為計算檔案的全部 bytes 數。

下面就是透過 ls 與計算行數的方式來算出目前目錄下的檔案與目錄數目:

ls | wc -l

Clears the terminal screen

如果需要清除 termial screen 的內容,可以使用 clear。
或者也可以使用 Ctrl + L 的組合鍵。

Copy and remove files

使用 cp 可以複製檔案或者是目錄。cp -r 可以遞迴地複製子目錄下的檔案。
使用 rm 可以刪除檔案或者是目錄。如果要強制刪除一個目錄以及目錄下的檔案,使用 rm -rf
如果要搬移目錄或者是檔案,使用 mv 這個命令。

建立連結檔

使用 ln 可以建立一個連結檔案。
所謂的硬連結就是使用相同 inode 的連結檔案,ln 指令預設就是建立硬連結(可以使用 ls -i 檢查)。
軟連結(符號連結)則是靠著絕對路徑或相對路徑來指向目標檔案的連結檔,若要使用 ln 指令建立軟連結,可以加上 -s 參數。

Create and remove directory

使用 mkdir 可以建立目錄。
使用 rmdir 可以刪除目錄。

Current working directory

使用 pwd 可以取得目前的目錄路徑。

grep

如果要搜尋檔案的內容,可以使用 grep,如果列出在哪一列 (-n) 以及搜尋各個目錄下的各個檔案 (-r),下面是一個範例:

grep -rn "tcl" .

find

如果要搜尋某些檔案以後殺掉,可以用:

find . -name "FILE-TO-FIND" -exec rm -rf {} \;

或者針對檔案的寫法:

find . -type f -name "FILE-TO-FIND" -exec rm -f {} \; 

如果要找出哪個 c 檔案中有 main 函式並且印出,則可以使用:

find . -type f -name *.c -print -exec grep main {} \;

head and tail

如果沒有指定參數,head 會列出一個檔案前十列的內容,而 tail 會列出一個檔案最後十列的內容。

下面是列出 Aapche Http Server error_log 最後十行的例子。

sudo tail /var/log/apache2/error_log

Compare text files

可以使用 diff 來比較文字檔案間的不同,我通常會使用 -Naur 參數取得更詳細的比較內容。

Compare binary files

可以嘗試使用下列的指令列出差異:

cmp -l file1.bin file2.bin | gawk '{printf "%08X %02X %02X\n", $1, strtonum(0$2), strtonum(0$3)}'

(這時候不是以 0,而是以 1 開始計算位置,和一般的慣例略有差異)

tar

tar 可以將多個目錄或檔案打包成一個檔案,而且可以透過 gzip/bzip2 的支援,對打包後的檔案進行壓縮。

壓 縮:tar -jcv -f filename.tar.bz2 要被壓縮的檔案或目錄名稱
查 詢:tar -jtv -f filename.tar.bz2
解壓縮:tar -jxv -f filename.tar.bz2 -C 欲解壓縮的目錄
選項與參數:
-c :建立打包檔案,可搭配 -v 來察看過程中被打包的檔名(filename)
-t :察看打包檔案的內容含有哪些檔名,重點在察看『檔名』就是了;
-x :解打包或解壓縮的功能,可以搭配 -C (大寫) 在特定目錄解開
特別留意的是, -c, -t, -x 不可同時出現在一串指令列中。
-J :透過 xz 的支援進行壓縮/解壓縮:此時檔名最好為 *.tar.xz
-j :透過 bzip2 的支援進行壓縮/解壓縮:此時檔名最好為 *.tar.bz2
-z :透過 gzip 的支援進行壓縮/解壓縮:此時檔名最好為 *.tar.gz
-v :在壓縮/解壓縮的過程中,將正在處理的檔名顯示出來!
-f filename:-f 後面要立刻接要被處理的檔名!建議 -f 單獨寫一個選項囉!
-C 目錄 :這個選項用在解壓縮,若要在特定目錄解壓縮,可以使用這個選項。

Report the file system disk space usage

可以使用 df 這個命令。

df -h

List block devices

可以使用 lsblk 這個命令來列出 block devices。

如果要確認,可以使用 fdisk 來確認:

sudo fdisk -l

List systemd services

如果需要列出目前 systemd 正在執行的 services,可以使用下列的指令:

pstree

工作管理員程式

top (table of processes)是一個工作管理員程式,用於監看目前所有程式的執行狀況, 顯示有關 CPU 和記憶體利用率的資訊。在程式執行後,按 q 可以離開程式。

檢查處理程序狀態

使用者可以使用 ps 指令來尋找正在執行中的處理程序,並顯示這些處理程序的相關資訊。

顯示所有在系統上執行的處理程序:

ps -ef

也可以使用下列的參數 (-aux) 顯示所有在系統上執行的處理程序:

ps -aux

在 Linux 中掛載 ISO 檔案

如果需要在 Linux 中掛載 ISO 檔案來存取資料的話,下面是如何掛載的範例:

# cd /mnt
# mkdir cdrom
# mount /home/danilo/Desktop/openSUSE-11.3-DVD-i586.iso /mnt/cdrom -o loop

在 Linux 中建立 ISO 檔案

使用 mkisofs 建立。下面是一個範例:

mkisofs -o home.iso /home/danilo

Create usb boot disk

  • 下載 live image
  • 使用下列的 command:
dd if=image.iso of=/dev/sdb bs=4M;sync

其中 image.iso 和 /dev/sdb 要換成符合自己環境的參數。

Increase The Maximum Number Of Open Files / File Descriptors (FD)

Use the following command command to display maximum number of open file descriptors:

cat /proc/sys/fs/file-max

The hard limit is the ceiling for the soft limit. The soft limit is what is actually enforced for a session or process. This allows the administrator (or user) to set the hard limit to the maximum usage they wish to allow. Other users and processes can then use the soft limit to self-limit their resource usage to even lower levels if they so desire.

針對各個使用者的設定,如果要查詢目前的情況:

ulimit -Sn
ulimit -Hn

要變更設定,可以編輯 /etc/security/limits.conf,下面是設定範例:

danilo soft nofile 80920
danilo hard nofile 102400

要重開機以後才會生效。

參考資料

2024/04/17

OpenCL

簡介

OpenCL(Open Computing Language,開放計算語言)是一個為異構平台編寫程式的框架, 此異構平台可由 CPU、GPU、DSP、FPGA 或其他類型的處理器與硬體加速器所組成。 Portable Computing Language (PoCL) 則是 OpenCL 的一個自由軟體實作, 可以在機器上沒有 GPU 的情況下使用 OpenCL API 進行運算。

OpenCL 包括一組 API 和一個程式語言。基本的原理是程式透過 OpenCL API 取得 OpenCL 裝置(例如顯示晶片)的相關資料, 並將要在裝置上執行的程式(使用 OpenCL 程式語言撰寫)編繹成適當的格式以後在裝置上執行。

An OpenCL application is split into host code and device kernel code. Execution of an OpenCL program occurs in two parts: kernelsthat execute on one or more OpenCL devices and a host program that executes on the host.

The most commonly used language for programming the kernels that are compiled and executed across the available parallel processors is called OpenCL C. OpenCL C is based on C99 and is defined as part of the OpenCL specification.

The core of the OpenCL execution model is defined by how the kernels execute. OpenCL regards a kernel program as the basic unit of executable code (similar to a C function). Kernels can execute with data or task-parallelism. An OpenCL program is a collection of kernels and functions (similar to dynamic library with run-time linking).

An OpenCL command queue is used by the host application to send kernels and data transfer functions to a device for execution. By enqueueing commands into a command queue, kernels and data transfer functions may execute asynchronously and in parallel with application host code.

The kernels and functions in a command queue can be executed in-order or out-of-order. A compute device may have multiple command queues.


A complete sequence for executing an OpenCL program is:

  1. Query for available OpenCL platforms and devices
  2. Create a context for one or more OpenCL devices in a platform
  3. Create and build programs for OpenCL devices in the context
  4. Select kernels to execute from the programs
  5. Create memory objects for kernels to operate on
  6. Create command queues to execute commands on an OpenCL device
  7. Enqueue data transfer commands into the memory objects, if needed
  8. Enqueue kernels into the command queue for execution
  9. Enqueue commands to transfer data back to the host, if needed

A host is connected to one or more OpenCL compute devices. Each compute device is collection of one or more compute units where each compute unit is composed of one or more processing elements. Processing elements execute code with SIMD (Single Instruction Multiple Data) or SPMD (Single Program Multiple Data) parallelism.


For example, a compute device could be a GPU. Compute units would then correspond to the streaming multiprocessors (SMs) inside the GPU, and processing elements correspond to individual streaming processors (SPs) inside each SM. Processors typically group processing elements into compute units for implementation efficiency through sharing instruction dispatch and memory resources, and increasing local inter-processor communication.

OpenCL's clEnqueueNDRangeKernel command enables a single kernel program to be initiated to operate in parallel across an N-dimensional data structure. Using a two-dimensional image as a example, the size of the image would be the NDRange, and each pixel is called a work-item that a copy of kernel running on a single processing element will operate on.

As we saw in the Platform Model section above, it is common for processors to group processing elements into compute units for execution efficiency. Therefore, when using the clEnqueueNDRangeKernel command, the program specifies a work-group size that represents groups of individual work-items in an NDRange that can be accommodated on a compute unit. Work-items in the same work-group are able to share local memory, synchronize more easily using work-group barriers, and cooperate more efficiently using work-group functions such as async_work_group_copy that are not available between work-items in separate work-groups.


OpenCL has a hierarchy of memory types:

  • Host memory - available to the host CPU
  • Global/Constant memory - available to all compute units in a compute device
  • Local memory - available to all the processing elements in a compute unit
  • Private memory - available to a single processing element

OpenCL memory management is explicit. None of the above memories are automatically synchronized and so the application explicitly moves data between memory types as needed.


在 openSUSE Tumbleweed 上安裝 OpenCL 的開發檔案:

sudo zypper in ocl-icd-devel opencl-headers clinfo

安裝後執行 clinfo 檢查目前的 OpenCL 裝置資訊。
如果沒有符合的實作,為了學習 OpenCL, 可以安裝 Portable Computing Language (pocl):

sudo zypper in pocl-devel

OpenCL Installable Client Driver (ICD) allows multiple OpenCL implementations to co-exist; also, it allows applications to select between these implementations at runtime.

Use the clGetPlatformIDs() and clGetPlatformInfo() functions to see the list of available OpenCL implementations, and select the one that is best for your requirements.

執行 clinfo 觀察目前的 OpenCL device 資訊。

下面的程式使用 clGetPlatformIDs 函式取得目前可用的 platform 數目 (編譯指令:gcc test.c `pkg-config --libs --cflags OpenCL`):

#include <stdio.h>

#ifdef __APPLE__
#include <OpenCL/opencl.h>
#else
#include <CL/cl.h>
#endif

int main( void ) {
    // OpenCL related declarations
    cl_int err;
    cl_uint num;

    err = clGetPlatformIDs( 0, NULL, &num );
    printf("%d\n", num);

}

下面是另外一個範例:

#include <stdlib.h>
#include <stdio.h>

#ifdef __APPLE__
#include <OpenCL/opencl.h>
#else
#include <CL/cl.h>
#endif

const char *kernel_code =
    "__kernel void vector_add(__global const int *A, __global const int *B, __global int *C) {"
    "    int i = get_global_id(0);"
    "    C[i] = A[i] + B[i];"
    "}";

int main( void ) {
    // OpenCL related declarations
    cl_int err;
    cl_platform_id platform;
    cl_device_id device;
    cl_context_properties props[3] = { CL_CONTEXT_PLATFORM, 0, 0 };
    cl_context ctx;
    cl_program program;
    cl_command_queue queue;
    cl_kernel kernel;
    int i;

    //
    const size_t N = 1024; // vector size
    size_t global_item_size = N; // Process the entire lists
    size_t local_item_size = 64; // Divide work items into groups of 64

    int *A, *B, *C;
    A = (int*) malloc(N * sizeof(*A));
    B = (int*) malloc(N * sizeof(*B));
    C = (int*) malloc(N * sizeof(*C));
    for (i=0; i<N; i++) {
        A[i] = i;
        B[i] = i + 1;
    }
    cl_mem d_A, d_B, d_C;

    /* Setup OpenCL environment. */
    err = clGetPlatformIDs( 1, &platform, NULL );
    err = clGetDeviceIDs( platform, CL_DEVICE_TYPE_DEFAULT, 1, &device, NULL );

    props[1] = (cl_context_properties)platform;
    ctx = clCreateContext( props, 1, &device, NULL, NULL, &err );
    queue = clCreateCommandQueueWithProperties( ctx, device, 0, &err );
    program = clCreateProgramWithSource(ctx, 1, (const char **) &kernel_code, NULL, &err);
    err = clBuildProgram(program, 0, NULL, NULL, NULL, NULL);
    kernel = clCreateKernel(program, "vector_add", &err);

    // initialize buffer with data
    d_A = clCreateBuffer( ctx, CL_MEM_READ_ONLY, N*sizeof(*A), NULL, &err );
    d_B = clCreateBuffer( ctx, CL_MEM_READ_ONLY, N*sizeof(*B), NULL, &err );
    d_C = clCreateBuffer( ctx, CL_MEM_WRITE_ONLY, N*sizeof(*C), NULL, &err );

    err = clEnqueueWriteBuffer( queue, d_A, CL_TRUE, 0, N*sizeof(*A), A, 0, NULL, NULL );
    err = clEnqueueWriteBuffer( queue, d_B, CL_TRUE, 0, N*sizeof(*B), B, 0, NULL, NULL );

    err = clSetKernelArg(kernel, 0, sizeof(cl_mem), (void *)&d_A);
    err = clSetKernelArg(kernel, 1, sizeof(cl_mem), (void *)&d_B);
    err = clSetKernelArg(kernel, 2, sizeof(cl_mem), (void *)&d_C);

    err = clEnqueueNDRangeKernel(queue, kernel, 1, NULL,
            &global_item_size, &local_item_size, 0, NULL, NULL);

    err = clFinish(queue);

    err = clEnqueueReadBuffer( queue, d_C, CL_TRUE, 0, N*sizeof(*C), C, 0, NULL, NULL );
    err = clFinish(queue);

    for(i = 0; i < N; i++)
        printf("%d + %d = %d\n", A[i], B[i], C[i]);

    err = clFlush(queue);
    err = clFinish(queue);

    /* Release OpenCL memory objects. */
    clReleaseMemObject( d_A );
    clReleaseMemObject( d_B );
    clReleaseMemObject( d_C );
    free(A);
    free(B);
    free(C);
    clReleaseKernel( kernel );
    clReleaseProgram( program );
    clReleaseCommandQueue( queue );
    clReleaseContext( ctx );

    return 0;
}

下面的程式是使用 stb_image 讀取圖檔,測試 image object 功能的程式。

#include <stdio.h>
#include <stdlib.h>

#ifdef __APPLE__
#include <OpenCL/opencl.h>
#else
#include <CL/cl.h>
#endif

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"

const char *kernel_code =
    "__kernel void PixelAccess(__read_only image2d_t imageIn,__write_only image2d_t imageOut)"
    "{"
    "  sampler_t srcSampler = CLK_NORMALIZED_COORDS_FALSE | "
    "    CLK_ADDRESS_CLAMP_TO_EDGE |"
    "    CLK_FILTER_NEAREST;"
    "  int2 imageCoord = (int2) (get_global_id(0), get_global_id(1));"
    "  uint4 pixel = read_imageui(imageIn, srcSampler, imageCoord);"
    "  write_imageui (imageOut, imageCoord, pixel);"
    "}";


int main( int argc, char *argv[] ) {
    // OpenCL related declarations
    cl_int err;
    cl_platform_id platform;
    cl_device_id device;
    cl_context_properties props[3] = { CL_CONTEXT_PLATFORM, 0, 0 };
    cl_context ctx;
    cl_program program;
    cl_command_queue queue;
    cl_kernel kernel;
    int i;
    int width = 0, height = 0, channel = 0;
    unsigned char *data = NULL;
    const char *filename = NULL;

    if (argc < 2) {
        printf("Please give a filename.\n");
        return 0;
    } else if (argc == 2) {
        filename =  argv[1];
    }

    // Load image data
    data = stbi_load(filename, &width, &height, &channel, 0);
    if(!data) {
        fprintf(stderr, "Open image failed.\n");
        return 0;
    }

    cl_mem myClImageInBuffer;
    cl_mem myClImageOutBuffer;
    cl_sampler sampler;

    cl_image_format format;
    if (channel==4) {
        format.image_channel_order = CL_RGBA;
    } else {
        printf("Not supported image format.\n");
        return 0;
    }
    format.image_channel_data_type = CL_UNSIGNED_INT8;

    err = clGetPlatformIDs( 1, &platform, NULL );
    err = clGetDeviceIDs( platform, CL_DEVICE_TYPE_DEFAULT, 1, &device, NULL );

    cl_bool imageSupport = CL_FALSE;
    clGetDeviceInfo(device, CL_DEVICE_IMAGE_SUPPORT, sizeof(cl_bool),
                    &imageSupport, NULL);

    if (imageSupport != CL_TRUE)
    {
        printf("OpenCL device does not support images.\n");
        return 1;
    }

    props[1] = (cl_context_properties)platform;
    ctx = clCreateContext( props, 1, &device, NULL, NULL, &err );
    queue = clCreateCommandQueueWithProperties( ctx, device, 0, &err );
    program = clCreateProgramWithSource(ctx, 1, (const char **) &kernel_code, NULL, &err);
    err = clBuildProgram(program, 0, NULL, NULL, NULL, NULL);
    kernel = clCreateKernel(program, "PixelAccess", &err);

    //
    // For OpenCL 1.2
    cl_image_desc clImageDesc;
    clImageDesc.image_type = CL_MEM_OBJECT_IMAGE2D;
    clImageDesc.image_width = width;
    clImageDesc.image_height = height;
    clImageDesc.image_row_pitch = 0;
    clImageDesc.image_slice_pitch = 0;
    clImageDesc.num_mip_levels = 0;
    clImageDesc.num_samples = 0;
    clImageDesc.buffer = NULL;

    myClImageInBuffer = clCreateImage(ctx, CL_MEM_READ_ONLY,
                            &format, &clImageDesc, NULL, &err);
    if (!myClImageInBuffer) {
        printf("Create myClImageInBuffer failed.\n");
    }

    myClImageOutBuffer = clCreateImage(ctx, CL_MEM_READ_WRITE,
                            &format, &clImageDesc, NULL, &err);
    if (!myClImageOutBuffer) {
        printf("Create myClImageOutBuffer failed.\n");
    }

    size_t origin[3] = {0, 0, 0};
    size_t region[3] = {width, height, 1};

    err = clEnqueueWriteImage(
            queue, myClImageInBuffer,
            CL_TRUE, origin, region,
            0,
            0, data,
            0, NULL, NULL);

    err = clSetKernelArg(kernel, 0, sizeof(cl_mem), (void *) &myClImageInBuffer);
    err = clSetKernelArg(kernel, 1, sizeof(cl_mem), (void *) &myClImageOutBuffer);

    size_t global_item_size[2] = {width, height};
    size_t local_item_size[2] = {1, 1};

    err = clEnqueueNDRangeKernel(queue, kernel, 2, NULL,
            global_item_size, local_item_size, 0, NULL, NULL);

    err = clFinish(queue);

    unsigned char *data2 = NULL;
    data2 = (unsigned char *) malloc(width * height  * channel);
    err = clEnqueueReadImage( queue,
          myClImageOutBuffer, CL_TRUE,
          origin, region,
          width * sizeof(unsigned char) * 4,
          0, data2,
          0, NULL, NULL);

    err = clFinish(queue);

    stbi_write_png("output.png", width, height, channel, data2, 0);

    free(data2);
    stbi_image_free(data);

    clReleaseMemObject( myClImageInBuffer );
    clReleaseMemObject( myClImageOutBuffer );
    clReleaseKernel( kernel );
    clReleaseProgram( program );
    clReleaseCommandQueue( queue );
    clReleaseContext( ctx );

    return 0;
}

參考連結

Audio library

簡介

如果是輕量的函式庫,可以分為二個部份:

  • 讀取 audio file 並且轉為 raw audio data
  • audio I/O

第一個部份,有二個函式庫可以考慮:

第二個部份,有下面的函式庫可以使用:

如果需要查詢與修改 audio 檔案的 meta-data,可以考慮 TagLib, 因為 TagLib 支援十分廣泛的音訊檔案格式。

libao

LibAO is developed under Xiph umbrella. Xiph is the organization who brought you Ogg/Vorbis, FLAC, Theora and currently they are hammering together next generation video codec Daala.

Opus-audio codec standard is also Xiph project. LibAO rised from Xiph’s need multi-platform audio output library for Vorbis-audio codec.

The libao API makes a distinction between drivers and devices. A driver is a set of functions that allow audio to be played on a particular platform (i.e. Solaris, ESD, etc.). A device is a particular output target that uses a driver. In addition, libao distinguishes between live output drivers, which write audio to playback devices (sound cards, etc.), and file output drivers, which write audio to disk in a particular format.

To use libao in your program, you need to follow these steps:

  • Include the <ao/ao.h> header into your program.
  • Call ao_initialize() to initialize the library. This loads the plugins from disk, reads the libao configuration files, and identifies an appropriate default output driver if none is specified in the configuration files.
  • Call ao_default_driver_id() to get the ID number of the default output driver. This may not be successful if no audio hardware is available, it is in use, or is not in the "standard" configuration. If you want to specify a particular output driver, you may call ao_driver_id() with a string corresponding to the short name of the device (i.e. "oss", "wav", etc.) instead.
  • If you are using the default device, no extra options are needed. However, if you wish to to pass special options to the driver, you will need to:
    • Create an option list pointer of type (ao_option *) and initialize it to NULL.
    • Through successive calls to ao_append_option(), add any driver-specific options you need. Note that the options take the form of key/value pairs where supported keys are listed in the driver documentation.
  • Call ao_open_live() and save the returned device pointer. If you are using a file output driver, you will need to call ao_open_file() instead.
  • Call ao_play() to output each block of audio.
  • Call ao_close() to close the device. Note that this will automatically free the memory that was allocated for the device. Do not attempt to free the device pointer yourself!
  • Call ao_shutdown() to close the library.
下面是一個使用 libao 與 libsndfile 的範例:
#include <ao/ao.h>
#include <signal.h>
#include <sndfile.h>

#define BUFFER_SIZE 8192

int cancel_playback;

void on_cancel_playback(int sig) {
    if (sig != SIGINT) {
        return;
    }

    cancel_playback = 1;
    exit(0);
}

static void clean(ao_device *device, SNDFILE *file) {
    ao_close(device);
    sf_close(file);
    ao_shutdown();
}

int play(const char *filename) {
    ao_device *device;
    ao_sample_format format;
    SF_INFO sfinfo;

    int default_driver;

    short *buffer;

    signal(SIGINT, on_cancel_playback);

    SNDFILE *file = sf_open(filename, SFM_READ, &sfinfo);
    if (file == NULL)
        return -1;

    printf("Samples: %d\n", sfinfo.frames);
    printf("Sample rate: %d\n", sfinfo.samplerate);
    printf("Channels: %d\n", sfinfo.channels);

    ao_initialize();

    default_driver = ao_default_driver_id();

    switch (sfinfo.format & SF_FORMAT_SUBMASK) {
    case SF_FORMAT_PCM_16:
        format.bits = 16;
        break;
    case SF_FORMAT_PCM_24:
        format.bits = 24;
        break;
    case SF_FORMAT_PCM_32:
        format.bits = 32;
        break;
    case SF_FORMAT_PCM_S8:
        format.bits = 8;
        break;
    case SF_FORMAT_PCM_U8:
        format.bits = 8;
        break;
    default:
        format.bits = 16;
        break;
    }

    format.channels = sfinfo.channels;
    format.rate = sfinfo.samplerate;
    format.byte_format = AO_FMT_NATIVE;
    format.matrix = 0;

    device = ao_open_live(default_driver, &format, NULL);

    if (device == NULL) {
        fprintf(stderr, "Error opening device.\n");
        return 1;
    }

    buffer = calloc(BUFFER_SIZE, sizeof(short));

    while (1) {
        int read = sf_read_short(file, buffer, BUFFER_SIZE);

        if (read <= 0) {
            break;
        }

        if (ao_play(device, (char *)buffer, (uint_32)(read * sizeof(short))) ==
            0) {
            printf("ao_play: failed.\n");
            clean(device, file);
            break;
        }

        if (cancel_playback) {
            clean(device, file);
            break;
        }
    }

    clean(device, file);

    return 0;
}

int main(int argc, char *argv[]) {
    if (argc > 1) {
        play(argv[1]);
    }
}
編譯方式:
gcc output.c -lsndfile -lao -o output

再來是 libao 與 libmpg123 的例子:
#include <ao/ao.h>
#include <mpg123.h>

#define BITS 8

int main(int argc, char *argv[]) {
    mpg123_handle *mh;
    unsigned char *buffer;
    size_t buffer_size;
    size_t done;
    int err;

    int driver;
    ao_device *dev;

    ao_sample_format format;
    int channels, encoding;
    long rate;

    if (argc < 2)
        exit(0);

    /* initializations */
    ao_initialize();
    driver = ao_default_driver_id();
    mpg123_init();
    mh = mpg123_new(NULL, &err);
    buffer_size = mpg123_outblock(mh);
    buffer = (unsigned char *)malloc(buffer_size * sizeof(unsigned char));

    /* open the file and get the decoding format */
    mpg123_open(mh, argv[1]);
    mpg123_getformat(mh, &rate, &channels, &encoding);

    /* set the output format and open the output device */
    format.bits = mpg123_encsize(encoding) * BITS;
    format.rate = rate;
    format.channels = channels;
    format.byte_format = AO_FMT_NATIVE;
    format.matrix = 0;
    dev = ao_open_live(driver, &format, NULL);

    /* decode and play */
    while (mpg123_read(mh, buffer, buffer_size, &done) == MPG123_OK)
        ao_play(dev, buffer, done);

    /* clean up */
    free(buffer);
    ao_close(dev);
    mpg123_close(mh);
    mpg123_delete(mh);
    mpg123_exit();
    ao_shutdown();

    return 0;
}

OpenAL

OpenAL (Open Audio Library) is a cross-platform audio application programming interface (API). It is designed for efficient rendering of multichannel three-dimensional positional audio, for creation of a virtual 3D world of sound. Its API style and conventions deliberately resemble those of OpenGL.

The application programmer can specify the location, the speed and the direction of the sources of sounds and of the listener.

Objects

Since OpenAL is about audio, it also introduces new concepts, in particular:
  • the listener object
  • the source object
  • the buffer object

Each of these different objects have properties which can be set with their respective API (al*Object*).

  • context: 要播放聲音的地方,可以想成OpenGL裡面的Window
  • listener: OpenAL 支援3D音效,所以在這裡設定聽者的資料
  • sources: 聲源的資訊
  • buffer: 負責聲源的內容

The very first thing to do is to open a handle to a device. This is done like this:

ALCdevice *device;

device = alcOpenDevice(NULL);
if (!device)
        // handle errors

Prior to attempting an enumeration, Open AL provides an extension querying mechanism which allows you to know whether the runtime Open AL implementation supports a specific extension. In our case, we want to check whether Open AL supports enumerating devices:

ALboolean enumeration;

enumeration = alcIsExtensionPresent(NULL, "ALC_ENUMERATION_EXT");
if (enumeration == AL_FALSE)
        // enumeration not supported
else
        // enumeration supported

If the enumeration extension is supported, we can procede with listing the audio devices. If the enumeration is not supported listing audio devices only returns the default device, which is expected in order not to break any application.

static void list_audio_devices(const ALCchar *devices)
{
        const ALCchar *device = devices, *next = devices + 1;
        size_t len = 0;

        fprintf(stdout, "Devices list:\n");
        fprintf(stdout, "----------\n");
        while (device && *device != '\0' && next && *next != '\0') {
                fprintf(stdout, "%s\n", device);
                len = strlen(device);
                device += (len + 1);
                next += (len + 2);
        }
        fprintf(stdout, "----------\n");
}

list_audio_devices(alcGetString(NULL, ALC_DEVICE_SPECIFIER));

Passsing NULL to alcGetString() indicates that we do not want the device specifier of a particular device, but all of them.


In order to render an audio scene, we need to create and initialize a context for this. We do this by the following calls:

ALCcontext *context;

context = alcCreateContext(device, NULL);
if (!alcMakeContextCurrent(context))
        // failed to make context current
// test for errors here using alGetError();

There is nothing specific for our context, so NULL is specified as argument.

Since there is a default listener, we do not need to explicitely create one because it is already present in our scene. If we want to define some of our listener properties however, we can proceed like this:

ALfloat listenerOri[] = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f };

alListener3f(AL_POSITION, 0, 0, 1.0f);
// check for errors
alListener3f(AL_VELOCITY, 0, 0, 0);
// check for errors
alListenerfv(AL_ORIENTATION, listenerOri);
// check for errors

In order to playback audio, we must create an audio source objet, this source is actually the "origin" of the audio sound. And as such must be defined in the audio scene. If you combine audio with graphics, most likely quite a lot of your graphics objects will also include an audio source object.

Note that you hold a reference (id) to a source object, you don’t manipulate the source object directly.

ALuint source;


alGenSources((ALuint)1, &source);
// check for errors

alSourcef(source, AL_PITCH, 1);
// check for errors
alSourcef(source, AL_GAIN, 1);
// check for errors
alSource3f(source, AL_POSITION, 0, 0, 0);
// check for errors
alSource3f(source, AL_VELOCITY, 0, 0, 0);
// check for errors
alSourcei(source, AL_LOOPING, AL_FALSE);
// check for errros

The buffer object is the object actually holding the raw audio stream, alone a buffer does not do much but occupying memory, so we will see later on what to do with it. Just like sources, we hold a reference to the buffer object.

ALuint buffer;

alGenBuffers((ALuint)1, &buffer);
// check for errors

下面就是載入 Audio raw data 以後的使用範例:

static inline ALenum to_al_format(short channels, short samples)
{
        bool stereo = (channels > 1);

        switch (samples) {
        case 16:
                if (stereo)
                        return AL_FORMAT_STEREO16;
                else
                        return AL_FORMAT_MONO16;
        case 8:
                if (stereo)
                        return AL_FORMAT_STEREO8;
                else
                        return AL_FORMAT_MONO8;
        default:
                return -1;
        }
}

alBufferData(buffer, to_al_format(wave->channels, wave->bitsPerSample),
                bufferData, wave->dataSize, wave->sampleRate);
// check for errors

In order to actually output something to the playback device, we need to bind the source with its buffer. Obviously you can bind the same buffer to several sources and mix different buffers to the same source. Binding is done like this:

alSourcei(source, AL_BUFFER, buffer);
// check for errors

We now have everything ready to start playing our source.

alSourcePlay(source);
// check for errors

alGetSourcei(source, AL_SOURCE_STATE, &source_state);
// check for errors
while (source_state == AL_PLAYING) {
        alGetSourcei(source, AL_SOURCE_STATE, &source_state);
        // check for errors
}

Obviously each and every single object "generated" must be freed, the following does this for us:

alDeleteSources(1, &source);
alDeleteBuffers(1, &buffer);
device = alcGetContextsDevice(context);
alcMakeContextCurrent(NULL);
alcDestroyContext(context);
alcCloseDevice(device);

問題與解法

Fix jack server is not running or cannot be started message (for openal-soft)

如果你在 OpenSUSE Leap 15.0 使用 OpenAL 時發現會出現 jack server is not running or cannot be started 的訊息, 這是因為 openal-soft 支援 jack,所以會嘗試進行初始化的動作。

解決的方法是在 /etc/openal 下新增加 alsoft.conf,並且加入下面的內容:
[general]
drivers = -jack,

Libao debug message

在 OpenSUSE Tumbleweed 中預設的 driver 是 PulseAudio,但是仍然會嘗試載入 ALSA driver 並且印出錯誤訊息。 如果遇到這個情況想要停止印出錯誤訊息,修改 /etc/libao.conf,加入一行 quiet

default_driver=pulse
quiet

參考連結

OpenCV

簡介

OpenCV 的全稱是 Open Source Computer Vision Library, 是一個跨平台的電腦視覺庫,由英特爾公司發起並參與開發。

在 openSUSE Tumbleweed 安裝:

sudo zypper in opencv-devel

安裝完成以後,接下來進行是否安裝成功的測試。下面是一個顯示圖片的 display.cpp

#include <stdio.h>
#include <opencv2/opencv.hpp>

int main(int argc, char* argv[]) {

  if ( argc != 2 ) {
    printf("usage: display <File_Path>\n");
    return -1;
  }

  cv::Mat image = cv::imread( argv[1], 1 );

  if ( !image.data ) {
    printf("No image data\n");
    return -1;
  }

  cv::namedWindow("Display Image", cv::WINDOW_AUTOSIZE);
  cv::imshow("Display Image", image);
  cv::waitKey(0);
  cv::destroyWindow("Display Image");

  return 0;
}

如果直接編譯,使用:

g++ display.cpp -lopencv_core -lopencv_imgproc -lopencv_imgcodecs -lopencv_highgui -o display

或者是使用 pkg-config,假設是 OpenCV 4,那麼使用下列的指令:

g++ display.cpp `pkg-config opencv4 --libs` -o display

或者使用 CMake。這個例子所需要的 CMake 設定檔 CMakeLists.txt 內容如下:

cmake_minimum_required(VERSION 2.8)
project( DisplayImage )
find_package( OpenCV REQUIRED )
add_executable( display display.cpp )
target_link_libraries( display ${OpenCV_LIBS} )

接下來使用下列的指令編譯:

cmake .
make

要注意的是,OpenCL 預設使用的 color model 為 BGR,而一般流行使用的 color model 為 RGB。

還有的 color model 為 LAB:

  • L – Lightness ( Intensity ).
  • a – color component ranging from Green to Magenta.
  • b – color component ranging from Blue to Yellow.
cv::cvtColor(bright, brightLAB, cv::COLOR_BGR2LAB);

The YCrCb color space is derived from the RGB color space and has the following three compoenents.

  • Y – Luminance or Luma component obtained from RGB after gamma correction.
  • Cr = R – Y ( how far is the red component from Luma ).
  • Cb = B – Y ( how far is the blue component from Luma ).
cv::cvtColor(bright, brightYCB, cv::COLOR_BGR2YCrCb);

The HSV color space has the following three components

  • H – Hue ( Dominant Wavelength ).
  • S – Saturation ( Purity / shades of the color ).
  • V – Value ( Intensity ).
cv::cvtColor(bright, brightHSV, cv::COLOR_BGR2HSV);

要注意的是,要觀察圖片的軌跡或者是偵測線條(edge detection)時,很常用的手法是將圖轉為灰階,然後再使用各種演算法取得結果。


再來是建立一個空白的 image 並且顯示:

#include <opencv2/opencv.hpp>
#include <string>

using namespace cv;
using namespace std;

int main(int argc, char** argv)
{
  Mat image(600, 800, CV_8UC3, Scalar(200, 150, 130));

  string windowName = "Window with Blank Image";

  namedWindow(windowName);
  imshow(windowName, image);
  waitKey(0);
  destroyWindow(windowName);

  return 0;
}

再來是讀取 Vedio 檔案並且播放的範例:

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;
using namespace cv;

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        cout << "Please give a vedio file." << endl;
        return 0;
    }

    VideoCapture vid_capture(argv[1]);

    if (!vid_capture.isOpened())
    {
        cout << "Error opening video stream or file" << endl;
    }

    else
    {
        int fps = vid_capture.get(CAP_PROP_FPS);
        cout << "Frames per second :" << fps;

        int frame_count = vid_capture.get(CAP_PROP_FRAME_COUNT);
        cout << "  Frame count :" << frame_count;
    }

    while (vid_capture.isOpened())
    {
        Mat frame;

        bool isSuccess = vid_capture.read(frame);

        if (isSuccess == true)
        {
            imshow("Frame", frame);
        }

        if (isSuccess == false)
        {
            cout << "Video camera is disconnected" << endl;
            break;
        }

        int key = waitKey(20);
        if (key == 'q')
        {
            cout << "q key is pressed by the user. Stopping the video" << endl;
            break;
        }
    }

    vid_capture.release();
    destroyAllWindows();
    return 0;
}

下面是 image resize 的例子:

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;
using namespace cv;

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        cout << "Please give a file." << endl;
        return 0;
    }

    Mat image = imread(argv[1]);
    imshow("Original Image", image);

    int down_width = 300;
    int down_height = 200;
    Mat resized_down;
    resize(image, resized_down, Size(down_width, down_height), INTER_LINEAR);

    int up_width = 600;
    int up_height = 400;
    Mat resized_up;
    resize(image, resized_up, Size(up_width, up_height), INTER_LINEAR);

    imshow("Resized Down by defining height and width", resized_down);
    waitKey();
    imshow("Resized Up image by defining height and width", resized_up);
    waitKey();

    destroyAllWindows();
    return 0;
}

下面是 crop image 的例子:

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;
using namespace cv;

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        cout << "Please give a file." << endl;
        return 0;
    }

    Mat img = imread(argv[1]);
    cout << "Width : " << img.size().width << endl;
    cout << "Height: " << img.size().height << endl;
    cout << "Channels: :" << img.channels() << endl;

    int firstx = img.size().width / 4;
    int firsty = img.size().height / 4;
    int secondx = img.size().width / 4 * 3;
    int secondy = img.size().height / 4 * 3;

    Mat cropped_image = img(Range(firsty, secondy), Range(firstx, secondx));

    imshow(" Original Image", img);
    imshow("Cropped Image", cropped_image);

    //Save the cropped Image
    imwrite("CroppedImage.jpg", cropped_image);

    waitKey(0);
    destroyAllWindows();
    return 0;
}

下面是 image rotation 的例子:

#include <iostream>
#include <opencv2/opencv.hpp>

using namespace cv;
using namespace std;

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        cout << "Please give a file." << endl;
        return 0;
    }

    Mat image = imread(argv[1]);
    imshow("image", image);
    waitKey(0);
    double angle = 45;

    Point2f center((image.cols - 1) / 2.0, (image.rows - 1) / 2.0);
    Mat rotation_matix = getRotationMatrix2D(center, angle, 1.0);

    Mat rotated_image;
    // rotate the image using warpAffine
    warpAffine(image, rotated_image, rotation_matix, image.size());
    imshow("Rotated image", rotated_image);
    waitKey(0);
    imwrite("rotated_im.jpg", rotated_image);

    return 0;
}

當我們對圖片加上某個常數,就是增加亮度或者是減少亮度。例如下面的式子是增加亮度:

new_image (i, j) = image(i, j) + c

所以我們可以這樣寫:

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        cout << "Please give a file." << endl;
        return 0;
    }

    Mat image = imread(argv[1]);

    if (image.empty())
    {
        cout << "Could not open or find the image" << endl;
        return 0;
    }

    Mat imageBrighnessHigh50;
    image.convertTo(imageBrighnessHigh50, -1, 1, 50); //increase the brightness by 50

    Mat imageBrighnessHigh100;
    image.convertTo(imageBrighnessHigh100, -1, 1, 100);

    Mat imageBrighnessLow50;
    image.convertTo(imageBrighnessLow50, -1, 1, -50); //decrease the brightness by 50

    Mat imageBrighnessLow100;
    image.convertTo(imageBrighnessLow100, -1, 1, -100);

    String windowNameOriginalImage = "Original Image";
    String windowNameBrightnessHigh50 = "Brightness Increased by 50";
    String windowNameWithBrightnessHigh100 = "Brightness Increased by 100";
    String windowNameBrightnessLow50 = "Brightness Decreased by 50";
    String windowNameBrightnessLow100 = "Brightness Decreased by 100";

    namedWindow(windowNameOriginalImage, WINDOW_NORMAL);
    namedWindow(windowNameBrightnessHigh50, WINDOW_NORMAL);
    namedWindow(windowNameWithBrightnessHigh100, WINDOW_NORMAL);
    namedWindow(windowNameBrightnessLow50, WINDOW_NORMAL);
    namedWindow(windowNameBrightnessLow100, WINDOW_NORMAL);

    imshow(windowNameOriginalImage, image);
    imshow(windowNameBrightnessHigh50, imageBrighnessHigh50);
    imshow(windowNameWithBrightnessHigh100, imageBrighnessHigh100);
    imshow(windowNameBrightnessLow50, imageBrighnessLow50);
    imshow(windowNameBrightnessLow100, imageBrighnessLow100);

    waitKey(0);
    destroyAllWindows();

    return 0;
}

對於 Contrast 來說,就是乘以某個數(> 1 就是增加對比,1 > c > 0 就是減少對比)。

new_image (i, j) = image(i, j) * c

所以我們可以這樣寫:

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        cout << "Please give a file." << endl;
        return 0;
    }

    Mat image = imread(argv[1]);
    if (image.empty())
    {
        cout << "Could not open or find the image" << endl;
        cin.get();
        return 0;
    }

    Mat imageContrastHigh2;
    image.convertTo(imageContrastHigh2, -1, 2, 0); //increase the contrast by 2

    Mat imageContrastHigh4;
    image.convertTo(imageContrastHigh4, -1, 4, 0);

    Mat imageContrastLow0_5;
    image.convertTo(imageContrastLow0_5, -1, 0.5, 0);

    Mat imageContrastLow0_25;
    image.convertTo(imageContrastLow0_25, -1, 0.25, 0);

    String windowNameOriginalImage = "Original Image";
    String windowNameContrastHigh2 = "Contrast Increased by 2";
    String windowNameContrastHigh4 = "Contrast Increased by 4";
    String windowNameContrastLow0_5 = "Contrast Decreased by 0.5";
    String windowNameContrastLow0_25 = "Contrast Decreased by 0.25";

    namedWindow(windowNameOriginalImage, WINDOW_NORMAL);
    namedWindow(windowNameContrastHigh2, WINDOW_NORMAL);
    namedWindow(windowNameContrastHigh4, WINDOW_NORMAL);
    namedWindow(windowNameContrastLow0_5, WINDOW_NORMAL);
    namedWindow(windowNameContrastLow0_25, WINDOW_NORMAL);

    imshow(windowNameOriginalImage, image);
    imshow(windowNameContrastHigh2, imageContrastHigh2);
    imshow(windowNameContrastHigh4, imageContrastHigh4);
    imshow(windowNameContrastLow0_5, imageContrastLow0_5);
    imshow(windowNameContrastLow0_25, imageContrastLow0_25);

    waitKey(0);
    destroyAllWindows();

    return 0;
}

OpenCV 也具有繪圖(例如畫線、畫圓,以及加上文字等)的能力。下面是一個例子。

// Import dependencies
#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;
using namespace cv;
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        cout << "Please give a file." << endl;
        return 0;
    }

    Mat img = imread(argv[1]);
    imshow("Original Image", img);
    waitKey();
    if (img.empty())
    {
        cout << "Could not read image" << endl;
    }

    Mat imageChanged = img.clone();
    Point pointA(350, 370);
    Point pointB(500, 370);
    line(imageChanged, pointA, pointB, Scalar(255, 255, 0), 3, 8, 0);

    Point circle_center(420, 130);
    int radius = 150;
    circle(imageChanged, circle_center, radius, Scalar(0, 0, 255), 3, 8, 0);

    putText(imageChanged, "The best!", Point(50, 500), FONT_HERSHEY_COMPLEX,
            1.5, Scalar(250, 225, 100));

    imshow("Changed Image", imageChanged);
    waitKey();

    // save image
    imwrite("ChangedImage.jpg", imageChanged);
    destroyAllWindows();

    return 0;
}

下面是輸出的結果:


OpenCV 提供了函數 cv::threshold(),可以用來執行圖像二值化的工作。 圖像的二值化就是將圖像上的像素點的灰度值設置為 0 或 255,這樣將使整個圖像呈現出明顯的黑白效果。 在圖像處理中,二值圖像佔有非常重要的地位,圖像的二值化使圖像的數據大為減少,從而凸顯出目標的輪廓。

#include "opencv2/opencv.hpp"

using namespace cv;
using namespace std;

int main( int argc, char** argv )
{
    if (argc != 2)
    {
        cout << "Please give a file." << endl;
        return 0;
    }

    Mat src = imread(argv[1], IMREAD_GRAYSCALE);
    Mat dst;


    // Basic threhold example
    threshold(src,dst,0, 255, THRESH_BINARY);
    imwrite("opencv-threshold-example.jpg", dst);

    // Thresholding with maxval set to 128
    threshold(src, dst, 0, 128, THRESH_BINARY);
    imwrite("opencv-thresh-binary-maxval.jpg", dst);

    // Thresholding with threshold value set 127
    threshold(src,dst,127,255, THRESH_BINARY);
    imwrite("opencv-thresh-binary.jpg", dst);

    // Thresholding using THRESH_BINARY_INV
    threshold(src,dst,127,255, THRESH_BINARY_INV);
    imwrite("opencv-thresh-binary-inv.jpg", dst);

    // Thresholding using THRESH_TRUNC
    threshold(src,dst,127,255, THRESH_TRUNC);
    imwrite("opencv-thresh-trunc.jpg", dst);

    // Thresholding using THRESH_TOZERO
    threshold(src,dst,127,255, THRESH_TOZERO);
    imwrite("opencv-thresh-tozero.jpg", dst);

    // Thresholding using THRESH_TOZERO_INV
    threshold(src,dst,127,255, THRESH_TOZERO_INV);
    imwrite("opencv-thresh-to-zero-inv.jpg", dst);
}

Histogram of an image is the graphical representation of the distribution of intensities of pixels. It provides an estimate of where pixel values are concentrated and whether there are unusual deviations.

#include "opencv2/opencv.hpp"

using namespace cv;
using namespace std;

int main( int argc, char** argv )
{
    if (argc != 2)
    {
        cout << "Please give a file." << endl;
        return 0;
    }

    Mat image = imread(argv[1]);

    if (image.empty())
    {
        cout << "Could not open or find the image" << endl;
        return 0;
    }

    cvtColor(image, image, COLOR_BGR2GRAY);

    Mat hist_equalized_image;
    equalizeHist(image, hist_equalized_image);

    String windowNameOfOriginalImage = "Original Image";
    String windowNameOfHistogramEqualized = "Histogram Equalized Image";

    namedWindow(windowNameOfOriginalImage, WINDOW_NORMAL);
    namedWindow(windowNameOfHistogramEqualized, WINDOW_NORMAL);

    imshow(windowNameOfOriginalImage, image);
    imshow(windowNameOfHistogramEqualized, hist_equalized_image);

    waitKey(0);
    destroyAllWindows();
}

下面是針對彩色的範例:

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;
using namespace cv;

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        cout << "Please give a file." << endl;
        return 0;
    }

    Mat image = imread(argv[1]);
    if (image.empty())
    {
        cout << "Could not read image" << endl;
    }

    Mat hist_equalized_image;
    cvtColor(image, hist_equalized_image, COLOR_BGR2YCrCb);

    vector<Mat> vec_channels;
    split(hist_equalized_image, vec_channels);

    equalizeHist(vec_channels[0], vec_channels[0]);

    merge(vec_channels, hist_equalized_image);

    cvtColor(hist_equalized_image, hist_equalized_image, COLOR_YCrCb2BGR);

    String windowNameOfOriginalImage = "Original Image";
    String windowNameOfHistogramEqualized = "Histogram Equalized Color Image";

    namedWindow(windowNameOfOriginalImage, WINDOW_NORMAL);
    namedWindow(windowNameOfHistogramEqualized, WINDOW_NORMAL);

    imshow(windowNameOfOriginalImage, image);
    imshow(windowNameOfHistogramEqualized, hist_equalized_image);

    waitKey(0); // Wait for any keystroke in any one of the windows

    destroyAllWindows(); //Destroy all opened windows

    return 0;
}

equalizeHist 在一些情況下會製造雜訊,也可以使用 CLAHE 來作為改進的演算法:

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;
using namespace cv;

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        cout << "Please give a file." << endl;
        return 0;
    }

    Mat image = imread(argv[1]);
    if (image.empty())
    {
        cout << "Could not read image" << endl;
    }

    Mat hist_equalized_image;
    cvtColor(image, hist_equalized_image, COLOR_BGR2YCrCb);

    vector<Mat> vec_channels;
    Ptr<CLAHE> clahe = createCLAHE();
    split(hist_equalized_image, vec_channels);

    clahe->apply(vec_channels[0], vec_channels[0]);

    merge(vec_channels, hist_equalized_image);

    cvtColor(hist_equalized_image, hist_equalized_image, COLOR_YCrCb2BGR);

    String windowNameOfOriginalImage = "Original Image";
    String windowNameOfHistogramEqualized = "Histogram Equalized Color Image";

    namedWindow(windowNameOfOriginalImage, WINDOW_NORMAL);
    namedWindow(windowNameOfHistogramEqualized, WINDOW_NORMAL);

    imshow(windowNameOfOriginalImage, image);
    imshow(windowNameOfHistogramEqualized, hist_equalized_image);

    waitKey(0); // Wait for any keystroke in any one of the windows

    destroyAllWindows(); //Destroy all opened windows

    return 0;
}

Edge detection is an image-processing technique, which is used to identify the boundaries (edges) of objects, or regions within an image.

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;
using namespace cv;

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        cout << "Please give a file." << endl;
        return 0;
    }

    Mat img = imread(argv[1]);

    if (img.empty())
    {
        cout << "Could not open or find the image" << endl;
        return 0;
    }
    imshow("original Image", img);
    waitKey(0);

    Mat img_gray;
    cvtColor(img, img_gray, COLOR_BGR2GRAY);
    Mat img_blur;
    GaussianBlur(img_gray, img_blur, Size(3,3), 0);

    Mat sobelx, sobely, sobelxy;
    Sobel(img_blur, sobelx, CV_64F, 1, 0, 5);
    Sobel(img_blur, sobely, CV_64F, 0, 1, 5);
    Sobel(img_blur, sobelxy, CV_64F, 1, 1, 5);
    imshow("Sobel X", sobelx);
    waitKey(0);
    imshow("Sobel Y", sobely);
    waitKey(0);
    imshow("Sobel XY using Sobel() function", sobelxy);
    waitKey(0);

    Mat edges;
    Canny(img_blur, edges, 100, 200, 3, false);
    imshow("Canny edge detection", edges);
    waitKey(0);

    destroyAllWindows();
    return 0;
}

The image filtering is a neighborhood operation in which the value of any given pixel in the output image is determined by applying a certain algorithm to the pixel values ​​in the vicinity of the corresponding input pixel.

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;
using namespace cv;

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        cout << "Please give a file." << endl;
        return 0;
    }

    Mat image = imread(argv[1]);
    if (image.empty())
    {
        cout << "Could not read image" << endl;
    }

    Mat image_blurred_with_3x3_kernel;
    blur(image, image_blurred_with_3x3_kernel, Size(3, 3));

    Mat image_blurred_with_5x5_kernel;
    blur(image, image_blurred_with_5x5_kernel, Size(5, 5));

    String window_name = "Display";
    String window_name_blurred_with_3x3_kernel = "Display Blurred with 3 x 3 Kernel";
    String window_name_blurred_with_5x5_kernel = "Display Blurred with 5 x 5 Kernel";

    namedWindow(window_name);
    namedWindow(window_name_blurred_with_3x3_kernel);
    namedWindow(window_name_blurred_with_5x5_kernel);

    imshow(window_name, image);
    imshow(window_name_blurred_with_3x3_kernel, image_blurred_with_3x3_kernel);
    imshow(window_name_blurred_with_5x5_kernel, image_blurred_with_5x5_kernel);

    waitKey(0);
    destroyAllWindows();

    return 0;
}

下面是另外的例子:

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;
using namespace cv;

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        cout << "Please give a file." << endl;
        return 0;
    }

    Mat image = imread(argv[1]);
    if (image.empty())
    {
        cout << "Could not read image" << endl;
    }

    Mat kernel1 = (Mat_<double>(3, 3) << 0, 0, 0, 0, 1, 0, 0, 0, 0);
    Mat identity;
    filter2D(image, identity, -1, kernel1, Point(-1, -1), 0, 4);
    imshow("Original", image);
    imshow("Identity", identity);
    waitKey();
    imwrite("identity.jpg", identity);
    destroyAllWindows();

    Mat kernel2 = Mat::ones(5, 5, CV_64F);
    kernel2 = kernel2 / 25;
    Mat img;
    filter2D(image, img, -1, kernel2, Point(-1, -1), 0, 4);
    imshow("Original", image);
    imshow("Kernel blur", img);
    imwrite("blur_kernel.jpg", img);
    waitKey();
    destroyAllWindows();
}

下面是 mouse callback function 的例子:

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace std;
using namespace cv;

void CallBackFunc(int event, int x, int y, int flags, void *userdata)
{
    if (event == EVENT_LBUTTONDOWN)
    {
        cout << "Left button of the mouse is clicked - position (" << x << ", " << y << ")" << endl;
    }
    else if (event == EVENT_RBUTTONDOWN)
    {
        cout << "Right button of the mouse is clicked - position (" << x << ", " << y << ")" << endl;
    }
    else if (event == EVENT_MBUTTONDOWN)
    {
        cout << "Middle button of the mouse is clicked - position (" << x << ", " << y << ")" << endl;
    }
    else if (event == EVENT_MOUSEMOVE)
    {
        cout << "Mouse move over the window - position (" << x << ", " << y << ")" << endl;
    }
}

int main(int argc, char **argv)
{
    if (argc != 2)
    {
        cout << "Please give a file." << endl;
        return 0;
    }

    Mat image = imread(argv[1]);
    if (image.empty())
    {
        cout << "Could not read image" << endl;
    }

    namedWindow("My Window", 1);
    setMouseCallback("My Window", CallBackFunc, NULL);
    imshow("My Window", image);
    waitKey(0);
    destroyAllWindows();
    return 0;
}

使用 SimpleBlobDetector 執行 Blob detection:

#include <opencv2/opencv.hpp>
#include <iostream>

using namespace cv;
using namespace std;

int main(int argc, char *argv[])
{
    Mat im = imread("blob.jpg", IMREAD_GRAYSCALE);

    // Setup SimpleBlobDetector parameters.
    SimpleBlobDetector::Params params;

    // Change thresholds
    params.minThreshold = 10;
    params.maxThreshold = 200;

    // Filter by Area.
    //params.filterByArea = true;
    //params.minArea = 1500;

    // Filter by Circularity
    //params.filterByCircularity = true;
    //params.minCircularity = 0.1;

    // Filter by Convexity
    params.filterByConvexity = true;
    params.minConvexity = 0.5;

    // Filter by Inertia
    //params.filterByInertia = true;
    //params.minInertiaRatio = 0.01;

    Ptr<SimpleBlobDetector> detector = SimpleBlobDetector::create(params);

    std::vector<KeyPoint> keypoints;
    detector->detect(im, keypoints);

    Mat im_with_keypoints;
    drawKeypoints(im, keypoints, im_with_keypoints, Scalar(0, 0, 255), DrawMatchesFlags::DRAW_RICH_KEYPOINTS);

    namedWindow("keypoints", 1);
    imshow("keypoints", im_with_keypoints);
    waitKey(0);
}

下面是針對單一圖片的 Face Detection 例子。如果要改寫成使用 camera,那就是你拿到 frame 以後,使用同樣的方式去偵測。

#include "opencv2/objdetect.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include <iostream>

using namespace std;
using namespace cv;

CascadeClassifier face_cascade;

int main( int argc, const char** argv )
{
    if ( argc != 2 ) {
        printf("usage: display <File_Path>\n");
        return -1;
    }

    /*
     * From https://github.com/opencv/opencv/tree/master/data/haarcascades
     */
    string face_cascade_name("haarcascade_frontalface_default.xml");

    if( !face_cascade.load( face_cascade_name ) )
    {
        cout << "--(!)Error loading face cascade\n";
        return -1;
    };

    cv::Mat frame = cv::imread( argv[1], 1 );
    if ( !frame.data ) {
        printf("No image data\n");
        return -1;
    }
    cv::namedWindow("Display", cv::WINDOW_AUTOSIZE);

    Mat frame_gray;
    cvtColor( frame, frame_gray, COLOR_BGR2GRAY );
    equalizeHist( frame_gray, frame_gray );

    std::vector<Rect> faces;
    face_cascade.detectMultiScale( frame_gray, faces );
    cout << "Find: " << faces.size() << endl;
    for (auto&& feature : faces) {
        cv::rectangle(frame, feature, cv::Scalar(0, 0, 255), 2);
    }

    imshow( "Display", frame );
    waitKey(0);
    return 0;
}

參考連結

2024/04/16

KDE Plasma

Linux(或者也可以說是 UNIX-like)系統在桌面環境上擁有非常多的選擇,而 KDE Plasma 是其中一個。

KDE Plasma 基於 Qt 框架所開發, 有一些 Linux 發行版的預設桌面環境是 KDE Plasma,為 Linux 最流行的幾個桌面環境之一。 因為 KDE 所採用的 Qt 框架一開始並非自由軟體, 導致了 GNOME 專案的誔生(在 GNOME 專案誔生之後,Qt 框架核心改為雙重授權, 因此自由軟體可以使用 GPL 授權的 Qt 框架,而在之後又加入可以選擇 LGPL 授權,但是因為非核心模組之間的授權不一, 如果使用 Qt 框架開發商業軟體需要仔細研究每個使用模組的授權以避免踩雷)。 而 GNOME 2 到 GNOME 3 引進了激烈的變動,又產生了幾個桌面環境的專案, 包含不贊同 GNOME 3 的改變而延續 GNOME 2 開發的 MATE, 以及基於 GNOME 3 原始碼實現 GNOME 2 功能的 Cinnamon

另外還有 Xfce,設計目的是「設計為可作為實際應用,快速載入及執行程式,並減少耗用系統資源」, 原本使用 XForms,之後改用 GTK 開發,是一個較為輕量但仍然提供豐富功能的桌面環境。

雖然 Xfce 已經比 KDE 與 GNOME 更為輕量,但是如果需要資源需求更少的視窗管理員, 可以考慮 IceWM

就我個人而言,我認為 GNOME 3 過度激烈的變動產生了很多問題,因此導致了很多重覆開發的桌面環境, 雖然使用者有很多選擇,但是也造成了開發資源上的浪費。

X11 and Wayland

目前 KDE Plasma 支援 X11 以及 Wayland 協定,openSUSE Tumbleweed 目前的預設為 X11(未來預設可能換成 Wayland)。 如果只是要暫時切換,logout 以後找到選擇 session 設定的地方設定即可。

首先確認 Display Manager 為 SDDM,如果是, 那麼如果要將登入的時候預設選項改為 Wayland,將 /etc/sddm.conf.d 目錄下的 kde_settings.conf 從 default 改為下列的值:

[Autologin]
Relogin=false
Session=plasmawayland
User=

有效的選項可以查看 /usr/share/xsessions 以及 /usr/share/wayland-sessions 二個目錄下的設定。

如果要查看目前的設定,在命令列使用下列的指令查看:

echo $XDG_SESSION_TYPE

PulseAudio and PipeWire

二者我都有用過,都工作的很好。

PipeWire 是目前 openSUSE Tumbleweed 的預設值 (在 20220708 之後,不過如果是從 openSUSE Leap 升上來的,如果使用者沒有手動變更,便用的仍然是 PulseAudio)。 就我來說,建議使用 PipeWire,因為提供了 PulseAudio 相容的軟體介面,但是延遲較低且通常使用的資源比較少。

下面是安裝的指令:

sudo zypper in pipewire-pulseaudio pipewire-alsa

要注意與 PulseAudio 無法共存,所以要選擇 deinstallation PulseAudio 的套件。

輸入法

目前 Linux 主流的輸入法框架為 IBus (Intelligent Input Bus), 以及 Fcitx (Free Chinese Input Tool for X)。

因為 Linux 是個自由的環境,所以關於輸入法的問題可能在各個層面產生。 就目前而言,輸入法框架我會比較推薦使用 Fcitx 5。 我之前使用的輸入法框架為 IBus,因為我是行列輸入法的使用者, 而 IBus 有 ibus-array, 相對符合我的使用習慣。不過因為 Fcitx 5 提供了足夠好的 sample code, 所以在我自己寫出來 fcitx5-array 之後, 目前我已經切換到 Fcitx 5,只有少數時候會使用 IBus。

Plasma desktop behaves strangely

Plasma problems are usually caused by unstable Plasma widgets (colloquially called plasmoids) or Plasma themes. First, find which was the last widget or theme you had installed and disable or uninstall it.

If you cannot find the problem, but you do not want all the settings to be lost, navigate to ~/.config/ and run the following command:

for j in plasma*; do mv -- "$j" "${j%}.bak"; done

Edit KDE Application Launcher Menus

使用 kmenuedit 這個程式編輯。

KDE File Associations

You can start this module by opening System Settings and selecting File Associations in the Common Appearance and Behavior category. Alternatively, you can start it by typing kcmshell6 filetypes from the terminal or KRunner.

Bookmarks Editor

KDE 一些程式(例如 Konsole)的 Edit Bookmarks 使用 keditbookmarks 實作, 如果沒有安裝,在選單上就不會出現相關的選項。

Double click

修改 ~/.config/kdeglobals,找到 [KDE],然後加入下面的設定:

SingleClick=false
就可以設定要 double click 才會啟動程式或者是開啟檔案。

Disable KWallet

KDE 錢包(KWallet)是一款密碼管理加密服務,可以幫助你記住所有的密碼,並加密確保密碼的安全。 你只需要將要記住的密碼加入到 KWallet 中,KWallet 會將儲存密碼加密,確保安全性。當軟體需要輸入密碼時會自動呼叫 KWallet, 輸入錢包密碼後,就會自動完成輸入密碼的工作。

如果你不需要 KWallet 並且想要永久停止 KWallet,編輯~/.config/kwalletrc

[Wallet]
Enabled=false

PPPOE (DSL setting)

使用 KDE Network Manager 管理連線,需要在 YaST Network setting 將設定網路的管理者設為 Network Manager

接下來在 KDE Network Manager 新增加連線。
按下 Create button,下一步是設定服務商所提供的 Username 與 Password。

儲存以後就可以試著連線看設定是否正確。

我的應用程式列表

程式用途 程式名稱 備註
檔案管理員 Dolphin

使用 KDE 內建的檔案管理員,支援分割畫面功能。

網頁瀏覽器 Mozilla Firefox

Firefox 支援佈景主題 (Themes),我偏好使用 Dark 佈景主題。

下面是我目前有在使用的套件:

  • uBlock Origin: 高效率的廣告攔截工具
  • Decentraleyes: 用於保護使用者免遭集中的內容交付網路(CDN)的跟蹤
  • Privacy Badger: 用於阻止那些不遵守 DNT 協定的廣告商跟蹤行為
  • Flagfox: 以旗幟圖示顯示出目前網頁伺服器實際位置的擴充套件
  • Livemarks: Get auto-updated RSS feed bookmark folders
BitTorrent 客戶端 KTorrent

與 KGet 使用相同的 BitTorrent 函式庫,所以也可以考慮使用 KGet。

辦公室軟體 LibreOffice

LibreOffice 套件包含文書處理器、電子試算表、演示文稿程式、向量圖形編輯器和圖表工具、 資料庫管理程式及建立和編輯數學公式的應用程式。

文字編輯器 Kate

Settings->Configure Kate->Appearance->Borders->Show minimap 不要勾選選項
Settings->Configure Kate->Open/Save->Remove trailing spaces:選擇 Never
同時我習慣取消 LSP Client plugin。

數位影像繪圖 GIMP

跨平台的影像編輯軟體。

向量圖型編輯 Inkscape

跨平台的向量圖型編輯軟體。

文件檢視器 Okular

用來閱讀 PDF 文件。CHM 文件支援部份 openSUESE 沒有加入編譯選項,需要安裝 KchmViewer。
PDF 檔案也可以使用內建 pdf.js 的 Firefox 閱讀。

圖像瀏覽器 Gwenview

目前預設的圖像瀏覽器

音樂播放器 Elisa

KDE 的另外一個選擇是 Amarok,都是不錯的音樂播放器。

多媒體播放器 VLC media player

跨平台的多媒體播放器

虛擬機器 Vistual Box 安裝後需要加入使用者到 vboxusers 群組(以帳號 danilo 為例):
sudo usermod -a -G vboxusers danilo
檢查是否加入成功:
groups danilo
加入群組成功後,還需要重開機一次才會真的生效。

如果要移除,首先將使用者從群組中移除:
sudo gpasswd -d danilo vboxusers
再來移除軟體:
sudo zypper remove --clean-deps virtualbox

2024/04/09

w3m browser

w3m 是由日本東北大學教授伊藤彰則等人開發的文字網頁瀏覽器。 w3m 支援表格、框架、SSL連線、顏色,但是不支援 CSS 樣式與 JavaScript。

使用下列的指令在 openSUSE Tumbleweed 下安裝:

sudo zypper in w3m

(如果需要 inline-image,還要安裝 w3m-inline-image)


上下與 Page Up/Page Down 可以用來瀏覽網頁,TAB 鍵可以在連結之間跳耀。
左鍵與右鍵可以在瀏覽網頁時往前一頁或者是往後一頁。使用 Enter 選擇連結可以前往選擇的網址。
在網頁需要輸入文字的地方,需要按 Enter,然後才輸入文字。
按 U 鍵可以輸入 URL 並且前往新的網址。
按 V 鍵可以輸入檔案名稱並且瀏覽檔案。
按 R 鍵可以重讀網頁。
使用 / 可以在網頁搜尋。
按 T 鍵可以新開一個 browser tab,使用 { 與 } 可以在 tabs 之間切換(或者是 Shift + [ 與 Shift + ])。
也可以對一個連結按 Ctrl-t,這樣就會使用這個連結開啟新的 browser tab。
如果要關閉 browser tab,使用 Ctrl-q 關閉。
當我們要退出此程序時,只需按 q 鍵,然後接著 y 確認退出。(也可以使用 Q 鍵,不過這樣就不會有確認訊息)

另外,可以藉由設定 HTTP_HOME 或者 WWW_HOME 環境變數, 這樣執行 w3m 但是在命令列參數不指定網址時就會前往環境變數設定的網址。

2024/04/08

Midnight Commander

Midnight Commander 是一套文字使用者介面下的跨平台檔案管理器, 主介面由兩個顯示檔案系統的面板組成,支援使用滑鼠,由 Miguel de Icaza 於 1994 年創立,作為 Norton Commander 的克隆版本。 從 4.7.0 版開始,Midnight Commander 已經支援 Unicode。

使用下列的指令在 openSUSE Tumbleweed 下安裝:

sudo zypper in mc

安裝後可以使用下列的命令查看版本資訊:

mc -V

Midnight Commander 提供了一些內建的工具(並且有其檔案連結可以使用),

  • mcedit - Text and binary file editor, with regex replace, syntax highlighting, macros and shell piping
  • mcview - Text and hex viewer with goto marks and regex search
  • mcdiff - Compares and edits two files in-place

如果要在二個左右兩個面板間切換,使用 TAB。
Ctrl-u 可以用來交換左右兩個面板。
如果你需要讓目前的使用的面板以及另外一個面板同一個目錄,使用 Alt-i。
如果只需要一個面板的時候,可以使用 Alt-t 來嘗試切換成為只有一個面板。
如果要啟用 User menu,使用 F2 按鍵。
如果要查看某個檔案的內容,使用 F3 按鍵(然後在 viewer 內按 F4 可以切換為 hex mode)。
如果要編輯檔案,使用 F4 按鍵(如果沒有特別設定,使用內建的文字編輯器)。
如果要啟用 menu bar,使用 F9 按鍵。
在程式的最下方是 Function Key 列表 (Functin key F1 ~ F10,也可以使用 Alt + 數字鍵代替,其中 F10 使用的數字鍵為 0)。
Shell 指令可以在底部的小命令列中使用。 只需像平常一樣輸入命令即可。使用 Ctrl-o 可以將在底部的小命令列切換為全螢幕的 subshell。

如果要在命令列直接使用內建的文字編輯器編輯檔案,使用 -e 選項:

mc -e filename

(如果安裝時有建立 mcedit 的連結,也可以使用 mcedit 編輯)

2024/03/24

VIM


以下是基本操作方法:
VIM 共分為三種模式,分別是指令模式編輯模式指令列命令模式
輸入:在指令模式按 a、i、o,就會進入編輯模式,這時候可以編輯文件。 如果要回到指令模式時, 則必須要按下 Esc 這個按鍵退出編輯模式。

到第一行:gg
到最後一行:G
到某一行:xxG (xx 要代入行數數字)
到行首:0
到行末:$

複製:yy
貼上:p
復原:u
把剛剛回復的動作再取消:ctrl-r

刪一個字:x
刪整行:dd

使用指令列命令模式儲存檔案或者是離開。
儲存: :w
離開: :q
儲存兼離開 ::wq 或 shift+zz
強制離開(不儲存): :q!
設定儲存的檔名: :w filename

Add space or words to mufti-line by using VIM

1) 按 CTRL + V 進入 visual mode
2) 按 j 選擇要進行操作的行數(要多選一行,就多按一個 j)
3) 接下來按 I,開始插入要插入的文字
4) 如果是要插入空白,就插入空白,如果是文字,就插入要插入的文字
5) 按 ESC 離開

如果要刪除空白字元或者是 TAB,可以使用反縮排 < 或者是退格 X 的功能;也就是第三步要做的事情。

Replace

如果要在 VIM 中全域取代字串,下面是一個例子
:%s/foo/bar/g

所以移除行尾的空白可以這樣做:

:%s/\s\+$//g
如果只是在該列取代字串,下面是一個例子:
:s/foo/bar/g

If you open a file that uses both tabs and spaces, assuming you've got

:set expandtab ts=4 sw=4 ai

You can replace all the tabs with spaces in the entire file with

:%retab

expandtab 會將 tab 填為 4 個 spaces;而 ai 為 autoindent,會複製目前的 indent到新的一行。 如果只是想更改 TAB 的顯示長度,可以使用:

:set tabstop=4 shiftwidth=4

開啟/關閉vim的vi相容模式

一般都會使用以下指令來關閉 vim 的 vi 相容模式(並且放在設定第一行):

:set nocompatible

文字編碼

將 Vim 內部對於文字編碼方式改為 UTF-8:

set encoding=utf8

VIM 會自動偵測文字檔案的編碼,當然有可能會偵測錯誤,可以使用下列的指令讓 VIM 使用某個編碼重讀檔案:

:e ++enc=encoding

其中 encoding 需要代換為 big5, utf8 等編碼方式。

開啟/關閉vim的自動縮排功能

vim的自動縮排有「autoindent」、「smartindent」和「cindent」三種。「autoindent」是最基本的縮牌方式, 會在建立出新的一行的同時,自動對齊上一行的縮排。「smartindent」則是會在「autoindent」的基礎上, 針對「{」(左大括號)後所產生的新行,再多往內縮排一次,且若新行的開頭是「}」(右大括號),則會取消往內縮多排一次的動作。 而「cindent」則是會在「smartindent」的基礎上,加入簡單的C/C++、Java等程式語法結構的判斷, 如果「{」是發生在註解之內,就不會進行縮排,並且也不是根據新行的上一行來決定新行的縮排, 而是會根據新行所在的程式區塊(block)來決定,另外也會根據其他符號進行縮排。

:set cindent

使用以下指令可以關閉vim的「cindent」自動縮排功能:

:set nocindent

設定「backspace」鍵的功能

vim可以透過「backspace」環境變數,設定其在編輯模式下,鍵盤上「backspace」鍵的功能。可以使用以下指令來開啟「backspace」鍵的所有功能:

:set backspace=indent,eol,start

或者是:

:set backspace=2

「indent」表示允許使用「backspace」鍵來刪除縮排;「eol」表示允許使用「backspace」來刪除換行字元, 使其可以退回至上一行;「start」表示允許使用「ctrl+w」和「ctrl+u」快速鍵來刪除獨立詞語和同類字元(縮排、非縮排字元)。

語法高亮

下面是設定將語法高亮開啟的方式:

:syntax on

個人設定檔案

VIM 可以在家目錄下的 .vimrc 設置自己的 VIM 設定,所以參考上面的設定,可以這樣寫:

set nocompatible
set encoding=utf8
set backspace=indent,eol,start
set tabstop=4 shiftwidth=4
syntax on

Shell command

有二個方式,一個是執行一個 command(! 然後加上要執行的 command):

:!command

一個是執行目前預設的 shell:

:shell

Hex editor

如果要使用 VIM 編輯二進位檔案,可以配合外部工具程式 xxd 將資料轉換為 16 進位的排版格式。
在 Vim 中輸入以下的指令然後按下 Enter 鍵。

:%! xxd

編輯後如果要轉回原來的格式,一樣使用外部工具程式 xxd

:%! xxd -r

如果編輯後要存檔,使用 VIM 的儲存檔案指令 :w

2024/03/10

SQLite

SQLite 是一個很小的 C 語言函式庫。 這個函式庫本身就完全包含資料庫引擎的功能,而且可以嵌入至其他程式中。

SQLite 支援 In-Memory 與 Disk-based 等形式的使用方式, 下面是一個使用 C API 來開啟一個 In-Memory Database 的例子(使用 ":memory:" 檔名):

rc = sqlite3_open(":memory:", &db);

列舉資料庫中的表格

From within a C/C++ program (or a script using Tcl/Ruby/Perl/Python bindings) you can get access to table and index names by doing a SELECT on a special table named "SQLITE_MASTER". Every SQLite database has an SQLITE_MASTER table that defines the schema for the database. The SQLITE_MASTER table looks like this:

CREATE TABLE sqlite_master (
  type TEXT,
  name TEXT,
  tbl_name TEXT,
  rootpage INTEGER,
  sql TEXT
);
所以我們可以使用下列的方式來列舉資料庫中的表格:
SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;

Auto-increment column

下面二種寫法都會建立 autoincrementing column; the AUTOINCREMENT keyword only prevents reusing deleted values.
CREATE TABLE t1 (col1 INTEGER PRIMARY KEY);
CREATE TABLE t1 (col1 INTEGER PRIMARY KEY AUTOINCREMENT);

UPSERT

對於 SQLite 而言,有幾種方式提供 UPSERT 的語法。SQLite 提供了 REPLACE INTO 的語法,同時提供了與 PostgreSQL 類似的 INSERT INTO ... ON CONFLICT clause 的方式來處理。

C API

使用 C 語言開發 Sqlite 的程式非常容易,下面是一個簡單的例子:
#include <sqlite3.h>
#include <stdio.h>

static char *createsql = "CREATE TABLE Contact("
                         "ID INTEGER PRIMARY KEY,"
                         "Name VARCHAR(10),"
                         "PhoneNumber VARCHAR(10));";

static char *insertsql =
    "INSERT INTO Contact VALUES(1, 'Richard', '09990123456');";

static char *querysql = "SELECT * FROM Contact;";

void main(void) {
    int rows, cols;
    sqlite3 *db;
    char *errMsg = NULL;
    char **result;
    int i, j;

    if (sqlite3_open_v2("example.db3", &db,
                        SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL)) {
        return;
    }

    sqlite3_exec(db, createsql, 0, 0, &errMsg);
    sqlite3_exec(db, insertsql, 0, 0, &errMsg);

    printf("%d\n", sqlite3_last_insert_rowid(db));

    sqlite3_get_table(db, querysql, &result, &rows, &cols, &errMsg);
    for (i = 0; i <= rows; i++) {
        for (j = 0; j < cols; j++) {
            printf("%s\t", result[i * cols + j]);
        }
        printf("\n");
    }

    sqlite3_free_table(result);
    sqlite3_close(db);
}

編譯時只要連結 sqlite 函式庫:

gcc  -lsqlite3  -o sqlite_ex sqlite_ex.c