読者です 読者をやめる 読者になる 読者になる

OpenCV3.0alpha 変更点レビュー ~ラッパー開発者の視点から~

OpenCvSharpを3.0alphaに対応させる取り組みを始めました。なりふり構わず書き換え、まずはエラーが止まった段階のがこれです。

Release 3.0alpha (08 Sep., 2014) · shimat/opencvsharp · GitHub

2.0のときもひどい目にあいましたが、今回も何度くじけそうになったことやら。いや、現在進行形でくじけていますが。この道中で気づいたことを雑多に書き残します。新しいコンピュータビジョンのアルゴリズムがどうしたこうした、という話は一切ありません。それどころじゃない...

以下だいたいネガティブなので、あらかじめ弁明しておきますが、C++から触る分にはよくできたAPIになってきています。ラッパーのことなど考える必要はありません、このまま突き進んでいってください。

C APIの構造体がC++で汚染された

初っ端から、ラッパー開発者以外には全くどうでもいい話。

C++ APIのstruct, classは、2.x時代は旧来Cのデータ型との相互利用が考慮されていました。CvSizeとcv::Sizeは相互に暗黙の変換が可能、などです。

3.0では、C++のクラスからはCの痕跡が全て消えました。代わりに、Cの構造体の方にそれが移されました。

typedef struct CvSize
{
    int width;
    int height;

// NEW!
#ifdef __cplusplus
    CvSize(int w = 0, int h = 0): width(w), height(h) {}
    template<typename _Tp>
    CvSize(const cv::Size_<_Tp>& sz): width(cv::saturate_cast<int>(sz.width)), height(cv::saturate_cast<int>(sz.height)) {}
    template<typename _Tp>
    operator cv::Size_<_Tp>() const { return cv::Size_<_Tp>(cv::saturate_cast<_Tp>(width), cv::saturate_cast<_Tp>(height)); }
#endif
}
CvSize;

ですので、C++から触っている分にはほぼ全く使い心地は変わりません。うまくやったなと思いますが、大いなる欠点が。ラッパー開発の根幹、ファクトリ関数での外出しにおける、Cリンケージ指定で窮します。

extern "C"
{
    __declspec(dllexport) CvSize __cdecl Hogehoge(CvRect rect);
}

このコードは、以下の警告を発します。なお警告が出るのは返却値だけですが、引数の方もC++データ型を値渡しするのは危険で、たまに落ちます。

警告 1 warning C4190: 'Hogehoge' は C リンケージ指定ですが、C と互換性のないユーザー定義の型 'CvSize' を返しています。

解決するには、以下いずれかになります。

  • ●昔のCvSizeとそっくりの純粋Cのstructを自前で用意して、それに変換してから返す
  • ●structのフィールドを1つ1つ、out引数をたくさん用意して返す

InputOutputArray が専用のクラスに

2.x時代は、InputOutputArray は OutputArray のtypedefの別名でした。3.0では、それ単独のクラスになっています。

ところで、InputArrayとかOutputArrayなどは、そのような名前のクラスがあるわけではなく、参照付きの別名です。実態はアンダースコア付きの_OutputArray。

typedef const _OutputArray& OutputArray;

またCリンケージ指定の話になります。C形式で公開する関数では参照は使えませんので、ポインタでこのように指定するしかありません。

extern "C" void Fuga(cv::OutputArray ptr);   // error

extern "C" void Fuga(cv::_OutputArray *ptr); // OK

そして、2.xでは InputOutputArray = OutputArray でしたので、InputOutputArray を取るところは _OutputArray* としてラップしていましたわけです。

おわかりですね。3.0でそれが全て死にました。


文字列は cv::String に

std::string を取っていたところが全て cv::String というクラスに代わりました。

なぜわざわざ車輪の再発明をしたのか。おそらくSTLが無い環境への対応をにらんだのでしょうか。

使い心地は結構よくできていて、C++から触る分には意識すらしないレベルかもしれません。ラッパー開発者にとっては、std::string 前提で書いていた処理の多くが死んだので、悪態の1つや2つはお許し願いたいところ。

Cは明示的にinclude

それでも私はCを使い続ける、その強い意志をもって書きましょう。以下のように明示的に書かないとincludeされなくなっています。

#include <opencv2/calib3d/calib3d_c.h>
#include <opencv2/core/core_c.h>
#include <opencv2/highgui/highgui_c.h>
#include <opencv2/imgproc/imgproc_c.h>
#include <opencv2/photo/photo_c.h>

一億総Algorithm化

色々なクラスが cv::Algorithm を継承するようになってきております。

一言二言では説明できないのですが、こいつは大変ラップしづらいです。AlgorithmInfoの仕組みとか、文字列から具象クラスを作る仕組みなど、鬼門しかありません。

一億総Ptr化

Algorithmと合わせて。こいつも何かと厄介です。

ちょっと話がそれますが、以下リンクにある話が、最近ようやくなんとなくわかりかけてきた気がします。cv::Ptrのリソース管理の仕組みと、C#で書くことになるラッパーの仕組みが、ごちゃごちゃになるのです。
Rubyist Magazine - Rubyist Hotlinks 【第 1 回】 まつもとゆきひろさん

まつもと

オブジェクト指向言語オブジェクト指向言語を実装するというのに、脳がついていけないんだけど。それって、僕だけ? (笑) C++ で言語作ってる人がいるけど、よくやるなぁ、とか思う。ベース言語のクラスとか継承とかと、実装言語のクラスや継承がごっちゃになて、訳わかんなくなっちゃう。設計して、どういう風に作ろうかなと思った時点で、もう、わかんなくなって「もういい、やめた」って。

ML刷新

ここは純粋に評価。opencv_ml がすっきりしました。

2.4.xあたりでは、EMだけは先んじて新しくなっていたはずですが、ほかのクラスも同様につくりかえられています。CvSVM -> cv::ml::SVM の要領で、頭のCvを取って、見てみましょう。

さりげなく cv::vector が消えた

あれはなんだったんですかね...

WImage がまだ生き残っている

1.1preの頃にちょっと注目を集めた、(当時としては)高度な画像クラス WImage。まだcoreの中にいます。直後に2.0のMatが登場し、あまり日の目を見なかったと思うのですが、しぶとい。

モジュール増え過ぎ

2.xの時も多すぎと思いましたが、甘かった。以下、contrib込みですが。

f:id:Schima:20140908214118p:plain

いろいろ事情は理解しますが、私はかねてより批判的です。OpenCV.dll 的なの1個に固まっているほうが喜ぶユーザが9割のはず。



まだまだある気がしますが、忘れてしまいました。この辺にしておきます。

このほかにも順当にさまざまな仕様変更があり、大変苦労しています。