OpenCV (Open Source Computer Vision)は、主にリアルタイムコンピュータビジョンを対象とするプログラミング関数ライブラリで、ロシアのニジニ・ノヴゴロドにあるIntelの研究センターによって開発されました。このライブラリはクロスプラットフォームで、主にリアルタイム画像処理に重点を置いています。
OpenCVは世界中で広く使用・採用されています。エンドユーザーにとっては成熟した強力なライブラリであり、開発者にとっては適切に実装・設計されたライブラリです。OpenCVの開発者は、コードを理解・保守しやすくする基本原則に従っています。
OpenCVの設計上の選択をいくつか見ていきましょう:
モジュール性
1. ライブラリベースのアーキテクチャ
ライブラリベースのアーキテクチャにより、提供される機能を他のプロジェクトでより簡単かつ柔軟に再利用・統合できます。さらに、ライブラリベースのアーキテクチャは、クリーンなAPIと関心の分離を促進し、開発者が全体像のごく一部に集中すれば済むため、コードを理解しやすくします。
OpenCVは、それぞれが特定の責務を持ち、すべてがopencv_coreライブラリを使用する複数のライブラリを定義することで、このアプローチを採用しています。

2. 名前空間によるモジュール化
OpenCVは、コードベースをモジュール化するために名前空間を多用しています。たとえば、opencv_coreプロジェクトの名前空間は次のとおりです:

OpenCVは「機能ごとの名前空間」アプローチを採用しています。機能ごとの名前空間では、機能セットを反映するように名前空間を使用します。単一の機能に関連するすべての項目(その機能に関連する項目だけ)を1つの名前空間に配置します。これにより、高い凝集性と高いモジュール性を持ち、名前空間間の結合が最小限の名前空間が得られます。密接に連携する項目は、互いに近くに配置されます。
OpenCVでは、主に3つの理由で名前空間が使用されています:
- ライブラリをモジュール化するため。
- 「cv::detail」名前空間のように、実装の詳細を隠すため。このアプローチにより、この名前空間内の型が内部使用を意図したものであり、直接使用すべきではないことがライブラリ利用者に明確になります。C#では「internal」キーワードがその役割を果たしますが、C++には公開型をライブラリ利用者から隠す方法がありません。
- 匿名名前空間:名前のない名前空間。グローバルな静的変数を必要としません。作成した匿名名前空間は、それを作成したファイル内でのみアクセスできます。
データモデルをPOD型として定義する
すべてのプロジェクトにはデータモデルがあり、このデータは POD 型として定義することが推奨されます。
OpenCVコードベースで、メソッドを持たずフィールドだけを持つ構造体を検索してみましょう。そのために CQLinq を使用してコードベースをクエリします。

このクエリの結果は、OpenCVプロジェクトで定義されている型の25%を網羅しています。OpenCVは、データモデルのほぼすべてをフィールドだけを持つ構造体として定義しています。
多重継承を避ける
多重継承は設計を複雑にし、デバッグを困難にする可能性があります。そのため、多くのC++エキスパートはそれを避けることを推奨しています。
OpenCVコードベースで、複数の具象基底クラスを継承しているクラスを検索してみましょう。

テストプロジェクトのごく少数のクラスだけが多重継承を使用しています。OpenCVコードベース全体では、この概念が避けられています。
複雑な関数を定義しない
複雑な関数を検出するには、多くの指標を使用できます。NBLinesOfCode、パラメータ数、ローカル変数の数は、最も基本的なものです。
複雑な関数を検出するための、他の興味深い指標もあります:
- 循環的複雑度は、手続き内で行われ得る決定の数を反映する、一般的な手続き型ソフトウェア指標です。
- Nesting Depthは、メソッド本体内の入れ子になったスコープの最大深度を表すメソッドレベルの指標です。
- Max Nested Loopsは、関数内のループの最大入れ子レベルです。
これらの指標で許容される最大値は、主にチームの選択によって決まります。標準的な値はありません。
OpenCVコードベースで、複雑と見なせるメソッドを検索してみましょう。

複雑さを減らすためにリファクタリングの候補となるのは1%だけです。
結合
アプリケーションのある領域での変更が、アプリケーション全体で必要となる変更を少なくするため、低結合は望ましい特性です。長期的には、アプリケーションの修正や新機能追加に伴う時間、労力、コストを大幅に節約できる可能性があります。
低結合は、抽象クラスを使用することで実現できます。抽象クラスを使用する主な利点は次の3つです:
- 抽象クラスは、再利用性を促進する契約を定義する方法を提供します。オブジェクトが抽象クラスを実装する場合、そのオブジェクトは標準に従わなければなりません。別のオブジェクトを使用するオブジェクトはコンシューマーと呼ばれます。抽象クラスは、オブジェクトとそのコンシューマー間の契約です。
- 抽象クラスは、プログラムを理解しやすくする抽象化レベルも提供します。抽象クラスにより、開発者は実装の詳細に踏み込まなくても、コードの一般的な振る舞いについて考えることができます。
- 抽象クラスはコンポーネント間の低結合を強制します。これにより、抽象クラスを実装するクラスで実装が変更されても、抽象クラスのコンシューマーを容易に保護できます。
OpenCVで定義されているすべての抽象クラスを検索してみましょう:

主な目的が低結合の強制である場合、抽象クラスの使用目的を損なう可能性のある、抽象クラス使用時のよくある誤りがあります。それは、抽象クラスではなく具象クラスを使用することです。この問題を説明するために、次の例を考えてみましょう:
クラスAは、calculate()メソッドを含む抽象クラスIAを実装しています。コンシューマークラスCは次のように実装されています:
public class C
{
….
public:
void calculate()
{
…..
m_a->calculate();
….
}
A* m_a;
};クラスCは、抽象クラスIAを参照する代わりにクラスAを参照しています。この場合、低結合の利点が失われます。この実装には2つの大きな欠点があります:
- IAの別の実装を使用することにした場合、クラスCのコードを変更しなければなりません。
- IAに存在しないメソッドがAへ追加され、Cがそれらを使用する場合、インターフェースを使用する契約上の利点も失われます。
C#では、IAのメソッドが具象クラスへの参照からは呼び出されず、インターフェースへの参照からのみ呼び出されることを保証するために、 明示的インターフェース実装 という機能が言語に導入されました。この技法は、開発者がインターフェースを使用する利点を失わないようにするうえで非常に有用です。
凝集性
単一責任の原則は、クラスに変更する理由が複数あってはならないと述べています。そのようなクラスは凝集性が高いと言われます。一般に、LCOM値が高いことは凝集性の低いクラスを示します。LCOMには複数の指標があります。LCOMは[0-1]の範囲の値を取り、LCOM HS(HSはHenderson-Sellersの略)は[0-2]の範囲の値を取ります。LCOM HS値が1を超える場合は、注意すべき状態と考えるべきです。LCOM指標の計算方法は次のとおりです:
LCOM = 1 – (sum(MF)/M*F) LCOM HS = (M – sum(MF)/F)(M-1)
ここで:
- Mはクラス内のメソッド数です(静的メソッドとインスタンスメソッドの両方が数えられ、コンストラクタ、プロパティのゲッター/セッター、イベントのadd/removeメソッドも含まれます)。
- Fはクラス内のインスタンスフィールドの数です。
- MFは、特定のインスタンスフィールドへアクセスするクラスのメソッド数です。
- sum(MF)は、クラスのすべてのインスタンスフィールドにわたるMFの合計です。
これらの式の背景にある基本的な考え方は、次のように述べられます。クラスのすべてのメソッドがすべてのインスタンスフィールドを使用する場合、そのクラスは完全に凝集しています。つまりsum(MF)=M*Fとなり、LCOM = 0、LCOMHS = 0になります。
LCOMHS値が1を超える場合は、注意すべき状態と考えるべきです。

凝集性の低い型はごく少数です。
結論
OpenCVのソースコードを見ると、その実装のシンプルさに驚くでしょう。高度な設計概念や過剰なエンジニアリングはなく、いくつかの基本原則が一貫して適用されているだけです。
