顯示具有 技術筆記 標籤的文章。 顯示所有文章
顯示具有 技術筆記 標籤的文章。 顯示所有文章

2021年6月13日 星期日

[筆記][C/C++]Class之間使用Callback Function

前言

最近工作需要想辦法優化架構,因此設計了兩個類別各自負責自己工作,但有些資訊是B類別動作後,A類別需要進行更新。

最直覺想到的寫法,是讓B物件認識A物件,但是這樣A物件就跟B物件產生了關聯性(耦合)。

第二種想到寫法,是A物件需要這個資訊的時候,檢查資訊有沒有更新。但這樣A物件需要多些判斷,且每個需要資訊的地方都需要加這個判斷式,後續維護不容易。

有沒有什麼辦法是,B不需要認識A,A也不需要一直檢查呢?

最後想到的解法是:使用Callback Function!!!!!

什麼是Callback Function?

一般Function是主動執行,比如說前言中提到最直覺的寫法,B物件呼叫A的方法,主動執行A更新的動作(或是可以想成是動態執行);至於Callback Function,則是B先記得A的更新Method的位址(指標),要動作時再呼叫這個函式指標對應到的Function(靜態執行)。

簡單的說,一般Call Function是直接呼叫Function,而Callback function則是記得function的位址,需要時再執行該Pointer指向的函式。

常見的Callback Function

Callback Function其實滿常見的,以下列出幾個可以看到Callback Function的API
  1. std::sort
  2. openCV MouseCallback
  3. gstreamer AppSink

Callback Function怎麼用?

Function Pointer

首先,先聊一下函式指標怎麼寫呢?

C++函式指標寫法如下:

void (*func )(int, int)

這一段翻譯成人類的語言就是:func是一個函式指標,這個指標指向的函式引數為兩個int,回傳值則為void。

不過為了使用上的便利性,通常會用typedef的方式給他名字,例如:

typedef void (*FUNC )(int, int)

後續就能宣告FUNC並給予別名。

宣告及使用Callback Function

先來一段範例程式碼

#include <iostream>
using namespace std;

typedef void (*CALLBACK)( int, int, void* );

class A{
public:
    A( void );
    // constructor

    static void UpdateCallback( int, int, void* );
    // callback function pointer pointed to

    void PrintMember( void ) const;
    // show the member
private:
    int a;
    int b;
};

class B{
public:
    B( void );
    // constructor

    void RegisterCallback( CALLBACK, void* );
    // register callback and the callee

    void CallFunction( void );
    // call by main

private:
    CALLBACK pFunc;

    void *m_pCallee;
};

A::A( void )
// constructor
{
    a = 0;
    b = 0;
}

void A::UpdateCallback( int inputA, int inputB, void *Self )
// callback function pointer pointed to
{
    // cast user data to A
    A* pA = (A*)Self;

    // update with input value
    pA->a = inputA;
    pA->b = inputB;
}

void A::PrintMember( void ) const
// show the member
{
    cout << "a = " << a << endl;
    cout << "b = " << b << endl;
}

B::B( void )
// constructor
{
    // do nothing
}

void B::RegisterCallback( CALLBACK FuncCB, void* pCallee )
// register callback and the callee
{
    pFunc = FuncCB;
    m_pCallee = pCallee;
}

void B::CallFunction( void )
// call by main
{
    cout << "execute function pointer" << endl;
    pFunc( 2, 3, m_pCallee ); 
}


int main( void )
{
    A instanceA;
    B instanceB;
    // register callback to B
    instanceB.RegisterCallback( instanceA.UpdateCallback, &instanceA );

    cout << "Before run B::CallFunction: " << endl;
    instanceA.PrintMember();

    instanceB.CallFunction();

    cout << "After run B::CallFunction: " << endl;
    instanceA.PrintMember();

    return 0;
}

從程式碼中可以看到,一開始要將A類別的UpdateCallback註冊到B類別,也就是告訴B類別Callback Function的位址以及該Callback function的使用者(Callee,傳進一個void pointer即可)。
要傳進物件A的pointer的原因是因為Callback Function中需要修改到A,如果沒有傳進A物件本身,那將無法直接修改到A物件中的值。(可能有其他更好的寫法,還要再研究)

另外要注意實作功能的Callback Function必須是static。
(註:C++11以後用function跟bind好像就可以不用宣告為static)

註冊Callback Function後,B物件執行CallFunction的時候,就會執行Callback指向的函式,也就是A的UpdateCallback,並將Callee傳入(在這個情境下是instanceA的pointer),在UpdateCallback中,會將void pointer轉型成class A的pointer,並將值填入A的pointer中。

可以發現class B並不需要知道class A(只需要傳入一個void pointer),也就是說透過Callback Function的機制,就達成B類別不需要認識A類別,就可以叫A做事情的目的!

另外以程式碼擴展性來說,Callback Function增加了更多彈性~如果我後續修改需求,有一個新的類別C,也是要進行類似的動作(比如說使用B傳入兩個int進行相加,再存進member),那我只要在C類別中實作Callback Function,然後註冊進B就可以了,完全不需要修改B。

結論

Callback Function的實用性很高,對於程式碼擴展性有很大的幫助。希望整理完後,之後可以用得更爐火純青~
另外也要找時間去熟悉C#的delegate,因為好像是類似的概念!

參考資料:

https://dangerlover9403.pixnet.net/blog/post/83880061-%5B%E6%95%99%E5%AD%B8%5Dc-c++-callback-function-%E7%94%A8%E6%B3%95-%E7%AF%84%E4%BE%8B-(%E5%85%A7%E5%90%ABfunctio

https://openhome.cc/Gossip/CppGossip/FunctionPointer.html

2019年7月24日 星期三

[筆記][macOS] Anaconda 安裝 Intel RealSense Python package on Mac

之前介紹過如何在windows上的anaconda安裝realsense的python套件
https://jennyli6079633.blogspot.com/2019/01/anaconda-intel-realsense-python-package.html
不過其實macOS才是最需要自己編譯library的系統啊...(Linux應該也是)
因為直接pip是沒辦法安裝的
官方是說還沒開發出適用Unix平台的pypi安裝法

在這邊介紹一下如何在mac上用cmake編譯pyrealsense並使用:
1. 下載cmake
google一下應該很多資料
連結是我參考的文章
https://blog.csdn.net/baimafujinji/article/details/78588488
照著做直到terminal輸入cmake是可以執行的就行了


2. 下載source code
直接從github clone下來最快
$git clone https://github.com/IntelRealSense/librealsense.git

3. 編譯pyrealsense2
在terminal中進入剛剛clone下來的realsense資料夾
輸入下面的指令
$mkdir build
$cd build
$cmake ../ -DBUILD_PYTHON_BINDINGS=TRUE
$make -j4
*如果environment的python版本不一樣
   在cmake指令後面再加上這個argument就可以了
   -DPYTHON_EXECUTABLE=[full path to the exact python executable]

接著讓電腦跑(伴隨mac的悲鳴??)
都沒有error就OK了

其實跟windows系統的大同小異
不過不用像windows的cmake需要那麼多設定

4. 移動.so並建立symbolic link
跑完之後在librealsens/build/wrappers/python 資料夾中會有這些檔案


我們要建立pyrealsense2與pybackend2移到python的lib並建立的sybolic link
系統才找得到

將pybackend2.2.24.0.cpython-37m-darwin.so
與pyrealsense2.2.24.0.cpython-37m-darwin.so
複製到anaconda3⁩/⁨envs⁩/⁨(Env name)/⁨lib⁩/python3.7/site-packages
並在這個資料夾
然後使用下面的command
$ln -s pyrealsense2.2.24.0.cpython-37m-darwin.so pyrealsense2.so
$ln -s pybackend2.2.24.0.cpython-37m-darwin.so pybackend2.so

目前測試在anaconda的virtual env可行
如果沒有裝虛擬環境
在python的site-package加入也可以

另外還有一個方式是用make install
但是我不想直接裝在我的系統上所以我沒有使用這方法

5. 測試(可import但無法測試是否真的能跑)
進入你的虛擬環境
然後import pyrealsense2
沒有跳出找不到的錯誤就可以了


可惜我的mac沒有usb 3.0可以插所以不能測能不能真的跑起來
看有沒有人可以幫忙測的XDD

歡迎留言討論

Reference:




2019年5月17日 星期五

[筆記][tensorflow]Batch Normalization Layer用法(Key Variable not found in checkpoint)

最近在改寫3D Faster RCNN時遇到了一些使用上的問題 ,
因此稍微筆記一下。

首先介紹一下Batch Normalization~
Batch Normalization 最早是在2015年被提出,
主要是解決network在訓練過程中遇到的Covariate shift問題
透過正規化minibatch,加速網絡訓練及穩定性。

最常用來解釋Batch nomalization對於增加訓練網絡的穩定性的方式為下圖:


圖片中我們可以看到,
加入Batch normalization的每一層網絡輸出的分布比較常態,
沒有發生有效值被shift到兩個極端,
這對於使用tanh或sigmoid兩種activation function的網絡來說有很大的幫助,
因為這兩個函數最敏感(變化最大)的區域有一定的範圍,
輸入的分佈超過範圍就會有飽和的問題。


因此如果加入Batch normalization,
會讓訓練避免陷入飽和的狀態。

而ReLU雖然不會遇到飽和的問題,
但是使用Batch Normalization還是可以避免梯度爆炸與加速訓練。

目前我謹了解其物理意義,
對於Batch Normalization的數學模型與詳細細節有興趣的朋友也可以去找他的原始paper或是其他資源來看。
或是之後我有空看完也會來努力補上。

接下來是Batch Normalization的tensorflow用法,
我使用的tensorflow版本是1.12。

要將Batch Normalization layer加在convolution layer的activation function之前,
這邊我用的是tf.layers.batch_normalization,
beta值、gamma值、mean與variance等超參數都使用預設值,
這些值可以自己調整,
(另外如果用的是tf.nn.batch_normalization,
就要自己先定義存超參數的tensor。)

要正確的訓練記得將training這個argument設為true。

接下來就是我遇到問題的地方。
因為batch_normalization的參數不屬於trainable variables,
若沒有設定tf.train.Saver儲存global_variable,
儲存的model將不包含訓練好的batch_normalization layer,
inference的時候會出現找不到的錯誤:Key Variable not found in checkpoint
因此在宣告saver時要加入save global variable的設定 var_list=tf.global_variables()



就可以解決了~~


本篇主要是筆記一下
(1) batch normalization的物理意義
(2) tensorflow的寫法
(3) save batch normalization model時的注意事項

參考資料:





https://www.tensorflow.org/api_docs/python/tf/layers/batch_normalization

https://www.tensorflow.org/api_docs/python/tf/nn/batch_normalization

2019年3月18日 星期一

[筆記][Git/Github][存參] 檔案過大 error: File ******** is 527.84 MB; this exceeds GitHub's file size limit of 100.00 MB

最近開始把一些時作放到github上
今天遇到了一個問題
就是我的pretrainied model太大了沒辦法push
但是刪掉了之後
error message還是一直出現

後來搜尋了解決辦法
這裡筆記下來
應該會記的比較清楚
(雖然之後會注意檔案大小不要再亂上傳一通)

Error message:
$ git push origin master
Enumerating objects: 20, done.
Counting objects: 100% (20/20), done.
Delta compression using up to 8 threads
Compressing objects: 100% (14/14), done.
Writing objects: 100% (17/17), 489.89 MiB | 14.18 MiB/s, done.
Total 17 (delta 5), reused 0 (delta 0)
remote: Resolving deltas: 100% (5/5), done.
remote: error: GH001: Large files detected. You may want to try Git Large File Storage - https://git-lfs.github.com.
remote: error: Trace: 4d46485696166b616f2204cef94eef80
remote: error: See http://git.io/iEPt8g for more information.
remote: error: File xxxxx is 527.84 MB; this exceeds GitHub's file size limit of 100.00 MB
To https://github.com/j6079633/CrossModalDistillation_Keras.git
 ! [remote rejected] master -> master (pre-receive hook declined)
error: failed to push some refs to 'https://github.com/j6079633/CrossModalDistillation_Keras.git'

Solution:
$ git filter-branch --tree-filter 'rm -rf path/to/your file' HEAD

這一個指令是過濾過去提交紀錄中超出限制大小的檔案移除。

結束後再下一個push的指令即可

但是因為前面整個流程我亂試一通變得有點亂
所以我有先下一個pull rebase的指令

$ git pull --rebase

如果是只有自己的project
很清楚明白有哪些地方被修改的話
可以使用force pull

$ git push -f

最後進行push就可以了

git 是專案管理滿重要的工具
CP值滿高的技能值
雖然大學時有稍微學一下但後來不常用就忘了
不過現在撿回來磨一下應該還來得及><

參考資料:

https://andy6804tw.github.io/2018/12/09/git-exceeds-size/

https://gitbook.tw/chapters/github/fail-to-push.html

https://blog.yorkxin.org/2011/07/29/git-rebase.html


2019年1月14日 星期一

[筆記][Python] XML檔案讀寫(ElementTree and Minidom)

目前用Pascal VOC的格式來存取我的資料,所以python讀寫XML檔就變成是重要的一環,以下紀錄是XML檔的讀寫,也方便自己複習,不用每次都還要問google先生。
以下皆使用ElementTree與Minidom這兩個XML的Python API

  • Import ElementTree
    用cElementTree會比較快因為它是C語言寫的,可以直接使用硬體,下面的import方式就是主要import cElementTree,若沒有C語言base的,才改用python base的套件
    try:
        import xml.etree.cElementTree as ET
    except ImportError:
        import xml.etree.ElementTree as ET
  • 讀取檔案與解析node
    ElementTree讀取檔案的方式也很簡單,只要在建立ElementTree物件的時候給他XML檔案位置即可
    file_name = 'path_to_file'
    tree = ET.ElementTree(file=file_name)

    至於為什麼物件的命名是用tree呢?這是因為XML的格式就好像一棵樹一樣,由一個跟節點延伸出分支,然後各分支又可延伸出更多分支
    以PascalVOC為例
    root
            |--folder
            |--filename
            |--source
                   |--database
                   |--annotation
                   |--image
                   |--flickrid
            |--owner
                   |--flickrid
                   |--name
            |--size
                   |--width
                   |--height
                   |--depth
            |--segmented
            |--object
                   |--name
                   |--pose
                   |--truncated
                   |--difficult
                   |--bndbox
                           |--xmin
                           |--ymin
                           |--xmax
                           |--ymax
            |--object
            ...
    有了xml tree物件之後,便可以開始讀取裡面的node。我自己會用到的為以下3種
    1. 直接遍歷屬於root的節點(不過需要事先知道要找的node在哪一層,才能再更進去找)
    root = tree.getroot()
    for elem in root:
        print(elem.tag, elem.attrib, elem.text)
    
    2. 利用iter()進行節點遍歷
    for elem in tree.iter():
        print(elem.tag, elem.attrib, elem.text)
    
    3.利用iter(tag="node_name")針對特定節點遍歷
    for elem in tree.iter(tag='bndbox'):
        print(elem.tag, elem.attrib, elem.text)
  • 寫入XML檔案
    要製作自己的資料集,寫入xml檔其實是最快的方式,接下來的紀錄是我用xml.dom.minidom來寫檔(VOC格式)
    1. 建立root
    import xml.dom.minidom    
    #create empty root
    doc = xml.dom.minidom.Document() 
    #create root node
    root = doc.createElement('annotation')
    doc.appendChild(root)
    2. 建立子節點並幫子節點接上文字節點(text部分視為末端點)
    nodeFolder = doc.createElement('folder')
    nodeFolder.appendChild(doc.createTextNode(folder))
    3. 建立將子節點接到根結點或其他子節點上
    root.appendChild(nodeFolder)
    4. 開檔以及將建立好的xml tree寫入
    f_n = "file_name.xml"
    fp = open(f_n, 'w')
    doc.writexml(fp, indent='\t', addindent='\t', newl='\n',
                                            encoding="utf-8")

    到這邊就完成了
    不過要注意的一點是節點順序,建立的順序錯誤,檔案的節點順序就會錯誤
以上就是有關於xml讀寫方式的小小記錄,不過我覺得寫入的方式有點冗長,或許用ElemntTree可以更快也更統一,之後有空會來研究看看。



參考資料:
https://pycoders-weekly-chinese.readthedocs.io/en/latest/issue6/processing-xml-in-python-with-element-tree.html

https://docs.python.org/2/library/xml.etree.elementtree.html

http://note.qidong.name/2017/11/write-xml-with-minidom/

https://docs.python.org/2/library/xml.dom.minidom.html

2019年1月4日 星期五

[筆記][電腦] Anaconda 安裝 Intel RealSense Python package

沒想到重新開始寫部落格竟然寫這個XD

目前碩二,實作的東西是利用深度學習模型來完成電腦視覺相關的任務,要做電腦視覺當然要有眼(攝)睛(影機)。因為要做的是堆疊物體的偵測,需要深度影像,但是kinect停產了...所以我們改用Intel RealSense D435~

不過RealSense的python package要裝在Anaconda,或是windows OS上,有點複雜,花了一點時間才裝好...也害怕以後忘記怎麼裝,因此在這裡紀錄一下

[6/12更新]
已經測試在anaconda直接使用pip安裝pyrealsense2不會有問題
應該是該團隊已經修正為可以簡單安裝的方式
如果使用
pip install pyrealsense2
指令安裝後使用有問題的話
再考慮自己make source的方式

好了廢話完畢,開始安裝

1. Git clone source code
首先去librealsense的github頁面下載或clone source code
(建議用git clone的方式,因為之後make的時候也會需要git,乾脆一次裝好)



2. Make source code
這裡要使用CMake,記得下載CMake Windows Source,解壓縮後打開<cmake path>/bin/cmake-gui.exe


source code的部分是剛剛clone下來的librealsense src code
build的destination則在librealsense src code的root directory下新建一個build
Group與Advanced兩個option要記得check
接下進行Configure


我之後會使用VS2015來build,也可以使用自己習慣的IDE來build
Finish之後進行第一次Configure
(這邊可能會發生CMake Error could not find git for clone of libusb,解決方式一是我一開始提到的安裝git,另一個方式是在error發生之後,在BUILD中,取消勾選BUILD_WITH_TM2,不過因為有看到有人說取消勾選並不會解決問題,所以我安裝git)

然後會出現一堆紅色底的configuration,


將BUILD>BUILD_PYTHON_BINDINGS勾選起來,再按一次Configure
然後又有error!!


在Ungrouped Entries>PYTHON_EXCUTABLE欄位中
填上你的conda environment中的python.exe路徑
以我電腦上的路徑就是C:\Users\(User name)\Anaconda3\envs\(Env name)\python.exe

然後就可以Generate VS專案
Generate好之後,點Open Project,VS就會被開啟,直接按Debug/Compile


等一段時間之後就make好了~

3. Install package to Anaconda virtual environment
make完成後,在librealsense/build/Debug中會有下列幾個檔案
"pybackend2.cp36-win_amd64.pyd"
"pyrealsense2.cp36-win_amd64.pyd" 
將這兩個檔案的檔名更改成
"pybackend2.pyd" 
"pyrealsense2.pyd"

接下來將這兩個檔案和"realsense2.dll"複製到C:\Users\(User name)\Anaconda3\envs\(Env name)\DLLs
還有"realsense2.lib"複製至C:\Users\(User name)\Anaconda3\envs\(Env name)\libs

4. Test installation
實際跑librealsense的python example看看有沒有裝成功
(env_name) D:\RealSense\librealsense\wrappers\python\examples>python python-tutorial-1-depth.py


出現以下畫面就是成功了!


感覺以後就會常常用到,因此做個紀錄,也希望幫助到大家

Reference: https://github.com/IntelRealSense/librealsense/issues/1657