想用OpenCV做AR該如何入手?
偶聞ar技術,感覺很不錯,想研究研究,但不知從何下手。
今天剛用知乎答題。
剛答了使用OpenCV和高通進行AR開發的區別:
基於OpenCV。Markerless的識別和跟蹤都沒有現成的比較能用的開源代碼,能找到的開源代碼大多是基於Marker的,而且效率都很低。做AR識別和跟蹤部分都需要自己開發。而此部分學習成本,開發成本都比較高。想達到高通的效率/準確度/robust 非常難。雖然這部分論文能找到還不少。但是實現起來會發現各種各樣問題。優點的話,就是提高了自學能力。但想達到商用的程度,難度很高。
我是從去年年底學習OpenCV並開發移動端的MarkerlessAR的識別和跟蹤。樓上各位提到的一些項目我都有嘗試。不過按照我的感覺,如果打算做商業化應用而不是個人學習,要走的路還很長。
針對Markerless的AR開發,我認為有幾個方面是比較難做到的。目前的開源代碼基本都做不到這幾個方面。
- 效率。大多數開源代碼都是在PC上運行。PC比起手機至少要快10倍以上。如何在手機,甚至配置較差的手機上也可以實時運行,需要演算法改進和效率優化。
- Robust。這一點是最頭疼的。用一下高通的效果就明白,他們這一點太強了。傳統跟蹤方法很容易跟丟的(比如手抖了一下,快速移動旋轉,Blur以及光照亮度變化很大時候)。
- Pose準確度以及消除累積誤差。需要保證跟蹤期間Pose始終正確,而且傾斜一定角度後依然完全匹配原圖。
takmin/OpenCV-Marker-less-AR :這個日本人寫的真的不錯,雖然上面三點都有很大問題。但可以作為一個切入點,注釋和說明也很齊全,識別和跟蹤也都包含了。網上能找到好的資源不多,這個還可以。這個很容易移植到安卓和ios。
補充修改:我已經把takmin的代碼去年移植到iOS的版本放到了github(代碼比較亂,但是可以跑,有視頻)。同時,也有一個移植了一半的Android版本(基於OpenCV的mix demo,打算使用GLES模塊顯示但沒做完,使用NDK編譯)。地址: https://github.com/meiroo/opencv-markerless-AR-Mobile
Mastering OpenCV with Practical Computer Vision Projects: 這本書代碼很好。包含一個ios的marker版本,以及一個PC版本的只有識別沒有跟蹤的版本。也可以作為一個切入點。
有了上面項目代碼的學習基本可以做出來簡單的demo,剩下的就要不斷學習這方面的論文,一個一個優化克服了。github上這方面的開源代碼非常少。《Mastering OpenCV with Practical Computer Vision Projects》這本書講用OpenCV開發實際的項目。全部都是講應用,每一章一個項目。理論知識涉及的比較少,需要你看《Learning OpenCV》和《OpenCV Cookbook》來自行彌補相關理論知識。
本書第二章講iPhone上的基於強標記(Marker-based)的增強現實應用。第三章講電腦上的基於弱標記(Marker-less)增強現實應用(但是再加上第一章,你就可以做一個Android的增強現實應用)。我覺得夠你學習了,學好這兩章,以後你就可以直接用OpenCV自己擼一個增強現實應用,而不需要再藉助什麼第三方的增強現實庫。
這本書還有完整源碼開源。每一章都有完整的示常式序:MasteringOpenCV/code · GitHub
我今年上半年看了第三章,還有後面兩章關於人臉的。寫了碩士階段的第一篇論文,Android手機端的關於人臉識別的增強現實應用。
如果你看這本書有不懂的,或者本書代碼運行不了,歡迎私信問問題。我看見就會解答,畢竟當時我也學的很痛苦。
另外,這本書在亞馬遜有一本中文版的翻譯。我就不說這本書翻譯的有多麼多麼爛了。真是爛的一比。歡迎看過英文版的同學去亞馬遜吐槽。
對了,網上有PDF版本,或者我直接給你一個我百度網盤的分享鏈接http://pan.baidu.com/s/1o7LbZVc
忘了說OpenGL了。使用OpenCV做增強現實必須要有OpenGL。要不然怎麼做到增強現實的效果?我當時直接用OpenGL寫了一段讀取obj文件的三維模型的,學習的時候寫一個簡單的就好了,能簡單讀取點 v 和面 f 這兩個參數就好了,其他的先不要考慮。
要在屏幕中看到三維模型在真實世界的效果,有兩個矩陣變換非常非常重要。這個要先有OpenGL的基礎,推薦OpenGL的紅寶書OpenGL編程指南+第七版+中文版.rar_免費高速下載。看視圖那一章,一定要把頂點變換流水線搞懂。因為這是圖形學最最基礎的東西了。其中OpenCV和OpenGL有兩個矩陣變換。分別是:OpenCV下的攝像機內參矩陣K與OpenGL頂點變換的投影矩陣,OpenCV下的攝像機外參矩陣[R|t]與OpenGL頂點變換的模型視圖矩陣。這個變換的結果已經在《Mastering OpenCV with Practical Computer Vision Projects》這本書中給你了,不過在代碼里code/ARDrawingContext.cpp at master · MasteringOpenCV/code · GitHub,重點看下面這三個函數。
buildProjectionMatrix():將內參 K轉換為 OpenGL 的投影矩陣;
drawAugmentedScene():將外參[R|t]轉換為 OpenGL 的模型視圖矩陣;
drawCameraFrame():對於背景紋理貼圖, 要讓圖片以正確的大小顯示在手機屏幕中, OpenGL 需要調用的投影矩陣。
如果自學OpenGL的頂點變換流水線有困難的話,非常非常推薦看這個頁面OpenGL Transformation,一定要學會裡面的推導過程,這樣你才能知道上面那三個函數里的矩陣變換是怎麼推導的。
關於剛才提到的「背景紋理貼圖」,我覺得應該是這裡。Nehe的OpenGL中文教程中的使用OpenGL播放AVI視頻的原理。用OpenCV對視頻的每一幀做圖像處理,每一幀圖像要調用OpenGL來顯示在屏幕中,這就要用到OpenGL的紋理貼圖。把每一幀圖像當作背景圖片用紋理貼圖的方式在OpenGL中顯示,然後顯示三維模型(這裡的一個矩陣變換,在函數drawCameraFrame())。
如果你要在Android手機上做增強現實程序。電腦端的OpenCV是支持OpenGL的,只需要重新編譯令其支持OpenGL即可。這個從網上搜一搜很多。
但是Android的OpenCV是不支持OpenGL的,那隻能硬來了。我用多線程,一個線程用OpenCV圖像處理,另外一個線程用OpenGL把處理過的圖片顯示在屏幕中。這個為了效率還是用C++來做,通過Android的NDK編譯,這一部分書里沒有介紹,我也是找了很多資料才完成的。這部分還挺關鍵的,如果你也找了很久,做了很長時間還是沒做出來,就發郵件給我吧,forestsen@qq.com,有問必答。
不用其他第三方增強現實庫,只用OpenCV和OpenGL結合擼增強現實應用差不多就這麼些內容吧。
Github 上有個日本人寫的項目,看看對你有沒有用。YouTube : https://www.youtube.com/watch?v=KgQguj78qMAGithub : takmin/OpenCV-Marker-less-AR · GitHub
OpenGL與OpenCV實現增強現實 |VR第一資訊 VR013
很久沒有寫博客了,最近在學習計算機視覺的相關知識,於是寫了一個AR的小Demo。
該程序通過OpenCV實現對Marker的識別和定位,然後通過OpenGL將虛擬物體疊加到攝像頭圖像下,實現增強現實。首先來看看我們使用的Marker:
這是眾多Marker中的一個,它們都被一圈的黑色邊框所包圍,邊框之中是編碼信息,白色代表1,黑色代表0。將每一行作為一個字,那麼每個字有5bits。其中,1、3、5位為校驗位,2、4位為信息位。也就是說,整個Marker的信息位只有10bits,所以最多可表示1024個數(0~1023)。這種編碼方式實際上是漢明碼的變種,唯一區別在於它的首位是對應漢明碼首位的反(比如漢明碼是00000,那麼Marker中的編碼為10000)。這麼做的目的是防止某一行全黑,從而提高識別率。漢明碼還有另一大優勢——不具有旋轉對稱性,因此程序能通過漢明碼確定Marker的方向,因此從Marker中解碼的信息是唯一的。
一、Marker的檢測與識別我們首先實現一個類,用於檢測圖像中的Marker,解碼信息,並計算Marker相對於攝像頭的坐標位置。
檢測部分比較簡單。首先將輸入圖像進行灰度變換,然後對灰度圖像進行自適應二值化。之所以使用自適應二值化,是因為它能更好的適應光照的變化。但有一點要注意,很多朋友使用自適應二值化後表示得到的結果很像邊緣檢測的結果,那是因為自適應窗口過小造成的。使用自適應二值化時,窗口的大小應大於二值化目標的大小,否則得到的閾值不具有適應性。在自適應二值化之後,為了消除噪音或小塊,可以加以形態學開運算。以上幾部可分別得到下列圖像(其中二值化的結果經過了反色處理,方便以後的輪廓提取)。
得到二值圖像後,就可以使用OpenCV中的findContours來提取輪廓了。一副二值圖像當中的輪廓有很多,其中有一些輪廓很小,我們通過一個閾值將這些過小的輪廓排除。排除過小輪廓後,就可以對輪廓進行多邊形近似了。由於我們的Marker是正方形,其多邊形近似結果應該滿足以下條件:
1、只有4個頂點
2、一定是凸多邊形
3、每一個邊的長度不能過小
通過以上幾個條件,我們可以排除絕大部分輪廓,從而找到最有可能為Marker的部分。找到這樣的候選輪廓後,我們將它的多邊形四個頂點保存下來,並做適當的調整,使所有頂點逆時針排序。代碼如下:
void MarkerRecognizer::markerDetect(Mat img_gray, vector&
{
Mat img_bin;
int thresh_size = (min_size/4)*2 + 1;
adaptiveThreshold(img_gray, img_bin, 255, ADAPTIVE_THRESH_GAUSSIAN_C, THRESH_BINARY_INV, thresh_size, thresh_size/3);
//threshold(img_gray, img_bin, 125, 255, THRESH_BINARY_INV|THRESH_OTSU);
morphologyEx(img_bin, img_bin, MORPH_OPEN, Mat()); //use open operator to eliminate small patch
vector&
vector&
findContours(img_bin, all_contours, CV_RETR_LIST, CV_CHAIN_APPROX_NONE);
for (int i = 0; i &< all_contours.size(); ++i)
{
if (all_contours[i].size() &> min_size)
{
contours.push_back(all_contours[i]);
}
}
vector& 下一步,從這些候選區域中進一步篩選出真正的Marker。首先,由於攝像機視角的關係,圖像中的Marker是經過透視變換的。為了方便提取Marker中的信息,要使用warpPerspective方法對候選區域進行透視變換,得到Marker的正視圖。之後,由於Marker只有黑白兩種顏色,其直方圖分布是雙峰的,所以用大津法(OTSU)對透視變換後的圖像做二值化。 由於Marker都有一圈黑色的輪廓,這成為了我們進一步判定Marker的標準。獲取正確的Marker圖像後,可能有4個不同方向。這時我們就可以通過Marker中的漢明碼確定Marker的正確朝向了,正確朝向的Marker,其漢明距離一定為零。得到Marker的朝向後,就可以提取Marker的信息(即ID),還可以調整Marker的4個頂點順序,使其不隨視角的變換而變換。在Demo中,我將正向放置的Marker的左上角作為1號頂點,逆時針旋轉依次為2號、3號和4號。
for (int i = 0; i &< contours.size(); ++i)
{
double eps = contours[i].size()*APPROX_POLY_EPS;
approxPolyDP(contours[i], approx_poly, eps, true);
if (approx_poly.size() != 4)
continue;
if (!isContourConvex(approx_poly))
continue;
//Ensure that the distance between consecutive points is large enough
float min_side = FLT_MAX;
for (int j = 0; j &< 4; ++j)
{
Point side = approx_poly[j] - approx_poly[(j+1)%4];
min_side = min(min_size, side.dot(side));
}
if (min_side &< min_side_length*min_side_length)
continue;
//Sort the points in anti-clockwise
Marker marker = Marker(0, approx_poly[0], approx_poly[1], approx_poly[2], approx_poly[3]);
Point2f v1 = marker.m_corners[1] - marker.m_corners[0];
Point2f v2 = marker.m_corners[2] - marker.m_corners[0];
if (v1.cross(v2) &> 0) //由於圖像坐標的Y軸向下,所以大於零才代表逆時針
{
swap(marker.m_corners[1], marker.m_corners[3]);
}
possible_markers.push_back(marker);
}
}
代碼如下:
void MarkerRecognizer::markerRecognize(cv::Mat img_gray, vector&
{
final_markers.clear();
Mat marker_image;
Mat bit_matrix(5, 5, CV_8UC1);
for (int i = 0; i &< possible_markers.size(); ++i)
{
Mat M = getPerspectiveTransform(possible_markers[i].m_corners, m_marker_coords);
warpPerspective(img_gray, marker_image, M, Size(MARKER_SIZE, MARKER_SIZE));
threshold(marker_image, marker_image, 125, 255, THRESH_BINARY|THRESH_OTSU); //OTSU determins threshold automatically.
//A marker must has a whole black border.
for (int y = 0; y &< 7; ++y)
{
int inc = (y == 0 || y == 6) ? 1 : 6;
int cell_y = y*MARKER_CELL_SIZE;
for (int x = 0; x &< 7; x += inc)
{
int cell_x = x*MARKER_CELL_SIZE;
int none_zero_count = countNonZero(marker_image(Rect(cell_x, cell_y, MARKER_CELL_SIZE, MARKER_CELL_SIZE)));
if (none_zero_count &> MARKER_CELL_SIZE*MARKER_CELL_SIZE/4)
goto __wrongMarker;
}
}
//Decode the marker
for (int y = 0; y &< 5; ++y)
{
int cell_y = (y+1)*MARKER_CELL_SIZE;
for (int x = 0; x &< 5; ++x)
{
int cell_x = (x+1)*MARKER_CELL_SIZE;
int none_zero_count = countNonZero(marker_image(Rect(cell_x, cell_y, MARKER_CELL_SIZE, MARKER_CELL_SIZE)));
if (none_zero_count &> MARKER_CELL_SIZE*MARKER_CELL_SIZE/2)
bit_matrix.at&
else
bit_matrix.at&
}
}
//Find the right marker orientation
bool good_marker = false;
int rotation_idx; //逆時針旋轉的次數
for (rotation_idx = 0; rotation_idx &< 4; ++rotation_idx)
{
if (hammingDistance(bit_matrix) == 0)
{
good_marker = true;
break;
}
bit_matrix = bitMatrixRotate(bit_matrix);
}
if (!good_marker) goto __wrongMarker;
//Store the final marker
Marker final_marker = possible_markers[i];
final_marker.m_id = bitMatrixToId(bit_matrix);
std::rotate(final_marker.m_corners.begin(), final_marker.m_corners.begin() + rotation_idx, final_marker.m_corners.end());
final_markers.push_back(final_marker);
__wrongMarker:
continue;
}
}
得到最終的Marker後,為了之後計算精確的攝像機位置,還需對Marker四個頂點的坐標進行子像素提取,這一步很簡單,直接使用cornerSubPix即可。
void MarkerRecognizer::markerRefine(cv::Mat img_gray, vector&
{
for (int i = 0; i &< final_markers.size(); ++i)
{
vector&
cornerSubPix(img_gray, corners, Size(5,5), Size(-1,-1), TermCriteria(CV_TERMCRIT_ITER, 30, 0.1));
}
}
為了檢查演算法的結果,將Marker的提取結果畫到了圖像上。其中藍色邊框標記了Marker的區域,空心圓圈代表Marker的1號頂點(正向放置時的左上角),較小的實心原點代表2號頂點,數字代表Marker的ID。
2、計算攝像機位置
計算攝像機的位置,首先需要對攝像機進行標定,標定是確定攝像機內參矩陣K的過程,一般用棋盤進行標定,這已經是一個很成熟的方法了,在這就不細說了。得到相機的內參矩陣K後,就可以使用solvePnP方法求取攝像機關於某個Marker的位置。攝像機成像使用小孔模型,如下:
x = K[R|T]X
其中,X是空間某點的坐標(相對於世界坐標系),[R|T]是攝像機外參矩陣,用於將某點的世界坐標變換為攝像機坐標,K是攝像機內參,用於將攝像機坐標中的某點投影的像平 面上,x 即為投影后的像素坐標。
對於一個確定的Marker,x是已知的,K是已知的,使用solvePnP求取相機位置實際上就是求取相機相對於Marker的外參矩陣[R|T],但現在X還不知道,如何確定呢?
外參矩陣與世界坐標系的選取有關,而世界坐標系的選取是任意的,因此我們可以將世界坐標系直接設定在Marker上,如下圖:
我們想像Marker位於世界坐標系的XY平面上(Z分量為零),且原點位於Marker的中心。由此,我們就確定了四個點的X坐標,將四個點的X坐標以及對應的像素坐標x傳入solvePnP方法,即可得到相機關於該Marker的外參了。為了方便之後的使用,使用Rodrigues方法將旋轉向量變為對應的旋轉矩陣。
void Marker::estimateTransformToCamera(vector&
{
Mat rot_vec;
bool res = solvePnP(corners_3d, m_corners, camera_matrix, dist_coeff, rot_vec, tvec);
Rodrigues(rot_vec, rmat);
}
那麼現在問題來了!我們設定世界坐標系時,只設定了X軸與Y軸的方向,由於Z分量為零,它的方向並不影響求得的外參矩陣。但之後將虛擬物體放入該世界坐標時,卻需要知道Z軸的方向,那麼我們的Z軸方向到底是什麼呢?
首先,solvePnP返回的結果是一個旋轉向量和一個平移向量,兩者構成一個剛體運動,剛體運動不會改變坐標系的手性(即右手坐標系經過剛體運動後還是右手坐標系),所以世界坐標系的手性應該和相機坐標系的手性一致。從相機的小孔成像模型中可以知道,相機坐標系的Z軸是指向像平面的,因此,通過下圖我們可以斷定,世界坐標系的Z軸是垂直於Marker向下的。
二、從OpenCV到OpenGL得到攝像機的內參K和相對於每個Marker的外參[R|T]後,就可以開始考慮將虛擬物體添加進來了。老慣例,我還是使用OpenFrameworks。OpenFrameworks是在OpenGL基礎上構建的一套框架,所以3D顯示上,其本質還是OpenGL。
OpenGL的投影模型和普通相機的小孔投影模型是類似的,其PROJECTION矩陣對應與相機的內參K,MODELVIEW矩陣對應與相機的外參。但是,我們之前求得的K和[R|T]還不能直接使用,原因有二,其一,OpenGL投影模型使用的坐標系與OpenCV的不同;其二,OpenGL為了進行Clipping,其投影矩陣需要將點投影到NDC空間中。
Perspective Frustum and Normalized Device Coordinates (NDC)
由上圖(左邊)可知,OpenGL的相機坐標系相當於OpenCV的相機坐標系繞著X軸旋轉了180度,因此,我們使用外參矩陣[R|T]對世界坐標系中的某點進行變換後,還需要左乘一個旋轉矩陣才能得到該點在OpenGL坐標系中的坐標。繞X軸旋轉180度的旋轉矩陣很簡單,如下:
[ 1, 0, 0,
0, -1, 0,
0, 0, -1 ]
總之,外參矩陣[R|T]左乘以上矩陣後,即得OpenGL中的MODELVIEW矩陣,代碼如下,注意OpenGL的矩陣元素是以列主序存儲的。
void ofApp::extrinsicMatrix2ModelViewMatrix(cv::Mat rotation, cv::Mat translation, float* model_view_matrix)
{
//繞X軸旋轉180度,從OpenCV坐標系變換為OpenGL坐標系
static double d[] =
{
1, 0, 0,
0, -1, 0,
0, 0, -1
};
Mat_&
rotation = rx*rotation;
translation = rx*translation;
model_view_matrix[0] = rotation.at&
model_view_matrix[1] = rotation.at&
model_view_matrix[2] = rotation.at&
model_view_matrix[3] = 0.0f;
model_view_matrix[4] = rotation.at&
model_view_matrix[5] = rotation.at&
model_view_matrix[6] = rotation.at&
model_view_matrix[7] = 0.0f;
model_view_matrix[8] = rotation.at&
model_view_matrix[9] = rotation.at&
model_view_matrix[10] = rotation.at&
model_view_matrix[11] = 0.0f;
model_view_matrix[12] = translation.at& 下一步是求PROJECTION矩陣。由於OpenGL要做Clipping,要求所有在透視椎體中的點都投影到NDC中,在NDC中的點能夠顯示在屏幕上,之外的點則不能。因此,我們的PROJECTION矩陣不僅要有與內參矩陣K相同的透視效果,還得把點投影到NDC中。 首先先看看內參矩陣K的形式: 首先假設OpenGL的投影椎體是對稱的,那麼PROJECTION矩陣的形式如下: 使用以上矩陣對某一點(X, Y, Z, 1)投影后,可以得到如下關係: 接下來,OpenGL會對該結果進行Clipping,具體方法是將四個分量都除以-Z,那麼,要使我們的點最終顯示到屏幕上,前三個分量在除以-Z後其變化範圍必須在[-1, 1]內。如下: 由攝像機投影模型(相似三角形)知: 其中由於OpenGL相機的相面在Z軸負方向上,所以是-fx和-fy。xp和yp分別為某點在相面上的橫坐標和縱坐標,這兩個坐標的原點在圖像的中心,圖像的寬度和高度分別為w和h,因此xp和yp的取值範圍分別為[-w/2, w/2]和[-h/2, h/2],可得: 於是 接下來,我們為OpenGL相機設定兩個面,near和far,只有處於這兩個面之間的點才能投影到NDC空間中,所以當 Z=-n 時,(AZ+B)/-Z = -1,當 Z=-f 時,(AZ+B)/-Z = 1,由此我們可以得到關於A和B的二元一次方程,從而解出A、B: 現在再來考慮OpenGL投影椎體不對稱的情況,這種情況下,PROJECTION矩陣的形式為:
model_view_matrix[13] = translation.at&
model_view_matrix[14] = translation.at&
model_view_matrix[15] = 1.0f;
}
由於椎體不對稱,這時xp和yp的變化範圍分別為[l, r]和[b, t],代表圖像左側(left)右側(right),以及底部(bottom)頂部(top),用同樣的方法,我們有:
可得:
,
於是:
關於l+r和b+t是怎麼計算的,可以參考下圖:
綜上所述,我們可以得到OpenGL投影矩陣的最終形式:
到此,我們就可以將這個矩陣的數據傳遞給PROJECTION矩陣了:
void ofApp::intrinsicMatrix2ProjectionMatrix(cv::Mat camera_matrix, float width, float height, float near_plane, float far_plane, float* projection_matrix)
{
float f_x = camera_matrix.at&
float f_y = camera_matrix.at&
float c_x = camera_matrix.at&
float c_y = camera_matrix.at&
projection_matrix[0] = 2*f_x/width;
projection_matrix[1] = 0.0f;
projection_matrix[2] = 0.0f;
projection_matrix[3] = 0.0f;
projection_matrix[4] = 0.0f;
projection_matrix[5] = 2*f_y/height;
projection_matrix[6] = 0.0f;
projection_matrix[7] = 0.0f;
projection_matrix[8] = 1.0f - 2*c_x/width;
projection_matrix[9] = 2*c_y/height - 1.0f;
projection_matrix[10] = -(far_plane + near_plane)/(far_plane - near_plane);
projection_matrix[11] = -1.0f;
projection_matrix[12] = 0.0f;
projection_matrix[13] = 0.0f;
projection_matrix[14] = -2.0f*far_plane*near_plane/(far_plane - near_plane);
projection_matrix[15] = 0.0f;
}
現在是時候放一些虛擬物體進來了,為了簡單,我就放了幾個立方體,由於OpenFrameworks繪製立方體時以立方體在中心為原點,所以為了使立方體的底面貼在Marker上,必須在Marker上方二分之一立方體邊長的地方繪製,也就是繪製立方體的坐標為(0, 0, -0.5*size),為什麼是負0.5呢?還記得之前所說的世界坐標系的Z軸是垂直於Marker並朝下的嗎?所以要畫在Marker上方,必須向Z軸負方向移動!
void ofApp::draw(){
ofSetColor(255);
float view_width = ofGetViewportWidth();
float view_height = ofGetViewportHeight();
m_video.draw(0, 0, view_width, view_height);
//Set camera matrix to the opengl projection matrix;
intrinsicMatrix2ProjectionMatrix(m_camera_matrix, 640, 480, 0.01f, 100.0f, m_projection_matrix);
ofSetMatrixMode(OF_MATRIX_PROJECTION);
//Openframeworks里將(-1, -1)映射到屏幕左上角,而非一般的左下角,所以需要一個矩陣進行垂直鏡像
static float reflect[] =
{
1, 0, 0, 0,
0, -1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
};
ofLoadMatrix(reflect);
//OpenGL默認為右乘
ofMultMatrix(m_projection_matrix);
//Reset model view matrix to identity;
ofSetMatrixMode(OF_MATRIX_MODELVIEW);
ofLoadIdentityMatrix();
//Set opengl parameters
ofSetColor(255);
& &ofEnableBlendMode(OF_BLENDMODE_ALPHA);
ofEnableDepthTest();
& &glShadeModel(GL_SMOOTH); //some model / light stuff
& &m_light.enable();
& &ofEnableSeparateSpecularLight();
vector&
Mat r, t;
for (int i = 0; i &< markers.size(); ++i)
{
//求出的旋轉矩陣r的行列式為+1,即為剛體變換,所以不改變坐標系的手性
markers[i].estimateTransformToCamera(m_corners_3d, m_camera_matrix, m_dist_coeff, r, t);
extrinsicMatrix2ModelViewMatrix(r, t, m_model_view_matrix);
ofLoadMatrix(m_model_view_matrix);
ofSetColor(0x66,0xcc,0xff);
//由於Marker坐標系與OpenCV坐標系的手性一致,所以Marker坐標系的Z軸垂直於Marker向下
//繪製Box時的Anchor在Box中心,所以需要-0.5*size的偏移才能使Box的底面在Marker上!!
ofDrawBox(0, 0, -0.4f, 0.8f);
}
//Reset parameters
ofDisableDepthTest();
& &m_light.disable();
& &ofDisableLighting();
& & ofDisableSeparateSpecularLight();
ofSetMatrixMode(OF_MATRIX_MODELVIEW);
ofLoadIdentityMatrix();
ofSetMatrixMode(OF_MATRIX_PROJECTION);
ofLoadIdentityMatrix();
}
注意,我在設置投影矩陣時,左乘了一個垂直方向的鏡像矩陣,這是因為,我發現OpenFrameworks將NDC空間中(-1, -1)點映射到屏幕的左上角,而非一般OpenGL所映射的左下角,如果不乘這個鏡像矩陣,得到的圖像就是上下顛倒的。至於為什麼OpenFrameworks是這樣,由於沒仔細研究它的代碼,我只能猜測是其在初始化時對OpenGL做了一些設置所致。所以,如果我的理解或猜測有錯誤,還請大家指出^_^
最後給出代碼的下載地址,程序用VS2012開發,解壓後放到」OpenFrameworks安裝目錄appsmyApps「下打開編譯:
OpenCV與OpenGL實現增強現實
--------------------------分割符------------------
http://qm.qq.com/cgi-bin/qm/qr?k=i70vfJ44qMkTqj423-1Jakyrdw1w9qw7 (二維碼自動識別)
高票答案說得很好了,補充一下。
《Mastering OpenCV with Practical Computer Vision Projects》裡面的兩個項目和源碼非常值得學習,了解AR的基礎。但是performance不會太好,有一些outofdates了。作為學慣用途可以,但是追求demo效果的話,需要用其他SDK。http://socialcompare.com/en/comparison/augmented-reality-sdks 這裡有一個AR SDK的比較。1.手機移動平台,Vuforia首選,渲染可以用unity,也可以用這個開源小引擎:Rajawali/Rajawali · GitHub ,(已經有VR和AR),個人非常喜歡。
2.PC平台,原來metaio太屌了,現在被蘋果收購了。看看開源的選擇吧,marker推薦aruco,開源,渲染用unity就好;markeless沒找到很好的;PTAM SLAM非常值得研究。歡迎關注,加入一個研究的圈子很重要
求交流,AR方向做opencv for unity 的,本人扣扣136841498
Tango和Hololens使用的是深度攝像機,和Opencv這樣的視覺計算不太一樣,不過最後都是可以拿到RT獲得物體的三維信息。
剛接觸AR,這方面資料太缺.作為入門,學習下日本人的項目:takmin/OpenCV-Marker-less-AR /多謝樓上分享;
兄台玩過ColarMix嗎? 最近也在研究。剛剛買了Opencv的書。如果同是AR+unity開發者的話,歡迎私信交流技術
可以看一下aruco這個庫
一、Marker的檢測與識別 主要為AR中的世界物體對象,一般使用檢測或是識別的方法確定,也可以手動選定初始對象。
二、計算攝像機位置 1、特徵點獲取與姿態估計 2、跟蹤與地圖繪製 3、迴環檢測
三、圖形渲染(從OpenCV到OpenGL) 根據姿態獲取的姿態矩陣,使用OpenGL或是metal等渲染工具實現2D或是3D渲染 文章具體參見:
1、VO部分 OpenGL與OpenCV實現增強現實 http://blog.csdn.net/qq_26499769/article/details/51549765 源代碼參見: http://download.csdn.net/detail/aichipmunk/8207875
2、SLAM部分
https://github.com/raulmur/ORB_SLAM2
ORB-SLAM2 is a real-time SLAM library for Monocular, Stereo and RGB-D cameras that computes the camera trajectory and a sparse 3D reconstruction (in the stereo and RGB-D case with true scale). It is able to detect loops and relocalize the camera in real time。
感覺搞Markerless AR前期的難點就是 3D reconstruction。目前貌似主流就是用TOF或者Prime的IR吧。用 Opencv做了一個Demo, 感覺難度一般,前提是Offline的情況,但是使用場景不多。微軟的KinectFusion可以做到Realtime,但是目前只能在PC上跑。只能在PC上跑的AR叫啥AR啊。。。難不成我搬個手提電腦到處跑?聯想出了一個名字很長的手機據說載入了Project Tangle可以做實時3D Reconstruction,準備入手一台玩玩。有立志獻身AR的同學求勾搭。。。
推薦閱讀:
※如何從入門開始學習OpenCV?
※如何在unity里使用opencv?
※關於MFC是否out了的問題?
※用OpenCV人臉檢測,出現這個錯誤,大神賜教?
※在 MFC 框架中,有什麼方法能直接將 OpenCV 2.0 庫中 Mat 格式的圖片傳遞到 Picture Control(圖片控制項)顯示?