vault backup: 2025-03-04 11:17:00

This commit is contained in:
2025-03-04 11:17:00 +08:00
parent d1e51bfd2f
commit ff12c4f4ca
161 changed files with 1 additions and 2 deletions

View File

@@ -0,0 +1,52 @@
## IID_PPV_ARGS Marco
我们已经知道 CoCreateInstance 和 QueryInterface 函数的最后一个参数都是 void** 类型,这就会带来潜在的类型不匹配的安全问题,考虑下面的代码段:
```cpp
// Wrong!
IFileOpenDialog *pFileOpen;
hr = CoCreateInstance(
__uuidof(FileOpenDialog),
NULL,
CLSCTX_ALL,
__uuidof(IFileDialogCustomize), // The IID does not match the pointer type!
reinterpret_cast<void**>(&pFileOpen) // Coerce to void**.
);
```
在这段代码中,想要获取的接口类型是 IFileDialogCustomize但是传入的却是一个 IFileOpenDialog 类型的指针变量。reinterpret_cast 表达式会绕过 C++ 类型系统的检测,所以编译器并不会主动抛出错误。
如果这段代码被运行,好的结果是函数返回失败,无法找到该接口;而坏的结果是,函数会返回成功,而你将获得一个类型结果不匹配的指针。换句话说,这个指针类型没有匹配到内存中真实的虚函数表,如果你所想不会有啥好事发生。
> 一个虚函数表virtual method table是一个函数指针表它被用来实现 COM 组件运行期间的动态绑定。这种方式也是大多数 C++ 编译器用来实现动态绑定(多态)的方法。
IID_PPV_ARGS 宏是一个帮助你避免类型错误的方法,使用方法如下:
```cpp
__uuidof(IFileDialogCustomize), reinterpret_cast<void**>(&pFileOpen)
```
替换为
```cpp
IID_PPV_ARGS(&pFileOpen)
```
这个宏会自动将 `__uuidof(IFileOpenDialog)` 参数插入。它可以保证你返回的接口指针类型的正确性。这是修改后的代码:
```cpp
// Right.
IFileOpenDialog *pFileOpen;
hr = CoCreateInstance(__uuidof(FileOpenDialog), NULL, CLSCTX_ALL,
IID_PPV_ARGS(&pFileOpen));
```
你可以在 QueryInterface 函数中使用一样的宏:
```cpp
IFileDialogCustomize *pCustom;
hr = pFileOpen->QueryInterface(IID_PPV_ARGS(&pCustom));
```
### 參考資料
- [COM 编码实践 - 没事造轮子](https://wangzhechao.com/com-bian-ma-shi-jian/)

View File

@@ -0,0 +1,134 @@
## Install MySQL
- 下載MySQL https://downloads.mysql.com/archives/installer/
- 安裝
- 安裝之後執行MSQL Installer - Community
- ![[Pasted image 20210423152623.png]]
- 選擇MySQL Server的Reconfigure
- ![[Pasted image 20210423152747.png]]
- 依序設定port、帳號密碼。
## Install MariaDB
1. `sudo apt install mariadb-server`
2. Configuring MariaDB: `sudo mysql_secure_installation`
這步驟會問幾個問題,大概是:
1. 輸入目前root的密碼
2. 是否要切換為unix_socket -> No
3. 要變更root的密碼嗎-> No
3. Testing MariaDB: `sudo systemctl status mariadb`
4. Show version: `sudo mysqladmin version`
## Syntax
### Basic
login as root: `mysql -u root -p`
### User management
#### Create user
```sql
CREATE USER 'testuser'@'localhost' IDENTIFIED BY 'test123test!';
```
#### Remove user
```sql
DROP USER 'testuser'@'localhost';
```
#### Grant user's permission
Below commands grant user's permission to *CREATE* and *DROP*.
```sql
GRANT CREATE ON *.* TO 'testuser'@'localhost';
GRANT DROP ON tutorial_database.* TO 'testuser'@'localhost';
FLUSH PRIVILEGES;
```
#### Remove user's permission
```sql
REVOKE <permission> ON database.table FROM 'user'@'localhost';
```
上面命令中的`<permission>`必須替換成下面的其中一個:
- ALL - Allow complete access to a specific database. If a database is not specified, then allow complete access to the entirety of MySQL.
- CREATE - Allow a user to create databases and tables.
- DELETE - Allow a user to delete rows from a table.
- DROP - Allow a user to drop databases and tables.
- EXECUTE - Allow a user to execute stored routines.
- GRANT OPTION - Allow a user to grant or remove another user's privileges.
- INSERT - Allow a user to insert rows from a table.
- SELECT - Allow a user to select data from a database.
- SHOW DATABASES- Allow a user to view a list of all databases.
- UPDATE - Allow a user to update rows in a table.
例如:`REVOKE CREATE ON *.* FROM 'testuser'@'localhost';`
最後,不要忘記`FLUSH PRIVILEGES;`
#### Show user's permission
```sql
SHOW GRANTS FOR 'testuser'@'localhost';
```
#### Change user's password
```sql
ALTER USER 'user-name'@'localhost' IDENTIFIED BY 'NEW_USER_PASSWORD';
```
#### View all users
```sql
SELECT User,Host FROM mysql.user;
```
### Database
#### Show all databases
```sql
SHOW DATABASES;
```
### Create table
The rule:
```sql
CREATE TABLE table_name (column_name column_type);
```
Example:
```sql
CREATE TABLE tutorials_tbl(
tutorial_id INT NOT NULL AUTO_INCREMENT,
tutorial_title VARCHAR(100) NOT NULL,
tutorial_author VARCHAR(40) NOT NULL,
submission_date DATE,
PRIMARY KEY ( tutorial_id )
);
```
```sql
use autostation;
CREATE TABLE station_state(
id CHAR(255) NOT NULL,
ip CHAR(255) NOT NULL,
dhcp_ip CHAR(255) NOT NULL,
name CHAR(255) NOT NULL,
setupfilemd5 CHAR(255) NOT NULL,
setupfileversion CHAR(255) NOT NULL,
status CHAR(255) NOT NULL,
update_time DATETIME NOT NULL,
PRIMARY KEY ( id )
);
```
### Insert
The rule:
```
INSERT INTO <table_name> ( field1, field2,...fieldN )
VALUES
( value1, value2,...valueN );
```
Example:
```
INSERT INTO client_state ( field1, field2,...fieldN )
VALUES
( value1, value2,...valueN );
```
## 參考
- [Create a MySQL User on Linux via Command Line | Liquid Web](https://www.liquidweb.com/kb/create-a-mysql-user-on-linux-via-command-line/)
- [Grant Permissions to a MySQL User on Linux via Command Line | Liquid Web](https://www.liquidweb.com/kb/grant-permissions-to-a-mysql-user-on-linux-via-command-line/)
- [Remove Permissions for a MySQL User on Linux via Command Line - Liquid Web](https://www.liquidweb.com/kb/remove-permissions-for-a-mysql-user-on-linux-via-command-line/)
- [Remove a MySQL User on Linux via Command Line - Liquid Web](https://www.liquidweb.com/kb/remove-a-mysql-user-on-linux-via-command-line/)

View File

@@ -0,0 +1,13 @@
## Build static library
1. Navigate to [https://www.sqlite.org/download.html](https://www.sqlite.org/download.html) and download latest `amalgamation` source version of SQLite.
2. Extract all the files into your project directory, or your include path, or separate path that you will add/added as include path in your project properties.
3. Run `Developer Command Prompt for VS ****` which is usually available at `Start -> Programs -> Visual Studio **** -> Visual Studio Tools`.
4. Navigate with command prompt to that directory where we extracted our SQLite.
5. Run next command to compile: `cl /c /EHsc sqlite3.c`
6. Run next command to create static library: `lib sqlite3.obj`
7. Open properties of your project and add `sqlite3.lib` to `Linker -> Input -> Additional Dependencies`.
Now you ready to include and use `sqlite3.h` in your project.
### 參考
- [How to add SQLite into your VS project as Static Library](https://gist.github.com/payalord/c87cbd1d12ea7712449657d1c6583e12)

View File

@@ -0,0 +1,240 @@
---
tags:
aliases:
date: 2022-05-26
time: 15:12:04
description:
---
## 策略模式Strategy
策略模式可以提供不一樣的演算法,但是又不用更改程式。
以常見的鴨子為例有一個基礎類別Duck如何衍生出會飛得鴨子跟不會飛的鴨子抑或是會叫的跟不會叫的
第一部是將會變動的部份分離出來,讓鴨子類別不需要去在乎飛跟叫的問題。
再來是把飛跟叫的部份包裝成另一個class並以之為基礎類別來實做出「實際的類別」。 ^e59e9f
以一般C++的override方法會用的方式大致是這樣
- !!!col
- 1
### 基礎類別
```cpp
class duck {
duck() {}
void fly() {}
void makeSound() {}
void run() {}
}
```
- 1
### 衍生類別,遙控鴨子,會飛不會叫
```cpp
class duckRC : public duck {
duckRC() {}
void fly() { printf("I can fly"); }
void makeSound() { printf("I cannot make sound!"); }
}
```
- 1
### 衍生類別,木頭鴨子,不會飛不會叫
```cpp
class duckWood : public duck {
duckWood() {}
void fly() { printf("I cannot fly"); }
void makeSound() { printf("I cannot make sound!"); }
}
```
但是這樣的話如果基礎類別會更改的話,那麼子類別也全部都會受影響,例如,現在希望所有的鴨子都要有一個「跑」的功能,所以我們就在基礎類別裡加一個`run()`
```cpp
class duck {
void fly() {}
void makeSound() {}
void run() { printf("I can run"); }
}
```
結果現在木頭鴨子也能跑了,這不符合我們的設計,所以我們必須回頭更改木頭鴨子的程式,改成
```cpp
class duckWood : public duck {
void fly() { printf("I cannot fly"); }
void makeSound() { printf("I cannot make sound!"); }
void run() { printf("I cannot run!"); } // <- 要改!
}
```
如果我們類別很多那麼就很可能有沒改到的漏網之魚bug就發生了。
### 封裝變動的部份
比較好的方法一旦類別寫完之後,我們就不要動它了,日後新增的功能也不可以影響到他。我們把「會變動」的部份分離出來,變成各自的類別。
為了簡化我們討論「飛」這個功能就好「飛」只分成「會飛」跟「不會飛」2種類別
- !!!col
- 1
### 基礎類別IFly
```cpp
class IFly {
void doFly() = 0;
}
```
- 1
### 衍生類別CanFly
```cpp
class CanFly {
void doFly() { printf("I can fly"); }
}
```
- 1
### 衍生類別CannotFly
```cpp
class CannotFly {
void doFly() { printf("I cannot fly"); }
}
```
回到鴨子的基礎類別這邊,基礎類別`duck`本來是直接擁有`fly()`這個member function現在我們把他改成他擁有的是`IFly`
```cpp
class duck {
duck() {}
fly() { fly->doFly() };
IFly* fly = nullptr;
...
}
```
重寫遙控鴨子這個衍生類別:
```cpp
class duckRC : public duck {
duckRC() {
this->fly = new CanFly();
}
...
}
```
重寫木頭鴨子,不會飛,所以:
```cpp
class duckWood : public duck {
duckWood() {
this->fly = new CannotFly();
}
}
```
現在,不管是遙控鴨子或是木頭鴨子,在被呼叫`fly()`的時候都是根據它fly的「實際類別」來動作遙控鴨子的`fly()`呼叫的是`CanFly`的`doFly()`,木頭鴨子的`fly()`呼叫的是`CannotFly`的`doFly()`它們的動作完全取決於他們初始化的方式而他們初始化的方式則取決於你的產品定義要是今天你需要不同的飛行方式增加新的fly類別即可不會影響到舊有的。要是鴨子基礎類別增加了新的行為如run那麼就幫鴨子基礎類別的`run()`加個什麼都不做的預設動作就好,反正舊有的鴨子衍生類別本來就對這個新行為沒反應。要是`CanFly`的定義改變了,那麼你就是改變了使用`CanFly`的所有類別,這是你的定義明確的改變了,不是有程式被「額外」的改變了。
- !!!col
- 1
### 原本直接繼承的方式
![[Pasted image 20220526182952.png]]
- 2
### 封裝變動的部份
![[Pasted image 20220526183019.png]]
這樣做的另一個好處是fly的初始化是動態的只要再多一個`set()` function就可以動態的切換實作也就是說你可以從設定檔來決定你的鴨子要長什麼樣子。
## 觀察者模式Observer Pattern
有一個會產生變動的主角subject與一堆需要觀察變動的觀察者Observer。觀察者向主角註冊當主角發生變化的時候發後通知給觀察者。
![[20220614154819_Observer_Pattern.png]]
Subject方面attach() 就是註冊觀察者,也可以叫做 register()、add() 之類。
detach() 用來移除用戶,也可以叫做 unregister()、remove() 之類。
notify()則是當發生變化時,用來通知所有觀察者的實作。
觀察者方面必須實作 update() 才能收到通知。
## 裝飾者模式Decorator Pattern
「裝飾者」通常與「被裝飾者」有同樣的界面,「裝飾者」會取代「被裝飾者」的界面,進而改變「被裝飾者」的行為。
裝飾者模式讓物件可以動態的改變行為,進為符合不同的需求。
以書上的例子來說我們多種飲料每種飲料都可以加上不同的配料。例如有奶茶、綠茶、紅茶3種飲料另外有珍珠、紅豆、綠豆、仙草4種配料我們要如何設計出適合的類別來讓每種飲料都可以隨寄的搭配配料呢
假設這樣寫:
- !!!col
- 1
### Ingredient class
```cpp
class IngredientBubble { ... };
class IngredientRedbean { ... };
class IngredientGreenbean { ... };
class IngredientFairyGrass { ... };
```
- 1
### Beverage class
```cpp
class Beverage {
int32_t cost() { return cost; }
int32_t cost;
}
class BeverageMilkTea : public Beverage { ... };
class BeverageGreenTea : public Beverage { ... };
class BeverageBlackTea : public Beverage {
IngredientBubble* bubble;
IngredientRedbean* redbean;
IngredientGreenbean* greenbean;
IngredientFairyGrass* fairyGrass;
}
```
每個飲料的class裡面都將每個配料定義為一個 member如果客人有加配料的話我們就將配料實例化假設奶茶加了珍珠
```cpp
class BeverageBlackTea {
void addBubble() {
if (!bubble) bubble = new IngredientBubble();
}
};
```
要算價格的時候:
```cpp
class BeverageBlackTea {
int32_t cost() {
if (bubble) cost += 10; // 珍珠要加10元
if (redbean) cost += 5; // 紅豆要加10元
if (greenbean) cost += 7; // 綠豆要加10元
if (fairyGrass) cost += 9; // 珍珠要加10元
return cost;
}
int32_t cost = 30; // 奶茶本身10元
};
```
這樣的問題是每當有一種新配料出現我們就要在奶茶類別裡修改至少2個 function`addXXX()` 與 `cost()`目前我們有3種飲料所以要修改6個 function更何況如果客人要加2份珍珠怎麼辦這明顯不利程式的維護必須有一種方法讓程式的修改最小讓寫好的程式不用被修改才行。
讓裝飾者模式來改善這個問題。首先讓配料跟飲料有同樣的界面,但是修改一下配料的 constructor當然也要實作 `cost()`,畢竟每個配料的價格不同:
```cpp
class IngredientBubble : public Beverage {
IngredientBubble(Beverage* beverage) {
this->beverage = beverage;
}
int32_t cost() {
return 10 + this->beverage->cost(); // 珍珠要加10元在加上原本飲料的價錢
}
Beverage* beverage;
};
```
珍珠這個 class 的 constructor 的參數是任何一個 Beverage現在飲料被包在珍珠裡面由珍珠來決定飲料最後的價格是多少。現在我們可以動態的決定飲料的組成假設點一杯紅茶加一些配料
```cpp
int main() {
...
// 為了讓程式看起來簡單這裡就先不考慮memory leak的問題...>_<
BeverageBlackTea* berverge = new BeverageBlackTea(); // 點一杯紅茶
IngredientBubble* berverge = new IngredientBubble(berverge); // 加珍珠
IngredientRedbean* berverge = new IngredientRedbean(berverge); // 加紅豆
IngredientFairyGrass* berverge = new IngredientFairyGrass(berverge); // 加仙草
...
return berverge->cost(); // 算價錢
}
```
可以看到第一個變數是飲料,然後被包珍珠裡面,變成珍珠紅茶,再被包到紅豆裡面,變成紅豆珍珠紅茶,再被包到仙草裡面,變成仙草紅豆珍珠紅茶,到這樣的好處是:
1. 有新的配料就寫新配料的 class
2. 因為可以動態的組合,原本寫好的飲料 class 就不用在去動它了,愈少修改,愈少 bug
3. 我們可以動態的組合配料要加2份以上也沒有問題

View File

@@ -0,0 +1,17 @@
- [FFmpeg](https://www.ffmpeg.org/)
- Open source
- For handling multimedia files and streams
- GPL/LGPS license
- Inspired by MPEG(Moving Picture Experts Group), FF means "Fast Foward"
- Libraries
- libavcodec: Contain all the encoders and decoders
- libavformat: Muxers and demuxers
- libavfilter: Filters that you can use on your video and audio according to your requirements
- libavdevice: Input and output devices
- libavutil
- libswscale
- libswresample
- Tools
- ffmpeg: Main transcoding engine
- ffplay: Minimum tool to play a video or audio
- ffprobe: For inspecting medias and extract valuable information

View File

@@ -0,0 +1,30 @@
FFmpeg doesn't support any official builds for any operation systems. You can build the codes from source, or use the prebuilt packages.
## From source
Get source code from [snapshot](https://ffmpeg.org/releases/ffmpeg-snapshot.tar.bz2) or git(`git clone https://git.ffmpeg.org/ffmpeg.git ffmpeg`).
## From prebuilt packages
### MacOS
Use homebrew to install FFMPEG package
```
brew install ffmpeg
```
### Ubuntu
```
sudo apt update ;\
sudo apt install ffmpeg
```
### Windows
Goto https://www.ffmpeg.org/download.html, and
![[Pasted image 20220607105807.png|640]]
And then browser jumps to https://www.gyan.dev/ffmpeg/builds/, click [release build](https://www.gyan.dev/ffmpeg/builds/#release-builds), download **release-full**.
![[Pasted image 20220607110211.png|640]]
Unzip the file, then put the folder to anywhere you like, e.g. `C:\libs`, so the full path of ffmpeg packages should looks like: `C:\libs\ffmpeg-5.0.1-full_build`
#### Add to system variable
Add the path of bin of ffmpeg packages to `PATH` system variable.
![[Pasted image 20220607110949.png|800]]

View File

@@ -0,0 +1,7 @@
使用`AV_CODEC_FLAG_GLOBAL_HEADER`的好處主要體現在減少資料大小和提高傳輸效率:
1. **減小資料包大小:**將頭資訊放在全域頭中,而不是在每個資料包中都重複,可以減少每個資料包的大小。這對於網路傳輸和儲存來說是很重要的,因為較小的資料包通常傳輸更有效率。
2. **提高傳輸效率:**全域頭資訊只需發送一次,而不是在每個資料包中重複。這降低了傳輸的開銷,特別是在頻寬受限或傳輸成本敏感的情況下。
3. **最佳化儲存:**如果你將音訊視訊資料進行存儲,使用全域頭可以減少儲存需求,因為頭資訊只需儲存一次,而不是與每個資料包一起儲存。
整體而言,`AV_CODEC_FLAG_GLOBAL_HEADER`的使用可以改善多媒體資料的傳輸效率和儲存效率,但在實際應用中,需要確保使用該標誌的影響不會導致相容性或其他問題。

View File

@@ -0,0 +1,303 @@
## Initialization
```cpp
av_register_all();
avformat_network_init();
```
## Command line
利用FFMpeg來轉檔
```
ffmpeg -i <SOURCE.mp4> -t 10 -s <W>x<H> -pix_fmt <OUTPUT_FORMAT> <OUTPUT_FILENAME>
```
- `-i`:來源檔名
- `-t`:影片長度(秒)
- `-s`影片大小長x寬
- `-pix_fmt`:輸出的影像格式
example:
```
ffmpeg -i test.mp4 -t 10 -s 240x128 -pix_fmt yuv420p out240x128.yuv
```
## Open file
使用`avformat_open_input()`來打開檔案。
```cpp
AVFormatContext* pFormatContext = NULL;
char* path = "E:\\download\\Incredible Patagonia_1920x1080.mp4";
AVDictionary* pDict = NULL;
int result = avformat_open_input(&pFormatContext, path, NULL, &pDict);
```
若是開啟失敗`result`會是一個非0的值。可以用`av_strerror()`來印出詳細的錯誤訊息。例:
```cpp
char errorMsg[1024] = { 0 };
av_strerror(result, errorMsg, sizeof(errorMsg) - 1);
printf("Open file %s error, code = %d, message = %s\n", path, result, errorMsg);
```
### Get file information
`avformat_find_stream_info()`來把檔案的video/audio相關訊息放到`AVFormatContext`裡面。例:
```cpp
result = avformat_find_stream_info(pFormatContext, NULL);
```
也可以用
```cpp
av_dump_format(pFormatContext, 0, path, 0);
```
來直接印出相關訊息不過這方法是Ffmpeg內定的如果要印出自己想要的內容就必須將`AVFormatContext`裡面的`AVStream`一一撈出來:
```cpp
for (int i = 0; i < pFormatContext->nb_streams; i++) {
AVStream* avStream = pFormatContext->streams[i];
if (avStream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
printf("Stream[%d] is a audio stream.\n", i);
printf(" Sample rate: %d\n", avStream->codecpar->sample_rate);
printf(" Format : %d\n", avStream->codecpar->format);
printf(" Channels : %d\n", avStream->codecpar->channels);
printf(" FPS : %d/%d\n", avStream->avg_frame_rate.num, avStream->avg_frame_rate.den);
printf(" Frame Size : %d\n", avStream->codecpar->frame_size);
printf(" Codec ID : %d\n", avStream->codecpar->codec_id);
} else if (avStream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
printf("Stream[%d] is a video stream.\n", i);
printf(" Width : %d\n", avStream->codecpar->width);
printf(" Height: %d\n", avStream->codecpar->height);
printf(" FPS : %d/%d\n", avStream->avg_frame_rate.num, avStream->avg_frame_rate.den);
printf(" Codec ID: %d\n", avStream->codecpar->codec_id);
}
}
```
這個for-loop可以找出檔案裡面的video stream index與audio stream index。
另一個找出video stream index與audio stream index的方法
```cpp
int videoStream = av_find_best_stream(pFormatContext, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
int audioStream = av_find_best_stream(pFormatContext, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
printf("Found best video stream index: %d\n", videoStream);
printf("Found best audio stream index: %d\n", audioStream);
```
## Demux
### 重要結構
#### AVPackt
##### 重要的member
- `AVBufferRef *buf`buffer的reference count基本上不會用到。
- `int64_t pts`:顯示順序
- `int64_t dts`decode順序
- `uint8_t *data`實際存data的buffer
- `int size`上面那個buffer的大小
##### 操作function
- `AVPacket* av_packet_alloc()`產生一個兂的AVPacket
- `AVPacket* av_packet_clone(const AVPacket*)`複製一個AVPacket
- `int av_packet_ref(AVPacket*, const AVPacket*)`增加reference count
- `av_packet_unref(AVPacket*)`減少reference count
- `void av_packet_free(AVPacket**)`釋放AVPacket
- `void av_init_packet(AVPacket*)`初始化AVPacket
- `int av_packet_from_data(AVPacket*, uint8_t*, int size)`從自己的data
- `av_seek_frame`
- `AVFormatContext*`
- `int stream_index`要seek的stream
- `int64_t timestamp`timestamp
- `int flag`seek的方法
- `#define AVSEEK_FLAG_BACKWARD 1`:往後找
- `#define AVSEEK_FLAG_BYTE 2`以byte為單位跳到timestamp相對應的byte
- `#define AVSEEK_FLAG_ANY 4`跳到任意frame不一定要key frame
- `#define AVSEEK_FLAG_FRAME 8`依照frame number來找
## Decode
### 重要結構
#### AVCodecContext
##### 重要的member
- `int thread_count`:執行續的數量
- `AVRational time_base`
##### 操作function
- `avcodec_register_all()`註冊所有的decoder。
- `AVCodec* avcodec_find_decoder(enum AVCodecID)`用codec ID來找到相對應的codec instance
- `AVCodec* avcodec_find_decoder_by_name(const char* name)`用codec的名子來找到相對應的codec instance
- `AVCodecContext* avcodec_alloc_context3(const AVCodec*)`用codec來建立`AVCodecContext`codec只是用來解碼解碼之後的相關資料要放在`AVCodecContext`裡面。
- `void avcodec_free_context(AVCodecContext**)`:釋放`AVCodecContext`
- `int avcodec_open2(AVCodecContext*, const AVCodec*, AVDictionary*)`
- 開始解碼
- `AVDictionary`的選項請看`libavcodec/options_table.h`
#### AVFrame
##### 重要的member
- `uint8_t* data[AV_NUM_DATA_POINTERS]`存放這個frame的資料
- `int linesize[AV_NUM_DATA_POINTERS]`存放指向各個plane的pointer
- Plane mode
- For video
- 0Y
- 1U
- 2V
- 3NULL
- For audio
- 0Left channel
- 1Right channel
- 2NULL
- Non plane mode
- For video
- 0RGB RGB RGB
- 1NULL
- `int width`For video, frame width
- `int height`For video, frame height
- `int nb_samples`For audio
- `int64_t pts`這個frame的pts
- `int64_t pkt_dts`Packet的dts
- `int sample_rate`For audiosampling rate
- `uint64_t channel_layout`For audio
- `int channels`For audio
- `int format`For video it's AVPixelFormat, for audio it's AVSampleFormat
##### 操作function
- `AVFrame* av_frame_alloc()`
- `void av_frame_free(AVFrame*)`
- `AVFrame* av_frame_clone(const AVFrame*)`
- `int av_frame_ref(AVFrame* dst, const AVFrame* src)`
- `void av_frame_unref(AVFrame*)`
- `int avcodec_send_packet(AVCodecContext*, const AVPacket*)`
- `int avcodec_receive_frame(AVCodecContext*, AVFrame*)`
## Frame scaling
##### 操作function
- `sws_getContext`
- `sws_getCachedContext`
```
SwsContext* sws_getCachedContext(
SwsContext*,
int srcW, int srcH, enum AVPixelFormat srcFormat,
int dstW, int dstH, enum AVPixelFormat dstFormat,
int flags,
SwsFilter srcFilter,
SwsFilter dstFilter,
const double* param)
```
- `sws_scale`
```
int sws_scale(
SwsContext*,
const uint8_t* const srcSlice[], // 來源buffer的pointer, 如果是plane mode要丟srcSlice[3]
const int srcStride[], // linesize
int srcSliceY,
int srcSliceH, // image height
uint8_t* const dst[], // 目標buffer的pointer, 如果是plane mode要丟dst[3]
const int dstStride[]) // linesize
```
- `void sws_freeContext(SwsContext*)`
## Audio resampling
- `SwrContext* swr_alloc()`
- `swr_alloc_set_opts`
```cpp
SwrContext* swr_alloc_set_opts(
SwrContext*,
int64_t out_ch_layout, // Output
AVSampleFormat outSampleFormat, // Output
int outSampleRate, // Output
int64_t in_ch_layout, // Input
AVSampleFormat inSampleFormat, // Input
int inSampleRate, // Input
int log_offset, // 一般丟0
void* log_ctx) // 一般丟0
```
- `int swr_init(SwrContext*)`
- `void swr_free(SwrContext**)`
- `swr_convert`
```cpp
int swt_convert(
SwrContext*,
uint8_t** outData,
int outCount, // Sample count for one channel
uint8_t** inData,
int inCount) // Sample count for one channel
```
## Audio 播放
### 會用到的class
#### QAudioFormat
- `setSampleRate`
- `setSampleSize`
- `setChannelCount`
- `setCodec("audio/pcm")`
- `setByteOrder(QAudioFormat::LittleEndian)`
- `setSampleType(QAudioFormat::UnSignedInt)`
#### QAudioOutput
- Constuctor參數就是一個`QAudioFormat`
- `QIODevice* start()`
- `suspend()`
- `resume()`
- `bufferSize()`
- `byteFree()`
- `periodSize()`
#### QIODevice
- `qint64 write(const char* data, qint64 len)`
#### QBuffer
```cpp
QBuffer buffer;
if (buffer.open(QIODevice::ReadWrite)) {
qInfo() << "Buffer Opened!";
QByteArray data("Hello World");
for (int i = 0; i < 5; i++) {
buffer.write(data);
buffer.write("\r\n");
}
//File and device access you may need to flush the data to the device
//buffer.flush()
//Move to the first position
buffer.seek(0);
qInfo() << buffer.readAll(); // retuen a QVariant
qInfo() << "closing the buffer";
//ALWAYS close your device!
buffer.close();
}
```
### Example
```cpp
QAudioFormat audioFormat;
audioFormat.setSampleRate(44100);
audioFormat.setSampleSize(16);
audioFormat.setChannelCount(2);
audioFormat.setCodec("audio/pcm");
audioFormat.setByteOrder(QAudioFormat::LittleEndian);
audioFormat.setSampleType(QAudioFormat::UnSignedInt);
QAudioOutput* audioOutput = new QAudioOutput(audioFormat);
QIODevice* ioDevice = audioOutput->start();
int size = audioOutput->periodSize();
char* buffer = new char[size];
FILE *pFile = fopen("E:\\download\\out.pcm", "rb");
while (!feof(pFile)) {
if (audioOutput->bytesFree() < size) {
QThread::msleep(1);
}
qint64 len = fread(buffer, 1, size, pFile);
if (len <= 0) {
break;
}
ioDevice->write(buffer, len);
}
fclose(pFile);
delete buffer;
buffer = NULL;
```
### 使用FFmpeg decode audio
- [音视频开发之旅60) -调试分析FFmpeg (解封装部分的)常用结构体](https://zhuanlan.zhihu.com/p/441055685)
## 參考資料
- [mpv.io](https://mpv.io/)
- [《FFmpeg基础知识》](https://opensourcelibs.com/lib/zhangfangtaozft-ffmpeg)
- [ffmpeg - 专题 - 简书](https://www.jianshu.com/c/21a6fafc8ee3)
- [FFmpeg打开输入文件 · FFmpeg原理](https://ffmpeg.xianwaizhiyin.net/api-ffmpeg/input.html)
- [FFplay视频同步分析 - 掘金](https://juejin.cn/post/7156903872789741581)

View File

@@ -0,0 +1,43 @@
- [H.264 profiles and levels](https://blog.mediacoderhq.com/h264-profiles-and-levels/)
H.264 有四種畫質級別,分別是`baseline`, `extended`, `main`, `high`
- baseline profile基本畫質。支援I/P 幀只支援無交錯Progressive和CAVLC
- extended profile進階畫質。支援I/P/B/SP/SI 幀只支援無交錯Progressive和CAVLC(用的少)
- main profile主流畫質。提供I/P/B 幀支援無交錯Progressive和交錯Interlaced也支援CAVLC 和CABAC 的支援
- high profile高階畫質。在main profile 的基礎上增加了8x8 內部預測、自訂量化、無損視訊編碼和更多的YUV 格式
H.264 baseline profile、extended profile 和 main profile 都是針對`8位`樣本資料、4:2:0 格式(YUV) 的視訊序列。在相同配置情況下high profileHP可以比main profileMP 降低10% 的碼率)
- `profile`: 規定了一個演算法特徵和限制的子集任何遵守某個profile 的解碼器都應該支援與其對應的子集是對視訊壓縮_特性的描述_CABAC、顏色採樣數等
- `level`: 規定了一組對標準中語法成員syntax element所採用的各種參數值的限制對_視頻本身特性的描述_(fps (幀率)、碼率、分辨率)
> 總的來說就是profile 越高表示採用了越高級的壓縮特性level 越高,表示影片的幀率、碼率、解析度越高
## profile
![[Pasted image 20231225164349.png]]
- Baseline Profile (BP):主要用於計算資源有限的低成本應用程序,此設定檔廣泛用於視訊會議和行動應用程式。
- Main Profile (MP)最初打算作為廣播和儲存應用程式的主流消費者個人資料當為這些應用程式開發High profile 時,此設定檔的重要性逐漸消失。
- Extended Profile (XP):旨在作為串流視訊設定文件,此設定檔具有相對較高的壓縮能力和一些額外的技巧,以提高資料遺失和伺服器串流切換的穩健性。
- High Profile (HiP)廣播和光碟儲存應用程式的主要設定文件特別是高清電視應用程式例如HD DVD 和藍光光碟採用的設定檔)。
- High 10 Profile (Hi10P) 超越當今的主流消費產品功能此配置文件建立在High Profile 之上—— 增加了對解碼圖像精度的每個樣本高達10 位的支援。
- High 4:2:2 Profile (Hi422P)主要針對使用隔行視頻的專業應用程序此配置文件建立在High 10 Profile 之上—— 增加了對4:2:2 色度子採樣格式的支持同時使用多達10 位元每個解碼影像精度的樣本。
- High 4:4:4 Predictive Profile (Hi444PP)此設定檔建立在高4:2:2 設定檔之上- 支援高達4:4:4 色度取樣每個取樣高達14 位,此外還支援高效率無損區域編碼並將每張圖片編碼為三個獨立的顏色平面。
## Level
![[Pasted image 20231225164413.png]]
level 的計算方法:
例如解析度: `1920x1080`, 60fps 的 H.264 碼流是哪個 level 呢?
H.264 的 marco block 大小為 `16×16` in H.264, 每frame的 marco block 的個數為**解析度除以16**。
- `max-fs = ceil( width / 16.0 ) * ceil( height / 16.0 )`
計算得8100
每秒 marco block 數量為每幀 marco block 數乘以幀率.
- max-mbps =max-fs * 參考幀率(60)
計算得486000
![[Pasted image 20231225164717.png]]
因此對比表上的數據 `1920x1080@60fps`level 應該選擇`4.2`

View File

@@ -0,0 +1,201 @@
- [FFmpeg 使用筆記 - 爱吃土豆的男孩 - 博客园](https://www.cnblogs.com/dk666/articles/7327265.html)
## AVInputFormat/AVOutputFormat
multimedia 檔案的 Muxer/Demuxer將多個 stream如 auodi 、video 等以單個媒體承載。媒體可以是檔案、 network session 等等。每一個 `AVFormat` 代表一種格式,像是`.avi``.wmv``.dat` 等等各種格式,使用不同的方式 mux/demux 多個 stream。每一 `AVFormat` 都實作特定的格式,使用時必需選擇和目的格式相符的 AVFormat 實作,才能正確的讀取 stream 的內容。
## AVFormatContext
`AVFormatContext``AVFormat` 的一個 instance ,用以存放 `AVFormat` 的執行狀態。使用 FFmpeg 處理檔案時,必需為 `AVInputFormat`/`AVOutputFormat` 建立一個 `AVFormatContext` 。同一個 format 可以建立多個 `AVFormatContext` ,各立獨立,不相互干擾,以同時處理多個檔案。
## AVStream
`AVStream` 用以對應到 `AVFormatContext` 裡的一個 stream 。因此同一個 `AVFormatContext` 管理了多個 `AVStream` 以對應到存在檔案或透過 network session 傳送的數個 stream。
## AVPacket
在 stream 裡的資料,是以 packet 為單位進行傳送/儲存。因此一般多媒體檔是將檔案當成 network session 一般處理。每一個 `AVPacket` 標注了所屬的 stream ,透過 `AVPacket` 的包裝,多個 stream 可以同時在一個媒介上傳送/儲存。因此,我們可以讀 stream 是由一系列的 packet 所組成。
## AVCodec
一個影音媒體檔裡,可以承載多個 stream。而 stream 內有 packet。 packet 的 payload ,也就是資料,是透過 codec 編碼/壓縮的。因此,必需透過 codec 進行讀取/寫入時的 decode/encode。AVCodec 就是實作 codec 演算法。同樣的codec 的演算法有各式各樣。使用時,必需選正確的 `AVCodec` ,才能正確的 decode/encode。
## AVCodecContext
`AVCodecContext``AVCodec` 的一個 instance ,用以存放 `AVCodec` 的執行狀態。同樣可以建立多個 `AVCodecContext` ,同時處理多個相同格式、不同格式的 stream 。
## AVFrame
`AVFrame` 的頭幾個欄位和 `AVPicture` 相同,也就是 `AVPicture``AVFrame` 的一小部分。
## 寫入多媒體檔
步驟
- Select an `AVFormat`
- allocate a `AVFormatContext`
- initialize `AVFormatContext` with selected `AVFormat`
- create and initialize an `AVStream` with SVGFormatContext and codec_id specified by `AVFormat`
- AVStream::codec is `AVCodecContext`
- initialize `AVCodecContext` with the `AVCodec` specified by codec_id
- `avcodec_open()`
- initialize output stream of `AVFormatContext`
- `av_write_header()`
- write frames
- create an `AVFrame`
- initialize an `AVPacket` with `AVFrame`
- `av_write_frame()`
- close streams
- `av_write_trailer()`
- free streams
- close output stream
- free `AVFromatContext`
## 讀取多媒體檔
產生檔案時,目的格式是由我們所選擇,因此問題往往較單純一點。但讀取檔案時,我們不確定檔案的格式,必需進行格式判斷後,才確定使用哪一個 AVInputFormat 。
- `av_open_input_file()`
- Read probe buffer
- `get_buffer()`
- `av_probe_input_format()` or `av_probe_input_format2()`
- return `AVFormat`
- Allocate `AVFormatContext`
- `av_open_input_stream()`
- av_find_stream_info
- setup codec for streams
- `avcodec_find_decoder()`
- `avcodec_open()`
- 讀取 frames
- `av_read_frame()`
- prepare an `AVFrame`
- `avcodec_get_frame_defaults()`
- decode the frame in pkt to `AVFrame`
- `avcodec_decode_video()`
- got_picture is true if a picture being returned by the AVFrame.
- close codec
## 範例:影片讀取
```cpp
#include <stdio.h>;
#include <string.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
int main(int argc, const char *argv[]) {
const char *fname;
AVFormatContext *ic;
AVStream *is, *tis;
AVCodec *codec;
AVFormatParameters params, *ap = &params;
AVPacket pkt;
AVFrame frame;
AVPicture *pic;
int got_picture;
int i, r;
av_register_all();
if(argc != 2) {
fprintf(stderr, "Usage: %s <media file>\n", argv[0]);
return 1;
}
fname = argv[1];
memset(ap, 0, sizeof(AVFormatParameters));
ap->video_codec_id = CODEC_ID_NONE;
printf("codec id %X\n", ap->video_codec_id);
r = av_open_input_file(&ic, fname, NULL, 0, ap);
if(r != 0 || ic == NULL) {
fprintf(stderr, "can not open input file %s\n", fname);
return 1;
}
av_find_stream_info(ic);
is = NULL;
for(i = 0; i < ic->nb_streams; i++) {
tis = ic->streams[i];
if(tis->codec->codec_type == CODEC_TYPE_VIDEO) {
printf("channel %d of %d\n", i, ic->nb_streams);
is = tis;
}
}
codec = avcodec_find_decoder(is->codec->codec_id);
if(codec == NULL) {
fprintf(stderr, "can not find codec %s\n", is->codec->codec_name);
return 1;
}
r = avcodec_open(is->codec, codec);
if(r != 0) {
fprintf(stderr, "can not initialize a AVCodecContext for codec %s\n",
codec->name);
return 1;
}
printf("Codec %s (%d x %d)\n", codec->name, is->codec->width,
is->codec->height);
for(i = 0; i < 10;) {
r = av_read_frame(ic, &pkt);
if(r != 0) {
fprintf(stderr, "no more frame\n");
return 1;
}
if(pkt.stream_index != is->index)
continue;
if(pkt.pts != AV_NOPTS_VALUE)
printf("Frame %d@%d: pts=%lld, dts=%lld, size=%d, data=%x\n",
i, pkt.stream_index, pkt.pts, pkt.dts, pkt.size, pkt.data);
else
printf("Frame %d@%d: pts=N/A, dts=%lld, size=%d, data=%x\n",
i, pkt.stream_index, pkt.dts, pkt.size, pkt.data);
av_pkt_dump(stdout, &pkt, 0);
avcodec_get_frame_defaults(&frame);
r = avcodec_decode_video(is->codec, &frame, &got_picture,
pkt.data, pkt.size);
if(r < 0) {
printf("decoding error\n");
return 1;
}
if(got_picture) {
printf("\tlinesize[4]={%d %d %d %d}, data[4]={%x %x %x %x)}\n",
frame.linesize[0], frame.linesize[1],
frame.linesize[2], frame.linesize[3],
frame.data[0], frame.data[1],
frame.data[2], frame.data[3]);
}
av_free_packet(&pkt);
i++;
}
avcodec_close(is->codec);
return 0;
}
```
FFMPEG中结构体很多。最关键的结构体可以分成以下几类
1. 解协议http, rtsp, rtmp, mms
`AVIOContext``URLProtocol``URLContext` 主要存储视音频使用的协议的类型以及状态。`URLProtocol` 存储输入视音频使用的封装格式。每种协议都对应一个 `URLProtocol` 结构。注意FFMPEG中文件也被当做一种协议“file”
2. 解封装flv, avi, rmvb, mp4
`AVFormatContext` 主要存储视音频封装格式中包含的信息;`AVInputFormat` 存储输入视音频使用的封装格式。每种视音频封装格式都对应一个 `AVInputFormat` 结构。
3. 解码h264, mpeg2, aac, mp3
每个 `AVStream` 存储一个视频/音频流的相关数据;每个 `AVStream` 对应一个 `AVCodecContext`,存储该视频/音频流使用解码方式的相关数据;每个 `AVCodecContext` 中对应一个 `AVCodec`,包含该视频/音频对应的解码器。每种解码器都对应一个 `AVCodec` 结构。
4. 存数据
视频的话,每个结构一般是存一帧;音频可能有好几帧
解码前数据:`AVPacket`
解码后数据:`AVFrame`
他们之间的对应关系如下所示:
![[Pasted image 20231220095827.png]]

View File

@@ -0,0 +1,7 @@
- [Encode/H.264 FFmpeg](https://trac.ffmpeg.org/wiki/Encode/H.264)
```cpp
av_opt_set(avStream->codecContext->priv_data, "profile", "baseline", 0);
```
[profile 的選項](https://trac.ffmpeg.org/wiki/Encode/H.264#Profile)

View File

@@ -0,0 +1,8 @@
`av_opt_set()` 可以設定很多東西,例如:
```cpp
av_opt_set(codecContext, "preset", "ultrafaset", 0); // 速度慢,高品質
av_opt_set(codecContext, "tune", "fastdecode", 0);
av_opt_set(avStream->codecContext->priv_data, "profile", "baseline", 0);
```
詳細請看[Encode/H.264 FFmpeg](https://trac.ffmpeg.org/wiki/Encode/H.264)。

View File

@@ -0,0 +1,45 @@
## 列出 hardware acceleration
```bash
$ ffmpeg -hide_banner -hwaccels
Hardware acceleration methods:
cuda
dxva2
qsv
d3d11va
opencl
vulkan
```
## 列出 codec
```bash
$ ffmpeg -hide_banner -codecs
Codecs:
D..... = Decoding supported
.E.... = Encoding supported
..V... = Video codec
..A... = Audio codec
..S... = Subtitle codec
...I.. = Intra frame-only codec
....L. = Lossy compression
.....S = Lossless compression
-------
D.VI.S 012v Uncompressed 4:2:2 10-bit
D.V.L. 4xm 4X Movie
D.VI.S 8bps QuickTime 8BPS video
.EVIL. a64_multi Multicolor charset for Commodore 64 (encoders: a64multi )
.EVIL. a64_multi5 Multicolor charset for Commodore 64, extended with 5th color (colram) (encoders: a64multi5 )
D.V..S aasc Autodesk RLE
```
### 只列出 encoder 或是 decoder
```bash
ffmpeg -hide_banner -encoders
ffmpeg -hide_banner -decoders
```
### 列出某個 encoder 或 decoder 的詳細資料
```bash
$ ffmpeg -hide_banner -h encoder=<codec_name>
or
$ ffmpeg -hide_banner -h decoder=<codec_name>
```

206
20.01. Programming/Flask.md Normal file
View File

@@ -0,0 +1,206 @@
## 執行
### Linux
```bash
export FLASK_APP=RobotRunAutoServer.py
export FLASK_DEBUG=1
flask run --reload
```
### Windows
```
set FLASK_APP=RobotRunAutoServer.py
set FLASK_DEBUG=1
flask run --reload
```
## 路由
```python
@app.route('/')
def index():
return 'Index Page'
@app.route('/query/<SOMETHING>', methods=['GET', 'POST'])
def query(SOMETHING):
return 'Hello, {}!'.format(SOMETHING)
```
## 靜態檔案
```
url_for('static', filename='style.css')
```
## 模板
```python
from flask import render_template
@app.route('/hello/<name>')
def hello(name=None):
return render_template('hello.html', name=name)
```
依照 Flask 的慣例,它會在 templates 資料夾內去找模板檔,而 templates 資料夾的位置會根據這支 web app 是模組或是套件而有所不同。
如果是模組:
```
/application.py
/templates
/hello.html
```
如果是套件:
```
/application
/__init__.py
/templates
/hello.html
```
## 存取 request 資料
客戶端傳來的資料都放在 `request` 這個全域變數內web app 利用 `request` 提供的資訊與客戶端互動。
客戶端的 HTTP 方法存放在 `request``method` 屬性內,而 form 就存放在 `request``form` 屬性內。示例:
```Python
from flask import request
from flask import render_template
@app.route('/login', methods=['GET', 'POST'])
def login():
error = None
if request.method == 'POST':
if valid_login(request.form['username'], request.form['password']):
return log_the_user_in(request.form['username'])
else:
error = 'Invalid username / password'
# the code below is executed if the request method was GET or the credentials were invalid
return render_template('login.html', error=error)
```
如果 `form` 屬性不存在,伺服器端會引發 `KeyError` 錯誤,客戶端則會收到 HTTP 400 錯誤。
如果是要取用 `URL` 參數,則使用 `args` 屬性內的 `get()` 方法:
```Python
searchword = request.args.get('key', '')
```
如果是要取用客戶端上傳的檔案,先確定在前端 HTML 表單的設定正確的屬性 `enctype="multipart/form-data"`,瀏覽器才會正確的把檔案上傳。調用 `request``files` 屬性就可以調用到檔案:
```Python
from flask import request
@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
f = request.files['the_file']
f.save('/var/www/uploads/uploaded_file.txt')
```
`files` 本身是個 dictionary所以必須呼叫它裡面的 key 才會接到真正的檔案,這個 `file` 物件的行為就像標準的 Python `file` 物件,但它有個 `save()` 方法讓我們把檔案存到自己想要的路徑。
如果想要沿用客戶端上傳的檔名,則調用 `filename` 屬性,但由於檔名的不可預知,可能會有安全風險,最好用 Werkzeug 的 `secure_filename()` 方法過濾掉:
```Python
from flask import request
from werkzeug.utils import secure_filename
@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
f = request.files['the_file']
f.save('/var/www/uploads/' + secure_filename(f.filename))
```
如果是要讀取 cookie 就調用 `request` 物件的 `cookies` 屬性,如果是要設置 cookie 就用 `response` 物件的 `set_cookie()` 方法。
`request``cookies` 也是一個 dictionary裡面放了所有可以調用的 cookie。
但是如果想使用 session 的話Flask 有提供更完善的 session 機制可以利用,不要手工用 cookie 來管理 session。
讀取 cookie 的範例:
```Python
from flask import request
@app.route('/')
def index():
username = request.cookies.get('username')
# use cookies.get(key) instead of cookies[key] to not get a KeyError if the cookie is missing.
```
存入 cookie 的範例:
```Python
@app.route('/')
def index():
resp = make_response(render_template(...))
resp.set_cookie('username', 'the username')
return resp
```
## 重導頁面與錯誤頁面
要重導使用 `redirect()` 方法,要中斷並報錯用 `abort()` 方法:
```Python
from flask import abort, redirect, url_for
@app.route('/')
def index():
return redirect(url_for('login'))
@app.route('/login')
def login():
abort(401)
this_is_never_executed()
```
如果不想使用 Flask 預設的陽春錯誤頁,則利用 `errorhandler()` 修飾子來做客製:
```Python
from flask import render_template
@app.errorhandler(404)
def page_not_found(error):
return render_template('page_not_found.html'), 404
```
注意到 `return` 那行最後面的 `404`,雖然上面的修飾子已經是 404`return` 後面還是要加 `404` Flask 才認得這是 404 錯誤頁。
## Session
Session 用來紀錄、辨識用戶的活動,實現方式是加密過的 cookie。
得先設置密鑰才能使用 session
```Python
from flask import Flask, session, redirect, url_for, escape, request
from werkzeug.utils import secure_filename
app = Flask(__name__)
# Set the secret key to some random bytes. Keep this really secret!
app.secret_key = b'_5#y2L"F4Q8z\n\xec]/'
@app.route('/')
def index():
if 'username' in session:
return f"Logged in as {escape(session['username'])}"
return 'You are not logged in'
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
session['username'] = request.form['username']
return redirect(url_for('index'))
return '''
<form method="post">
<p><input type=text name=username>
<p><input type=submit value=Login>
</form>
'''
@app.route('/logout')
def logout():
# remove the username from the session if it's there
session.pop('username', None)
return redirect(url_for('index'))
```
前面模板的章節有說過,模板引擎會幫我們把表單的 HTML 過濾掉,而在這裡沒有使用模板引擎,所以手動調用了 `escape()` 方法來濾掉 HTML 碼。
最後附註一點,瀏覽器可能會限制單一 cookie 容量,如果發現某個值應該要有卻調用不出來的話,想想看是不是超過 cookie 的容量上限了。
## Log 紀錄
Flask app 物件有使用 Python 內建的 `logger` 模組,可以簡單調用:
```Python
app.logger.debug('A value for debugging')
app.logger.warning('A warning occurred (%d apples)', 42)
app.logger.error('An error occurred')
```

View File

@@ -0,0 +1,10 @@
可以使用 `tensorflow.keras.utils.image_dataset_from_directory` 來建立 dataset。
dataset 會有 `data_batch``label_batch` 這兩個 member分別代表資料與標籤。
可以用 `dataset.batch(32)` 改變 batch size。
還有一些其他的有用function:
- `shuffle(buffer_size)`: 打亂順序,可參考[[Keras.tensorflow - shuffle#^832c8c]]
- `prefetch(buffer_size)`: 設定預讀的大小
- `map(callback_func)`: 用 callback_func 來處理資料
- `take(N)`: 取出第N筆的批次資料注意這一筆是一個批次資料裡面可能有32筆資料或其他數量看你的 `dataset.batch(N)` 怎麼設定)。
打亂data的方法請看[[Keras.tensorflow - shuffle]]

View File

@@ -0,0 +1,37 @@
如果想用同時打亂x_train與y_train可以參考這2個方法。
## 1. 用 `tf.random.shuffle`
```python
indices = tf.range(start=0, limit=tf.shape(x_data)[0], dtype=tf.int32)
idx = tf.random.shuffle(indices)
x_data = tf.gather(x_data, idx)
y_data = tf.gather(y_data, idx)
```
先建立一個跟array一樣大的list然後打亂它再用這個已打亂的list當作索引來建立一個新的data list。
## 2. 用 `Dataset.shuffle`
^832c8c
```python
x_train = tf.data.Dataset.from_tensor_slices(x)
y_train = tf.data.Dataset.from_tensor_slices(y)
x_train, y_train = x_train.shuffle(buffer_size=2, seed=2), y_train.shuffle(buffer_size=2, seed=2)
dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))
```
或者
```python
BF = 2
SEED = 2
def shuffling(dataset, bf, seed_number):
return dataset.shuffle(buffer_size=bf, seed=seed_number)
x_train, y_train = shuffling(x_train, BF, SEED), shuffling(y_train, BF, SEED)
dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train))
```
概念跟第一點是一樣的,但是這是先轉成 `tf.data.Dataset`然後把x_train跟y_train都用同樣的seed打亂。

View File

@@ -0,0 +1,14 @@
## 問題分類
| 問題類型 | 啟動函數 | 損失函數 | 範例 |
|:--------------:|:--------:|:------------------------------------------------------------------------------:|:------------------------------------------------------------------:|
| 二元分類 | sigmoid | binary_crossentropy二元交叉熵 | |
| 單標籤多元分類 | softmax | [[categorical_crossentropy]](分類交叉熵)<br> sparse_categorical_crossentropy | 範例:[[An example that use categorical_crossentropy and softmax]] |
| 多標籤分類 | sigmoid | binary_crossentropy | |
| 回歸求值 | None | mse均方誤差 | |
| 回歸求0~1值 | sigmoid | mse或binary_crossentropy | |
## Callback
- [ReduceLROnPlateau](https://keras.io/api/callbacks/reduce_lr_on_plateau/)

View File

@@ -0,0 +1,3 @@
- 僅適用於 one-hot 編碼。
- 如果輸出不是 one-hot而是整數標籤也就是直接輸出 0、1、2而不是一個array`[0, 0, 0, 1, 0]` 之類),那就需要 sparse_categorical_crossentropy。
- 範例:[[An example that use categorical_crossentropy and softmax]]

View File

@@ -0,0 +1,61 @@
[AtomicBoolean  |  Android Developers](https://developer.android.com/reference/java/util/concurrent/atomic/AtomicBoolean)
## 初始化
```kotlin
val init = AtomicBoolean(false)
or
val inti = AtomicBoolean() // Default false
```
## read/write
`get()`取值,用`set()`設值
例:
```kotlin
if (init.get()) { ... } // 如果 init 是 true 的話
init.set(true) // 將 init 設為 true
```
## 其他function
### compareAndExchange
- [compareAndExchange](https://developer.android.com/reference/java/util/concurrent/atomic/AtomicBoolean#compareAndExchange(boolean,%20boolean))
```java
public final boolean compareAndExchange (
boolean expectedValue,
boolean newValue)
```
如果目前的值跟`expectedValue`相等,回傳目前值,並將目前值設為`newValue`
如果目前的值跟`expectedValue`不相等,回傳目前值,不做任何設定。
### compareAndSet
- [compareAndSet](https://developer.android.com/reference/java/util/concurrent/atomic/AtomicBoolean#compareAndSet(boolean,%20boolean))
```java
public final boolean compareAndSet (
boolean expectedValue,
boolean newValue)
```
如果目前的值跟`expectedValue`相等return `true`,並將目前值設為`newValue`
如果目前的值跟`expectedValue`不相等return `false`
## 用途
一般而言,`set``get` 已經夠用,但如果需要讀值並設定一個新值的話,那就需要 `compareAndSet` 或是 `compareAndExchange`,不然就需要另一個 `mutex` 來達到同樣效果。
假設一個情況,假設目前是 `false` 的情況下,我們可以存取某些資源,所以也要把值設為 `true`,用 `mutex` 的作法如下:
```kotlin
mutex.lock()
if (useable.get()) {
useable.set(false)
// Do something
} else {
...
}
mutex.unlock()
```
改用 `compareAndSet` 就是:
```kotlin
if (useable.compareAndSet(false, true)) {
// Do something
}
```
## 資料
- [java - compareandexchange() vs compareandset() of Atomic-Integer - Stack Overflow](https://stackoverflow.com/questions/60648557/compareandexchange-vs-compareandset-of-atomic-integer)

View File

@@ -0,0 +1,62 @@
## 最簡單 class 定義
Kotlin 中的 class 由 `class` 關鍵字開始一個簡單的class如下
```kotlin
class VerySimple {
}
```
如果需要 constructor 的話,則在 class 名稱之後加入所需的參數,如下:
```kotlin
class VerySimple(val para1: Int, val para2: String ) {
}
```
加在 constructor 中的參數會自動變成 class 的「成員變數」,如果在參數前面加上 `private`,則會成「私有成員變數」,也就是無法被外部所存取。
## 多個 constructor
前面所的 constructor 是建立class 的「主要constructor」kotlin 也允許建立其他 constructor但是這些「次要constructor」都必須呼叫「主要constructor」來進行初始化如下
```kotlin
class VerySimple(val para1: Int, val para2: String ) {
constructor(val para1: Int): this(para1, para2="someText") // 第一個「次要constructor」
constructor(val para2: String): this(para1=123, para2) // 第二個「次要constructor」
}
```
不管是第一個「次要constructor」或是第二個「次要constructor」都必須呼叫主要的`(val para1: Int, val para2: String )`這一個「主要constructor」。
如果有邏輯上的需求「次要constructor」也可以加上邏輯判斷區塊如下
```kotlin
class VerySimple(val para1: Int, val para2: String ) {
constructor(val para1: Int): this(para1, para2="someText") // 第一個「次要constructor」
constructor(val para2: String): this(para1=123, para2) { // 第二個「次要constructor」
if (para2 == "error") {
println("Something wrong")
para1 = 25 // 這行會錯誤因為para1是被宣告成val而不是var
}
}
}
```
## `init` 區塊
`init`是個優先權高於「主要constructor」的執行區塊如下
```kotlin
class VerySimple(val para1: Int, val para2: String ) {
init {
require(para1 > 0, {"para1 must larger than 0"})
require(para2.isNotBlank(), {"para2 cannot be a empty string"})
}
constructor(val para1: Int): this(para1, para2="someText") // 第一個「次要constructor」
constructor(val para2: String): this(para1=123, para2) { // 第二個「次要constructor」
if (para2 == "error") {
println("Something wrong")
para1 = 25 // 這行會錯誤因為para1是被宣告成val而不是var
}
}
}
```
`init`區塊會在初始化之前進行檢查,如果不符合條件,則會丟出`IllegalArgumentException`異常。

View File

@@ -0,0 +1,80 @@
`run`, `let`, `with`, `also``apply` 這幾個都是可以搭配 object 使用的函數,它們之間的差異不大,主要是讓程式在語意上更加流暢。
以下解釋各個的差別。
## 歸納
| | 變數名稱 | 自訂變數名稱 | 回傳 |
|:--------------:|:----------:|:------------:|:--------:|
| `run()` | | | 最後一行 |
| `with()` | `this` | No | 最後一行 |
| `object.run` | `this` | No | 最後一行 |
| `object.let` | `it` | Yes | 最後一行 |
| `object.also` | `it` | Yes | `this` |
| `object.apply` | `this` | No | `this` |
## run
`run` 後面的區塊會**回傳「最後一行」**,所以可以進行「串接」。如下:
```kotlin
run {
val telephone = Telephone()
telephone.whoCallMe = "English"
telephone // <-- telephone 被帶到下一個 Chain
}.callMe("Softest part of heart") // <-- 這裡可以執行 `Telephone` Class 的方法
```
## object.run
`object.run` 跟[[#run]]是一樣的,也是**回傳「最後一行」**,只是 `object.run` 是讓 object 呼叫的,而且 lambda scope 中的物件會變成 `this``this` 可以省略,但是不可以自訂變數名稱,如下:
```kotlin
val anObject = MyObject()
anObject.run {
this.doSomething() // this就是anObjectthis可以省略
}
```
## with
`with(T)` 之中的傳入值可以以 `this` (稱作 identifier) 在 scope 中取用,不用打出 `this`也沒關係。雖然, `with` 也會**將最後一行回傳**。但是因為 `with` 沒有先判斷物件是否為 `null`,所以 scope 中還是要用`?`來判斷是否 `null`,如下:
```kotlin
val anObject = makeMyObject() // anObject有可能是null
with(anObject) {
this?.doSomething() // this就是anObjectthis可能是null
}
```
## object.let
`let` 的 scope 裡,物件是 `it`,可以自訂變數名稱,一樣會**回傳最後一行**。如下:
```kotlin
val anObject = makeMyObject()
anObject?.let { // anObject有可能是null所以要先用?判斷
it.doSomething() // it就是anObject在scope內一定不是null
}
```
## object.also
`also` 的 scope 裡,物件是 `it`,可以自訂變數名稱,但是不是回傳最後一行,是**回傳自己**(也就是 `it`)。如下:
```kotlin
val anObject = makeMyObject()
anObject?.also { // anObject有可能是null所以要先用?判斷
it.doSomething() // it就是anObject在scope內一定不是null
}.also {
it.doSomething2() // it就是anObject
}.also {
it.doSomething3() // it就是anObject
}
```
## object.apply
`apply` 的 scope 裡,物件是 `this`,不可以自訂變數名稱,但是不是回傳最後一行,是**回傳自己**(也就是 `this`)。如下:
```kotlin
val anObject = makeMyObject()
anObject?.also { // anObject有可能是null所以要先用?判斷
this.doSomething() // this就是anObject在scope內一定不是null
}.also {
this.doSomething2() // this就是anObject
}.also {
this.doSomething3() // this就是anObject
}
```
## 參考
- [簡介 Kotlin: run, let, with, also 和 apply](https://louis383.medium.com/%E7%B0%A1%E4%BB%8B-kotlin-run-let-with-also-%E5%92%8C-apply-f83860207a0c)
- [Kotlin 的 scope function: apply, let, run..等等](https://jchu.cc/2018/05/05-kotlin.html)
- [Kotlin 線上讀書會 筆記 (五) apply、let、run、with、also和takeIf](https://medium.com/evan-android-note/kotlin-%E7%B7%9A%E4%B8%8A%E8%AE%80%E6%9B%B8%E6%9C%83-%E7%AD%86%E8%A8%98-%E4%BA%94-apply-let-run-with-also%E5%92%8Ctakeif-2c09d42b09b5)

View File

@@ -0,0 +1,228 @@
- [媒體基礎程式設計指南 - Win32 apps | Microsoft Docs](https://docs.microsoft.com/zh-tw/windows/win32/medfound/media-foundation-programming-guide)
## 研究MFVideoEVRWebcam.cpp
- IMFActivate: 代表實際的device但是實際的device object是在`IMFActivate->ActivateObject()`才真的建立。
- IID_PPV_ARGS取得interface的IID value並取得其pointer。詳細參考[[20210726 - COM Interface#IID_PPV_ARGS Marco]]。
-
```
CoInitializeEx
MFStartup
↳ ListVideoDevicesWithBriefFormat
MFCreateAttributes(&pDeviceAttributes, 1)
pDeviceAttributes->SetGUID()
MFEnumDeviceSources(pDeviceAttributes, &ppDevices, &deviceCount)
ppDevices[i]->GetAllocatedString
ppDevices[i]->GetAllocatedString(MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, &friendlyName, &friendlyNameLength)
ppDevices[i]->ActivateObject(IID_PPV_ARGS(&pMediaSource))
MFCreateSourceReaderFromMediaSource(...)
↳ ListModes
pReader->GetNativeMediaType(0, dwMediaTypeIndex, &pType)
↳ GetMediaTypeDescription
pMediaType->GetMajorType(&MajorType)
pMediaType->GetCount(&cAttrCount)
MediaType->GetItemByIndex
pMediaType->GetItemType
↪ GetVideoTypeDescriptionBrief(...)
pMediaType->GetGUID(MF_MT_SUBTYPE, &subType)
MFGetAttributeSize(pMediaType, MF_MT_FRAME_SIZE, &width, &height)
MFGetAttributeRatio(pMediaType, MF_MT_FRAME_RATE, &fpsNum, &fpsDen)
↳ MFCreateVideoRendererActivate(_hwnd, &pActive)
pActive->ActivateObject(IID_IMFMediaSink, (void**)&pVideoSink)
pVideoSink->QueryInterface(__uuidof(IMFVideoRenderer), (void**)&pVideoRenderer)
pVideoRenderer->InitializeRenderer(NULL, NULL)
pVideoSink->QueryInterface(__uuidof(IMFGetService), (void**)&pService)
pService->GetService(MR_VIDEO_RENDER_SERVICE, __uuidof(IMFVideoDisplayControl), (void**)&pVideoDisplayControl)
pVideoDisplayControl->SetVideoWindow(_hwnd)
pVideoDisplayControl->SetVideoPosition(NULL, &rc)
pVideoSink->GetStreamSinkByIndex(0, &pStreamSink)
pStreamSink->GetMediaTypeHandler(&pSinkMediaTypeHandler)
pSinkMediaTypeHandler->GetMediaTypeCount(&sinkMediaTypeCount)
GetVideoSourceFromDevice(WEBCAM_DEVICE_INDEX, &pVideoSource, &pVideoReader)
pVideoReader->SetStreamSelection((DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, TRUE)
pVideoSource->CreatePresentationDescriptor(&pSourcePresentationDescriptor)
pSourcePresentationDescriptor->GetStreamDescriptorByIndex(0, &fSelected, &pSourceStreamDescriptor)
pSourceStreamDescriptor->GetMediaTypeHandler(&pSourceMediaTypeHandler)
pSourceMediaTypeHandler->GetMediaTypeCount(&srcMediaTypeCount)
MFCreateMediaType(&pWebcamSourceType)
FindMatchingVideoType(pSourceMediaTypeHandler, ...)
pSourceMediaTypeHandler->IsMediaTypeSupported(pWebcamSourceType, &pWebCamMatchingType)
pSourceMediaTypeHandler->SetCurrentMediaType(pWebCamMatchingType)
pSourceMediaTypeHandler->SetCurrentMediaType(pWebcamSourceType)
pSourceMediaTypeHandler->GetCurrentMediaType(&pVideoSourceOutputType)
```
## 建立Video capture device
### 1. 取得video capture的數量
```cpp
IMFAttributes* pDeviceAttributes = NULL;
IMFActivate** ppDevices = NULL;
UINT32 deviceCount = 0;
CHECK_HR(MFCreateAttributes(&pDeviceAttributes, 1),
"Error creating device attributes.");
// Request video capture devices.
CHECK_HR(pDeviceAttributes->SetGUID(
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID),
"Error initialising video configuration object.");
CHECK_HR(MFEnumDeviceSources(pDeviceAttributes, &ppDevices, &deviceCount),
"Error enumerating devices.");
```
### 2. 用Symbolic來建立IMFActivate
`MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK`可以用來取得一個獨一無二的ID相對的也可以用來建立IMFActivate。
```cpp
IMFAttributes* pDeviceAttributes = NULL;
IMFActivate* pDevice = NULL;
UINT32 deviceCount = 0;
bool result = false;
CHECK_HR(MFCreateAttributes(&pDeviceAttributes, 1),
"Error creating device attributes.");
CHECK_HR(pDeviceAttributes->SetGUID(
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID),
"Error initialising video configuration object.");
CHECK_HR(pDeviceAttributes->SetString(
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK,
deviceSymbolic.c_str()),
"Error setting video capture symbolic link");
CHECK_HR(MFCreateDeviceSourceActivate(pDeviceAttributes, &pDevice),
"Error create activate device");
```
API:
- [MFCreateDeviceSourceActivate function (mfidl.h)](https://docs.microsof.com/en-us/windows/win32/api/mfidl/nf-mfidl-mfcreatedevicesourceactivate)
- [IMFAttributes::SetGUID (mfobjects.h)](https://docs.microsoft.com/en-us/windows/win32/api/mfobjects/nf-mfobjects-imfattributes-setguid)
- [IMFAttributes::SetString (mfobjects.h)](https://docs.microsoft.com/en-us/windows/win32/api/mfobjects/nf-mfobjects-imfattributes-setstring)
- [IMFAttributes::GetString (mfobjects.h)](https://docs.microsoft.com/en-us/windows/win32/api/mfobjects/nf-mfobjects-imfattributes-getstring)
## 建立Audio capture device
相對於video capture是用`MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK`來建立IMFActivateaudio capture是用`MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_AUDCAP_ENDPOINT_ID`來建立獨一無二的ID
## IAMCameraControl interface
使用`IAMCameraControl`來控制webcam的行為例如曝光與[[20210604 - Windows media foundation#Pan Tilt Relative Control]]。
### Pan/Tilt Relative Control
1. 需要先用`IMFMediaSource->QueryInterface()`取得`IID_IAMCameraControl`
```cpp
hret = mfMediaSource->QueryInterface(
IID_IAMCameraControl,
(void**)&pCameraControl
);
```
2. 接下來就可以用`IAMCameraControl->GetRange()`、`Set()`、`Get()`來取得property根據[CameraControlProperty](https://docs.microsoft.com/en-us/windows/win32/api/strmif/ne-strmif-cameracontrolproperty)可以看到可以用的property有
1. `CameraControl_Pan`
2. `CameraControl_Tilt`
3. `CameraControl_Roll`
4. `CameraControl_Zoom`
5. `CameraControl_Exposure`
6. `CameraControl_Iris`
7. `CameraControl_Focus`
3. 但裡面並沒有關於PanRelative或是TiltRlative的控制在google之後在Stackoverflow上發現這篇[Media Foundation Exposure in milliseconds](https://stackoverflow.com/questions/63124813/media-foundation-exposure-in-milliseconds)裡面有人提到可以用ksmedia的property`KSPROPERTY_CAMERACONTROL_EXTENDED_EXPOSUREMODE`要使用這個property必須include `<ks.h>`與`<ksmedia.h>`你會發現這個property定義在`<ksmedia.h>`裡面的`KSPROPERTY_CAMERACONTROL_EXTENDED_PROPERTY`這個enum裡面然後順藤摸瓜的可以找到`KSPROPERTY_VIDCAP_CAMERACONTROL`這個enum裡面就有定義`KSPROPERTY_CAMERACONTROL_PAN_RELATIVE`、`KSPROPERTY_CAMERACONTROL_TILT_RELATIVE`這些relative相關的東西了。
4. 現在我們可以用`IAMCameraControl->GetRange()`來把`KSPROPERTY_CAMERACONTROL_PAN_RELATIVE`的值讀出來。
```cpp
hret = pCameraControl->GetRange(
KSPROPERTY_CAMERACONTROL_PAN_RELATIVE,
&min,
&max,
&steppingDelta,
&default,
&capsFlags
);
printf("KSPROPERTY_CAMERACONTROL_PAN_RELATIVE hret = 0x%08X\n", hret);
printf("min = %d\n", min); // get 1
printf("max = %d\n", max); // get 1
printf("steppingDelta = %d\n", steppingDelta); // get 1
printf("default = %d\n", default); // get 1
printf("capsFlags = %d\n", capsFlags); // get 0
```
但你會發現min、max的值都一樣。
但是`IAMCameraControl->Set()`還是能用用1跟-1來表示左轉與右轉。
```cpp
hret = pCameraControl->Set(
KSPROPERTY_CAMERACONTROL_PAN_RELATIVE,
-1, // or 1
flag
);
```
5. `KSPROPERTY_CAMERACONTROL_TILT_RELATIVE`的控制方法跟Pan一樣只是用1跟-1是表示上與下。
6. Pan relative test code
```cpp
HRESULT hret;
IAMCameraControl* pCameraControl = NULL;
hret = mfMediaSource->QueryInterface(
IID_IAMCameraControl,
(void**)&pCameraControl
);
printf("hret = 0x%08X\n", hret);
long min;
long max;
long steppingDelta;
long default;
long capsFlags;
long value;
KSPROPERTY_CAMERACONTROL_EXTENDED_EXPOSUREMODE;
hret = pCameraControl->GetRange(
KSPROPERTY_CAMERACONTROL_PAN_RELATIVE,
&min,
&max,
&steppingDelta,
&default,
&capsFlags
);
printf("KSPROPERTY_CAMERACONTROL_PAN_RELATIVE hret = 0x%08X\n", hret);
printf("min = %d\n", min);
printf("max = %d\n", max);
printf("steppingDelta = %d\n", steppingDelta);
printf("default = %d\n", default);
printf("capsFlags = %d\n", capsFlags);
hret = pCameraControl->Get(
KSPROPERTY_CAMERACONTROL_PAN_RELATIVE,
&value,
&capsFlags
);
printf("CameraControl_Pan hret = 0x%08X\n", hret);
printf("value = %d\n", value);
printf("capsFlags = 0x%08X\n", capsFlags);
long flag = CameraControl_Flags_Manual;
hret = pCameraControl->Set(
KSPROPERTY_CAMERACONTROL_PAN_RELATIVE,
-1,
flag
);
printf("pCameraControl->Set(KSPROPERTY_CAMERACONTROL_PAN_RELATIVE, -1) hret = 0x%08X\n", hret);
Sleep(500);
hret = pCameraControl->Set(
KSPROPERTY_CAMERACONTROL_PAN_RELATIVE,
1,
flag
);
printf("pCameraControl->Set(KSPROPERTY_CAMERACONTROL_PAN_RELATIVE, 1) hret = 0x%08X\n", hret);
Sleep(500);
```
## IAMVideoProcAmp interface
使用`IAMVideoProcAmp`來控制webcam的影像相關部份。可以從[VideoProcAmpProperty enumeration](https://docs.microsoft.com/en-us/windows/win32/api/strmif/ne-strmif-videoprocampproperty)這個頁面看到詳細定義。節錄如下:
- `VideoProcAmp_Brightness`
- `VideoProcAmp_Contrast`
- `VideoProcAmp_Hue`
- `VideoProcAmp_Saturation`
- `VideoProcAmp_Sharpness`
- `VideoProcAmp_Gamma`
- `VideoProcAmp_ColorEnable`
- `VideoProcAmp_WhiteBalance`
- `VideoProcAmp_BacklightCompensation`
- `VideoProcAmp_Gain`

View File

@@ -0,0 +1,186 @@
OpenCL 可以使用 GPU 來實現異質運算。
OpenCL使用上的概念
1. 選擇 platform
2. 由 platform 來選擇device
3. 由 device 來建立 context透過 context 才能控制 device
4. 由 context 來建立 programprogram 即是要執行在 GPU 上面的程式
5. 編譯 program
6. 建立 kernel 來執行 program
7. 建立 queue用來跟 program 溝通
8. 建立 `cl::Buffer``cl::Buffer` 是 GPU 能使用的 memory把 PC 端的資料 copy 進去
9. 呼叫 `kernel.setArg(cl::Buffer)` 來設定參數
10. 呼叫 `queue.enqueueNDRangeKernel()` 來執行 kerne l並設定工作組大小
11.`queue.enqueueReadBuffer()` 來讀回處理好的資料
## 1. 選擇 platform
```cpp
std::vector<cl::Platform> platforms;
cl::Platform::get(&platforms);
for (size_t i = 0; i < platforms.size(); ++i) {
printf("Name: %s\n", platforms[i].getInfo<CL_PLATFORM_NAME>().c_str());
printf("Vendor: %s\n", platforms[i].getInfo<CL_PLATFORM_VENDOR>().c_str());
printf("Version: %s\n", platforms[i].getInfo<CL_PLATFORM_VERSION>().c_str());
printf("Profile: %s\n", platforms[i].getInfo<CL_PLATFORM_PROFILE>().c_str());
printf("Extensions: %s\n", platforms[i].getInfo<CL_PLATFORM_EXTENSIONS>().c_str());
if (platformName.find("NVIDIA") != std::string::npos) {
this->choosenPlatform = platforms[i];
break;
}
}
```
## 2. 選擇 device
```cpp
std::vector<cl::Device> clDevices;
platform.getDevices(CL_DEVICE_TYPE_GPU, &clDevices); // 檢查裝置數目
if (!clDevices.empty()) {
choosenDevice = clDevices[0];
std::cout << "Devices size = " << clDevices.size() << std::endl;
// Print device info
std::cout << "********** DEVICE **********\n";
for (auto& device : clDevices) {
std::cout << " Device name: " << device.getInfo<CL_DEVICE_NAME>() << std::endl;
std::cout << " Device vendor: " << device.getInfo<CL_DEVICE_VENDOR>() << std::endl;
std::cout << " Device version: " << device.getInfo<CL_DEVICE_VERSION>() << std::endl;
std::cout << " Device profile: " << device.getInfo<CL_DEVICE_PROFILE>() << std::endl;
std::cout << " Device extensions: " << device.getInfo<CL_DEVICE_EXTENSIONS>() << std::endl;
std::cout << "\n";
}
}
```
## 3. 建立 context
```cpp
auto context = cl::Context({ this->choosenDevice });
```
## 4. 建立 program
```cpp
auto program = cl::Program(this->context, textCode);
```
第1個參數就是前一步建立好的 context第二個參數是你要在GPU上執行的程式碼。型別是 `std::string`
這裡 `textCode` 的內容是:
```cpp
static std::string textCode =
R"(
__kernel void HistogramCalculator(
__global const uchar* buffer,
const uint width,
const uint height,
__global uint* grayHistogram,
__global uint* redHistogram,
__global uint* greenHistogram,
__global uint* blueHistogram)
{
uint id = get_global_id(0) + get_global_id(1) * width;
uchar b = buffer[id * 3];
uchar g = buffer[id * 3 + 1];
uchar r = buffer[id * 3 + 2];
uchar gray = (r + g + b) / 3;
atomic_inc(&grayHistogram[gray]);
atomic_inc(&redHistogram[r]);
atomic_inc(&greenHistogram[g]);
atomic_inc(&blueHistogram[b]);
}
)";
```
## 5. 編譯 program
```cpp
if (program.build() != CL_SUCCESS) {
printf("[ERROR] Fail to build program.\n");
printf(" LOG: %s\n", this->program.getBuildInfo<CL_PROGRAM_BUILD_LOG>(this->choosenDevice).c_str());
break;
}
```
OpenCL 是執行時才編譯,如果編譯錯誤則用 `program.getBuildInfo<CL_PROGRAM_BUILD_LOG>(this->choosenDevice).c_str())` 取得錯誤訊息。
## 6. 建立 kernel
```cpp
cl_int kernel_creation_result = 0;
this->kernel = cl::Kernel(program, kernelName.c_str(), &kernel_creation_result);
if (this->kernel() == NULL) {
// Failed to create kernel object
printf("Error: Failed to create kernel object! kernel_creation_result = %d\n", kernel_creation_result);
break;
}
```
這邊將 program 與 kernel 連接起來,由 kernel 來執行。要注意的是第二個參數就是你要在 GPU 上執行的程式碼的「函式名稱」。不可以不一樣,否則這裡會報錯。
第三個參數用來接受錯誤代碼。
## 7. 建立 queue
```cpp
auto queue = cl::CommandQueue(this->context, this->choosenDevice);
```
到了這邊OpenCL 的執行單元已經建立完成,可以開始執行了。
## 8. 建立 `cl::Buffer`
因為是執行在 GPU 上,所以必須透過 `cl::Buffer` 來將資料送進 GPU。
參考[[OpenCL#4. 建立 program]]的 `textCode` 可以知道我們要傳入7個參數第一個是圖片的buffer第二個與第三個分別是圖片的寬與高第四、五、六、七參數則用來輸出算好的histogram。
寬與高是 `uint32_t`可以直接傳入但是buffer不行直接傳所以要先建立 `cl::Buffer`
以下建立要傳到 GPU 的 `cl::buffer`
```cpp
cl::Buffer bufferCl(context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, imageWidth * imageHeight * byteDepth, imageBuffer.get());
```
第一個參數是[[OpenCL#3. 建立 context]]所建立的 context。
第二個參數是記憶體的屬性,因為不需要 GPU 寫回,所以這裡是 `CL_MEM_READ_ONLY`
第三個參數是記憶體的長度。
第四個參數是記憶體的address。
再來建立要讀回的 `cl::buffer`,用 grayHistogram 當作範例,其他三個行為都一樣:
```cpp
cl::Buffer clGrayHistogram(context, CL_MEM_WRITE_ONLY, 256 * sizeof(uint32_t));
```
第一個參數是[[OpenCL#3. 建立 context]]所建立的 context。
第二個參數是記憶體的屬性,這個 `clGrayHistogram` 因為需要 GPU 寫回,所以記憶體屬性是 `CL_MEM_WRITE_ONLY`
第三個參數是記憶體的長度。
注意沒有第四個參數。
## 9. 設定參數
用[[OpenCL#6. 建立 kernel]]所建立的 kernel 來傳入剛剛建立好的 buffer。
```cpp
this->kernel.setArg(0, bufferCl);
this->kernel.setArg(1, imageWidth);
this->kernel.setArg(2, imageHeight);
this->kernel.setArg(3, clGrayHistogram);
this->kernel.setArg(4, clRedHistogram);
this->kernel.setArg(5, clGreenHistogram);
this->kernel.setArg(6, clBlueHistogram);
```
注意這裡有7個參數跟[[OpenCL#4. 建立 program]]的 `textCode`所定義的一樣。
## 10. 執行 kernel 並設定工作組大小
```cpp
auto err = queue.enqueueNDRangeKernel(this->kernel, cl::NullRange, cl::NDRange(imageWidth, imageHeight), cl::NullRange);
if (err != CL_SUCCESS) {
printf("OpenCL kernel error. err = %d\n", err);
}
```
第一個參數是[[OpenCL#6. 建立 kernel]]所建立的kernel。
第二個參數是偏移量,我們假設它在所有維度上都是 0。`cl::NullRange` 對象將滿足該 0 規範。
第三個參數是**全局大小它指定希望執行與內核對象K**關聯的內核源代碼中指定的工作項
第四個參數是本地大小,它指定應將多少工作項分組到一個工作組中。
重點在於第三個參數,因為 GPU 可以平行運算,這裡指定平行運算的數量。
## 11. 讀回資料
```cpp
grayHistogram.resize(sizeof(uint32_t) * 256);
err = queue.enqueueReadBuffer(clGrayHistogram, CL_TRUE, 0, 256 * sizeof(uint32_t), grayHistogram.data());
if (err != CL_SUCCESS) {
printf("OpenCL read clGrayHistogram error.\n");
}
```
第一行的 `grayHistogram` 是讀回 CPU 的記憶體。
[`enqueueReadBuffer`](https://registry.khronos.org/OpenCL/sdk/1.2/docs/man/xhtml/clEnqueueReadBuffer.html)的參數解釋:
第一個參數就是[[OpenCL#8. 建立 `cl::Buffer`]]所建立的其中一個 buffer。
第二個參數指定是否 **blocking_read**
第三個參數是 `cl::Buffer` 的偏移量。
第四個參數要讀取的長度。
第五個參數是要寫入的 memory address。

View File

@@ -0,0 +1,38 @@
## cv2.erode()
### 用途
1. Erosion 影像侵蝕對於移除影像中的小白雜點很有幫助,可用來去噪,例如影像中的小雜點,雜訊。
2. 細化影像,消除毛刺。
### Define
```python
cv.erode(src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]]) ->dst
```
### Arguments
`src`: 二值化的影像
`kernel`: 捲積 kernel
`iterations`: 迭代次數預設為1
## cv2.dilate()
### 用途
1. Dilation 影像膨脹通常是配合著影像侵蝕 Erosion 使用,先使用侵蝕的方式使影像中的線條變窄,同時也去除雜訊,之後再透過 Dilation 將影像膨脹回來。
2. 用來連接兩個很靠近但分開的物體。
### Define
```python
cv.dilate(src, kernel[, dst[, anchor[, iterations[, borderType[, borderValue]]]]]) ->dst
```
### Arguments
`src`: 二值化的影像
`kernel`: 捲積 kernel
`iterations`: 迭代次數預設為1
## cv2.GaussianBlur()
高斯濾波與平均濾波 Averaging 類似,平均濾波 Averaging 的 kernel 裡的每個 pixel 權重都是1而高斯濾波給予每個 pixel 不同權重,中心 pixel 的權重最高,越往邊角權重就越低,相較於平均濾波 Averaging 這樣可以讓圖片失真較少,高斯濾波通常去除雜訊也有不錯的效果。
## cv2.canny()
```python
cv.Canny( image, threshold1, threshold2[, edges[, apertureSize[, L2gradient]]] ) -> edges
```
Canny函数的使用很简单指定最大`threshold2`)和最小 threshold`threshold1`),其中 `apertureSize` 預設為3。

View File

@@ -0,0 +1,389 @@
## Build code
### 前置作業
1. 要有NVIDIA GPU
2. 安裝NVIDIA GPU driver
3. 安裝[CUDA](https://developer.nvidia.com/cuda-downloads)
4. 安裝[cuDNN](https://developer.nvidia.com/rdp/cudnn-download)
5. 安裝[CMake](https://cmake.org/download/)
### CMake && Visual Studio
1. 下載OpenCV[opencv](https://github.com/opencv/opencv)
2. 解壓縮後放到`D:\temp\build_opencv\opencv-4.5.3\source`
3. 建立`D:\temp\build_opencv\opencv-4.5.3\build`
4. 下載OpenCV contrib[opencv_contrib](https://github.com/opencv/opencv_contrib)
5. 解壓縮後放到`D:\temp\build_opencv\opencv_contrib`
6. 打開cmake-gui
7. 如圖設定
![[Pasted image 20210818115317.png]]
8.`Add Entry`先加入以下define
![[Pasted image 20210818115507.png]]
如果是檔名type就選FILEPATH如果是目錄type就選PATH
- PYTHON3_EXECUTABLE=C:/python39/python.exe
- PYTHON3_INCLUDE_DIR=C:/python39/include
- PYTHON3_LIBRARY=C:/python39/libs/python39.lib
- PYTHON3_NUMPY_INCLUDE_DIRS=C:/python39/Lib/site-packages/numpy/core/include
- PYTHON3_PACKAGES_PATH=C:/python39/Lib/site-packages
如果要build win32 + Python 3.6.3 x86的話改為以下設定
- PYTHON3_EXECUTABLE=C:/Python363/python.exe
- PYTHON3_INCLUDE_DIR=C:/Python363/include
- PYTHON3_LIBRARY=C:/Python363/libs/python36.lib
- PYTHON3_NUMPY_INCLUDE_DIRS=C:/Python363/Lib/site-packages/numpy/core/include
- PYTHON3_PACKAGES_PATH=C:/Python363/Lib/site-packages
9. 按Configure會跳出一個視窗platform選x64
![[Pasted image 20210818115809.png]]
10. 這些要打勾
- BUILD_opencv_world
- BUILD_opencv_python3
- OPENCV_DNN_CUDA
- OPENCV_PYTHON3_VERSION
- OPENCV_FORCE_PYTHON_LIBS
- OPENCV_ENABLE_NONFREE
- ENABLE_FAST_MATH
- WITH_CUDA
- WITH_OPENMP
- OPENCV_EXTRA_MODULES_PATH=D:/temp/build_opencv/opencv_contrib/modules
11. 再按一次Configure必須沒有錯誤的跑完像是
```
General configuration for OpenCV 4.5.3 =====================================
Version control: unknown
Extra modules:
Location (extra): D:/temp/build_opencv/opencv_contrib/modules
Version control (extra): 4.5.3-6-g907efb96
Platform:
Timestamp: 2021-08-18T03:40:06Z
Host: Windows 10.0.19043 AMD64
CMake: 3.21.1
CMake generator: Visual Studio 16 2019
CMake build tool: C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional/MSBuild/Current/Bin/MSBuild.exe
MSVC: 1929
Configuration: Debug Release
CPU/HW features:
Baseline: SSE SSE2 SSE3
requested: SSE3
Dispatched code generation: SSE4_1 SSE4_2 FP16 AVX AVX2 AVX512_SKX
requested: SSE4_1 SSE4_2 AVX FP16 AVX2 AVX512_SKX
SSE4_1 (17 files): + SSSE3 SSE4_1
SSE4_2 (2 files): + SSSE3 SSE4_1 POPCNT SSE4_2
FP16 (1 files): + SSSE3 SSE4_1 POPCNT SSE4_2 FP16 AVX
AVX (5 files): + SSSE3 SSE4_1 POPCNT SSE4_2 AVX
AVX2 (31 files): + SSSE3 SSE4_1 POPCNT SSE4_2 FP16 FMA3 AVX AVX2
AVX512_SKX (7 files): + SSSE3 SSE4_1 POPCNT SSE4_2 FP16 FMA3 AVX AVX2 AVX_512F AVX512_COMMON AVX512_SKX
C/C++:
Built as dynamic libs?: YES
C++ standard: 11
C++ Compiler: C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional/VC/Tools/MSVC/14.29.30037/bin/Hostx64/x64/cl.exe (ver 19.29.30040.0)
C++ flags (Release): /DWIN32 /D_WINDOWS /W4 /GR /D _CRT_SECURE_NO_DEPRECATE /D _CRT_NONSTDC_NO_DEPRECATE /D _SCL_SECURE_NO_WARNINGS /Gy /bigobj /Oi /fp:fast /EHa /wd4127 /wd4251 /wd4324 /wd4275 /wd4512 /wd4589 /MP /MD /O2 /Ob2 /DNDEBUG
C++ flags (Debug): /DWIN32 /D_WINDOWS /W4 /GR /D _CRT_SECURE_NO_DEPRECATE /D _CRT_NONSTDC_NO_DEPRECATE /D _SCL_SECURE_NO_WARNINGS /Gy /bigobj /Oi /fp:fast /EHa /wd4127 /wd4251 /wd4324 /wd4275 /wd4512 /wd4589 /MP /MDd /Zi /Ob0 /Od /RTC1
C Compiler: C:/Program Files (x86)/Microsoft Visual Studio/2019/Professional/VC/Tools/MSVC/14.29.30037/bin/Hostx64/x64/cl.exe
C flags (Release): /DWIN32 /D_WINDOWS /W3 /D _CRT_SECURE_NO_DEPRECATE /D _CRT_NONSTDC_NO_DEPRECATE /D _SCL_SECURE_NO_WARNINGS /Gy /bigobj /Oi /fp:fast /MP /MD /O2 /Ob2 /DNDEBUG
C flags (Debug): /DWIN32 /D_WINDOWS /W3 /D _CRT_SECURE_NO_DEPRECATE /D _CRT_NONSTDC_NO_DEPRECATE /D _SCL_SECURE_NO_WARNINGS /Gy /bigobj /Oi /fp:fast /MP /MDd /Zi /Ob0 /Od /RTC1
Linker flags (Release): /machine:x64 /INCREMENTAL:NO
Linker flags (Debug): /machine:x64 /debug /INCREMENTAL
ccache: NO
Precompiled headers: NO
Extra dependencies: cudart_static.lib nppc.lib nppial.lib nppicc.lib nppidei.lib nppif.lib nppig.lib nppim.lib nppist.lib nppisu.lib nppitc.lib npps.lib cublas.lib cudnn.lib cufft.lib -LIBPATH:C:/Program Files/NVIDIA GPU Computing Toolkit/CUDA/v11.4/lib/x64
3rdparty dependencies:
OpenCV modules:
To be built: aruco barcode bgsegm bioinspired calib3d ccalib core cudaarithm cudabgsegm cudacodec cudafeatures2d cudafilters cudaimgproc cudalegacy cudaobjdetect cudaoptflow cudastereo cudawarping cudev datasets dnn dnn_objdetect dnn_superres dpm face features2d flann fuzzy gapi hfs highgui img_hash imgcodecs imgproc intensity_transform line_descriptor mcc ml objdetect optflow phase_unwrapping photo plot python3 quality rapid reg rgbd saliency shape stereo stitching structured_light superres surface_matching text tracking ts video videoio videostab wechat_qrcode world xfeatures2d ximgproc xobjdetect xphoto
Disabled: -
Disabled by dependency: -
Unavailable: alphamat cvv freetype hdf java julia matlab ovis python2 python2 sfm viz
Applications: tests perf_tests apps
Documentation: NO
Non-free algorithms: NO
Windows RT support: NO
GUI:
Win32 UI: YES
VTK support: NO
Media I/O:
ZLib: build (ver 1.2.11)
JPEG: build-libjpeg-turbo (ver 2.1.0-62)
WEBP: build (ver encoder: 0x020f)
PNG: build (ver 1.6.37)
TIFF: build (ver 42 - 4.2.0)
JPEG 2000: build (ver 2.4.0)
OpenEXR: build (ver 2.3.0)
HDR: YES
SUNRASTER: YES
PXM: YES
PFM: YES
Video I/O:
DC1394: NO
FFMPEG: YES (prebuilt binaries)
avcodec: YES (58.134.100)
avformat: YES (58.76.100)
avutil: YES (56.70.100)
swscale: YES (5.9.100)
avresample: YES (4.0.0)
GStreamer: NO
DirectShow: YES
Media Foundation: YES
DXVA: YES
Parallel framework: Concurrency
Trace: YES (with Intel ITT)
Other third-party libraries:
Intel IPP: 2020.0.0 Gold [2020.0.0]
at: D:/temp/build_opencv/opencv-4.5.3/build/3rdparty/ippicv/ippicv_win/icv
Intel IPP IW: sources (2020.0.0)
at: D:/temp/build_opencv/opencv-4.5.3/build/3rdparty/ippicv/ippicv_win/iw
Lapack: NO
Eigen: NO
Custom HAL: NO
Protobuf: build (3.5.1)
NVIDIA CUDA: YES (ver 11.4, CUFFT CUBLAS)
NVIDIA GPU arch: 35 37 50 52 60 61 70 75 80 86
NVIDIA PTX archs:
cuDNN: YES (ver 8.2.2)
OpenCL: YES (NVD3D11)
Include path: D:/temp/build_opencv/opencv-4.5.3/source/3rdparty/include/opencl/1.2
Link libraries: Dynamic load
Python 3:
Interpreter: C:/python39/python.exe (ver 3.9.6)
Libraries: C:/python39/libs/python39.lib (ver 3.9.6)
numpy: C:/python39/Lib/site-packages/numpy/core/include (ver 1.19.5)
install path: C:/python39/Lib/site-packages/cv2/python-3.9
Python (for build): C:/python39/python.exe
Java:
ant: NO
JNI: NO
Java wrappers: NO
Java tests: NO
Install to: D:/temp/build_opencv/opencv-4.5.3/build/install
-----------------------------------------------------------------
Configuring done
```
12. 按下Generate按鈕就會生sln檔案。
### CMake by command
用command line的話就可以將多個平台一次編起來不用一直改GUI。
寫了一個`build.bat`可以編譯不同版本與平台:
```bat
rem build.bat
echo on
rem build.bat
rem win32 ARCH=%1, win32/x64
rem 15 VS_CODE=%2, 15/16
rem 2017 VS_VERSION=%3, 2017/2019
rem DEBUG BUILD_TYPE=%4, DEBUG/RELEASE
rem "C:/Python363/python.exe" PYTHON3_EXE=%5
rem "C:/Python363/include" PYTHON3_INCLUDE=%6
rem "C:/Python363/libs/python36.lib" PYTHON3_LIB=%7
rem "C:/Python363/Lib/site-packages/numpy/core/include" PYTHON3_NP_INCLUDE=%8
rem "C:/Python363/Lib/site-packages" PYTHON3_PACKAGES=%9
rem 3.6.3 PY_VERSION=%10
rem 1 CLEAN_BUILD=%11, 1: Delete build folder, 0: Do nothing
set DO_CONFIG=1
set DO_BUILD=1
set ARCH=%1
set VS_CODE=%2
set VS_VERSION=%3
set BUILD_TYPE=%4
set PYTHON3_EXE=%5
set PYTHON3_INCLUDE=%6
set PYTHON3_LIB=%7
set PYTHON3_NP_INCLUDE=%8
set PYTHON3_PACKAGES=%9
set GENERATOR="Visual Studio %VS_CODE% %VS_VERSION%"
shift
set PY_VERSION=%9
set CV_VERSION=4.5.3
set CV_SOURCE="opencv-%CV_VERSION%\source"
set CV_BUILD=build\vs%VS_VERSION%.%ARCH%.%BUILD_TYPE%
set CV_EXTRA_MODULES="opencv_contrib\modules"
shift
set CLEAN_BUILD=%9
if %CLEAN_BUILD% == 1 (
rmdir /s /q %CV_BUILD%
)
if %DO_CONFIG% == 1 (
mkdir %CV_BUILD%
"C:\Program Files\CMake\bin\cmake.exe" ^
-B%CV_BUILD% ^
-H%CV_SOURCE% ^
-G%GENERATOR% ^
-A%ARCH% ^
-DCMAKE_BUILD_TYPE=%BUILD_TYPE% ^
-DOPENCV_EXTRA_MODULES_PATH=%CV_EXTRA_MODULES% ^
-DCMAKE_INSTALL_PREFIX=%CV_BUILD% ^
-DINSTALL_PYTHON_EXAMPLES=OFF ^
-DINSTALL_C_EXAMPLES=OFF ^
-DBUILD_opencv_python3=ON ^
-DPYTHON3_EXECUTABLE=%PYTHON3_EXE% ^
-DPYTHON3_INCLUDE_DIR=%PYTHON3_INCLUDE% ^
-DPYTHON3_LIBRARY=%PYTHON3_LIB% ^
-DPYTHON3_NUMPY_INCLUDE_DIRS=%PYTHON3_NP_INCLUDE% ^
-DPYTHON3_PACKAGES_PATH=%PYTHON3_PACKAGES% ^
-DOPENCV_PYTHON3_VERSION=%PY_VERSION% ^
-DOPENCV_FORCE_PYTHON_LIBS=ON ^
-DBUILD_opencv_world=ON ^
-DOPENCV_ENABLE_NONFREE=ON ^
-DENABLE_FAST_MATH=ON ^
-DWITH_OPENMP=ON ^
-DWITH_OPENGL=ON
)
if %DO_BUILD% == 1 (
"C:\Program Files\CMake\bin\cmake.exe" --build %CV_BUILD% --target INSTALL --config %BUILD_TYPE%
)
```
這樣以後就可以用參數的方法來設定譬如要使用vs2019來編譯x64 release的話就可以這樣下
```bat
rem build_vs2019.x64.RELEASE.bat
echo off
rem ----------------------------------------------------------------------------------------------------
rem
rem x64, RELEASE, 2019, CLEAN
rem
set ARCH=x64
set VS_CODE=16
set VS_VERSION=2019
set BUILD_TYPE=RELEASE
set PYTHON3_EXE="C:/Python39/python.exe"
set PYTHON3_INCLUDE="C:/Python39/include"
set PYTHON3_LIB="C:/Python39/libs/python39.lib"
set PYTHON3_NP_INCLUDE="C:/Python39/Lib/site-packages/numpy/core/include"
set PYTHON3_PACKAGES="C:/Python39/Lib/site-packages"
set PY_VERSION=3.9
set CLEAN_BUILD=1
echo "============================================================"
echo "Build %ARCH%.%VS_VERSION%(%VS_CODE%).%BUILD_TYPE%, Python=%PY_VERSION%, CLEAN_BUILD=%CLEAN_BUILD%"
echo ""
call build.bat ^
%ARCH% %VS_CODE% %VS_VERSION% %BUILD_TYPE% %PYTHON3_EXE% ^
%PYTHON3_INCLUDE% %PYTHON3_LIB% %PYTHON3_NP_INCLUDE% %PYTHON3_PACKAGES% ^
%PY_VERSION% %CLEAN_BUILD%
rem ----------------------------------------------------------------------------------------------------
```
### 參考
- [Build OpenCV GPU Version On Windows 10](https://medium.com/chung-yi/build-opencv-gpu-version-on-windows-10-c37a33437525)
- [Cannot install openCV 3.1.0 with python3. CMAKE not including or linking python correctly](https://stackoverflow.com/questions/42638342/cannot-install-opencv-3-1-0-with-python3-cmake-not-including-or-linking-python)
- [在Windows上编译带CUDA(GPU)的OpenCV](https://shaogui.life/2021/03/08/%E5%9C%A8Windows%E4%B8%8A%E7%BC%96%E8%AF%91%E5%B8%A6CUDA(GPU)%E7%9A%84opencv/)
- [How to use OpenCV DNN Module with Nvidia GPU on Windows](https://learnopencv.com/how-to-use-opencv-dnn-module-with-nvidia-gpu-on-windows/)
- [Accelerate OpenCV 4.5.0 on Windows build with CUDA and python bindings - James Bowley](https://jamesbowley.co.uk/accelerate-opencv-4-5-0-on-windows-build-with-cuda-and-python-bindings/#python_bindings)
## 影像處理
- sift
```python
def adjust_gamma(image, gamma=1.0):
invGamma = 1.0 / gamma
table = np.array([((i / 255.0) ** invGamma) * 255
for i in np.arange(0, 256)]).astype("uint8")
return cv2.LUT(image, table)
def sift_compare(path_a, path_b):
'''
Use SIFT features to measure image similarity
@args:
{str} path_a: the path to an image file
{str} path_b: the path to an image file
@returns:
TODO
'''
# initialize the sift feature detector
orb = cv2.ORB_create()
# get the images
img_a = cv2.imread(path_a)
img_b = cv2.imread(path_b)
img_a = adjust_gamma(img_a, 0.1)
img_b = adjust_gamma(img_b, 0.1)
# find the keypoints and descriptors with SIFT
kp_a, desc_a = orb.detectAndCompute(img_a, None)
kp_b, desc_b = orb.detectAndCompute(img_b, None)
# print(f'len kp_a = {len(kp_a)}, len desc_a = {len(desc_a)}')
# print(f'type kp_b = {type(kp_b)}, type desc_b = {type(desc_b)}')
# print(f'len kp_b = {len(kp_b)}, len desc_b = {len(desc_b) if desc_b else 0}')
if desc_a is None or desc_b is None:
# rr.LOG('Error: desc_a = {}, desc_b = {}'.format(type(desc_a), type(desc_b)))
# rr.LOG('Error: Return score 0.')
return 0
# Initialize the bruteforce matcher
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
# match.distance is a float between {0:100} - lower means more similar
matches = bf.match(desc_a, desc_b)
similar_regions = [i for i in matches if i.distance < 70]
# print('Length of similar_regions = {}'.format(len(similar_regions)))
# print('Length of matches = {}'.format(len(matches)))
sorted_matches = sorted(matches, key = lambda x:x.distance)
good_matches = sorted_matches[:int(len(sorted_matches) * 0.5)]
similar_regions2 = [i for i in good_matches if i.distance < 70]
# print('Length of good_matches = {}'.format(len(good_matches)))
if len(matches) == 0:
return 0
return len(similar_regions) / len(matches)
```
### Color tempertature
code
```
img1 = cv2.imread(file)
B, G, R = cv2.split(img1)
avgB = cv2.mean(B)[0]
avgG = cv2.mean(G)[0]
avgR = cv2.mean(R)[0]
# X = avgR * -0.14282 + avgG * 1.54924 + avgB * -0.95641
# Y = avgR * -0.32466 + avgG * 1.57837 + avgB * -0.73191
# Z = avgR * -0.68202 + avgG * 0.77073 + avgB * 0.56332
X = 2.789 * avgR + 1.7517 * avgG + 1.1302 * avgB
Y = 1 * avgR + 4.5907 * avgG + 0.0601 * avgB
Z = 0 * avgR + 0.0565 * avgG + 5.5943 * avgB
x = X / (X + Y + Z)
y = Y / (X + Y + Z)
n = (x - 0.3320) / (0.1858 - y)
# n = (0.23881 * avgR + 0.25499 * avgG + -0.58291 * avgB) / (0.11109 * avgR + -0.85406 * avgG + 0.52289 * avgB)
CCT = 449 * pow(n, 3) + 3525 * pow(n, 2) + 6823.3 * n + 5520.33
# CCT = 437 * pow(n, 3) + 3601 * pow(n, 2) + 6831 * n + 5517
# print(f'x = {x}')
# print(f'y = {y}')
# print(f'n = {n}')
print(f'{file}: CCT = {CCT}')
```
- [How to Calculate the Color Temperature / Tint of the Colors in an Image?](https://dsp.stackexchange.com/questions/8949/how-to-calculate-the-color-temperature-tint-of-the-colors-in-an-image)
- [Calculating Color Temperature and Illuminance using the TAOS TCS3414CS Digital Color Sensor_](https://ams.com/documents/20143/80162/TCS34xx_AN000517_1-00.pdf)
- [Java RGB转色温(CCT)](https://www.codeleading.com/article/16404945886/)
- [Calculate color temperature (CCT) from CIE 1931 xy coordinates](https://www.waveformlighting.com/tech/calculate-color-temperature-cct-from-cie-1931-xy-coordinates)
- [OpenCV: Color conversions](https://docs.opencv.org/3.4/de/d25/imgproc_color_conversions.html)
- [sRGB色彩空間 - 維基百科,自由的百科全書](https://zh.wikipedia.org/wiki/SRGB%E8%89%B2%E5%BD%A9%E7%A9%BA%E9%97%B4)
- [Color temperature - Wikipedia](https://en.wikipedia.org/wiki/Color_temperature#Approximation)
- [What color is a blackbody? - some pixel rgb values](http://www.vendian.org/mncharity/dir3/blackbody/)

View File

@@ -0,0 +1,2 @@
- [opengl-tutorial](http://www.opengl-tutorial.org/cn/beginners-tutorials/tutorial-1-opening-a-window/)
- [Learn OpenGL, extensive tutorial resource for learning Modern OpenGL](https://learnopengl.com/)

View File

@@ -0,0 +1,14 @@
```python
import os
import sys
config_name = 'myapp.cfg'
# determine if application is a script file or frozen exe
if getattr(sys, 'frozen', False):
application_path = os.path.dirname(sys.executable)
elif __file__:
application_path = os.path.dirname(__file__)
config_path = os.path.join(application_path, config_name)
```

View File

@@ -0,0 +1,34 @@
---
tags:
aliases:
date: 2024-11-10
time: 16:58:43
description:
---
**可以用來代替[Matplotlib](https://matplotlib.org/)**
Yes, `Matplotlib` is classic-its virtually the standard to go to when it comes to visualizing data in Python. But to be frank, it feels so much like trying to use an axe for delicate brain surgery, and its syntax? A little verbose, if were being honest. If youre not creating highly customized visualizations, there are better options with a more straightforward syntax.
## Why [Matplotlib](https://matplotlib.org/) is Overrated:
**Clunky syntax**: Even simple charts take an amazingly large number of lines to plot sometimes.
**Outdated default style:** The default style is configurable, but it isnt exactly inspiring-or, for that matter, particularly readable.
## What You Should Replace It With: Plotly
Where visualization cleanliness and interactivity matter, and definitely dont want a pile of code, `Plotly` is great. This is especially useful when you have to share visuals fast or within presentations on the web.
```python
import plotly.express as px
df = px.data.iris()
fig = px.scatter(df, x="sepal_width", y="sepal_length", color="species")
fig.show()
```
With `Ploty`, you immediately get interactive graphs with great default visuals. The code is more concise and, by default, includes things like tooltips and zooming.
# 參考來源
- [5 Overrated Python Libraries (And What You Should Use Instead) | by Abdur Rahman | Nov, 2024 | Python in Plain English](https://python.plainenglish.io/5-overrated-python-libraries-and-what-you-should-use-instead-106bd9ded180)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,34 @@
---
tags:
aliases:
date: 2024-11-10
time: 16:57:31
description:
---
**可以用來代替[pandas](https://pandas.pydata.org/)**
Now, listen up-the thing is, `Pandas` is great at data exploration and for middle-sized datasets. But people just use it for everything, like its some magic solution thats going to solve every problem in data, and quite frankly, it isnt. Working with `Pandas` on huge datasets can turn your machine into a sputtering fan engine, and memory overhead just doesnt make sense for some workflows.
## **Why [pandas](https://pandas.pydata.org/) Is Overrated:**
**Memory Usage:** As `Pandas` operates mainly in-memory, any operation on a large dataset will badly hit performance.
**Limited Scalability:** Scaling with `Pandas` isnt easy. It was never designed for big data.
## What You Should Use Instead: Polars
`Polars` is an ultra-fast DataFrame library in Rust using Apache Arrow. Optimized for memory efficiency and multithreaded performance, this makes it perfect for when you want to crunch data without heating up your CPU.
```python
import polars as pl
df = pl.read_csv("big_data.csv")
filtered_df = df.filter(pl.col("value") > 50)
print(filtered_df)
```
**Why** `**Polars**`**?** It will process data that would bring `Pandas` to its knees, and it handles operations in a fraction of the time. Besides that, it also has lazy evaluation-meaning it is only computing whats needed.
# 參考來源
- [5 Overrated Python Libraries (And What You Should Use Instead) | by Abdur Rahman | Nov, 2024 | Python in Plain English](https://python.plainenglish.io/5-overrated-python-libraries-and-what-you-should-use-instead-106bd9ded180)

View File

@@ -0,0 +1,45 @@
---
tags:
aliases:
date: 2024-11-10
time: 17:00:12
description:
---
**可以用來代替[scikit-learn](https://scikit-learn.org/stable/)**
I know, `Scikit-Learn` isnt supposed to be a deep learning library, but people use it as if it were. It is incredibly handy at quick prototyping and traditional machine learning models, but when it comes to neural networks, its just not in the same league as a library designed with tensors in mind.
## Why [scikit-learn](https://scikit-learn.org/stable/) is Overrated:
**No GPU Support:** Deep learning can be life-changing when training on GPUs. However, this is something that is not supported in `Scikit-Learn`.
**Not Optimized for Neural Networks:** `Scikit-learn` wasnt designed for doing deep learning; using it this way is reactively assured poor results.
## What You Should Use Instead: PyTorch
`PyTorch` is more general and supports GPU. Hence, its perfect for deep learning projects. Its Pythonic-this means for one coming from `Scikit-Learn`, it will feel natural, but with much more power.
import torch
import torch.nn as nn
import torch.optim as optim
# Define a simple model
```python
model = nn.Sequential(
nn.Linear(10, 5),
nn.ReLU(),
nn.Linear(5, 2)
)
```
# Define optimizer and loss
```python
optimizer = optim.SGD(model.parameters(), lr=0.01)
loss_fn = nn.CrossEntropyLoss()
```
If youre serious about deep learning, youll want to use a library worked out for the task at hand-which will save you from such limitations and inefficiencies. You will fine tune models with `PyTorch` and leverage the GPUs to your hearts content.
# 參考來源
- [5 Overrated Python Libraries (And What You Should Use Instead) | by Abdur Rahman | Nov, 2024 | Python in Plain English](https://python.plainenglish.io/5-overrated-python-libraries-and-what-you-should-use-instead-106bd9ded180)

View File

@@ -0,0 +1,173 @@
---
tags:
aliases:
date: 2025-03-02
time: 20:53:47
description:
---
# Project Structure
```
mlpredictor/
├── mlpredictor/
│ ├── __init__.py
│ ├── model.py
├── tests/
│ ├── test_model.py
├── LICENSE
├── README.md
├── pyproject.toml
└── .gitignore
```
## Content of `setup.py`
```
from setuptools import setup
setup(
name='mypackage',
version='0.1',
packages=['mypackage'],
install_requires=['requests'])
```
## Content of `pyproject.toml`
```toml
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "mlpredictor"
version = "0.1.0"
description = "A simple machine learning package using scikit-learn"
authors = [
{name = "Ebrahim", email = "ebimsv0501@gmail.com"}
]
license = {text = "MIT"}
readme = "README.md"
requires-python = ">=3.6"
dependencies = [
"scikit-learn>=1.0",
]
[project.urls]
"Homepage" = "https://github.com/ebimsv/mlpredictor"
```
- **[build-system]**: Specifies the build system requirements (i.e., using `setuptools` and `wheel`).
- **[project]**: Contains metadata about the package, like name, version, description, and dependencies.
## Content of `README.md`
<pre><code>
# MLPredictor
MLPredictor is a simple machine learning package that trains a RandomForest model using the Iris dataset and enables users to make predictions. The package is built using `scikit-learn` and is intended as a demonstration of packaging Python machine learning projects for distribution.
## Features
- Train a RandomForestClassifier on the Iris dataset.
- Make predictions on new data after training.
- Save and load trained models.
## Installation
You can install the package via **PyPI** or from **source**.
### Install from PyPI
```bash
pip install mlpredictor
```
### Install from Source (GitHub)
```bash
git clone https://github.com/ebimsv/mlpredictor.git
cd mlpredictor
pip install .
```
## Usage
After installation, you can use `MLPredictor` to train a model and make predictions.
### Example: Training and Making Predictions
```python
from mlpredictor import MLPredictor
# Initialize the predictor
predictor = MLPredictor()
# Train the model on the Iris dataset
predictor.train()
# Make a prediction on a sample input
sample_input = [5.1, 3.5, 1.4, 0.2]
prediction = predictor.predict(sample_input)
print(f"Predicted class: {prediction}")
```
</code></pre>
## Content of `.gitignore`
```
*.pyc
__pycache__/
*.pkl
dist/
build/
```
# Test
## Content of `tests/test_model.py`
```python
import pytest
from mlpredictor import MLPredictor
def test_train_and_predict():
model = MLPredictor()
model.train()
result = model.predict([5.1, 3.5, 1.4, 0.2])
assert len(result) == 1
if __name__ == "__main__":
pytest.main()
```
## Run test
```bash
pytest tests
```
# Install
## Test locally
```
pip install .
```
## Publish on PyPI
1. **Install 'Twine' and 'build'**:
```
pip install twine build
```
2. **Build the Package**:
```
python -m build
```
3. **Upload to PyPI**
```
twine upload dist/*
```
# 參考來源
- [Building Python Packages. A Comprehensive Guide to setup.py and… | by Ebrahim Mousavi | Medium](https://medium.com/@ebimsv/building-python-packages-07fbfbb959a9)

View File

@@ -0,0 +1,56 @@
一個範例:
```python
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-u", "--update_tool", default=WIN_FW_UPDATER_PATH, help="The path of win_fw_updater.exe.")
parser.add_argument("-f", "--firmware", required=True, help="The path of ITB file.")
parser.add_argument("-w", "--waittime_update_firmware", default=600, type=int, help="Wait time for update the firmware.")
parser.add_argument("-g", "--ignore_check_file_path", action='store_true', help="Skip check the existence of file.")
args = parser.parse_args()
```
#### 要求user一定要設定的參數
使用`required=True`
例如:
```python
parser.add_argument("-f", "--firmware", required=True, help="The path of ITB file.")
```
如果使用者沒有下`-f`(或者`--firmware=XXX`)就會報錯,如下:
```bash
FwUpdateCheck.py: error: the following arguments are required: -f/--firmware
```
#### 有設定才會產生的參數
使用`action='store_true'``action='store_false'`
例如:
```python
parser.add_argument("-g", "--ignore_check_file_path", action='store_true', help="Skip check the existence of file.")
```
當使用者沒有設置`-g`時,`args.ignore_check_file_path``False`,當設置時,`args.ignore_check_file_path``True`
#### 使用預設值
例如:
```python
parser.add_argument("-u", "--update_tool", default="C:\\tool.exe", help="The path of win_fw_updater.exe.")
```
`default=<Something>`來設定參數的預設值,上面的例子中,`args.update_tool`的預設值為`C:\tool.exe`
另外可以用`type=<Object type>`來指定預設值的型別。例如:
```python
parser.add_argument("-n", "--number", default=50, type=int, help="Assign a number")
```
上例中,`args.number`的預設值是50型別是`int`,所以可以直接運算,不需要再經過`int(args.number)`這樣的轉換。
#### 限制使用者的選擇
Example:
```python
parser.add_argument('move', choices=['rock', 'paper', 'scissors'])
```
使用`choices=<list>`來限定輸入的選項,上例中,使用者只能輸入'rock'、'paper'、'scissors'這三個字串中的其中一個,否則會報錯:
```bash
error: argument move: invalid choice: 'fire' (choose from 'rock', 'paper', 'scissors')
```
-----
- https://docs.python.org/zh-tw/3/library/argparse.html

View File

@@ -0,0 +1,5 @@
從list中選出n個項目有可能重複
```python
import random
random.choices(seq, n)
```

View File

@@ -0,0 +1,48 @@
## 在decorator內取得function的default argument與class member
```python
import sys
import inspect
from functools import wraps
def exampleDecorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
print(f"Decorator: call by {func.__name__}")
def get_default_args(func):
signature = inspect.signature(func)
return {
k: v.default for k, v in signature.parameters.items() if v.default is not inspect.Parameter.empty
}
## Get default
defaultKwargs = get_default_args(func)
defaultKwargs.update(kwargs)
print(f"Decorator: args = {args}, kwargs = {kwargs}, defaultKwargs = {defaultKwargs}")
objectInstance = args[0]
if hasattr(objectInstance, 'defaultArg1'):
print(f'objectInstance has defaultArg1, a.defaultArg1({type(objectInstance.defaultArg1)}) = {objectInstance.defaultArg1}')
if objectInstance.defaultArg1:
## Do something here
print("Decorator: some message...")
else:
print('objectInstance does not have defaultArg1')
return func(*args, **kwargs)
return wrapper
class ExampleClass():
def __init__(self, defaultArg1=True, defaultArg2="SomeString"):
self.defaultArg1 = defaultArg1
self.defaultArg2 = defaultArg2
print(f'self.defaultArg1 = {self.defaultArg1}, self.defaultArg2 = {self.defaultArg2}')
@exampleDecorator
def run(self, arg1=1, arg2=2):
print(f"ExampleClass.run(), arg1 = {arg1}, arg2 = {arg2}")
example = ExampleClass()
example.run()
```

View File

@@ -0,0 +1,36 @@
---
tags:
aliases:
date: 2024-11-10
time: 16:54:12
description:
---
**可以用來代替[requests](https://pypi.org/project/requests/)**
## **Why [requests](https://pypi.org/project/requests/) is Overrated:**
**Blocking IO:** `Requests` is synchronous, which means each call waits for the previous call to finish. This is less than ideal when working with I/O-bound programs.
**Heavy:** Its got loads of convenience baked in, but it does have a cost in terms of speed and memory footprint. Not a big deal on a simple script, but on larger systems this can be a resource hog.
## **What You Should Instead Use:** `httpx`
For parallel processing of requests, `httpx`provides a similar API but with asynchronous support. So, if you make many API calls, itll save you some time and resources because it will process those requests concurrently.
```python
import httpx
async def fetch_data(url):
async with httpx.AsyncClient() as client:
response = await client.get(url)
return response.json()
# Simple and non-blocking
data = fetch_data("https://api.example.com/data")
```
> **Pro Tip:** Asynchronous requests can reduce the processing time by a great amount if the task at hand is web scraping or ingesting data from somewhere.
# 參考來源
- [5 Overrated Python Libraries (And What You Should Use Instead) | by Abdur Rahman | Nov, 2024 | Python in Plain English](https://python.plainenglish.io/5-overrated-python-libraries-and-what-you-should-use-instead-106bd9ded180)

View File

@@ -0,0 +1,4 @@
- [Python 中的 Log 利器:使用 logging 模組來整理 print 訊息 - zhung to be lazy…](https://zhung.com.tw/article/python%E4%B8%AD%E7%9A%84log%E5%88%A9%E5%99%A8-%E4%BD%BF%E7%94%A8logging%E6%A8%A1%E7%B5%84%E4%BE%86%E6%95%B4%E7%90%86print%E8%A8%8A%E6%81%AF/)\
- [[Python] logging 教學](https://zwindr.blogspot.com/2016/08/python-logging.html)
- [How can I color Python logging output? - Stack Overflow](https://stackoverflow.com/questions/384076/how-can-i-color-python-logging-output)
- [logging如何使用 logging 紀錄事件 - Andrew Li](https://orcahmlee.github.io/python/python-logging/)

View File

@@ -0,0 +1,126 @@
### 準備
```
import logging
```
### logging level
| level | level number | funtion |
|:---------|:-------------|:---------------------|
| NOTSET | 0 | |
| DEBUG | 10 | `logging.debug()` |
| INFO | 20 | `logging.info()` |
| WARNING | 30 | `logging.warning()` |
| ERROR | 40 | `logging.error()` |
| CRITICAL | 50 | `logging.critical()` |
```
import logging
LOG_FORMAT = '%(asctime)s %(levelname)s: %(message)s'
LOG_FILENAME = 'C:\\RobotRun\\Output\\RobotRunDocUpdater.log'
logging.basicConfig(level=logging.INFO, filename=LOG_FILENAME, filemode='a', format=LOG_FORMAT)
logging.info('logging start')
```
### Print Exception
`logging` 模組也提供可以紀錄完整的堆疊追蹤 (stack traces),若在 `logging.error()` 加上 `exc_info` 參數,並將該參數設為 `True`,就可以紀錄 Exception如下
```python
import logging
try:
x = 5 / 0
except:
logging.error("Catch an exception.", exc_info=True)
```
也可以使用`logging.exception("Catch an exception.")`,效果跟`logging.error("Catch an exception.", exc_info=True)`一樣。
### 自訂 logging 輸出格式
預設的訊息輸出格式只有 `levelname``name``message`,下面是其他相關的資訊:
| 格式化字串 | 說明 |
|:------------------|:---------------------------------------------------------------------|
| `%(asctime)s` | 日期時間, 格式為 `YYYY-MM-DD HH:mm:SS,ms`例如2018-12-13 17:20:30,567 |
| `%(filename)s` | 模組檔名 |
| `%(funcName)s` | 函數名稱 |
| `%(levelname)s` | 日誌的等級名稱 |
| `%(levelno)s` | 日誌的等級數值 |
| `%(lineno)d` | 呼叫日誌函數所在的行數 |
| `%(message)s` | 訊息 |
| `%(module)s` | 模組名稱 |
| `%(name)s` | logger 的名稱 |
| `%(pathname)s` | 檔案的完整路徑 (如果可用) |
| `%(process)d` | process ID (如果可用) |
| `%(thread)d` | 執行緒 ID (如果可用) |
| `%(threradName)s` | 執行緒名稱 |
例:
```python
FORMAT = '%(asctime)s %(levelname)s: %(message)s'
logging.basicConfig(level=logging.DEBUG, format=FORMAT)
logging.debug('debug message') --> 2018-12-13 17:40:34,604 DEBUG: debug message
```
### 儲存log
只要在 `logging.basicConfig()` 內的 `filename` 參數設定要儲存的日誌檔名,就可以將 logging 儲存:
```python
import logging
FORMAT = '%(asctime)s %(levelname)s: %(message)s'
logging.basicConfig(level=logging.DEBUG, filename='myLog.log', filemode='w', format=FORMAT)
logging.debug('debug message')
```
預設 `filemode` 參數是設為 `a`,代表 append (附加) 的意思每次執行程式時Logging 會將新的訊息加在舊的訊息後面,不會覆蓋舊的訊息。若要改成新訊息覆蓋就訊息,那可以將 `filemode` 參數設為 `w`,代表 write 的意思。
### 儲存log也輸出到console
`logging`有4個主要module
- Logger暴露了應用程式程式碼能直接使用的介面。
- Handler記錄器產生的日誌記錄傳送至合適的目的地。
- Filter提供了更好的粒度控制它可以決定輸出哪些日誌記錄。
- Formatter指明瞭最終輸出中日誌記錄的佈局。
#### Handler
其中`Handlers`有以下幾類:
1. `logging.StreamHandler` -> 控制檯輸出
使用這個Handler可以向類似與`sys.stdout`或者`sys.stderr`的任何檔案物件(file object)輸出資訊。
它的建構函式是: `StreamHandler([strm])` 其中`strm`引數是一個檔案物件。預設是`sys.stderr`
2. `logging.FileHandler` -> 檔案輸出
和StreamHandler類似用於向一個檔案輸出日誌資訊。不過`FileHandler`會幫你開啟這個檔案。
它的建構函式是:`FileHandler(filename[,mode])` filename是檔名必須指定一個檔名。 `mode`是檔案的開啟方式。預設是`'a'`,即新增到檔案末端。
3. `logging.handlers.RotatingFileHandler` -> 按照大小自動分割日誌檔案,一旦達到指定的大小重新生成檔案
這個Handler類似於上面的`FileHandler`但是它可以管理檔案大小。當檔案達到一定大小之後它會自動將當前日誌檔案改名然後建立一個新的同名日誌檔案繼續輸出。比如日誌檔案是chat.log。當chat.log達到指定的大小之後`RotatingFileHandler`自動把 檔案改名為chat.log.1。不過如果chat.log.1已經存在會先把chat.log.1重新命名為chat.log.2。
最後重新建立 chat.log繼續輸出日誌資訊。它的建構函式是`RotatingFileHandler(filename[, mode[, maxBytes[, backupCount]]])`,其中`filename``mode`兩個引數和FileHandler一樣。`maxBytes`用於指定日誌檔案的最大檔案大小。如果maxBytes為0意味著日誌檔案可以無限大這時上面描述的重新命名過程就不會發生。 `backupCount`用於指定保留的備份檔案的個數。比如如果指定為2當上面描述的重新命名過程發生時原有的chat.log.2並不會被更名,而是被刪除。
4. `logging.handlers.TimedRotatingFileHandler` -> 按照時間自動分割日誌檔案
這個Handler和`RotatingFileHandler`類似,不過,它沒有通過判斷檔案大小來決定何時重新建立日誌檔案,而是間隔一定時間就自動建立新的日誌檔案。重新命名的過程與`RotatingFileHandler`類似,不過新的檔案不是附加數字,而是當前時間。它的建構函式是:`TimedRotatingFileHandler( filename [,when [,interval [,backupCount]]])`,其中`filename`引數和`backupCount`引數和`RotatingFileHandler`具有相同的意義。`interval`是時間間隔。 `when`引數是一個字串。表示時間間隔的單位,不區分大小寫。它有以下取值: S 秒 M 分 H 小時 D 天 W 每星期(`interval==0`時代表星期一) midnight 每天凌晨。
#### Formatters
Formatters預設的時間格式為`%Y-%m-%d %H:%M:%S`
#### Example
新增2個handler一個輸出到螢幕上一個寫到檔案裡。寫到檔案裡的那個handler必須是`logging.handlers.RotatingFileHandler`超過1MB時會自動分割。
```python
import logging
import logging.handlers
logger = logging.getLogger(filename) # filename就是你要存log的檔名
shell_print = logging.StreamHandler() # 往螢幕上輸出
shell_print.setFormatter(format_str) # 設定螢幕上顯示的格式
file_print = logging.handlers.RotatingFileHandler(
filename=filename,
mode='a',
maxBytes=1024*1024,
backupCount=backCount,
encoding='utf-8')
file_print.setFormatter(format_str) # 設定檔案裡寫入的格式
logger.addHandler(sh) # 把物件加到logger裡
logger.addHandler(th)
```
-----
參考:
- [Python - 日誌 (logging) 模組](https://titangene.github.io/article/python-logging.html)
- [`logging` — Logging facility for Python](https://docs.python.org/3/library/logging.html#module-logging "logging: Flexible event logging system for applications.")

View File

@@ -0,0 +1,72 @@
## 基本折線圖
給2個list一個 x一個 y
```python
plt.clf() # 把圖清掉,變空白
plt.plot(xList, yList)
```
## XY軸標籤
```python
plt.xlabel(
'Focus setting', # 標籤
fontsize=15, # 字型大小
labelpad=10, # 標籤留白
color='red', # 文字顏色
rotation=90, # 文字旋轉角度
fontweight='bold', # 粗體
)
```
## 不要顯示軸的刻線
```python
plt.gca().axes.get_xaxis().set_visible(False)
```
## 畫2張圖
```python
figure, axis = plt.subplots(2, 2)
```
`plt.subplots()` 來指定要畫幾張圖,第一個參數是要有幾個 row第二個參數是要有幾個 column。
`axis` 會是一個 array可以用類似座標的方式來控制你要的圖例如
```python
axis[0, 0].set_title("Sine Function")
axis[0, 1].set_title("Cosine Function")
```
`figure` 則是指外圍的大圖。
## 畫2條線
```python
plt.plot(x, y1, label='sine curve',color='b')
plt.plot(x, y2, label='cosine curve',color='r')
```
## 畫大圖
```python
figure(figsize=(12, 9), dpi=120)
```
`12``9`指的是英吋,`dpi`是每英吋幾個點,所以就是`12*120``9*120`,也就是`1440x1080`
## 存檔
```python
plt.savefig(f'plot_{folder}.png')
```
## 註記annotation
```python
ax = plt.gca()
ax.annotate(
'local max', # 註記文字
xy=(xmax, ymax), # 點的座標
xytext=(xmax, ymax + 5), # 文字的座標
arrowprops=dict( # 箭頭的屬性
facecolor='black', # 顏色:黑色
shrink=0.05), #
)
```
官方說明:[matplotlib.axes.Axes.annotate — Matplotlib 3.7.1](https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.annotate.html)
## 在X軸上畫一個範圍
```python
plt.gca().axvspan(startXPos, endXPos, alpha=0.2, color='red')
```

View File

@@ -0,0 +1,61 @@
### 將camera包裝成class
```python
class CameraCv(object):
def __init__(self, videoSource=0):
self.videoSource = videoSource
self.camera = None
self.cameraWidth = 0
self.cameraHeight = 0
self.cameraPreviewThreadHandle = None
self.cameraPreviewThreadStopEvent = threading.Event()
self.lastframeRGB = None
self.latestFrame = None
def start(self):
print("Open Camera")
self.camera = cv2.VideoCapture(self.videoSource, cv2.CAP_DSHOW)
if not self.camera.isOpened():
raise ValueError("Unable to open video source {}".format(self.videoSource))
# Get video source width and height
self.cameraWidth = self.camera.get(cv2.CAP_PROP_FRAME_WIDTH)
self.cameraHeight = self.camera.get(cv2.CAP_PROP_FRAME_HEIGHT)
self.cameraPreviewThreadStopEvent.clear()
self.cameraPreviewThreadHandle = threading.Thread(target=self.collectFrame, daemon=True, args=())
self.cameraPreviewThreadHandle.start()
def stop(self):
print("Close Camera")
self.cameraPreviewThreadStopEvent.set()
if self.camera.isOpened():
self.camera.release()
cv2.destroyAllWindows()
def collectFrame(self):
while True:
ret, frame = self.camera.read()
if ret:
# Return a boolean success flag and the current frame converted to BGR
self.lastframeRGB = frame
self.latestFrame = ImageTk.PhotoImage(image=Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)))
if self.cameraPreviewThreadStopEvent.is_set():
break
time.sleep(0.016)
def draw(self, container):
if self.latestFrame is not None:
container.imgtk = self.latestFrame
container.configure(image=self.latestFrame)
def read(self):
return self.camera.read()
def getLastFrameRgb(self):
return self.lastframeRGB
def saveFrame(self, filepath):
cv2.imwrite(filepath, self.getLastFrameRgb())
```

View File

@@ -0,0 +1,7 @@
從一個list中選出n個不重複的項目
```python
import random
random.sample(seq, n)
```
不像 [[choices()]] 是會重複的。

View File

@@ -0,0 +1,35 @@
---
tags:
aliases:
date: 2024-11-10
time: 16:55:41
description:
---
**可以用來代替[Beautiful Soup Documentation](https://www.crummy.com/software/BeautifulSoup/bs4/doc/)**
## **Why [Beautiful Soup Documentation](https://www.crummy.com/software/BeautifulSoup/bs4/doc/) is Overrated:**
**Speed:** Not very fast, when the size of a document is very big.
**Thread blocking:** Much like `Requests` itself, it is not designed with async in mind, which certainly makes it ill-suited for scraping dynamic websites.
## **Instead What you should use:** `selectolax`
`selectolax` is a less famous library that uses `libxml2` for better performance and with less memory consumption.
```python
from selectolax.parser import HTMLParser
html_content = "<html><body><p>Test</p></body></html>"
tree = HTMLParser(html_content)
text = tree.css("p")[0].text()
print(text) # Output: Test
```
As it will turn out, by using `Selectolax`, you retain the same HTML parsing capabilities but with much-enhanced speed, making it ideal for web scraping tasks that are quite data-intensive.
> **“Do not fall in love with the tool; rather, fall in love with the outcome.” Choosing the proper tool is half the battle.**
# 參考來源
- [5 Overrated Python Libraries (And What You Should Use Instead) | by Abdur Rahman | Nov, 2024 | Python in Plain English](https://python.plainenglish.io/5-overrated-python-libraries-and-what-you-should-use-instead-106bd9ded180)

View File

@@ -0,0 +1,49 @@
### subprocess.Popen
```python
import subprocess
process = subprocess.Popen(['echo', 'More output'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = process.communicate()
stdout, stderr
```
Input arguments is a list.
Notice `communicate()` will **block** until process was finished.
And the output string `stdout` and `stderr` is of type `byte`. You can convert the output to `string` by:
```python
new_string = stdout.decode('utf-8')
```
or use `universal_newlines=True` in `subprocess.Popen()`. Example:
```python
process = subprocess.Popen(['ping', '-c 4', 'python.org'],
stdout=subprocess.PIPE,
universal_newlines=True)
```
The `.poll()` will return the exit code of process. If process is still running. `.poll()` will return `None`. Example:
```python
process = subprocess.Popen(['ping', '-c 4', 'python.org'], stdout=subprocess.PIPE, universal_newlines=True)
while True:
output = process.stdout.readline()
print(output.strip())
# Do something else
return_code = process.poll()
if return_code is not None:
print('RETURN CODE', return_code)
# Process has finished, read rest of the output
for output in process.stdout.readlines():
print(output.strip())
break
```
-----
參考:
- [docs.python.org: `subprocess.Popen`](https://docs.python.org/3/library/subprocess.html#subprocess.Popen)
### subprocess.run
`subprocess.run()``subprocess.Popen()`是一樣的行為,差別是`subprocess.run()`會在process執行完畢之後才return也就是說流程會被block住。
`subprocess.run()`會回傳一個型別是`subprocess.CompletedProcess`的object.
-----
參考:
- [docs.python.org: _class_ `subprocess.CompletedProcess`](https://docs.python.org/3/library/subprocess.html#subprocess.CompletedProcess)

View File

@@ -0,0 +1,2 @@
- [Python 多執行緒 threading 模組平行化程式設計教學 - G. T. Wang](https://blog.gtwang.org/programming/python-threading-multithreaded-programming-tutorial/)
- [Python — 多線程. 介紹 | by Jease | Jease隨筆 | Medium](https://medium.com/jeasee%E9%9A%A8%E7%AD%86/python-%E5%A4%9A%E7%B7%9A%E7%A8%8B-eb36272e604b)

View File

@@ -0,0 +1,96 @@
### 把[[matplotlib]]包裝成獨立視窗
```python
class Plot2D(Frame):
def __init__(self, parent, dataCollector, **kwargs):
Frame.__init__(self, parent.mainWindow, **kwargs)
self.parent = parent
self.mainWindows = Toplevel(parent.mainWindow)
self.mainWindows.title("AF State")
self.figure = plt.Figure(figsize=(9,5), dpi=100)
self.figure.suptitle('AF value plot', fontsize=16)
self.ax = self.figure.add_subplot(111)
self.canvas = FigureCanvasTkAgg(self.figure, master=self.mainWindows)
self.canvas.get_tk_widget().pack(fill='both')
self.axline = None
self.dataCollector = dataCollector
self.dataCollector.start()
def close(self):
print("Plot2D close")
self.mainWindows.destroy()
self.dataCollector.stop()
self.dataCollector = None
def draw(self):
if self.dataCollector:
datax, datay = self.dataCollector.getPlotData()
self.ax.clear()
self.ax.set_xlabel('Last {} datas'.format(self.dataCollector.getDataLength()))
self.axline, = self.ax.plot(datax, datay)
self.canvas.draw()
def getWindow(self):
return self.mainWindows
def getLastData(self):
return self.dataCollector.getLastData()
```
其中這一行:
```python
self.mainWindows = Toplevel(parent.mainWindow)
```
是用來開一個新的視窗,其中的`parent.mainWindow`就是用`tk.TK()`所產生出來的root。
因為需要一直更新資料,所以需要的一個`DataCollector`來提供資料,`DataCollector`會提供畫圖需要的list
```python
datax, datay = self.dataCollector.getPlotData()
```
`DataCollector`的定義如下:
```python
class AfStateCollector(threading.Thread):
def __init__(self, dataLength=100, pollingInterval=0.033):
threading.Thread.__init__(self)
self.dataLength = dataLength
self.pollingInterval = pollingInterval
self.stopEvent = threading.Event()
self.data = []
self.xdata = []
def run(self):
while True:
if self.stopEvent.is_set():
break
afValue = self.readAf()
self.data.append(afValue)
self.xdata.append(len(self.xdata))
if len(self.data) > self.dataLength:
self.data = self.data[-self.dataLength:]
self.xdata = list(range(self.dataLength))
# print(f'afValue = {afValue}')
time.sleep(self.pollingInterval)
print("AfStateCollector stopped.")
def readAf(self):
ReadTestXUreg_cmd = "lvreg testxu read 10"
ReadTestXUreg_cmd_process = subprocess.Popen(ReadTestXUreg_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
outstring, err = ReadTestXUreg_cmd_process.communicate()
outstring = outstring.strip().decode('utf-8')
outstring = int(outstring, 16)
outstring_H = (outstring & 0xFF00) / 256
outstring_L = outstring & 0xFF
outAFStat = int(outstring_L * 256 + outstring_H)
return outAFStat
```
- [Python GUI之tkinter視窗視窗教程大集合看這篇就夠了 - IT閱讀](https://www.itread01.com/content/1547705544.html)
- [【Python】改善 VideoCapture 的影像延遲 | 夏恩的程式筆記 - 點部落](https://dotblogs.com.tw/shaynling/2017/12/28/091936)
- [Displaying a video feed with OpenCV and Tkinter - PyImageSearch](https://www.pyimagesearch.com/2016/05/30/displaying-a-video-feed-with-opencv-and-tkinter/)

View File

@@ -0,0 +1,96 @@
## 單元測試
### [pytest](https://docs.pytest.org/en/7.1.x/)
Pytest 不僅可以幫助我們運行測試還可以幫助我們配置如何運行它們、運行哪些文件等等……Pytest 有一個配置文件 `pytest.ini`,您可以在其中描述它的配置,例如哪個版本應該是 Pytest 或者哪些是測試文件,例如下列。
```ini
# pytet.ini
[pytest]
minversion = 6.0
addopts = -ra -q — cov=src — cov-report=html
python_files = test_*.py
```
### [tox](https://tox.wiki/en/latest/)
Tox 是一個通用的virtualenv管理和測試命令行工具。
使用不同的 Python 版本和解釋器檢查您的包是否正確安裝
在每個環境中運行您的測試,配置您選擇的測試工具
作為持續集成服務器的前端,大大減少樣板文件並合併 CI 和基於 shell 的測試。
Tox 也有它的配置文件。
```ini
[tox]
isolated_build = True
envlist = py{38}
[testenv]
usedevelop = true
deps = -r src/requirements_dev.txt
```
## 程式檢查工具
用來檢查程式是否符合coding style、PEP8之類的規範
### [pylint](https://github.com/PyCQA/pylint)
Pylint config: create `.pylintrc` file
```
[MESSAGES CONTROL]
disable=
missing-docstring,
too-few-public-methods[REPORTS]
output-format=colorized
files-output=no
reports=no
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
```
### [flake8](https://github.com/pycqa/flake8)
Flake8 config: create `.flake8` file
```
[flake8]
ignore = E203, E266, E501, W503, F403, F401, E402
max-line-length = 120
max-complexity = 18
select = B,C,E,F,W,T4,B9
exclude =
.git,
tests
```
### [mypy](http://www.mypy-lang.org/)
## Git hook
### pre-commit
Pre-commit 是一個創建 git hook的framework以確保您的代碼編寫與您定義的代碼樣式相對應。
它會掃描您的原始碼並運行您將在預提交配置文件中定義的所有檢查器:`.pre-commit-config.yaml`
```
repos:
- repo: 'https://gitlab.com/pycqa/flake8'
rev: 3.8.2
hooks:
- id: flake8
name: Style Guide Enforcement (flake8)
args:
- '--max-line-length=120'
- repo: 'https://github.com/pre-commit/mirrors-mypy'
rev: v0.720
hooks:
- id: mypy
name: Optional Static Typing for Python (mypy)
```
## 漏洞檢查
### [SonarQube](https://www.sonarqube.org/)
有很多用於漏洞掃描的工具,但我們將看看[Sonarqube](https://www.sonarqube.org/)。Sonarqube 是用於代碼質量和安全掃描的開源強大工具,是該行業的領先工具之一。
更多在[官方文檔](https://docs.sonarqube.org/latest/)中。
您可以使用 Docker 映像設置本地 Sonarqube 服務器並定義`sonar-project.properties`
```
# must be unique in a given SonarQube instance
sonar.projectKey=python_app_blueprint
# --- optional properties ---
# defaults to project key
#sonar.projectName=My project
# defaults to 'not provided'
#sonar.projectVersion=1.0
# Path is relative to the sonar-project.properties file. Defaults to .
#sonar.sources=.
# Encoding of the source code. Default is default system encoding
#sonar.sourceEncoding=UTF-8
```

View File

@@ -0,0 +1,9 @@
## Add icon of application
1. Create a `*.ico` file of whatever you want to use as a logo. I called mine `favicon.ico`.
2. Create a file named `yourapplication.rc`.
3. Add the following line to `yourapplication.rc`: `IDI_ICON1 ICON DISCARDABLE "favicon.ico"`.
4. Add the `*.rc` file to your buildsystem as a source file.
5. Make sure that your `*.exe` ships along side the `favicon.ico` file.
### Source
- [c++ - Set icon for Qt application built in Visual Studio - Stack Overflow](https://stackoverflow.com/questions/65818269/set-icon-for-qt-application-built-in-visual-studio)

View File

@@ -0,0 +1,21 @@
![[Pasted image 20220519094358.png]]
1. Button必須是QToolButton.
2. 建立menu.
3. 建立action.
4. 把action加到menu裡面
5. 把menu設定給button
6. code example:
```cpp
QMenu* saveFrameMenu = new QMenu;
saveRawAction = new QAction(QIcon(QPixmap(":/image/resources/button-raw.png")), "SaveRaw", this);
saveJpgAction = new QAction(QIcon(QPixmap(":/image/resources/button-jpg.png")), "SaveJpg", this);
saveBmpAction = new QAction(QIcon(QPixmap(":/image/resources/button-bmp.png")), "SaveBmp", this);
saveFrameMenu->addAction(saveRawAction);
saveFrameMenu->addAction(saveJpgAction);
saveFrameMenu->addAction(saveBmpAction);
ui.toolButtonSaveFrame->setMenu(saveFrameMenu);
ui.toolButtonSaveFrame->setDefaultAction(saveJpgAction);
ui.toolButtonSaveFrame->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
ui.toolButtonSaveFrame->setPopupMode(QToolButton::MenuButtonPopup);
```

View File

@@ -0,0 +1 @@
- [Qt之QTableWidget详细使用_qt的setitem_m沉默01的博客-CSDN博客](https://blog.csdn.net/u010780613/article/details/50442639)

View File

@@ -0,0 +1,9 @@
Qt有許多UI元件都可以另外設定相依的「資料」例如`QCombobox`,這樣我們就不必老是依賴選項的文字來做許多麻煩的處理。
例如,`QCombobox.addItem()`可以夾帶一個`QVariant`,這個`QVariant`就可以夾帶一個pointer。
要讓`QVariant`支援`std::shared_ptr`的方式很簡單只要在你程式的最上方加入你要使用的type就可以例如我要支援我自己的class AwSentinelDevice就這樣寫
```cpp
#include <memory>
Q_DECLARE_SMART_POINTER_METATYPE(std::shared_ptr);
Q_DECLARE_METATYPE(std::shared_ptr<AwSentinelDevice>);
```

734
20.01. Programming/QT/Qt.md Normal file
View File

@@ -0,0 +1,734 @@
## Prepare
### Windows 10 SDK
下載並安裝[Windows 10 SDK](https://developer.microsoft.com/zh-tw/windows/downloads/windows-10-sdk/),安裝
**Debugging Tools for Windows**就可以了。
![[chrome_20210309_110223_934x685.png]]
然後在QtCreator裡面設置Debugger。
![[NVIDIA_Share_20210309_110738_1920x1080.png]]
### Visual Studio 2017
- ![[Pasted image 20210312144218.png]]
- ![[Pasted image 20210312144242.png]]
- ![[Pasted image 20210312144405.png]]
- ![[Pasted image 20210312144523.png]]
## PRO file 語法
### 導入lib
```
QT += <lib> <lib2>
```
### 導入include path
```
INCLUDEPATH += <INCLUDE_PATH>
```
若使用相對路徑必須相對於makefile生成的路徑。
也可以使用`$$PWD`來生成絕對路徑,例:
```
INCLUDEPATH += $$PWD/../../include
```
> 可以用`message()`來印出變數:`message($$PWD)`
### 導入lib
`-L`指定library path`-l`指定libraray name。例
```
LIBS += -L"../../lib" -lopencv_world320
```
### 指定執行目錄
```
DESTDIR += ../../bin
```
### 指定執行檔檔名
```
TARGET = <NEW_FILENAME>
```
- Import QT pro file into Visual Studio.
- `qmake -tp vc <QT_PROJECT_FILE>`
- 使用VS工具import。
## 基本類別class
### QIODevice
An abstract class that wraps input and output for most devices
### QDataStream
依序寫入的資料也可以依序被讀出來。資料會被編碼過所以用Text editor會看到亂碼。
### QTextStream
可以依序寫入字串也可以依序被讀出來因為寫入的是字串所以用Text editor就可以看到資料。
### QLockFile
創造一個在操作時可以獨佔的檔案。避免race condition的情形。
```cpp
QLockFile lock(fileName() +".lock");
lock.setStaleLockTime(30000);
if(lock.tryLock()) {
...
lock.unlock();
} else {
qInfo() << "Could not lock the file!";
qint64 pid;
QString host;
QString application;
if(lock.getLockInfo(&pid,&host,&application)) {
qInfo() << "The file is locked by:";
qInfo() << "Pid: " << pid;
qInfo() << "Host: " << host;
qInfo() << "Application: " << application;
} else {
qInfo() << "File is locked, but we can't get the info!";
}
}
```
### QtMessageHandler
可以用來重新導向或攔截`qInfo()``qDebug()``qWarning()``qCritical()``qFatal()`這些function
```cpp
const QtMessageHandler QT_DEFAULT_MESSAGE_HANDLER = qInstallMessageHandler(nullptr);
void lhandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) {
QString txt;
switch (type) {
case QtInfoMsg:
txt = QString("Info: %1 in %2").arg(msg);
break;
case QtDebugMsg:
txt = QString("Debug: %1").arg(msg);
break;
case QtWarningMsg:
txt = QString("Warning: %1").arg(msg);
break;
case QtCriticalMsg:
txt = QString("Critical: %1").arg(msg);
break;
case QtFatalMsg:
txt = QString("Fatal: %1").arg(msg);
break;
}
...
(*QT_DEFAULT_MESSAGE_HANDLER)(type, context, msg);
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qInstallMessageHandler(lhandler);
qInfo() << "This is a info message";
qDebug() << "This is a debug message";
qWarning() << "This is a warning message";
qCritical() << "This is a critical message";
qFatal("THIS IS SPARTA!!!");
return a.exec();
}
```
也可以把這機制封裝在class裡面
```cpp
// logger.h
#ifndef LOGGER_H
#define LOGGER_H
#include <QObject>
#include <QDebug>
#include <QFile>
#include <QDateTime>
#include <QDir>
#include <iostream>
#include <QTextStream>
class logger : public QObject
{
Q_OBJECT
public:
explicit logger(QObject *parent = nullptr);
static bool logging;
static QString filename;
static void attach();
static void handler(QtMsgType type, const QMessageLogContext &context, const QString & msg);
signals:
public slots:
};
#endif // LOGGER_H
```
```cpp
// logger.cpp
#include "logger.h"
QString logger::filename = QDir::currentPath() + QDir::separator() + "log.txt";
bool logger::logging = false;
static const QtMessageHandler QT_DEFAULT_MESSAGE_HANDLER = qInstallMessageHandler(nullptr);
logger::logger(QObject *parent) : QObject(parent)
{
}
void logger::attach()
{
logger::logging = true;
qInstallMessageHandler(logger::handler);
}
void logger::handler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
// Here, write log message to file
(*QT_DEFAULT_MESSAGE_HANDLER)(type, context, msg);
}
```
### Logging category
Logging category可以用來把輸出的log前面加上一個字串以便分類。
```cpp
#include <QCoreApplication>
#include <QDebug>
#include <QLoggingCategory>
//Declare a logging category
Q_DECLARE_LOGGING_CATEGORY(network);
Q_LOGGING_CATEGORY(network, "network");
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qInfo() << "test"; // stdout: "test"
qInfo(network) << "test"; // stdout: "network: test"
//turn it off
QLoggingCategory::setFilterRules("network.debug=false");
//Will not log
qDebug(network) << This message will not shown;
if(!network().isDebugEnabled()) {
QLoggingCategory::setFilterRules("network.debug=true");
qDebug(network) << "We turned it back on";
}
qDebug(network) << "You see this because you turn the message on";
return a.exec();
}
```
## QThread
1. 繼承自QThread。
2. 重載`run()`
3. 呼叫`start()`來開始thread。
## QWidget
### 設定signal與對應的slot
1. 在class header裡面加入`Q_OBJECT`
2. 在constructor裡呼叫`connect()`來建立對應關係:`connect(SOURCE_OBJECT, SIGNAL(clicked()), DEST_OBJECT, SLOT(close()))`
### Postion
1. `QRect geometry();` 取得x, y, width, height
2. `setGeometry();` 設定x, y, width, height
3. `x()`, `y()`, `width()`, `height()` 單獨取得x, y, width, height
4. `move(x, y);` 只設定x, y
5. `resize(width, height);` 只設定width, height
### Window state
1. `setWindowState(Qt::WindowMaximized);`
1. `Qt::WindowMaximized` -> `showMaximized()`
2. `Qt::WindowMinimized` -> `showMinimized()`
3. `Qt::WindowNoState` -> `showNormal()`
4. `Qt::WindowFullScreen` -> `showFullScreen()`
### Window Style
1. `CustomizeWindowHint()`
2. `setWindowFlags(Qt::WindowCloseButtonHint | Qt::WindowMinimizeButtonHint)`
1. Qt::WindowCloseButtonHint
2. Qt::WindowMinimizeButtonHint
3. `setWindowFlag(Qt::WindowCloseButtonHint, false)` // 取消close按鈕
1. Qt::WindowCloseButtonHint
2. Qt::WindowMinimizeButtonHint
4. 沒有邊框的視窗:`setWindowFlags(Qt::FramelessWindowHint)`
5. 透明背景: `setAttribute(Qt::WA_TranslucentBackground, true)`
6. Style sheet: https://doc.qt.io/qt-5/stylesheet-syntax.html#style-rules
### Layout
- QVBoxLayout
```cpp
QVBoxLayout* pVlayout = new QVBoxLayout();
widget->setLayout(pVlayout);
// Add button to layout
QPushButton* button = new QPushButton("My Button");
pVlayout->addWidget(button);
```
- `setContentsMargin()`設定元與layout的距離
- `setSpacing()`:設定元件與元件之間的距離
- QFormLayout
- Iterate all items
```cpp
QFormLayout* layout = (QFormLayout*)this->layout();
for (int i = 0; i < layout->rowCount(); i++) {
QLayoutItem* item = layout->itemAt(i, QFormLayout::FieldRole);
QWidget* widget = (QWidget*)item->widget();
if (widget) {
QString className = widget->metaObject()->className();
if (className == "QLineEdit") {
QLineEdit* edit = (QLineEdit*)widget;
edit->setText("Text changed");
}
} else {
continue;
}
}
```
#### QSizePolicly
- QSizePolicly::GrowFlag必要時可以超過
- QSizePolicly::ExpandFlag放到最大
- QSizePolicly::ShrinkFlag必要時可以小於建議尺寸
- QSizePolicly::IgnoreFlag忽略
## QLabel
- `QLabel.setTextIneractionFlags()`
## QSlider
### Style
#### groove
```
QSlider::groove {
border: 1px solid #999999;
height: 28px;
background: rgba(155, 155, 155, 200);
border-radius: 10px;
}
```
#### handle
```
QSlider::handle {
border: 1px solid #FF0000;
width: 20px;
margin: -10px 0;
background: rgba(255, 0, 0, 200);
border-radius: 10px;
}
```
#### add-page
```
QSlider::add-page {
background: rgba(255, 0, 0, 200);
}
```
#### sub-page
```
QSlider::add-page {
background: rgba(0, 255, 0, 200);
}
```
![[Pasted image 20210318115725.png]]
## QListWidget
- Insert a QLineEdit
```cpp
QLineEdit* lineEdit = new QLineEdit("A line edit here");
ui->listWidget->setItemWidget(new QListWidgetItem("", ui->listWidget), lineEdit);
```
- Insert a QSpinBox
```
QSpinBox* spinBox = new QSpinBox();
ui->listWidget->setItemWidget(new QListWidgetItem("Test item 5", ui->listWidget), spinBox);
```
### Stylesheet example
```
QTableView {
selection-background-color: qlineargradient(x1: 0, y1: 0, x2: 0.5, y2: 0.5, stop: 0 #FF92BB, stop: 1 white);
}
```
```
QTableView QTableCornerButton::section {
background: red;
border: 2px outset red;
}
```
```
QTableView::indicator:unchecked {
background-color: #d7d6d5
}
```
## QTableWidget
- setColumnCount()
- https://doc.qt.io/qt-5/qtablewidget.html#setColumnCount
- setHorizontalHeaderItem(column_index, new QTableWidgetItem("Header1"))
- https://doc.qt.io/qt-5/qtablewidget.html#setHorizontalHeaderItem
- setColumnWidth(column_index, column_width)
- https://doc.qt.io/qt-5/qtableview.html#setColumnWidth
- Bind Header's event
- `connect(ui.tableWidget->horizontalHeader(), SIGNAL(sectionClicked(int)), this, SLOT(SectionClicked(int)));`
- Style
```
QHeaderView::section:checked{ /* Focus */
background-color: rgba(55, 55, 55,255 );
}
QHeaderView::section:hover{ /* 滑鼠懸停 */
background-color: rgba(85, 10, 10,255 );
}
QTableWidget{
background-color: rgb(27, 122, 239);
alternate-background-color: rgb(26, 81, 232);
color:white;
}
```
## QTreeWidget
- Data create & insert
- `insertTopLevelItem(int index, QTreeWidgetItem*)`
- `addTopLevelItem(QTreeWidgetItem*)`
- `new QTreeWidgetItem(QTreeWidget*)`
- Insert widget
- `void setItemWidget(QTreeWidgetItem* item, int column, QWidget* widget)`
- Iterate items
- `QTreeWidgetItem* topLevelItem(int index) const`
- `int topLevelItemCount() const`
- Get item
- `QList<QTreeItemWidget*> selectedItems() const`
- Style
```cpp
/* 有旁節點,沒有與其他元素相鄰 */
QTreeView::branch:has-siblings:!adjoins-item {
border-image: url(vline.png) 0;
}
/* 有旁節點,與其他元素相鄰 */
QTreeView::branch:has-siblings:adjoins-item {
border-image: url(branch-more.png) 0;
}
/* 沒有子節點,沒有旁節點,與其他元素相鄰 */
QTreeView::branch:!has-children:!has-siblings:adjoins-item {
border-image: url(branch-end.png) 0;
}
/* 收合狀態,有子節點,沒有旁節點 */
QTreeView::branch:closed:has-children:!has-siblings,
/* 收合狀態,有子節點,有旁節點 */
QTreeView::branch:closed:has-children:has-siblings {
border-image: none; image: url(branch-closed.png);
}
/* 打開狀態,有子節點,沒有旁節點 */
QTreeView::branch:open:has-children:!has-siblings,
/* 打開狀態,有子節點,有旁節點 */
QTreeView::branch:open:has-children:has-siblings {
border-image: none;
image: url(branch-open.png);
}
```
## QStatusBar
- `showMessage(QString, int miniSeconds)`: 顯示文字,一段時間後消失
## QToolBar
- `addAction(QIcon, QString)`
- `addAction(QString)`
- `setToolButtonStyle(int)`
## QDockWidget
- https://blog.csdn.net/czyt1988/article/details/51209619
## QJson
- [C++(Qt5)入門範例各種Json範例](https://lolikitty.pixnet.net/blog/post/206254516)
## QFile
Read UTF-8 content from file.
```cpp
QString filename = QString::fromLocal8Bit("testplan中文檔名也可以.json");
QFile file(filename);
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QTextStream fileRead(&file);
fileRead.setCodec("UTF-8");
QString fileContent = fileRead.readAll();
printf("fileContent = %s\n", fileContent.toLocal8Bit().toStdString().c_str());
file.close();
parseJson(fileContent);
}
else {
printf("Open failed. %s\n", filename.toLocal8Bit().toStdString().c_str());
}
```
## String
### UTF-8
1. 對於n bytes的字元第一個byte的前n個bits是1第n+1個bit是0第2個byte以後都以`10`開頭。
1. 例:
2. 1 byte character: 0x0XXXXXXX。
3. 2 bytes character: 0x110XXXXXX 0x10XXXXXX。
4. 3 bytes character: 0x1110XXXXX 0x10XXXXXX 0x10XXXXXX。
5. 4 bytes character: 0x11110XXXX 0x10XXXXXX 0x10XXXXXX 0x10XXXXXX。
2. 例:
1. 「A」是一個1 byte字元編碼為`0x01000001`。
2. 「Á」是一個2 bytes字元編碼為`0x11000011 0x10000001`。
3. 「好」是一個3 bytes字元編碼為`0x11100101 0x10100101 0x10111101`。
3. [UTF-8 to binary轉換網站](https://onlineutf8tools.com/convert-utf8-to-binary)
2. BOM
1. Big endian: `FE FF`
2. Little endian: `FF FE`
### QString
- 16 bits QChar(unsigned short), UNIDODE 4.0
- `==`: compare string
- `isNull()`: If has a value
- `isEmpty()`: If contained value == ""
- `QString::number()`: 接收一個數字,把數字轉為字串。
- `QString::toInt()`、`QString::toDouble()`把字串轉為Int或Double。
- 讀取UTF-8的字串
```
codec = QTextCodec::codecFromName("UTF-8");
QTextCodec::setCodecForLocale(codec);
```
然後用`QString::fromLocal8Bit()`來讀取字串該字串就會被當作UTF-8來處理。
- `QTextCodec::availableCodecs()`可以列出QT所支援的格式。
- 在Visual Studio中強制文件用UTF-8來存檔`#pragma execution_character_set("UTF-8")`
- 在Visual Studio中預設是以CP950存檔所以字串**都不是**unicode而是**multi-character**所以要從Windows讀進來的字串一律需要使用`QString::fromLocal8Bit("")`而一旦轉為QString之後就是UTF-8了若需要再輸出回Windows則需要`QString.toLocal8Bit().toStdString()`。
- 例:
```cpp
QString filename = QString::fromLocal8Bit("testplan中文檔名也可以.json");
QFile file(filename);
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QTextStream fileRead(&file);
fileRead.setCodec("UTF-8");
QString fileContent = fileRead.readAll();
printf("fileContent = %s\n", fileContent.toLocal8Bit().toStdString().c_str());
file.close();
parseJson(fileContent);
}
else {
printf("Open failed. %s\n", filename.toLocal8Bit().toStdString().c_str());
}
```
- Visual Studio 2017 debug QString: [调试时直接显示QString的字符值包含windows和linux](https://blog.csdn.net/iamdbl/article/details/78343642)
#### Search
- `QString.indexOf()`
- `QString.lastIndexOf()`
#### Trim
- `QString.chop(5)`去掉結尾的5個字元inplace轉換沒有回傳新字串。
- `QString.left(5)`取最左邊5個字元
- `QString.right(5)`取最右邊5個字元
#### Replace
- `QString.replace("original", "new")`:將"original"替換為"new"。
- 也支援regular expression
#### Split
- `QString.split(",")`:用","把字串分割,並回傳一個`QStringList`。
#### Regular Expression
- `.`匹配任意1個字元
- `+`此類型的匹配字元可以1個以上
- `*`:匹配任意多個字元
- `^`:表示開始位置
- `$`:表示結束位置
- `?`:表示前一個匹配條件不一定要成立
- `[]`:用括號來限制要匹配的字元
## Event
- 如果有特殊需求,可以重載`QWidget::event(QEvent*)`來達成。
### MouseEvent
- Get coordinate of component: `QMouseEvent::x()` & `QMouseEvent::y()`
- Get coordinate of window: `QMouseEvent::windowPos().x()` & `QMouseEvent::windowPos().y()`
- Get coordinate of screen: `QMouseEvent::screenPos().x()` & `QMouseEvent::screenPos().y()`
- `QPoint mapToGlobal(QPoint)`: Translates the widget coordinate pos to global screen coordinates.
- `QCursor::pos().x()` & `QCursor::pos().x()`: 也可以獲得screen coordinate。
- Get click
```
if (mouseEvent->buttons() & Qt::LeftButton) // or Qt::RightButton, Qt::MiddleButton
{
}
```
### MouseCursor
- 設定cursor click的位置`QCursor cursor = QCursor(pixmap, -1, -1)`
- (-1, -1) 表示用cursor中心點為click點
- ( 0, 0) 表示用cursor左上角為click點
- 更換cursor`setCursor(Qt::ArrowCursor)`
### ResizeEvent
- `void resizeEvent(QResizeEvent*)`
## QOpenGLWidget
- `void paintGL()`
- `void initialGL()`
- `void resizeGL(int w, int h)`
## QGLShaderProgram
- `addShaderFromSourceCode`
- `bindAttributeLocation`:設定傳入的變數
- `uniformLocation`:取得變數
## GL概念
### Vertex
#### Coordinate
```
float *vertexData = new float[12] {
1.0f, -1.0f, 0.0f,
-1.0f, -1.0f, 0.0f,
1.0f, 1.0f, 0.0f,
-1.0f, 1.0f, 0.0f,
};
```
### Texture
#### Coordinate
材質座標永遠都在第一象限
```
float* textureVertexData = new float[8] {
1.0f, 0.0f,
0.0f, 0.0f,
1.0f, 1.0f,
0.0f, 1.0f,
};
```
#### 建立材質
- `glGenTextures(1, t);`
- `glBindTexture(GL_TEXTURE_2D, *t);`
- `glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);`
- `glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);`
#### 寫入材質
- `glActiveTexture(GL_TEXTURE0);`
- `glBindTexture(GL_TEXTURE_2D, idY);`
- `glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, pixelW, pixelH, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, plane[0]);`
```
glTexImage2D(
GL_TEXTURE_2D,
0, // 細節預設0
GL_LUMINANCE, // GPU內部格式
pixelW,
pixelH,
0,
GL_LUMINANCE, // Pixel format
GL_UNSIGNED_BYTE, // Data format
plane[0]);
```
- `glTexSubImage2D()`修改texture
- `glUniform1i(textureUniformY, 0);`
- `glDrawArray(GL_TRIANGLE_STRIP, 0, 4);`
## GLSL
### 內建變數
- `gl_FragColor`
### 3種變數類型
- `varying`vertex與shader共用vertex運算完之後傳給shader使用。
- `attribute`vertex使用由`bindAttributeLocation`傳入
- `uniform`由user傳入的資料由`uniformLocation`得到pointer address再由`glUniform1i(textureUniformY, 0)`來設定。
### Vertex Shader
```
attribute vec4 vertexIn;
attribute vec2 textureIn;
varying vec2 textureOut;
void main() {
gl_position = vertexIn;
textureOut = textureIn;
}
```
### Fragment Shader
```
varying vec2 textureOut;
uniform sampler2D texY;
uniform sampler2D texU;
uniform sampler2D texV;
void main() {
vec3 yuv;
vec3 rgb;
yuv.x = textue2D(texY, textureOut).r;
yuv.y = textue2D(texU, textureOut).r - 0.5;
yuv.z = textue2D(texV, textureOut).r - 0.5;
rgb = mat3(
1, 1, 1,
0, -0.39465, 2.03211,
1.13983, -0.5806, 0) * yuv;
gl_FragColor = vec4(rgb, 1);
}
```
### APIs
畫出矩形與材質
- `glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, vertexVertices);`
- `glEnableVertexAttribArray(ATTRIB_VERTEX);`
- `glVertexAttribPointer(ATTRIB_TEXTURE, 2, GL_FLOAT, 0, 0, textureVertices);`
- `glEnableVertexAttribArray(ATTRIB_TEXTURE);`
# 檔案操作
## 開啟檔案對話框
```c++
QString selectedFilepath = QFileDialog::getOpenFileName(
this,
tr("Select test plan file"), // 檔案選擇對話框左上角的提示
tr("."), // 檔案選擇對話框一開始的路徑
"(*.json);;All files(*.*)"); // 可選擇的副檔名
```
使用者所選擇的檔名會放在`selectedFilepath`,可以用`selectedFilepath.isEmpty()`來判斷使用者是不是真的有選擇檔案或是按了取消鍵。
## 存檔對話框
```c++
QString fileName = QFileDialog::getSaveFileName(
this,
tr("Save Address Book"), "", // 檔案選擇對話框左上角的提示
tr("Address Book (*.abk);;All Files (*)")); // 存檔的副檔名
```
# MISC
## 教學文
- [Qt教程Qt5编程入门教程非常详细](http://c.biancheng.net/qt/)

View File

@@ -0,0 +1,19 @@
使用QTimer來反覆執行工作。
QTimer可以單次執行也可以多次執行。
使用概念如下:
1. 建立 QTimer 物件,例如叫做 timer。
2. 連接QTimer的timeout signal到你的反應 function
3. 呼叫 timer->start()
Example:
```cpp
QTimer* timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(update()));
timer->start(1000);
```
QTimer可以單次執行
```cpp
QTimer::singleShot(200, this, SLOT(update());
```

View File

@@ -0,0 +1,9 @@
`qt-unified-windows-x86-4.0.1-1-online.exe` 預設的 server 真的很慢,甚至會發生校驗錯誤的情況。
可以在[Qt Downloads](https://download.qt.io/static/mirrorlist/)選 mirror server然後在安裝的時候指定使用它。
例如我選的是 Yamagata University 的mirror server如下
```cmd
qt-unified-windows-x64-4.7.0-online.exe --mirror https://ftp.yz.yamagata-u.ac.jp/pub/qtproject/
```
下載速度從1 ~ 2 MB/s 進步到 25MB/s 左右。

View File

@@ -0,0 +1,2 @@
- `QApplication.setStyle()` 可以設定視窗風格Windows、Vista、Fusion
-

130
20.01. Programming/UML.md Normal file
View File

@@ -0,0 +1,130 @@
![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/52b6c5b4b26a40bf88dd85a54a441ef0~tplv-k3u1fbpfcp-watermark.image)
玩轉設計模式 - 看懂UML類圖
================
前言
--
作為設計模式系列的第一篇我準備先分享下如何看懂UML類圖看完這篇文章你就能看懂類圖中各個類之間的關係以及線條、箭頭都代表什麼意思。 同時,我們會將類圖所表達的含義和最終的代碼實現對應起來; 有了這些知識,看後面章節的設計模式結構圖就沒有什麼問題了。
先看一個例子
------
下面是我畫的一張類圖使用的工具是StarUMLMac版本[下載地址](https://macwk.com/soft/staruml)
![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/63e4f3342c734005bff326e52adf45c7~tplv-k3u1fbpfcp-watermark.image)
- 數碼是斜體的,表示數碼是一個抽象類
- 數碼有兩個繼承類,分別是手機和電腦,他們之間的關係是**實現**關係,用帶空心箭頭的虛線表示
- iPhone與手機之間也是繼承關係他們之間的關係是**泛化**關係,用帶空心箭頭的實線表示
- 手機由屏幕和主板等組合而成,他們之間的關係是**組合**關係,用帶實心菱形的實線表示
- 程序員上班必須要用電腦,他們之間的關係是**依賴**關係,用帶箭頭的虛線表示
- 程序員與公司之間是**聚合**關係,用帶空心菱形的實線表示
- 程序員一般都會有自己的工牌,他們之間的關係是**關聯**關係,用實線表示
以上例子和圖中我都把類的六種關係特別標註出來了,下面我們具體講講這六種關係
類之間的關係
------
類的繼承結構表現在UML中為**實現**和**泛化**
繼承關係為 is-a的關係兩個對象之間如果可以用 is-a 來表示,就是繼承關係
### 實現Realization
“數碼”是一個抽象概念在現實中並不能直接用來定義對象必須指明它的子類比如手機和電腦。“數碼”這個類在java中可以用接口或者抽象類表示在C++中用抽象類表示。
代碼實現:實現關係表現為繼承抽象類。
### 泛化Generalization
手機在現實中有實現可以用來定義具體的對象iphone是手機的子類手機和iphone的關係為泛化關係。
代碼實現:泛化關係表現為繼承非抽象類。
### 聚合Aggregation
聚合表示兩個對象之間是整體和部分的**弱關係,生命週期不同步**,部分的生命週期可以超越整體,比如程序員和公司,公司倒閉了,但程序員還在,還可以去其他公司。
代碼實現:聚合關係以**成員變量**的形式實現,只是成員變量的賦值時機是在**類方法**裡,代碼如下:
```java
//第一種是在set方法裡直接設置子類的實例
class Company {
private Programmer programmer;
public void setProgrammer(JavaProgrammer javaProgrammer) {
programmer = javaProgrammer;
}
}
//第二種方式是在set方法裡直接寫死直接定義一個初始值
class Company {
private Programmer programmer;
public void setProgrammer() {
programmer = new JavaProgrammer();
}
}
複製代碼
```
### 組合Composition
組合表示兩個對象之間是整體和部分的**強關係,生命週期一致**,部分的生命週期不能超越整體,或者說組合中的整體不能缺少部分。就像手機不能缺少屏幕和主板一樣。
代碼實現:組合關係以**成員變量**的形式實現,只是成員變量的賦值時機是在**類構造方法**裡,代碼如下:
```java
//手機由屏幕組成
class Phone {
private Screen screen;
//創建手機的時候同時創建屏幕
public Phone() {
screen = new Screen();
}
}
複製代碼
```
### 關聯Association
關聯表示的是兩個不同對象之間存在的固定的對應關係,**它是一種靜態關係,和運行狀態無關**。
代碼實現:關聯關係也是以成員變量的形式實現,只是成員變量的賦值時機是在聲明這個變量的時候,代碼如下:
```java
//程序員都會有工牌
class Programmer {
private Card card = new Card();
}
複製代碼
```
### 依賴Dependency
依賴表示的是**一個對象在運行期間會用到另一個對象的關係**,與關聯關係不同的是,它是一種臨時性的關係,通常在**運行期間**產生,並且隨著運行時的變化,依賴關係也可能發生變化。比如程序員在工作的時候會用到電腦,程序員是依賴電腦的。
代碼實現:依賴關係主要可以通過**方法參數、方法局部變量、靜態方法**三種方式實現,方法參數代碼如下:
```java
class Programmer {
//程序員工作的時候需要用到電腦
public void work(Computer computer) {
computer.work();
}
}
複製代碼
```
總結
--
![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e419db264b724e899767abb2adb5aed9~tplv-k3u1fbpfcp-watermark.image)
-----
- [玩轉設計模式 - 看懂UML類圖](https://juejin.cn/post/6914946238756421639)

View File

@@ -0,0 +1,9 @@
從 http://www.mega-nerd.com/libsndfile/#Download 下載的windows版本裡面只有DLL沒有lib所以需要從DLL導出lib這樣才方便使用。
導出方式:
```console
cd ~/OneDrive/codes/test/C++/testWavFft/fftw-3.3.5-dll64
/c/Program\ Files\ \(x86\)/Microsoft\ Visual\ Studio\ 12.0/VC/bin/lib.exe //machine:x64 //def:libfftw3f-3.def
/c/Program\ Files\ \(x86\)/Microsoft\ Visual\ Studio\ 12.0/VC/bin/lib.exe //machine:x64 //def:libfftw3-3.def
/c/Program\ Files\ \(x86\)/Microsoft\ Visual\ Studio\ 12.0/VC/bin/lib.exe //machine:x64 //def:libfftw3l-3.def
```

View File

@@ -0,0 +1,10 @@
如果多個array的順序有相關性我們要用同樣的順序來打亂他們可以用以下方法
```python
array1 = np.array([[1, 1], [2, 2], [3, 3]])
array2 = np.array([1, 2, 3])
shuffler = np.random.permutation(len(array1))
array1_shuffled = array1[shuffler]
array2_shuffled = array2[shuffler]
```

View File

@@ -0,0 +1,121 @@
numpy有很多運算都可以指定axis例如`x.sum(axis=0)`或是`x.max(axis=0)`之類。
axis在2軸像是 `[[1, 2], [3, 4]]` 可以理解成x方向或是y方向。但是在更多軸的情況下就很難這樣理解了。
我的理解方式是把axis當作「第幾層」。例如`x.sum(axis=0)`就是把「第0層」之下的東西都加起來例如說有一個array `a`長這樣:
```python
array([[[1. , 2. , 3. ],
[4. , 5. , 6. ],
[7. , 8. , 9. ]],
[[0.1, 0.2, 0.3],
[0.4, 0.5, 0.6],
[0.7, 0.8, 0.9]]])
```
## axis=0
那麼`np.sum(a, axis=0)`就是把「第0層」之下的東西都加起來`a`的shape是(2, 3, 3)所以第0層之下就是**有2個3x3的array**,也就是
```python
[[1. , 2. , 3. ],
[4. , 5. , 6. ],
[7. , 8. , 9. ]]
```
```python
[[0.1, 0.2, 0.3],
[0.4, 0.5, 0.6],
[0.7, 0.8, 0.9]]
```
要加起來也就是:
```python
np.array([[1. , 2. , 3. ],
[4. , 5. , 6. ],
[7. , 8. , 9. ]]) +
np.array([[0.1, 0.2, 0.3],
[0.4, 0.5, 0.6],
[0.7, 0.8, 0.9]])
```
答案跟`np.sum(a, axis=0)`是一樣的。
## axis=1
那麼`np.sum(a, axis=1)`也就是把「第1層」之下的東西都加起來`a`的shape是(2, 3, 3)所以「第1層」有2個分別是
```python
[[1. , 2. , 3. ],
[4. , 5. , 6. ],
[7. , 8. , 9. ]]
```
```python
[[0.1, 0.2, 0.3],
[0.4, 0.5, 0.6],
[0.7, 0.8, 0.9]]
```
這2個各自會產生各自的結果先看第一個。我們要把「第1層」之下的東西都加起來「第1層」之下的東西就是
```python
[1. , 2. , 3. ],
[4. , 5. , 6. ],
[7. , 8. , 9. ]
```
我要把他們加起來,也就是`[1. , 2. , 3. ] + [4. , 5. , 6. ] + [7. , 8. , 9. ] = [12., 15., 18.]`
再看第二個我們要把「第1層」之下的東西都加起來「第1層」之下的東西就是
```python
[0.1, 0.2, 0.3],
[0.4, 0.5, 0.6],
[0.7, 0.8, 0.9]
```
我要把他們加起來,也就是`[0.1, 0.2, 0.3] + [0.4, 0.5, 0.6] + [0.7, 0.8, 0.9] = [1.2, 1.5, 1.8]`
所以`np.sum(a, axis=1)`的答案就是:
```python
[[12., 15., 18.],
[1.2, 1.5, 1.8]]
```
## axis=2
那麼`np.sum(a, axis=2)`也就是把「第2層」之下的東西都加起來`a`的shape是(2, 3, 3)所以「第1層」有2個分別是
```python
[[1. , 2. , 3. ],
[4. , 5. , 6. ],
[7. , 8. , 9. ]]
```
```python
[[0.1, 0.2, 0.3],
[0.4, 0.5, 0.6],
[0.7, 0.8, 0.9]]
```
而這2個第1層又各自有3個的第2層分別是
```python
[##第0層
[##第1層-0
[1. , 2. , 3. ] ##第2層-0 <-- 裡面要加起來
[4. , 5. , 6. ] ##第2層-1 <-- 裡面要加起來
[7. , 8. , 9. ] ##第2層-2 <-- 裡面要加起來
],
[##第1層-1
[0.1, 0.2, 0.3] ##第2層-0 <-- 裡面要加起來
[0.4, 0.5, 0.6] ##第2層-1 <-- 裡面要加起來
[0.7, 0.8, 0.9] ##第2層-2 <-- 裡面要加起來
]
]
```
總共有6個加起來之後就變成
```python
[
[
[1. , 2. , 3. ] # 1+2+3 = 6
[4. , 5. , 6. ] # = 15
[7. , 8. , 9. ] # = 24
],
[
[0.1, 0.2, 0.3] # 0.1+0.2+0.3 = 0.6
[0.4, 0.5, 0.6] # = 1.5
[0.7, 0.8, 0.9] # = 2.4
]
]
```
所以`np.sum(a, axis=2)`的答案就是:
```python
[[ 6. , 15., 24. ],
[ 0.6, 1.5, 2.4]]
```

View File

@@ -0,0 +1,61 @@
## 輸出
{{message}}
## 迴圈
v-for
```javascript
<li v-for="item in list">{{ item }}</li>
```
## 輸入雙向綁定:`v-model`
```html
<input v-model="message">
```
```javascript
var app = Vue.createApp({
data: function() {
return {
message: "要被綁定的訊息"
}
}
});
```
## 事件:`v-on`
```html
<button v-on:click="testEvent">按我</button>
```
```javascript
var app = Vue.createApp({
method: {
testEvent: function() {
alert("你按了button");
}
}
});
```
## 條件式:`v-if`
```html
<div v-if="isShow"></div>
```
```javascript
var app = Vue.createApp({
data: function() {
return {
message: "要被綁定的訊息",
isShow: true
}
}
});
```
## 屬性綁定
### `v-bind`

View File

@@ -0,0 +1,3 @@
# 介紹文
- [VisuAlgo - visualising data structures and algorithms through animation](https://visualgo.net/en?fbclid=IwAR1b6wbTcgKJg3T14ehAPMpHO_QWvnj1evMxrshrAvqukHH2ZPXJUWgAvd4)
- [The Algorithms](https://the-algorithms.com/)