ページ

2019年8月23日金曜日

GLSLでGPUを活用するROSノードを作ってみました


TL;DR

  • OpenGL + GLSLでsensor_msg::Imageの画像をGPUで処理できるROSノードを作りました (GitHubレポジトリ)
  • EGL + OpenGLで実装しているため、X serverが起動していない環境でも(多分)動作します
  • OpenGLは勉強してて色々限界を感じたので、Vulkanが使える環境ならVulkanを使ったほうが良いかもしれません

はじめに


最近、NVIDIAのJetsonシリーズを始めとしたGPUが強い組み込みデバイスが登場してきました。Project C.G.S.ではJetson TX2をドローンに搭載して自律飛行にチャレンジしているのですが、Jetson TX2にはせっかく強いGPUが載っているのにもかかわらず、全く使いこなせていませんでした。

GPUを活用しようとした場合、CUDAやOpenCL、OpenCVのGPU実装を使う手もありますが、なるべく移植性の高い方法で手軽に書けた方が幸せになれそうです。GLSLなら移植性が高いですし、画像を扱う分には手軽で、CG分野の知見を活かせそうだったので、ROS環境でOpenGL + GLSLを使ってGPUによる処理をしてみることにしました。

※本当はVulkanが使いたかったのですが、手持ちのノートPCのCPUが古く(Broadwell; Core i5-5300U)、搭載しているIntel HD Graphics 5500はVulkanに対応してなかったので、ノートPCでも開発するために仕方なくOpenGLにしました
※この直後の世代のSkylakeから、Intel HD GraphicsがVulkan対応になってます、惜しい!

Direct Xのほうは9と11を触ったことがあったのですが、OpenGLはほとんど素人で、全く知らないところから手探りで進めてます。そのあたりも含めて、OpenGLについて全然知らないところから学んだ軌跡についても、ここに残しておきたいと思います。なるべく正確な記述を心がけていますが、情報が間違っている箇所があるかもしれません。もし何かありましたら、コメントで指摘していただけると幸いです。


オフスクリーンレンダリングについて


まず、今回ROSでOpenGLを使う上で、一般的なCG分野でのOpenGLの使用とは大きく違う点が一つあります。普通、OpenGLを使う際には何かしらレンダリングした結果を画面に表示すると思うのですが、今回ROSでOpenGLを使うにあたって、入出力ともROSのメッセージを使用するため、画面へのレンダリングは行いません。これをオフスクリーンレンダリングと呼ぶようです。

※Windows環境でオフスクリーンレンダリングされている方の記事 : ウインドウを作らずにOpenGLでレンダリングする方法

Ubuntu上でどのようにレンダリングが行われているか理解するのに、WikipediaのLinux Graphics Stackの図が非常にわかりやすくまとまっていました。



※Attribution: Shmuel Csaba Otto Traian; This file is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported license.

図中の色分けにはちゃんと意味があって、重要な登場人物は


  • 緑 : libGL ... OpenGL系APIの実装、GPUベンダやMesaが提供するライブラリ、プロプライエタリなものは直接カーネルを叩くものもあるが、Mesaが提供するものは命令をハードウェア固有のAPI呼び出しに変換してlibDRMに渡す
  • 紫 : Display Server / Window Manager ... OSのウィンドウを管理する仕組み、X Window Systemが最も普及している仕様で、Xorg / libX がそれぞれ、サーバー / クライアントのリファレンス実装


の2人です。Ubuntu上で画面にOpenGLでレンダリングをしようとした場合、ウィンドウ(=ディスプレイの領域)を管理している紫の人たちにお伺いを立ててから、そのウィンドウに紐付いたコンテキストを使って緑の人たちを初期化する必要がありました。
※それによって、GPUはレンダリングしようとしているウィンドウの上に別のウィンドウが重なっている場合に、そこ部分のレンダリングを行わないということが可能になります(Pixel Ownership Test)

上の図を見ながらThe Linux Graphics Stack (翻訳してくれた方のブログ) のブログ記事を読むと歴史の流れがよくわかります。当初、ウィンドウに紐付いたOpenGLの初期化を行う方法は、プラットフォーム固有のAPIでウィンドウを作成した後にWGL/GLX APIというAPIを使う必要がありました。WGLがWindows向けのAPIで、GLXがLinux向けのAPIです。このあたりをラップして簡単にクロスプラットフォームでOpenGLの初期化ができるようにしたのがGLUTやGLFWです。

名前から想像できる通り、GLX APIはX Window Systemに依存したAPIなのですが、モバイルデバイスがGPUを搭載するようになって問題が出てきました。当然モバイルデバイスではX Window Systemは動作していないで、OpenGLの初期化をする上でGLX APIは使用できません。そこで、OpenGL ESの登場とともに生まれたのがEGLというAPIです。EGLが作られたことで、紫の部分とは全く関係なく、独立して緑の部分の初期化を行うことができるようになりました。

EGLは当初OpenGL ESしかサポートしていなかったのですが、EGL 1.4からOpenGL (デスクトップ用のフル機能)をサポートするようになりました。よく勘違いされがちなのですが、EGLとはOpenGL ESに紐付いたものではなく、OpenGLコンテキストを作成するための独立したAPIセットです。現在EGLは、WGL/GLXを置き換えるクロスプラットフォームのAPIとなっています。

普通にOpenGLアプリケーションを作ろうとすると、GLUTやGLFWを使用するのが簡単だと思います。GLUTやGLFWは、ウィンドウの作成(紫の部分)とOpenGL Contextの作成(緑の部分)をセットで行い、簡単にクロスプラットフォームのデスクトップアプリケーションを作る思想で作られています。GLUTのglutCreateWindowやGLFWのglfwCreateWindowでは、Display Serverでウィンドウが作成された後、それに紐付いたOpenGL Contextがセットで作られます。そのため、OpenGLを使用するためにはDisplay Server(Xorg等)が動作している必要がありますし、アプリケーションはlibX等をリンクしなくてはいけませんし、アプリケーションはOpenGLの機能を使う前にWindowを作成しないといけません。

一方、EGLを直接使用することで、Display Serverとは独立してOpenGL Contextを作る事が可能です。今回作成するROSノードは組み込みデバイス上で動作することを想定していますので、ディスプレイは繋がっていないかもしれませんし、X Window Serverも動作していないかもしれません。そのため、EGLを使用したOpenGLの初期化を行う必要があります。


EGLを使ったコンテキストの作成


EGLに関することは公式ドキュメントに詳細が書かれていますが、ここでも簡単にまとめておきたいと思います。


EGL Introduction

https://www.khronos.org/registry/EGL/sdk/docs/man/html/eglIntro.xhtml


EGLが提供しているのは、従来GLXなどのプラットフォーム依存APIで提供されていた、OpenGLの初期化部分の処理になります。オフスクリーンレンダリングに必要なのは、EGLを使ってディスプレイハンドルの取得(GPUの指定)をした後に、パラメータを指定して
OpenGLコンテキストを作成するところのみです。GLXのAPIと手順が近いですが、ウィンドウを作成しなくてよいのでシンプルですね。


//Get default display handle
auto display = eglGetDisplay(EGL_DEFAULT_DISPLAY);

//Bind OpenGL API to use full desktop function other than OpenGLES
eglBindAPI(EGL_OPENGL_API),

//Create default configuration
constexpr std::array<EGLint, 1> configAttributes = {
    EGL_NONE,
};

EGLConfig config;
EGLint numConfig;
eglChooseConfig(display, configAttributes.data(), &config, 1, &numConfig);

//Specifying OpenGL Core 4.5; this should be match glad loader profile.
const std::array<EGLint, 7> contextAttributes = {
    EGL_CONTEXT_MAJOR_VERSION, 4,
    EGL_CONTEXT_MINOR_VERSION, 5,
    EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_CORE_PROFILE_BIT,
    EGL_NONE,
};

auto context = eglCreateContext(display_.get(), config, EGL_NO_CONTEXT, contextAttributes.data());


ここではデフォルトディスプレイを使用していますが、GPUが複数搭載されている環境では、明示的にディスプレイIDを指定する必要があるかもしれません。このあたりはDirectXでも同様の仕組みがあるので、比較的とっつきやすいかもしれません。

次に、作成したEGLコンテキストを使ってOpenGLコンテキストの作成(OpenGLの初期化)を行います。OpenGLコンテキストはOpenGLの全ての内部状態を保持するインスタンスです。
プロセス内に複数作ることも可能ですが、スレッドに対して1つしか持つことができません。スレッドローカルにCurrentなContextを設定できて、すべてのOpenGL APIはCurrentContextに作用します。


OpenGL Context
https://www.khronos.org/opengl/wiki/OpenGL_Context


今回はOpenGLESではなくデスクトップ版のフル機能のOpenGLを使うので、eglBindAPIでOpenGLを指定してからコンテキストを作成する必要があります。また、コンテキスト作成時のAttributesには、使用するOpenGLのバージョンを指定しておきます。ここで指定するバージョンは、後述するOpenGLローダーで指定するバージョンと一致している必要があります。

OpenGLのコンテキストには、Core Profile / Compatibility Profileという2つのContextが存在します。Compatibilityはすべての機能を実装しますが、CoreはObsoleteな機能を実装しません。自分で一から実装を行う範囲ではCore Profileで十分だと思うので、これもAttributesに設定しておきます。

実装にあたっては、こちらのブログ記事(Kabuku; GCPのGPUインスタンスでレンダリングを高速化)も参考にさせていただきました。また、この方の別の記事にあったのですが、NVIDIAの最新ドライバをEGL使用する場合には、libGLではなく、libOpenGLをリンクする必要があるようです。NVIDIAのGPUをお使いの方でうまく行かない場合には、試してみてください。

NVIDIA公式も、EGL + OpenGL without an X serverという記事を公開しています。libOpenGLをリンクする必要がある話についても、ここに記載されていました。


OpenGL APIのエントリポイントをロード


Contextさえ作ってしまえば、普通にgl〜の関数を利用してレンダリングすることが可能になります。ところが、残念ながらOpenGL 1.1以降のCore APIと拡張は、エントリポイントがデフォルトで定義されていないので、Contextからポインタを取得しないと使用することができません。EGLでContextを作ったならeglGetProcAddress関数でひとつずつ必要なAPIのエントリポイントを取得することもできるのですが、サードパーティのOpenGL Loading Libraryを使うと便利である、とKhronosがアナウンスしています


OpenGL Loading Library
https://www.khronos.org/opengl/wiki/OpenGL_Loading_Library


正直この辺はサードパーティに頼るのではなくてちゃんと手順を整えて欲しいとは思うのですが、どうなんでしょう…
※現状、紹介されている多くのライブラリがメンテナンスの手が足りず、更新されなくなってしまっています

最も広く使われているのはGLEWだと思うのですが、Ubuntu 16.04にaptで入るバージョンだとEGLに対応していないのと、直近2年ほど更新が止まっているので、代わりにGladを使用することにしました。Gladは継続的に更新されており、GitHubのスターも多く、GLFWのチュートリアルでも使われています。

Gladでは、このページのWeb UIを使ってOpenGL APIのバージョンとプロファイル、使用する拡張機能を指定して、ヘッダファイルとコードを自動生成することができるようになっています。

今回は、Ubuntu16.04 にaptで入る最新のlibglx1-mesa-glxが対応するOpenGL Core 4.5を選択しました。指定するバージョンはコンテキストを作成した際のAttributeのバージョンと合わせておきます。また、他にも必要な拡張機能があれば追加します。
※ハードウェアがサポートする拡張機能はglxinfoで調べることが可能です

生成されたGladのOpenGLローダーコードが提供しているgladLoadGLLoaderは、プラットフォーム依存のOpenGLエントリポイント取得関数を外部から渡すことが可能になっています。EGLでコンテキストを作成した場合には、eglGetProcAddress関数のポインタをgladLoadGLLoaderに渡せば後はよろしくやってくれます。


gladLoadGLLoader(reinterpret_cast<GLADloadproc>(eglGetProcAddress))


OpenGLの拡張機能を使用する場合には一つ注意点があって、OpenGLでは同一の機能がVendor/EXT/ARB/Coreで提供されているので、まず機能がCoreに無いか探す -> 次にARB、EXT、Vendorの順で提供されている機能を探す必要があります。これは、OpenGLに新しい機能が実装される場合、まずVendorが独自拡張で機能を実験的に実装し、それがARBに認められてから、最終的にCoreに統合されるといったステップを踏んでいるためです。


今回のROSノードの処理の流れ


ここまでで、OpenGLの初期化処理まで実装することができました。ここからは、OpenGLのAPIを使ってシェーダ、頂点バッファ、テクスチャ、サンプラ、フレームバッファとレンダリングに必要なパイプラインのセットアップを行います。

処理の基本的な流れは、

  1. 頂点バッファ、シェーダ、FBOをセットアップ
  2. ROSノードに届いたsensor_msgs::Imageをcv_bridgeでOpenCV画像に変換
  3. 画像データを入力テクスチャとしてGPUに転送
  4. シェーダを実行、FBO経由でテクスチャにレンダリング結果を書き込み
  5. レンダリング結果が書き込まれたテクスチャをCPUに転送
  6. 画像データをsensor_msgs::Imageとして出力

と言った流れになります。

以降の記事では、この図のOpenGL APIが絡むそれぞれの要素について、コード例を交えながら説明したいと思います。詳細な実装については、GitHubレポジトリのSimpleRenderer.cppと、cgs::egl名前空間cgs::gl名前空間のそれぞれの機能のラッパーの実装を参照してください。


-----


頂点バッファ


頂点バッファとインデックスバッファはそれぞれ、cgs::gl::VertexBuffercgs::gl::ElementBufferのかたちにまとめてみました。基本的にはglCreateBuffersしてからglNamedBufferDataでデータを転送しているだけです。この2つをcgs::gl::VertexArrayでシェーダと紐付けて使用します。


//Prepare simple vertex type
struct Vertex
{
    float x, y, z;
};

//Prepare verticies and indicies for a quad that covers the whole screen
static constexpr std::array VERTICIES = { 
    -1.f, -1.f, 0.0f,
     1.f, -1.f, 0.0f,
     1.f,  1.f, 0.0f,
    -1.f,  1.f, 0.0f,
};
static constexpr std::array INDICIES = { 
    0, 1, 2,  
    2, 3, 0,
};

cgs::gl::VertexBuffer vbo(ERTICIES, GL_STATIC_DRAW); 
cgs::gl::ElementBuffer  ebo(INDICIES, GL_STATIC_DRAW); 
cgs::gl::FrameBuffer          fbo;

vao.mapVariable(vbo, glGetAttribLocation(program.get(), "position"), 3, GL_FLOAT, 0);
vao.mapVariable(ebo);


OpenGLの頂点バッファの扱いについては、こちらの記事(算譜記録帳; OpenGLでの頂点データの扱いの変化)が非常に参考になりました。ただ、ブログ中のコードではDSAではない古いAPIが使われているので、実装の際にはglCreateBuffers/glNamedBufferDataを使用するようにしたほうが良いと思います。より詳細な情報に関しては、公式ドキュメントを参照してください。

Vertex Specification


シェーダ


シェーダのコードも同様にcgs::gl::Shadercgs::gl::Programにまとめています。cgs::gl::Shaderで個々のシェーダをファイルから読み込みコンパイルし、cgs::gl::Programに指定してリンクします。


std::array shaders({
    cgs::gl::Shader(GL_VERTEX_SHADER,   "vertex_shader.glsl"),
    cgs::gl::Shader(GL_FRAGMENT_SHADER, "fragment_shader.glsl"),
}); 
std::gl::program program(shaders); 

//Uniform parameters  setup
program.use();
glUniform2f(glGetUniformLocation(program_.get(), "resolution"), width_, height_);
glUniform1i(glGetUniformLocation(program_.get(), "texture"), 0);


より詳細な情報に関しては、公式ドキュメントを参照してください。



テクスチャ


テクスチャは2Dのみをcgs::gl::Texture2Dとしてまとめています。これもglCreateTexturesして、glTextureStorage2Dしているだけですね… レンダリングパイプラインの初期化では、シェーダに対して設定する入力テクスチャと、レンダリング結果を保存する出力テクスチャの2つを同じサイズで作成しています。


cgs::gl::Texture2D textureIn(GL_SRGB8, width_, height_);  //
cgs::gl::Texture2D textureOut(GL_SRGB8, width_, height_); //Assuming sRGB input and output


ここでポイントになるのは、テクスチャのinternalFormatにSRGBを指定している点になります。カメラ画像などを含む一般的な画像のRGB画素値には、輝度に対して線形の値ではなく、一般に1/2.2乗のガンマ補正を適用した値(SRGB値)が格納されています。ところが、GLSLを始めとするシェーダー内の処理ではRGB画素値が線形である前提で書かれることが多いため、どこかでSRGB値から線形RGB値に変換する必要があります。OpenGLはこれを簡単に行う仕組みが用意されており、テクスチャのフォーマットにSRGBを指定することで、テクスチャから読み出す際にSRGB -> 線形RGB、テクスチャに書き込む際に線形RGB -> SRGBへの変換を自動で行ってくれます。

現在のROSノードでは画像の入出力はSRGB色空間で行われることを前提にしていますが、Bayerパターンのカメラを扱う場合等では線形RGBを直接扱うこともあると思いますし、2.2以外のガンマ補正が書けられた画像を扱う場合もあると思います。そういった場合にはテクスチャのフォーマットはRGBとして、GLSL内で適切な補正を行う必要があります。

入力テクスチャは毎フレームレンダリング開始前にCPUからデータを転送し、出力テクスチャはcgs::gl::FrameBufferにセットして、レンダーターゲットに設定、レンダリング結果をCPUに転送します。


//Write input image to the texture
textureIn_.write(GL_BGR, GL_UNSIGNED_BYTE, src.data);

//Perform rendering here...

//Read result
textureOut_.read(GL_BGR, GL_UNSIGNED_BYTE, dest.data, dest.rows * dest.cols * dest.channels());


CPU/GPU間のテクスチャの受け渡しにはPixel Transferを使うと速いという回答をよく見かけますが、Pixel TransferはCPU/GPU間のデータ転送を非同期で行い、レンダリングパイプラインを待機させずにテクスチャデータを書き換える用途に使われます。

Pixel Transfer
https://www.khronos.org/opengl/wiki/Pixel_Transfer

今回のオフラインレンダリングでは毎フレームGPUを動かす前にテクスチャの転送を完了させ、描画結果をCPUに戻してPublishする必要があるので、Pixel Transferによる非同期転送の恩恵を受けることができません。そのため、今回の実装ではPixel Transferは使用せず、glTextureSubImage2DとglGetTextureImageで転送を行いました。


GPU/CPUの実行速度の比較


ここまでで、OpenGLを使ったレンダリングパイプラインの初期化があらかたできました。あとは頂点バッファ、シェーダ、フレームバッファをバインドして、レンダリング関数を呼びだせばレンダリングができます。


//Bind OpenGL Objects
textureIn_.bindToUnit(0);
sampler_.bindToUnit(0);
fbo_.bind();
vao_.bind();

//Perform rendering
glViewport(0, 0, width_, height_);
glDrawElements(GL_TRIANGLES, INDICIES.size(), GL_UNSIGNED_INT, nullptr);
glFinish();


次に、実際に実行して速度を比較してみたいと思います。比較はUSBカメラから取得した画像をimage_transportで転送し、CIELab色空間に変換してから線形SVMで特定の色を分離する、という処理で行ってみました。CIELab色空間を線形カーネルで分離するのが適切かは置いておいて(一応、赤色であればそれっぽく分離は可能です)、GPU版はGLSL(Fragment Shader)でCPU版はOpenCVで実装を行いました。

該当処理にかかった時間をnsで出力しながら計測し、記録されたログの中間50フレームの平均をとったものがこちらです。計測はIntel Core i5 5300U 2.3GHz (Intel HD Graphics 5500)で行いました。
※GPU版の方はGPUへのテクスチャ転送と、CPUへの書き戻し時間も含めた時間で計測しています


解像度CPUGPU
320x2405.85 ms1.25 ms
640x48021.8 ms4.30 ms
1280x72060.0 ms12.3 ms


低い解像度ではGPU/CPU間の転送でGPUが不利になるかと思ったのですが、CPU/GPUとも解像度に応じてリニアに処理時間が増えているようです。全体としてGPUの方が5倍程度早い結果となりましたが、CPUの方のコードは残念な感じの実装なので、この程度の処理の場合は工夫すればCPUでも十分なパフォーマンスが出せるかもしれないです。

以降の記事では、OpenGLの実装に関する雑多な情報を(恨み混じりで)補足します。


-----


OpenGLについて


DirectXと違って、OpenGLのAPIは同じ名前空間に新しいAPIと互換性のための古いAPIが混在している上に、拡張APIも多くの種類があるので、ぱっと見どうすればよいのか非常に分かりにくいです。新しいAPIを使えば非常に簡単にできる場合でも、互換性のために残されている古いAPIを使って面倒な実装をしてしまうこともありますし、特に警告が出るわけでも無いのでそれに気づくことも難しいです。また、インターネット上にある情報は古いものが多く、参考にしているページが古いAPIを使っている場合も多いです。

Khronosが公開している CommonMistakes のWikiページには、インターネット上にはびこる古いコードの問題と、新しいAPIでそれを代替する情報が含まれているので、OpenGLを使った開発を始める前には是非読んでおいた方が良いと思います。


Common Mistakes
https://www.khronos.org/opengl/wiki/Common_Mistakes


ハマりがちな落とし穴として個人的に気になったのが


  • テクスチャに適切なパラメータを一括で設定するために、glTextureImage2Dではなくて、glTextureStore2Dでテクスチャを作成してからglTextureSubImage2Dでデータを転送する
  • API呼び出し毎にglGetErrorを呼び出すのをやめて、glDebugMessageCallbackを設定して全てのエラーを補足する
  • internalFormatはテクスチャのフォーマットを決めるだけで実際にGPUがどんなメモリ配置でデータを格納するかは実装依存で、internalFormatが合っていても転送時に変換が起きる可能性がある
  • パラメータを変更する際にglBind系APIを使うとグローバルな状態(何がBindされているか)が変化してしまうので、Direct State Access(DSA)を使う


でした。特にDirect State Accessは非常に重要で、パラメータ設定時に毎回glBind〜系のAPIを呼び出す苦悩から開放されます。


Direct State Access
https://www.khronos.org/opengl/wiki/Direct_State_Access


OpenGL 4.5でDirect State Accessの導入に伴って、OpenGL Objectに対するDSA版のAPIが新たに一式追加されたのですが、DSAではない元のAPIとの名前の対応が本当に意味不明で辛いです…また、glGen〜系呼び出すOpenGL Objectの場合、DSAを使用するためにはglCreate〜系のAPIを呼び出す必要がある点にも注意が必要です。

Texture、Framebuffer、Bufferオブジェクトに対するDSAのやり方は、こちらのページ(A Guide to Modern OpenGL Functions)が本当に参考になりました。また、同ページには他にも新しいOpenGL APIを使った実装についての記述がたくさん含まれているので、是非読んでいただくと良いと思います。


OpenGLとOpenCVとの相互運用について


もちろんOpenCVにもCUDAやOpenCLのモジュールがあって、GPUを活用した実装が可能になっています。必要な機能が既にそこに実装されているのであれば、まずはそれを使うのが良いと思います。しかし、いくつかの処理を組み合わせたり、一部CPUで処理を行おうとすると、GPGPUではどうしてもCPU/GPU間のコピーがボトルネックになりがちです。一方、OpenGL + GLSLを使ってシェーダーで処理を書けば、レンダリングパイプラインをつかった処理を比較的簡単に柔軟に記述することができます。

また、OpenCVのcv::ogl名前空間には、OpenGLとの相互利用のためのクラスが提供されていますが、ROSのOpenCVではオプションが有効になっていなかったのと、ドキュメントにGTKとQtバックエンドのみ対応と書かれていたので、今回の用途には使えなさそうでした。


OpenCVのCIELab色空間変換とガンマ補正について


今回、GLSL内でRGBからCIELab色空間への変換を実装する際に、OpenCVのドキュメントを参考にして実装しました。ドキュメントには線形RGB -> XYZ -> CIELabと変換する際の式が書いてあり、GLSL内にこれを実装することで、GPUを使ったCIELab色空間への変換が可能になります。入力テクスチャをSRGBフォーマットに指定しておけば、シェーダー実行前に自動的にSRGB -> RGBの変換は自動的に行われるので、GLSLで実装する場合にはドキュメントの式をそのまま実装して問題ありません。

ところが、1点注意しなくてはいけない落とし穴があります… 実はOpenCVのcvtColorにcv::COLOR_BGR2Labを設定して変換した場合、このドキュメント通りの変換は行われません。cv::COLOR_BGR2Labは入力をSRGBとして解釈し、ガンマ補正を戻して線形RGBに変換してからドキュメントの式を適用するためです。

リリースノートによるとどうやらこのあたりの挙動はOpenCV 2.2のリリース時に変化したらしく、以前はcv::COLOR_BGR2Labはちゃんとドキュメント通りの変換を行っていたのですが、現在は入力をSRGBとして解釈しているようです。従来通りの線形RGBに対する変換は、cv::COLOR_LBGR2Labを使えと書いてあります。


用語まとめメモ


X Window System ... ウィンドウ管理システムの仕様
X11 ... X Window Systemで使われるプロトコル
libX ... X Window Systemのクライアントのリファレンス実装
libXCB ... libXの低レイヤーが分離されたもの
Xorg ... X Window Systemのサーバーのリファレンス実装
DIX ... Driver Independent X、Xorgのグラフィクスサブシステム
DDX ... Driver Dependent X、Xorgのグラフィクスサブシステム
fglrx ... FireGL and Radeon for X、AMDのプロプライエタリなlibGL実装
OpenGLES ... OpenGL Embedded System、OpenGLの組み込み向けプロファイル
GLX/WGL ... X Window System / Windows向けのOpenGL拡張API、libGL.soに含まれる、プラットフォーム固有のウィンドウシステムで作成したフレームバッファを使用するために作られたAPI
AIGLX ... Accelerated Indirect GLX、X11を通してOpenGLを使うAPI?
EGL ... GLX/WGLを置き換えるプラットフォーム非依存のOpenGL Context構築用API、間違えられやすいがGLX/WGLのような特定プラットフォームに依存した拡張ではないし、GLUT/GLFWのようなそれらの上にあるライブラリでもない

0 件のコメント:

コメントを投稿