想用OpenCV做AR該如何入手?

偶聞ar技術,感覺很不錯,想研究研究,但不知從何下手。


今天剛用知乎答題。

剛答了使用OpenCV和高通進行AR開發的區別:

基於OpenCV。Markerless的識別和跟蹤都沒有現成的比較能用的開源代碼,能找到的開源代碼大多是基於Marker的,而且效率都很低。做AR識別和跟蹤部分都需要自己開發。而此部分學習成本,開發成本都比較高。想達到高通的效率/準確度/robust 非常難。雖然這部分論文能找到還不少。但是實現起來會發現各種各樣問題。優點的話,就是提高了自學能力。但想達到商用的程度,難度很高。

我是從去年年底學習OpenCV並開發移動端的MarkerlessAR的識別和跟蹤。樓上各位提到的一些項目我都有嘗試。不過按照我的感覺,如果打算做商業化應用而不是個人學習,要走的路還很長。

針對Markerless的AR開發,我認為有幾個方面是比較難做到的。目前的開源代碼基本都做不到這幾個方面。

  1. 效率。大多數開源代碼都是在PC上運行。PC比起手機至少要快10倍以上。如何在手機,甚至配置較差的手機上也可以實時運行,需要演算法改進和效率優化。
  2. Robust。這一點是最頭疼的。用一下高通的效果就明白,他們這一點太強了。傳統跟蹤方法很容易跟丟的(比如手抖了一下,快速移動旋轉,Blur以及光照亮度變化很大時候)。
  3. 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=KgQguj78qMA

Github : 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& possible_markers, int min_size, int min_side_length)
{
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&&> all_contours;
vector&&> contours;
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& approx_poly;
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);
}
}

下一步,從這些候選區域中進一步篩選出真正的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號。

代碼如下:

void MarkerRecognizer::markerRecognize(cv::Mat img_gray, vector& possible_markers, vector& final_markers)
{
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&(y, x) = 1;
else
bit_matrix.at&(y, x) = 0;
}
}

//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& final_markers)
{
for (int i = 0; i &< final_markers.size(); ++i) { vector& corners = final_markers[i].m_corners;
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& corners_3d, cv::Mat camera_matrix, cv::Mat dist_coeff, cv::Mat rmat, cv::Mat tvec)
{
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_& rx(3, 3, d);

rotation = rx*rotation;
translation = rx*translation;

model_view_matrix[0] = rotation.at&(0,0);
model_view_matrix[1] = rotation.at&(1,0);
model_view_matrix[2] = rotation.at&(2,0);
model_view_matrix[3] = 0.0f;

model_view_matrix[4] = rotation.at&(0,1);
model_view_matrix[5] = rotation.at&(1,1);
model_view_matrix[6] = rotation.at&(2,1);
model_view_matrix[7] = 0.0f;

model_view_matrix[8] = rotation.at&(0,2);
model_view_matrix[9] = rotation.at&(1,2);
model_view_matrix[10] = rotation.at&(2,2);
model_view_matrix[11] = 0.0f;

model_view_matrix[12] = translation.at&(0, 0);
model_view_matrix[13] = translation.at&(1, 0);
model_view_matrix[14] = translation.at&(2, 0);
model_view_matrix[15] = 1.0f;
}

下一步是求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矩陣的形式為:

由於椎體不對稱,這時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&(0,0);
float f_y = camera_matrix.at&(1,1);

float c_x = camera_matrix.at&(0,2);
float c_y = camera_matrix.at&(1,2);

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& markers = m_recognizer.getMarkers();
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(圖片控制項)顯示?

TAG:增強現實AR | OpenCV | 機器視覺 |