OpenGLの基本的な使い方をメモします.特に,Computer Vision の分野でよく使う「デジタル画像座標系」で画像や点群を表示する実装例を載せていきます.
*準備
まずは,GLFWやGLUTなどのライブラリを利用して,ウィンドウを表示できるようにします.
[link:GLFW メモ]
以降,GLFWを利用した例で説明を進めます.
*2D編
**最初のサンプル
座標(0,0)から(1,1)に向かって直線を引きます.
下記,サンプルの // 描画 と書いてある部分に注目してください.
{#
#include "GLFW/glfw3.h"
int main() {
// ライブラリglfw の初期化
if (!glfwInit()) return -1;
// ウィンドウを作成
GLFWwindow* window = glfwCreateWindow(640, 480, "Hello World", NULL, NULL);
if (!window) {
glfwTerminate();
return -1;
}
// 作成したウィンドウを,OpenGLの描画関数のターゲットにする
glfwMakeContextCurrent(window);
// 描画のループ
while (!glfwWindowShouldClose(window)) {
// 画面を塗りつぶす
glClear(GL_COLOR_BUFFER_BIT);
// 描画
{
glColor3d(1.0, 1.0, 1.0);
glLineWidth(3.0f);
glBegin(GL_LINES);
glVertex2d(0.0, 0.0);
glVertex2d(1.0, 1.0);
glEnd();
}
// 上記描画した図形を表画面のバッファにスワップする
glfwSwapBuffers(window);
// 受け取ったイベント(キーボードやマウス入力)を処理する
glfwPollEvents();
}
glfwTerminate();
return 0;
}
#}
[img:kglh]
{{small:実行結果}}
OpenGLでは何も設定していないと画面の中心が原点(0, 0),右上が(1, 1),というような座標系になります.このままだと,デジタル画像座標系の数値を扱いにくいため設定を変えた方がよさそうです.
[img:ryt3]
{{small:デフォルトの座標系}}
**座標系の調整
座標系をデジタル画像座標系に近づけます.最初のサンプルの // 描画 の部分を下記のように変更します.
w = 300, h = 200 の画像サイズを想定して,その矩形に対角線を引いてみます.
{#
// 画像のサイズ(例)
const int w = 300;
const int h = 200;
GLint viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-0.5, viewport[2] - 0.5, viewport[3] - 0.5, -0.5, -1.0, 1.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glColor3d(1.0, 1.0, 1.0);
glLineWidth(3.0f);
glBegin(GL_LINES);
glVertex2d(0.0, 0.0);
glVertex2d(w, h);
glVertex2d(w, 0.0);
glVertex2d(0.0, h);
glEnd();
#}
[img:bfkg]
{{small:実行結果}}
glOrthoを使って画面の4点の座標を指定しています。
上記のプログラムでは,画面の左上の位置が(-0.5, -0.5),右下の位置が(viewport[2]-0.5, viewport[3]-0.5)と設定されます.
[img:lfng]
{{small:調整後の座標系}}
ここで,-0.5のオフセットには理由があります.例えば,ある画像があったとして,その左上のピクセルはデジタル画像座標系で(0, 0)と表記します.しかし,少しややこしいのですが,ピクセルには1という大きさがあるので,(0, 0)のピクセルの左上の角の位置は(-0.5, -0.5)となります.この半ピクセルの存在を考慮して,画像を余さず表示するには座標系の位置を-0.5分ずらして設定する必要があります.
**座標系の調整2
画面の中央に画像を表示するように座標系をずらします.あと,retinaディスプレイ(解像度2倍)のように特殊なディスプレイの場合,その倍率に合わせた変更が必要なのでついでにその実装も紹介します.
{#
const int w = 300;
const int h = 200;
GLint viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-0.5, viewport[2] - 0.5, viewport[3] - 0.5, -0.5, -1.0, 1.0);
double mat[4 * 4] = { 0 };
// 座標系を中央に寄せるための変換行列を設定
{
const double shiftx = (viewport[2] - 1) * 0.5 - (w - 1) * 0.5;
const double shifty = (viewport[3] - 1) * 0.5 - (h - 1) * 0.5;
mat[0 * 4 + 0] = 1.0;
mat[1 * 4 + 1] = 1.0;
mat[2 * 4 + 2] = 1.0;
mat[3 * 4 + 3] = 1.0;
mat[3 * 4 + 0] = shiftx;
mat[3 * 4 + 1] = shifty;
}
// retinaディスプレイ用の調整
{
int fw, fh;
glfwGetFramebufferSize(window, &fw, &fh);
int ww, wh;
glfwGetWindowSize(window, &ww, &wh);
const double pixScale = (static_cast<double>(fw) / ww + static_cast<double>(fh) / wh) / 2.0;
mat[0 * 4 + 0] /= pixScale;
mat[1 * 4 + 1] /= pixScale;
mat[3 * 4 + 0] /= pixScale;
mat[3 * 4 + 1] /= pixScale;
}
glMultMatrixd(mat);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glColor3d(1.0, 1.0, 1.0);
glLineWidth(3.0f);
glBegin(GL_LINES);
glVertex2d(0.0, 0.0);
glVertex2d(w, h);
glVertex2d(w, 0.0);
glVertex2d(0.0, h);
glEnd();
#}
[img:q1cw]
{{small:調整後の座標系}}
**画像を表示
画像表示用の関数を作ります.OpenGLで画像を表示させたい場合,平面にテクスチャを張り付けるような操作になります.
{#
void dispImg(const unsigned char *ptr, const int w, const int h, const int ch) {
int format;
switch (ch) {
case 1: format = GL_LUMINANCE; break;
case 3: format = GL_RGB; break;
default: return;
}
unsigned int texId;
{
glGenTextures(1, &texId);
glBindTexture(GL_TEXTURE_2D, texId);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, format, GL_UNSIGNED_BYTE, ptr);
}
glPushAttrib(GL_ENABLE_BIT);
glEnable(GL_TEXTURE_2D);
{
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
glBindTexture(GL_TEXTURE_2D, texId);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
//glShadeModel(GL_FLAT);
glColor3d(1.0, 1.0, 1.0);
glColorMask(1, 1, 1, 1);
glBegin(GL_QUADS);
glTexCoord2i(0, 0); glVertex2d(0 - 0.5, 0 - 0.5);
glTexCoord2i(0, 1); glVertex2d(0 - 0.5, h - 0.5);
glTexCoord2i(1, 1); glVertex2d(w - 0.5, h - 0.5);
glTexCoord2i(1, 0); glVertex2d(w - 0.5, 0 - 0.5);
glEnd();
glBindTexture(GL_TEXTURE_2D, 0);
}
glPopAttrib();
glDeleteTextures(1, &texId);
}
#}
この関数を使って,適当な画像を表示してみます.
下記のプログラムを使って、最初のプログラムの // 描画 の部分を書き換えてください。
{#
const int w = 300;
const int h = 200;
GLint viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-0.5, viewport[2] - 0.5, viewport[3] - 0.5, -0.5, -1.0, 1.0);
double mat[4 * 4] = { 0 };
{
const double shiftx = (viewport[2] - 1) * 0.5 - (w - 1) * 0.5;
const double shifty = (viewport[3] - 1) * 0.5 - (h - 1) * 0.5;
mat[0 * 4 + 0] = 1.0;
mat[1 * 4 + 1] = 1.0;
mat[2 * 4 + 2] = 1.0;
mat[3 * 4 + 3] = 1.0;
mat[3 * 4 + 0] = shiftx;
mat[3 * 4 + 1] = shifty;
}
// for retina display
{
int fw, fh;
glfwGetFramebufferSize(window, &fw, &fh);
int ww, wh;
glfwGetWindowSize(window, &ww, &wh);
const double pixScale = (static_cast<double>(fw) / ww + static_cast<double>(fh) / wh) / 2.0;
mat[0 * 4 + 0] /= pixScale;
mat[1 * 4 + 1] /= pixScale;
mat[3 * 4 + 0] /= pixScale;
mat[3 * 4 + 1] /= pixScale;
}
glMultMatrixd(mat);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
// 画像を表示
{
unsigned char *img = new unsigned char[w * h];
for (int v = 0; v < h; v++) {
for (int u = 0; u < w; u++) {
const double i = sqrt(u * u + v * v) * 0.05;
img[v * w + u] = static_cast<unsigned char>(255 * 0.5 * (::sin(i) + 1) + 0.5);
}
}
dispImg(img, w, h, 1);
delete[]img;
}
// 点を表示
{
glColor3d(0.0, 1.0, 0.0);
glPointSize(9.0f);
glBegin(GL_POINTS);
glVertex2d(0.0, 0.0);
glVertex2d(w - 1, h - 1);
glEnd();
}
#}
[img:7wf5]
{{small:実行結果}}
*3D編
次に,これまで説明した2次元画像の表示と整合するように,3次元点群を表示する手順を説明します.例えば,画像上から検出した3次元の点群を画像上に重畳表示するときに利用できると思います.
最初のプログラムの //描画 の部分を以下のように変更してください.
{#
const int w = 300;
const int h = 200;
const double fx = 1000.0;
const double fy = 1000.0;
const double cx = (w - 1) * 0.5;
const double cy = (h - 1) * 0.5;
const double nearPlane = 1.0;
const double farPlane = 1000.0;
double mat[4 * 4] = { 0 };
{
GLint viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport);
const double dx = (viewport[2] - 1) * 0.5 - ((w - 1) * 0.5 - cx);
const double dy = (viewport[3] - 1) * 0.5 - ((h - 1) * 0.5 - cy);
const double nx = nearPlane / fx;
const double ny = nearPlane / fy;
const double sw = (viewport[2] - 1);
const double sh = (viewport[3] - 1);
const double l = (-dx) * nx;
const double r = (-dx + sw) * nx;
const double t = (-dy) * ny;
const double b = (-dy + sh) * ny;
const double n = nearPlane;
const double f = farPlane;
mat[0 * 4 + 0] = 2 * n / (r - l);
mat[1 * 4 + 1] = 2 * n / (t - b);
mat[2 * 4 + 0] = -(r + l) / (r - l);
mat[2 * 4 + 1] = -(t + b) / (t - b);
mat[2 * 4 + 2] = +(f + n) / (f - n);
mat[2 * 4 + 3] = 1.0;
mat[3 * 4 + 2] = -2 * f * n / (f - n);
mat[3 * 4 + 3] = 1.0;
}
glMatrixMode(GL_PROJECTION);
glLoadMatrixd(mat);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
{
glPointSize(9.0f);
glBegin(GL_POINTS);
glColor3d(1.0, 1.0, 1.0);
glVertex3d(0.0, 0.0, 100.0);
glColor3d(1.0, 0.0, 0.0);
glVertex3d(10.0, 0.0, 100.0);
glColor3d(0.0, 1.0, 0.0);
glVertex3d(0.0, 10.0, 100.0);
glEnd();
}
#}
[img:yrbv]
{{small:実行結果}}
3次元の座標を設定して点を表示できました.2D画像上に点を表示することもできます.
3次元の点群を2次元に投影する場合,[link:カメラモデル]を考える必要があります.カメラモデルとは,3次元と画像上の2次元の関係を表すモデルで,カメラパラメータを使って計算式が定義されます.具体的にOpenGLで設定する場合,プロジェクション行列に,カメラパラメータ由来の数値を設定することになります.
>> ご意見・ご質問など お気軽にご連絡ください.info