2019年4月20日 星期六

anyPortrait 使用心得、簡易教學


官網在這:
https://www.rainyrizzle.com/anyportrait-eng

之前有陣子想做2d hame,用了dragon bones好一陣子,雖然demo是做出來了但dragon bones 的API有些問題一直都沒解決,更重要的是hgame最重要的乳搖用dragon bones做不出來,但spine要用free form deformation的功能的話會相當的昂貴,後來找到了anyPortrait這個軟體。


anyPortrait是韓國RainyRizzle出的一款在unity上做骨架動畫和free form deformation的軟體,在unity asset裡面可以買,約一千多塊左右,逢年過節會打折,聖誕節會打50%。

我們先來看官方demo影片



可以看得出來這家公司想把類似live2d的表情變化跟spine的骨架動畫做在一起

實際操作之後有幾個感想,RainyRizzle果然是做過遊戲的公司(雖然好像沒人玩),能想到的在unity裡面弄2D遊戲的人會想用到的功能基本上都有做( 即時的IK、把2D人物放在3D空間中、風吹頭髮的物理模擬等),但是用起來實在是很不順手,介面跟spine系列的差太多,而且很多滑鼠該要有熱鍵的功能也沒有( 按了右鍵沒得取消 ),有些功能藏在ctrl裡面要按下去才會看到等,好在這家公司的員工都不睡覺的,真的不知道怎麼做的話,寄信或留言基本上當天都匯回。

本著惺惺相惜情不自禁的初衷,寫個review算是推廣一下。


-------------------------------------------------------------------------------------------------------




裝好之後開啟2d editor,一開始點進來畫面長這樣,現在甚麼都沒有我們創一個新的人物,點下Make New Portrait


建立Portrait後你會看到頁面左邊會分成幾類,分別是
Root unit: 就是最後整隻人物的樣子

Image: 你讀進來的PSD檔會被做成texture圖貼在image上

Meshs:PSD的每一個圖層會被拆解成一個一個的mesh

Mesh group:基本上是照你PSD的layer來排,也是在做變形的時候主要操作的地方。

animation clip: 之後你做好的每個動畫會放這裡

control parameter: 這東西跟live2d控制表情的控制器有點像,你可以設定不同的controller例如這個controller數值是0的時候閉眼、1的時候是開眼,或是做成2維的數值讓眼睛跟著數值飄之類的。

然後我們先讀一個PSD進來,用的是我們的雜魚醬,按下Import PSD file

給他準備好的PSD檔後他會告訴你裡面有啥

可以一層一層檢查是不是東西都到齊了,右邊那個clipping不知道是幹嘛的


一直按下一步按到有Bake的選項出來,設好path(通常是你的專案的目錄),按下bake後,你的素材就會跑出來像框框3那樣。按下complete
你就看到那些image啊,mesh什麼的,全都在這了,預設的mesh是正方形的,假如我們想讓mesh在隨著骨架變形的時候就要考慮佈線,想佈線的話還是得一張一張去調整mesh,現在隨便點一個mesh。



我點到一個腿,左鍵是增加vertex右鍵是刪除,按住ctrl的話會自動幫你找最近的點,按shift的話會讓你可以拉一條線出來,連線上所有的交叉點都會生成vertex。


接好之後按Auto Link Edge。電腦會自動幫你把vertex連成一塊一塊的三角形(有時候沒那麼聰明連錯就得自己連了),接著按make polygons,如果你的mesh發亮的話代表有連成功。沒發亮的話等下做動畫就不會顯示了。

不過以這隻腳來說,為了能讓他在彎取的時候不會有奇怪的變型,可以試著在關節的地方做出類似這種三角形的布線。
全部做完之後,我們就可以來玩變形了。

接著來到mesh group的地方準備來綁骨架了,靜待下回分曉。















2018年12月12日 星期三

Hgame demo

各位先進同仁們大家好,在這裡為大家介紹我最近做的Hgame

用的遊戲引擎是unity

動畫軟體是dragonbones

先上下載網址:https://tinyurl.com/yadgx8a8



首先進入遊戲,點選orc,在畫面上創造一隻orc



接著蓋房間,左上角有個地板,點選後在泥土地上蓋上地板,接著就可以開始在上面蓋房間了


蓋了一張床,你有了只會睡覺的orc


接著來到unit,點選camps換陣營,做一個敵方的girl


就被rape了,被動式raped


大概是這樣的遊戲


然後選擇手銬,蓋一個刑求室,

把女生裝上去〈別裝男的,我沒試過〉


orc會走過來

就被rape了,被動式raped


大概是這樣的遊戲


選擇load進入戰役,想做成地城守護者那樣

 畫面上有很多作為工人的spriggan,呆呆的


 基本上就是個挖牆蓋房間的過程,右邊這是個怪物傳送門


來看敵方領地,這個有黃色圈圈的boss,主角畫不出來只好拿雜魚醬代替


直接用滑鼠點選拉到我們這邊來,就看到雜魚醬英勇奮戰然後用一招強力的閃電把自己電死了


破關畫面


謝謝大家

2018/12/16 修正
--連續讀檔會爆掉
--按了room再按unit還是room
--unit會被放進牆壁裡



2018年10月28日 星期日

Review: 仿3D之2D軟體心得

2D仿3D技術中最有名的大概就spine、live2D及e-mote,不太有名的有blender的cut-out animation tool、中國的dragonbones、unity內建的anima2D等,這領域的技術基本上都是10幾年前的東西,最近因為手遊及獨立遊戲興起,能用很少的原畫做出動畫效果、甚至能進一步做出仿3D畫面的技術變成各個軟體的賣點。

 一、名詞解釋

骨架動畫(skeletal animation):

傳統遊戲中的動畫是以sprite sheet animation(中文大概叫幀動畫之類的)來呈現,如下圖,將人物的動作一張一張畫出來後全部塞到同一張圖裡,程式只要一開始讀取這張圖,然後計算現在播放到第幾格就可以變成動畫,像是這樣的東西

(from: https://gamedevelopment.tutsplus.com/tutorials/an-introduction-to-spritesheet-animation--gamedev-13099 )

但是要畫那麼多圖實在是太麻煩了(沒錢),所以引申出一種使用單圖來做動畫的方式,首先先準備一張圖,把圖分割後綁上骨架,之後只要調整骨架,圖片跟著骨架動之後就會像是動畫一樣了。

拿我現在在試的例子來說,首先我有個人物,把人物分割成數塊(在畫圖的時候就要先用圖層分好),拼裝起來把骨架分配到各個部位,調好角度之後看起來就像這樣。用dragonbones做的,骨架動畫是種畫的人畫得很開心看是眼尖的顧客看了會很煩躁的技術,用的時候要斟酌一下。


 free-form deformation (FFD):
只用骨架動畫基本上作不出來那種「圖只有X、Y軸卻往Z軸方向旋轉」的感覺,這時候就要靠FFD了,FFD簡單來說就是把一張圖分成好幾個點,每個點都可以自由的變形,靠著扭曲這些點來作出圖像變形的效果,可以用來作些只有骨架做不到的事



圖中任意的三個點可以形成一個三角形,從上圖對應到下圖的三角形可以算出一個變換矩陣,所以上圖中的三角形中任意點通過得到的矩陣可以找到下圖中對應的位置。


當然當上圖的三角形跟下圖的三角形面積不一樣的時候對應下去會有空的點。所以實作上會反過來,把右圖中三角形用反矩陣對應到左圖。

數學可看下面影片:





二、軟體介紹:



1.Spine:
目前來說功能最強大的骨架動畫軟體,各家2D遊戲如果有做出可操作的人物有仿3D的轉動幾乎都是用spine做的。

在仿3D這事上一開始我認為spine有導入類似於live2d或emote那樣自動幫使用者調整圖片形狀的功能,實際使用後發現spine最主要的功能是他的權重筆刷,所謂的筆刷就是當你移動一個點的時候,旁邊離得近一點的點會跟著90%的位移、離得遠一點的70%位移、更遠一點的有60%的位移...就好像在素描的時候會有人拼命的抹畫面抹出漸層一樣。

可以看這篇用筆刷刷出臉部表情的實例

http://zh.esotericsoftware.com/forum/Facial-animation-with-Spine-6729

筆刷工具成功的另一個實例便是3D界熟悉的zbrush,以前做3D的如果要做出真正栩栩如生的作品都會把粗模用zbrush「雕刻」一番,後來這種筆刷的雕刻工具也漸漸被其他3D軟體導入。


從spine這個軟體來看,一個成功的仿3D的2D軟體最重要的特質是什麼,我認為有兩個。一、能夠讓使用者同時控制一堆vertex的能力。二、能夠做出前景遮擋背景的效果的能力。對於(一)來說,我們可以想像,所謂三度空間的旋轉這件事對於2D視覺的我們來說,用下圖來解釋
在t1時間時,物體面對我們,當他開始轉動時每個vertex移動量在我們視點上的投影為d1,在t2時間時則為d2,我們可以看到當物體的角度越與我們垂直,我們看到的vertex的移動量就越小。這個投影是個很簡單的三角函數(大概是一個sin之類的),但是如果軟體本身不處理這投影而讓使用者在那邊慢慢調出這種旋轉的效果,那就失職了。

用面積的想法來說,便是當物體轉到面對你的角度時,他的面積會變大,當他轉到其他角度時,面積會變小,

以下連結有效果很好的範例,圖片中呈現了一個搖晃的戰鬥機在小角度搖擺時的假3D效果

http://zh.esotericsoftware.com/forum/Fake-3D-in-Spine-9533

對於(二)來說,如下圖
從方塊左道方塊右看起來是向左轉了一個角度,在某個角度之後因為遮擋的關係所以藍線不見了。可以看以下討論,連結中呈現了一個大角度搖擺的方塊。

http://zh.esotericsoftware.com/forum/How-to-turn-your-2d-character-looking-3D-9187



2.dragonbones:

中國那邊某公司為了推自己的HTML5遊戲引擎所設計的骨架動畫軟體,我現在在用這個因為不用錢,但是用久了之後還是頗有微詞。

dragonbones不論是介面還是功能上都模仿舊版的spine。雖然有骨架、有inverse kinematics(IK)、有 free-form deformation(FFD),但也都只是堪用等級,主要缺點有下:

Inverse kinematics只能連兩個骨架: 雖然學理上IK真的只有在兩個骨架下得到IK的公式解(他們做機器人學的把這些會轉動的點叫frame,這讓我們做影像的人很困擾,因為我們把時間軸的時間點也叫frame),但一般來說使用者有需要的話我們還是會把一堆骨架分成兩組解完再分成兩組再解再分成兩組這樣逼近。不過他們沒想做這件事。

free-form deformation:只能一個點一個點調,就像我在spine講的,要做出好的轉動效果軟體上就必須支援同時對多個點漸變移動的功能,而不是讓使用者一個一個去調,使用者一個一個調很快的你就會看到你輸入的圖片變得不成人形。

導入其他引擎的plugin因為太執著於ergt的框架導致很多功能都埋著頭硬幹,例如unity有自己的動畫架構dragbones偏偏要自己寫自己的動畫播放器,導致想在unity中對於完成的動畫外加功能(改變sorting layer等)變數被定死沒辦法自己調等,不過他們沒想改這件事。

技術總結來說,dragonbones是相當好上手功能也非常直覺,然而想做太多仿3D的事只能靠根性慢慢磨,而且論壇上基本上沒人。最近他們要做mask了預計做出來後可以解決物體重疊的問題。


 3.live2D 與 e-mote

大概是近年來遊戲界用很多編輯軟體,這兩個軟體出來後也導致有些小公司也試著做出類似的東西想分一杯羹,嚴格說起這兩個軟體能做到的事用adobe的after effect都能做到,但日本人就是喜歡用自己的東西。這兩個軟體的介面,老實說,還真難用。

有人說e-mote跟live2D比起來,每個表情之間會有很明顯的「頓點」

我們可以從展示影片來看



你可以從影片中看到基本上每個表情你都可以很明顯的看到他的「原畫」,導致表情與表情之間沒那麼連續感,有的人喜歡有的人不喜歡。


相比之下live2D 的表情就相當滑順


但有人會覺得很沒有原畫感

從結果來看e-mote表情切換的方式比較類似於電腦視覺中的morphing,也就是把一張圖片漸變成另一張圖,而在表情方面的特化型可以參考這篇研究

https://homes.cs.washington.edu/~seitz/vmorph/vmorph.htm

其中的demo影片的效果就很像e-mote做出的效果

至於live2d,live2d的表情是你得從一個表情自己「拉」成另一個表情--當然他提供工具讓你很好拉,但是由於他的閉眼是睜著的眼睛的睫毛拉過去的,形狀上就不太會有像morphing那樣頓頓的感覺,在技術來說比較像是這篇研究As-Rigid-As-Possible 2D Shape Manipulation
,翻成中文就是儘可能維持原本形狀的變形

http://www-ui.is.s.u-tokyo.ac.jp/~takeo/research/rigid/index.html

這篇的技術主要是在講說怎麼讓一個已經三角化的mesh(我們把這種布滿vertex彼此間用小三角形連結,可以靠移動vertex改變形狀的圖形叫mesh)用最滑順的方式變形,核心理念就是想辦法在所有的三角形改變最小大小的狀況下,把形狀變成你要的樣子。這種演算法用來做軟軟妹子的動畫效果相當好。

有興趣實作相關演算法的可以參考這個人的作品
http://www.dgp.toronto.edu/~rms/software/Deform2D/index.html
網路上「現存」唯一的現在電腦還能跑的As-Rigid-As-Possible 2D Shape Manipulation演算法的code

但是不論live2D還是e-mote都是動畫播放器面相的軟體,雖然可以導入遊戲引擎但基本上只能做過場動畫或互動式動畫,拿來作動作遊戲功能上略顯不足。

4.anima 2D

Unity底下工作室推出的骨架動畫產品,使用unity內建sprite及動畫功能做出骨架動畫的效果,號稱有FFD但其實沒有,沒有FFD的骨架動畫工具就只是平庸的學生專題而已。

5.AnyPortrait

韓國的東西,功能上類似spine但在介面設計上比較接近live2d,用過live2d應該對於用一根可以左右拉的棒子控制「差分」的介面不陌生。 不過如果一開始就從spine類的入門的話遇到這軟體功能可能要找一下。

實際踩了一下發現這東西真的是不錯,是實際有在做遊戲的人設計出來的,像是把2D人物裝到3D環境中軟體便實現了讓人物模隨時lookat camera的功能。另外在FFD上提供了類似CSP的九宮格自由變形、類似筆刷的變形等功能,在FFD上操作起來至少每個點都是平滑的。軟體中內建了一些現成的物裡模擬(主要就飄動跟乳搖),而且還支援kinect操作可以用來做Vtuber,重點是,跟其他軟體比起來這個還滿便宜的(1000多買斷,逢年過節打五折,相比spine要能夠操作FFD至少要快一萬,而且是用租的,live2d大約4000元/年)。而且草創初期在推特上問問題基本都會回。

要說缺點的話,一個是框選mesh的操作上有點卡,常常要選好幾次才選得到目標,再來是基本上這是在unity底下做的,大概沒辦法移到其他軟體裡面。





6.Cutout Animation Tool - Blender

業界良心,開源奇蹟。特化blender中的plane物件,把2D的原圖變成3D的一塊一塊的板子,做成看似2D的3D動畫(FGO就是類似的做法,不過他們是用Maya做)。因為有Z軸,諸如重疊、投影之類的問題自動就被Z軸解決了。由於動畫部分是用blender做,輸出後就可以直接當3D物件使用。

缺點是沒人維護了,作者去玩開源的遊戲引擎了,而且不支援PSD檔直接匯入所以得把圖層一層一層存出來,也許git的分枝中可以找到有用的東西






7.AnimeEffects

日本同人做的開源骨架動畫軟體,曾經在Pixiv上紅了一小陣子,沒有做As-Rigid-As-Possible 2D Shape Manipulation,效果上比起很多西洋人賣的商用軟體好很多,直接用PSD檔的圖層來區分身體零件,有類似筆刷的功能可以一次調很多點。但畢竟只有很少的人在維護功能上大概也就demo中可以看到的那樣了。





剩下的想到再寫。

2017年6月2日 星期五

simple opencv 2411 flann point match example practice

flann function in opencv is good for point match, which find nearest point from A points to B points, it is useful for tracking, iterative closest point (ICP) algorithm...

I copy the code from this website:

https://github.com/royshil/morethantechnical/blob/master/ICP/MyICP.cpp

and make it simple for me to understand.

code:

#include "cv.h"
#include "highgui.h"
#include <iostream>
#include <vector>
#include <limits>
using namespace cv;
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
 Mat X(5,2,CV_32SC1); 
 X.at<Point>(0,0)=Point(1,1);
 X.at<Point>(1,0)=Point(5,5);
 X.at<Point>(2,0)=Point(10,10);
 X.at<Point>(3,0)=Point(15,15);
 X.at<Point>(4,0)=Point(20,20);
 Mat destinations(10,2,X.type());
 destinations.at<Point>(0,0)=Point(30,30);
 destinations.at<Point>(1,0)=Point(6,6);
 destinations.at<Point>(2,0)=Point(11,11);
 destinations.at<Point>(3,0)=Point(17,17);
 destinations.at<Point>(4,0)=Point(23,23);
 destinations.at<Point>(5,0)=Point(25,25);
 destinations.at<Point>(6,0)=Point(2,2);
 destinations.at<Point>(7,0)=Point(16,16);
 destinations.at<Point>(8,0)=Point(40,40);
 destinations.at<Point>(9,0)=Point(21,21);
 cv::Mat m_indices(X.rows, 1, CV_32S);
 cv::Mat m_dists(X.rows, 1, CV_32F);
 Mat dest_32f; destinations.convertTo(dest_32f,CV_32FC2);  //convert
 Mat X_32f; X.convertTo(X_32f,CV_32FC2);
 cv::flann::Index flann_index(dest_32f, cv::flann::KDTreeIndexParams(2));  // using 2 randomized kdtrees, 1 is ok ,too
 flann_index.knnSearch(X_32f, m_indices, m_dists, 1, cv::flann::SearchParams(64) );
 cout<<"indice:"<<m_indices<<endl;
 cout<<"dist  :"<<m_dists<<endl;
 for(int i=0;i<5;i++)
 {
  cout<<i<<" in X match to "<<m_indices.at<int>(i)<<" in destinations"<<endl;
  cout<<"distance is : "<<m_dists.at<float>(i)<<endl;
 }
 system("pause");
}

the output should be:

indice:[6;
  1;
  2;
  7;
  9]
dist  :[2;
  2;
  2;
  2;
  2]
0 in X match to 6 in destinations
distance is : 2
1 in X match to 1 in destinations
distance is : 2
2 in X match to 2 in destinations
distance is : 2
3 in X match to 7 in destinations
distance is : 2
4 in X match to 9 in destinations
distance is : 2
Press any key to continue . . .

2017年2月1日 星期三

simple opencv drawing opengl example

draw 2 sphere and a rotating cube in opencv by opengl using opencv setopengldrawcallback
the opencv should be cmaked and check the "WITH_OPENGL"





float gggg=55;
float gginin=3.14/6;
void on_opengl(void* param)
{
 //draw a cube
 glLoadIdentity();
 gluLookAt(20*cos(gginin), 0, 20*sin(gginin), 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
 glTranslated(0.0, 0.0, -1.0);
 glDrawArrays(GL_POINTS, 0, 1);
 glRotatef( gggg, 1, 0, 0 );
 //glRotatef( gggg, 0, 1, 0 );
 //glRotatef( gggg, 0, 0, 1 );
 static const int coords[6][4][3] = {
  { { +1, -1, -1 }, { -1, -1, -1 }, { -1, +1, -1 }, { +1, +1, -1 } },
  { { +1, +1, -1 }, { -1, +1, -1 }, { -1, +1, +1 }, { +1, +1, +1 } },
  { { +1, -1, +1 }, { +1, -1, -1 }, { +1, +1, -1 }, { +1, +1, +1 } },
  { { -1, -1, -1 }, { -1, -1, +1 }, { -1, +1, +1 }, { -1, +1, -1 } },
  { { +1, -1, +1 }, { -1, -1, +1 }, { -1, -1, -1 }, { +1, -1, -1 } },
  { { -1, -1, +1 }, { +1, -1, +1 }, { +1, +1, +1 }, { -1, +1, +1 } }
 };
 for (int i = 0; i < 6; ++i) {
  glColor3ub( i*20, 100+i*10, i*42 );
  glBegin(GL_QUADS);
  for (int j = 0; j < 4; ++j) {
   glVertex3d(0.2 * coords[i][j][0], 0.2 * coords[i][j][1], 0.2 * coords[i][j][2]);
  }
  glEnd();
 }
 //draw a sphere
 glLoadIdentity();
 gluLookAt(20*cos(gginin), 0, 20*sin(gginin), 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
 glTranslatef(1.0f,2.0f,1.0f);
 glColor3ub( 255,0,0);
 glBegin(GL_TRIANGLES);
 GLUquadricObj *quadric;
 quadric = gluNewQuadric();
 gluQuadricDrawStyle(quadric, GLU_FILL );
 gluSphere( quadric , .1 , 36 , 18 );
 glEnd();
 //draw another sphere
 glLoadIdentity();
 gluLookAt(20*cos(gginin), 0, 20*sin(gginin), 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
 glTranslatef(4.1f, 2.45f, 10.0f);
 glBegin(GL_TRIANGLES);
 GLUquadricObj *quadric2;
 quadric2 = gluNewQuadric();
 gluQuadricDrawStyle(quadric2, GLU_FILL );
 gluSphere( quadric , .6 , 36 , 18 );
 glEnd();
 glLoadIdentity();
}
int _tmain(int argc, _TCHAR* argv[])
{
 string openGLWindowName = "OpenGL Test";
 namedWindow(openGLWindowName, WINDOW_OPENGL);
 resizeWindow(openGLWindowName, 640, 480);
 glMatrixMode( GL_PROJECTION );
 glLoadIdentity();
 gluPerspective(90.0, 640/480, 1, 1000000.0);
 glMatrixMode(GL_MODELVIEW);
 setOpenGlContext(openGLWindowName);
 setOpenGlDrawCallback(openGLWindowName, on_opengl, NULL);
 while(true)
 {
  updateWindow(openGLWindowName);// when needed
  gggg++;
  gginin+=3.14/36;
  cvWaitKey(30);
  cout<<"ggininder"<<endl;
 }
return 0;
}

2016年12月30日 星期五

2D tip detection的一點心得

最近工作做human computer interface的東西需要找到端點,一般會把這問題當成2D local maxium的問題,像是影像中的手指或是人體的頭、手等端點,local maxium的方法很多但是網路上能找到的跟所謂要做的tip detection大抵上都是雞同鴨講、鴨同雞講。最近一些方法做一做寫點心得留在後人驗證。


遇到的影像類型大概是這兩種:


1.影像中的極值本身很接近、平緩區域多,通常發生在物體離攝影機靠得很近的狀況,例如手指偵測(通常手指不能離攝影機太遠,會偵測不到),stackoverflow上面這類問題最多人點讚的一篇做狗的手掌指點的大概也是這問題。


畫成波形圖大概是這種感覺,其中紅色星號是要找的地方
這種近距離的影像找極值會遇到的困難點是什麼,第一、有些地方是平緩的、有些地方不是端點卻有微微凸起,用一階微分來找正負號變換處的話平緩的地方會變消掉、微凸的地方會被標到。當然還有些模擬退火法之類的,但是我在2d的狀況遇到了點問題。

所以後來我就用opencv裡的floodfill做了很簡單的事,先掃過一遍找山頂,用floodfill把山挖掉,接下來就能找下一座山頭了。floodfill中有參數可以調整pixel值最多上升多少或下降多少,善用這功能可以一座一座山挖掉。

假設今天有這樣一隻手

先找到極值

然後用floodfill向下填滿


找下一個極值
 再挖掉
 再找下一個
 再挖掉
 最後剩一個
 找了極值、挖掉

大概是這樣,有點慢,但是沒啥好方法的話可以考慮。



 


2.影像中極值距離遠、每個極值之間大小有一定差距、通常出現在目標物較遠的影像中。


畫成圖大概像這樣:




這種問題就比較像一般local maxium會討論的問題,在影像上通常目標物夠大,不用很精準的抓到一個特定的點也是能輸出的很好。用一階微分(如laplace filter或canny filter)效果通常不會太差,但是要抓到tip點有時候會有問題。最近在網路上看到一個Local Binary Patterns的方法還行,最近在嘗試。


做法大概是這樣:


1.先選一個點,比較外圍一圈的pixel值,如果大於中間一定的threshold則填1









所以一個圓形的feature可能是000000或1111111,大多數都是0100101這種,我們選擇只有一端開口或沒有開口的0000000、1111111、0111111、0011111這種,開口數可以比較0轉換成1和1轉換成0的次數(記得尾端要跟第一個數字比),像0110111就是4、0010111也是4,我們選擇2跟0。然後再限制一下開口的0的次數,例如7/4這樣。


最後就會如下圖,紅色是找到的地方,黑色是沒找到的地方,大致上可以把身體濾掉。


詳細的論文請看An adaptive local binary pattern for 3D hand tracking這篇論文



2016年12月11日 星期日

不懂git bash 打槍槍

寫在這裡防止忘記

裝好git後
準備好repo檔案
https://github.com/esrlabs/git-repo

首先呢要sync東西下來,假設你有一個資料夾在D:/ggininder

git bash中(要開Run as administrator)

先

cd /D/ggininder/

mkdir ~/bin
curl https://raw.githubusercontent.com/esrlabs/git-repo/stable/repo > ~/bin/repo
curl https://raw.githubusercontent.com/esrlabs/git-repo/stable/repo.cmd > ~/bin/repo.cmd

這時候資料夾裡就有.repo資料夾了,可以開始init跟sync

repo init -u ssh://[gerrit account]@192.168.1.1:5566/biggg_project -g common,all –q 

repo sync


2016年9月8日 星期四

看看教育史打槍槍

人生的理想大概無望了,得找下一個目標來作。對於我這種不上不下的人來說呢,搞教育也許是滿足小小金字塔頂端的一種麻痺吧。教師是當不成了,然而這世界上知識業已爆炸,某些領域之間的交流甚至是資訊化程度卻是少得令人發汗,也許可以朝著這方面努力吧。


我一直相信教育有某種萬用解,不管是抽象或具體的,能用一句話解釋的概念便不用兩句來使人明瞭,亦或相反,能用兩句話使人永生難忘,又何妨吝嗇於只肯付出一句話的心力。我想這該稱作教育法或教學法吧。搜索網路,教育法這詞定義分歧。就我手邊看到的資料,不外乎怎麼跟學童玩樂間讓學童肯坐在位置上乖乖聽課,要怎麼用尖銳的聲音使學童聽到媽媽聲音的感覺。學童學童,台灣的初等教育沒那麼不堪,我想考慮的是大人的問題,至少是年長於大學生的對象。


在網路上問了成人教學法的問題,被專業人士薛了一頓,大意上是學童才有可塑性,成人沒救了,聽得懂就聽,聽不懂就放他去死。這顯然與我所追求的萬用解不僅是背道而馳,根本是信仰上的針鋒相對了。


http://www.ryerson.ca/content/dam/lt/resources/handouts/adult-education-methods_handout.pdf


網路上找了一下,中文找成人教育法不外乎失學者的成人教育跟社區大學這種學不學也無所謂的教育體系,用英文找就至少還有一些。內容與我的經驗非常相容,也非常合理,即使我根本沒經驗。


上面連結的這篇還沒看完,但大致上是說成人受教育時非常的目的導向,成人知道--或是他覺得自己知道--自己要什麼,你教的東西必須非常合用於他的生活或工作。適當的與過去經驗結合可以幫助學習,意思是要很多機會給他發表言論多講話。然後增加互動,不論是討論、實驗課或是情境演練。


就像我研究所養蜜蜂的經驗,當實驗者試圖讓蜜蜂飛到一個平常不可能飛到的地方喝糖水就必須一步一步從巢箱洞口開始,先從近的地方放糖水再慢慢把糖水拉遠,而當中最關鍵的訣竅在於必須提供蜜蜂一個可預測的獎勵以及明瞭的目的地。換成成人教育就像是成人要知道學這要幹嘛以及要學到哪些程度。聽起來合情合理,但我想,只少以我的大學就學經驗來說,系上開的本科課程倒是從來沒做到這點,而往後不管是跳船的我還是繼續深造的同學們,事實證明沒有釐清目的的課程對於往後發展即使在相同領域也沒有幫助,特別是生物這種跨了一個走廊的兩間實驗室的分歧與跨了一個系館的分歧差不多的地方。


台大葉丙成教授推展翻轉教室大概也是基於成人教育的理念吧。大學生該作為成人對待了,然而台灣教育這塊卻對成人教育法毫無聞問(從我網路發問被專業人士洗臉可見),當我研究所跑去多個系館看所謂有用的科系的上課以及教課態度中我看到了希望。而那些力倡不要作為職業訓練所的科系、那些有著「博雅」妄想的學院,不僅在教學上沉淪無力,在學習品質以及成果展現上更是一塌糊塗。人有追求職業價值的本能,一想著畢業後不工作用遺產過一輩子的貴族生活肉體上精神上便會自動關機了。


所以還有很多要實驗的呢,等我解完現階段bug再來考慮考慮。

2016年8月5日 星期五

2016年7月15日 星期五

ggininder

modify from http://www.geeksforgeeks.org/greedy-algorithms-set-7-dijkstras-algorithm-for-adjacency-list-representation/

solve the memory leak problem

#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include<iostream>
using namespace std;
// A structure to represent a node in adjacency list
struct AdjListNode
{
 int dest;
 int weight;
 struct AdjListNode* next;
};
// A structure to represent an adjacency liat
struct AdjList
{
 struct AdjListNode *head;  // pointer to head node of list
};
// A structure to represent a graph. A graph is an array of adjacency lists.
// Size of array will be V (number of vertices in graph)
struct Graph
{
 int V;
 struct AdjList* array;
};
// A utility function to create a new adjacency list node
struct AdjListNode* newAdjListNode(int dest, int weight)
{
 struct AdjListNode* newNode =
  (struct AdjListNode*) malloc(sizeof(struct AdjListNode));
 newNode->dest = dest;
 newNode->weight = weight;
 newNode->next = NULL;
 return newNode;
}
// A utility function that creates a graph of V vertices
struct Graph* createGraph(int V)
{
 struct Graph* graph = (struct Graph*) malloc(sizeof(struct Graph));
 graph->V = V;
 // Create an array of adjacency lists.  Size of array will be V
 graph->array = (struct AdjList*) malloc(V * sizeof(struct AdjList));
 // Initialize each adjacency list as empty by making head as NULL
 for (int i = 0; i < V; ++i)
  graph->array[i].head = NULL;
 return graph;
}
void delete_graph(Graph* old_graph)
{
 //  struct AdjListNode* ptr;
 for(int i=0;i<old_graph->V;i++)
 {
  free(old_graph->array[i].head);
 }
 free (old_graph->array);
 free(old_graph);

}
// Adds an edge to an undirected graph
void addEdge(struct Graph* graph, int src, int dest, int weight)
{
 // Add an edge from src to dest.  A new node is added to the adjacency
 // list of src.  The node is added at the begining
 struct AdjListNode* newNode = newAdjListNode(dest, weight);
 newNode->next = graph->array[src].head;
 graph->array[src].head = newNode;
 // Since graph is undirected, add an edge from dest to src also
 newNode = newAdjListNode(src, weight);
 newNode->next = graph->array[dest].head;
 graph->array[dest].head = newNode;
}
void delete_all_Edge(struct Graph* graph)
{
 for(int i=0;i<graph->V;i++)
 {
  if(graph->array[i].head==NULL)
  {
  }
  while (graph->array[i].head!=NULL)
  {
   struct AdjListNode* ptr;
   ptr=graph->array[i].head;
   ptr->dest=graph->array[i].head->dest;
   graph->array[i].head=graph->array[i].head->next;

   free(ptr);
  }
 }
}
void see_all_Edge(struct Graph* graph)
{
 //to see if there is any edge between node, just for debugging
 struct AdjListNode* ptr;
 for(int i=0;i<graph->V;i++)
 {
  if(graph->array[i].head==NULL)
  {
   cout<<i<<": nothing here!"<<endl;
  }
  if(graph->array[i].head!=NULL)
  {
   ptr=graph->array[i].head;
   //ptr->dest=graph->array[i].head->dest;
   while (ptr->next!=NULL)
   {
    cout<<i<<":"<<ptr->dest<<endl;
    ptr=ptr->next;
   }
   cout<<i<<":"<<ptr->dest<<endl;
   ptr=ptr->next;
  }
 }
}
// Structure to represent a min heap node
struct MinHeapNode
{
 int  v;
 int dist;
};
// Structure to represent a min heap
struct MinHeap
{
 int size;      // Number of heap nodes present currently
 int capacity;  // Capacity of min heap
 int *pos;     // This is needed for decreaseKey()
 struct MinHeapNode **array;
};
// A utility function to create a new Min Heap Node
struct MinHeapNode* newMinHeapNode(int v, int dist)
{
 struct MinHeapNode* minHeapNode =
  (struct MinHeapNode*) malloc(sizeof(struct MinHeapNode));
 minHeapNode->v = v;
 minHeapNode->dist = dist;
 return minHeapNode;
}
// A utility function to create a Min Heap
struct MinHeap* createMinHeap(int capacity)
{
 struct MinHeap* minHeap =
  (struct MinHeap*) malloc(sizeof(struct MinHeap));
 minHeap->pos = (int *)malloc(capacity * sizeof(int));
 minHeap->size = 0;
 minHeap->capacity = capacity;
 minHeap->array =
  (struct MinHeapNode**) malloc(capacity * sizeof(struct MinHeapNode*));
 return minHeap;
}
void delete_minHeap(MinHeap* minHeap)
{
 cout<<"size"<<minHeap->capacity<<endl;
 free(minHeap->pos);
 free(minHeap->array);
 free(minHeap);
}
// A utility function to swap two nodes of min heap. Needed for min heapify
void swapMinHeapNode(struct MinHeapNode** a, struct MinHeapNode** b)
{
 struct MinHeapNode* t = *a;
 *a = *b;
 *b = t;
}
// A standard function to heapify at given idx
// This function also updates position of nodes when they are swapped.
// Position is needed for decreaseKey()
void minHeapify(struct MinHeap* minHeap, int idx)
{
 int smallest, left, right;
 smallest = idx;
 left = 2 * idx + 1;
 right = 2 * idx + 2;
 if (left < minHeap->size &&
  minHeap->array[left]->dist < minHeap->array[smallest]->dist )
  smallest = left;
 if (right < minHeap->size &&
  minHeap->array[right]->dist < minHeap->array[smallest]->dist )
  smallest = right;
 if (smallest != idx)
 {
  // The nodes to be swapped in min heap
  MinHeapNode *smallestNode = minHeap->array[smallest];
  MinHeapNode *idxNode = minHeap->array[idx];
  // Swap positions
  minHeap->pos[smallestNode->v] = idx;
  minHeap->pos[idxNode->v] = smallest;
  // Swap nodes
  swapMinHeapNode(&minHeap->array[smallest], &minHeap->array[idx]);
  minHeapify(minHeap, smallest);
 }
}
// A utility function to check if the given minHeap is ampty or not
int isEmpty(struct MinHeap* minHeap)
{
 return minHeap->size == 0;
}
// Standard function to extract minimum node from heap
struct MinHeapNode* extractMin(struct MinHeap* minHeap)
{
 if (isEmpty(minHeap))
  return NULL;
 // Store the root node
 struct MinHeapNode* root = minHeap->array[0];
 // Replace root node with last node
 struct MinHeapNode* lastNode = minHeap->array[minHeap->size - 1];
 minHeap->array[0] = lastNode;
 // Update position of last node
 minHeap->pos[root->v] = minHeap->size-1;
 minHeap->pos[lastNode->v] = 0;
 // Reduce heap size and heapify root
 --minHeap->size;
 minHeapify(minHeap, 0);

 return root;
}
// Function to decreasy dist value of a given vertex v. This function
// uses pos[] of min heap to get the current index of node in min heap
void decreaseKey(struct MinHeap* minHeap, int v, int dist)
{
 // Get the index of v in  heap array
 int i = minHeap->pos[v];
 // Get the node and update its dist value
 minHeap->array[i]->dist = dist;
 // Travel up while the complete tree is not hepified.
 // This is a O(Logn) loop
 while (i && minHeap->array[i]->dist < minHeap->array[(i - 1) / 2]->dist)
 {
  // Swap this node with its parent
  minHeap->pos[minHeap->array[i]->v] = (i-1)/2;
  minHeap->pos[minHeap->array[(i-1)/2]->v] = i;
  swapMinHeapNode(&minHeap->array[i],  &minHeap->array[(i - 1) / 2]);
  // move to parent index
  i = (i - 1) / 2;
 }
}
// A utility function to check if a given vertex
// 'v' is in min heap or not
bool isInMinHeap(struct MinHeap *minHeap, int v)
{
 if (minHeap->pos[v] < minHeap->size)
  return true;
 return false;
}
// A utility function used to print the solution
void printArr(int dist[], int n)
{
 printf("Vertex   Distance from Source\n");
 for (int i = 0; i < n; ++i)
  printf("%d \t\t %d\n", i, dist[i]);
}
void showminheap(struct MinHeap* minHeap)
{
 int V=minHeap->size;
 for(int i=0;i<V;i++)
 {
  cout<<minHeap->pos[i]<<"this!!"<<endl;
  cout<<minHeap->array[i]->v<<"this v!!"<<endl;
 }
}
// The main function that calulates distances of shortest paths from src to all
// vertices. It is a O(ELogV) function
void dijkstra(struct Graph* graph, int src)
{
 int V = graph->V;// Get the number of vertices in graph
 int* dist=new int[V];      // dist values used to pick minimum weight edge in cut
 // minHeap represents set E
 struct MinHeap* minHeap = createMinHeap(V);
 // Initialize min heap with all vertices. dist value of all vertices
 for (int v = 0; v < V; ++v)
 {
  dist[v] = INT_MAX;
  minHeap->array[v] = newMinHeapNode(v, dist[v]);
  minHeap->pos[v] = v;
 }
 // Make dist value of src vertex as 0 so that it is extracted first

 dist[src] = 0;
 decreaseKey(minHeap, src, dist[src]);
 // Initially size of min heap is equal to V
 minHeap->size = V;

 // In the followin loop, min heap contains all nodes
 // whose shortest distance is not yet finalized.
 while (!isEmpty(minHeap))
 {
  // Extract the vertex with minimum distance value
  struct MinHeapNode* minHeapNode = extractMin(minHeap);
  int u = minHeapNode->v; // Store the extracted vertex number
  // Traverse through all adjacent vertices of u (the extracted
  // vertex) and update their distance values
  struct AdjListNode* pCrawl = graph->array[u].head;
  while (pCrawl != NULL)
  {
   int v = pCrawl->dest;
   // If shortest distance to v is not finalized yet, and distance to v
   // through u is less than its previously calculated distance
   if (isInMinHeap(minHeap, v) && dist[u] != INT_MAX &&
    pCrawl->weight + dist[u] < dist[v])
   {
    dist[v] = dist[u] + pCrawl->weight;
    // update distance value in min heap also
    decreaseKey(minHeap, v, dist[v]);
   }
   pCrawl = pCrawl->next;
  }
  free(pCrawl);
  free(minHeapNode);
  
 }

 delete_minHeap(minHeap);
 // print the calculated shortest distances
 printArr(dist, V);
 delete dist;
}

// Driver program to test above functions
int main()
{
 // create the graph given in above fugure
 int V = 9;
 struct Graph* graph = createGraph(V);
 while(true)
 {
 addEdge(graph, 0, 1, 4);
    addEdge(graph, 0, 7, 8);
    addEdge(graph, 1, 2, 8);
    addEdge(graph, 1, 7, 11);
    addEdge(graph, 2, 3, 7);
    addEdge(graph, 2, 8, 2);
    addEdge(graph, 2, 5, 4);
    addEdge(graph, 3, 4, 9);
    addEdge(graph, 3, 5, 14);
    addEdge(graph, 4, 5, 10);
    addEdge(graph, 5, 6, 2);
    addEdge(graph, 6, 7, 1);
    addEdge(graph, 6, 8, 6);
    addEdge(graph, 7, 8, 7);
 dijkstra(graph, 0);
 delete_all_Edge(graph);
 }


 return 0;
}


2016年5月5日 星期四

OpenCV QR decomposition

openCV的QR分解



參考這篇的
http://suzuichibolgpg.blog.fc2.com/blog-entry-258.html
http://rosettacode.org/wiki/QR_decomposition
跟維基百科
https://en.wikipedia.org/wiki/QR_decomposition#Example_2




OpenCV裡面似乎沒有直接得到QR 分解的函式,輾轉找了一下最後在一個日文網誌上找到,試著照著上面的步驟寫了個自己的,有問題再留言跟我說。記得H.rows>H.cols




#include <iostream>
#include <fstream>
#include <sstream>
#include <opencv2/core/core.hpp>
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
using namespace cv;
using namespace std;
int main(){
Mat H = (Mat_<double>(3,3) << 12,-51,4,6,167,-68,-4,24,-41);
Mat Q;
Mat R;
QRDecomp( H, Q, R );
cout << Q << "\n";
cout << R << "\n";
cout << Q*R << "\n";
return 0;
}

void QRDecomp( Mat& m, Mat& Q, Mat& R)
{
 Mat e;
 Mat m2=m;  //current A matrix, it would be smaller and smaller for each loop
 Mat Qtemp=Mat::eye(m.rows,m.rows,CV_32F); //square identical matrix for temporarily saving the Q matrix in for loop
 Mat Q1;
 float sign;
 for(int i=0;i<m.rows-1;i++)
 {
  e=Mat::zeros(m2.rows,1,CV_32F);
  e.at<float>(0,0)=1;
  Mat a1=m2.col(0);
  float u_n=float(norm(a1,NORM_L2));
  sign=a1.at<float>(0.0)>0?1:-1;
  Mat u=m2.col(0)+sign*u_n*e;
  Mat v=2*u*u.t()/((float)(norm(u,NORM_L2))*(norm(u,NORM_L2)));
  Q1=Mat::eye(m2.rows,m2.rows,CV_32F)-v;
  cout<<Q1<<Q1.rows<<" Q1"<<endl;

  Mat Q1_2=Mat::eye(m.rows,m.rows,CV_32F);  //make a identical matrix and fill the QA matrix to the buttom-right
  Mat Q1_2_roi=Q1_2(Rect(i,i,m.rows-i,m.rows-i));  //cols=width row=height   To make another matrix for iterating
 
  addWeighted(Q1_2_roi,0,Q1,1,0,Q1_2_roi); 


  Mat QA=Q1*m2;

  {
   m2=QA(Rect(1,1,QA.cols-1,QA.rows-1)).clone(); //cols=width row=height   To make another matrix for iterating
  }

  Qtemp=Q1_2*Qtemp;

 }

 R=Qtemp*m;
 Q=Qtemp.t();
 cout<<R<<"R"<<endl;
 cout<<Q<<"Q"<<endl;

}