【C++】OpenCVを使った画像クラスタリングの基本と実装方法
画像のクラスタリングはC++とOpenCVでシンプルに実装でき、cv::kmeans
でk平均法を使ってピクセルや特徴量をグループ化できます。
大規模データにはcv::flann::hierarchicalClustering
で階層型k平均を適用して処理を高速化し、セグメンテーションやカラー量子化に役立ちます。
画像クラスタリングアルゴリズムの種類
画像処理やデータ分析において、クラスタリングは非常に重要な役割を果たします。
特に、画像のセグメンテーションや色の量子化、特徴抽出などのタスクでは、適切なクラスタリング手法を選択することが結果の精度や効率に直結します。
ここでは、代表的な画像クラスタリングのアルゴリズムについて詳しく解説します。
k平均法
特徴と用途
k平均法は、最も広く使われているクラスタリング手法の一つです。
シンプルで計算コストが比較的低いため、多くの実用例に適しています。
基本的なアイデアは、データポイントをあらかじめ決めたクラスタ数kに分割し、各クラスタの中心点(セントロイド)を反復的に更新しながら最適なクラスタリングを行うことです。
この手法は、画像の色量子化や画像のセグメンテーションにおいてよく利用されます。
例えば、画像の色数を削減したい場合や、画像内の異なる領域を識別したい場合に有効です。
k平均法は、データの分布が球状であり、クラスタ間の距離が明確に分離されている場合に特に効果的です。
パラメータ解説
k平均法の主なパラメータは以下の通りです。
パラメータ | 内容 | 例・備考 |
---|---|---|
k | クラスタの数 | 画像の色数やセグメント数に応じて設定 |
criteria | 収束条件 | cv::TermCriteria で指定し、反復回数や誤差閾値を設定 |
attempts | 初期値の試行回数 | 複数回実行して最良結果を選択 |
flags | 初期クラスタ中心の決定方法 | cv::KMEANS_PP_CENTERS (推奨)やcv::KMEANS_RANDOM_CENTERS |
cv::TermCriteria
は、反復の停止条件を設定します。
例えば、最大反復回数やクラスタ中心の変化が一定以下になった時点で終了させることが可能です。
階層型k平均法
特徴と用途
階層型k平均法は、従来のk平均法の改良版ともいえる手法で、クラスタの階層構造を作りながらクラスタリングを行います。
OpenCVの標準機能としてはcv::flann::hierarchicalClustering
を利用しますが、これはk平均法の結果を階層的に整理し、クラスタの数を自動的に推定することができる点が特徴です。
この方法は、クラスタ数を事前に決めたくない場合や、データの階層的な構造を理解したい場合に適しています。
画像の階層的なセグメンテーションや、多段階の色抽出処理に利用されることがあります。
パラメータ解説
階層型クラスタリングのパラメータは、使用するライブラリや実装によって異なりますが、一般的な設定例は以下の通りです。
パラメータ | 内容 | 例・備考 |
---|---|---|
distanceType | 距離の種類 | cvflann::L2<float> (ユークリッド距離)など |
branching | 分岐の深さ | 例:10(階層の深さ) |
maxIterations | 最大反復回数 | 例:500 |
centersInit | 初期クラスタ中心の決定方法 | cvflann::FLANN_CENTERS_KMEANSPP など |
クラスタ数は、実行結果から自動的に推定され、結果として得られるクラスタの中心と数を確認できます。
その他のアルゴリズム
クラスタリングには、k平均法や階層型以外にもさまざまな手法があります。
例えば、
- DBSCAN(Density-Based Spatial Clustering of Applications with Noise)
密度に基づくクラスタリングで、ノイズや異常値に強く、非球状のクラスタも検出可能です。
画像の異常検知や、複雑な形状のセグメンテーションに適しています。
- Mean Shift
密度のピークを見つけることでクラスタを形成します。
クラスタ数を事前に決める必要がなく、画像の特徴点抽出や色のクラスタリングに利用されます。
- Spectral Clustering
グラフ理論に基づき、データの類似度行列からクラスタを抽出します。
複雑なデータ構造や非線形な分離に適しています。
これらのアルゴリズムは、OpenCVや他のライブラリを用いて実装可能であり、画像の性質や目的に応じて使い分けることが重要です。
OpenCVでのクラスタリング実装
データ準備
クラスタリングを行う前に、まず対象となるデータを適切なフォーマットに整える必要があります。
OpenCVでは、クラスタリングに使用するデータはcv::Mat
型の行列として表現されます。
データの次元や型に注意しながら準備を進めることが重要です。
入力フォーマット
クラスタリングに用いるデータは、一般的に次のようなフォーマットで用意します。
- 1次元データの場合
例:輝度値や色の成分など、単一の特徴量を持つデータ
cv::Mat data = (cv::Mat_<float>(n, 1) << val1, val2, ..., valn);
ここでn
はデータポイントの数です。
- 多次元データの場合
例:画像の各ピクセルのRGB値や、特徴量ベクトル
cv::Mat data = (cv::Mat_<float>(n, d) <<
feat1_x, feat1_y, feat1_z,
feat2_x, feat2_y, feat2_z,
...,
featn_x, featn_y, featn_z);
ここでd
は特徴量の次元数です。
データはfloat
型に変換しておくと、cv::kmeans
や他のクラスタリング関数での処理がスムーズに進みます。
cv::kmeansを使った実装
cv::kmeans
はOpenCVの標準関数で、非階層的クラスタリングを簡単に実装できます。
関数のシグネチャとパラメータを理解し、適切に設定することが成功の鍵です。
関数シグネチャとパラメータ
double cv::kmeans(
InputArray data, // 入力データ(cv::Mat)
int K, // クラスタ数
OutputArray bestLabels, // 各データポイントのクラスタラベル
TermCriteria criteria, // 収束条件
int attempts, // 試行回数(複数回実行し最良結果を選択)
int flags, // 初期クラスタ中心の決定方法
OutputArray centers // クラスタ中心
);
data
:クラスタリング対象のデータ。cv::Mat
型で、行がデータポイント、列が特徴量を表しますK
:クラスタの数。事前に決めておく必要がありますbestLabels
:各データポイントのクラスタラベルを格納するcv::Mat
criteria
:反復の停止条件。cv::TermCriteria
で設定attempts
:異なる初期値での試行回数。多いほど良い結果が得られる可能性が高いでしょうflags
:クラスタ中心の初期化方法。cv::KMEANS_PP_CENTERS
が推奨されますcenters
:クラスタの中心点を格納するcv::Mat
ラベルとセンターの取得
クラスタリングの結果は、bestLabels
とcenters
に格納されます。
bestLabels
:各データポイントが属するクラスタのインデックス(0からK-1までの整数)centers
:各クラスタの中心点の座標(特徴量の平均値)
これらを用いて、画像のセグメンテーションや色の置換、特徴の抽出などに応用します。
階層型クラスタリング(FLANN)
モジュールとクラス
OpenCVのflann
モジュールは、高速な近傍探索とクラスタリングをサポートします。
階層型クラスタリングにはcv::flann::hierarchicalClustering
関数を使用します。
cv::flann
:高速近傍探索のためのライブラリcv::flann::HierarchicalClusteringIndexParams
:クラスタリングのパラメータ設定に用いるクラス
実装手順
- データの準備
cv::Mat
形式でクラスタリング対象のデータを用意します。
- パラメータの設定
cv::flann::HierarchicalClusteringIndexParams
を用いて、クラスタリングの詳細設定を行います。
- クラスタリングの実行
cv::flann::hierarchicalClustering
関数を呼び出し、クラスタの中心とクラスタ数を取得します。
- 結果の取得と利用
推定されたクラスタ数と中心点をもとに、画像のセグメンテーションや特徴抽出を行います。
#include <opencv2/opencv.hpp>
#include <opencv2/flann/flann.hpp>
#include <iostream>
int main() {
// サンプルデータの作成
cv::Mat data = (cv::Mat_<float>(5, 1) << 100.0, 100.0, 80.0, 70.0, 50.0);
// クラスタリングのパラメータ設定
cv::flann::HierarchicalClusteringIndexParams params(10, 500, cv::flann::FLANN_CENTERS_KMEANSPP, 0.2);
// クラスタの中心とクラスタ数を格納する変数
cv::Mat centers;
int clusterCount = cv::flann::hierarchicalClustering<cv::flann::L2<float>>(data, centers, params);
// 結果の表示
std::cout << "推定されたクラスタ数: " << clusterCount << std::endl;
std::cout << "クラスタ中心:\n" << centers << std::endl;
return 0;
}
この例では、単純な1次元データに対して階層型クラスタリングを行い、クラスタ数と中心点を出力しています。
ピクセルベースの画像クラスタリング
ピクセルベースの画像クラスタリングは、画像の各ピクセルを特徴点として扱い、色や輝度などの情報をもとにクラスタリングを行う手法です。
この方法は、画像の色数削減やセグメンテーションにおいて非常に有効であり、画像処理の基本的な技術の一つです。
カラー量子化
カラー量子化は、画像の色数を減らすための代表的な手法です。
画像内の色の分布をクラスタリングし、代表色に置き換えることで、画像の情報を圧縮しつつ視覚的な情報を保持します。
前処理とカラー空間変換
カラー量子化を行う前に、画像の前処理とカラー空間の変換を行います。
これにより、クラスタリングの精度や効率を向上させることが可能です。
- 画像の読み込みとリサイズ
画像のサイズを調整し、処理負荷を軽減します。
cv::Mat img = cv::imread("sample.jpg");
cv::resize(img, img, cv::Size(256, 256));
- カラー空間の変換
RGB空間は人間の視覚に直感的ですが、クラスタリングにはLabやHSV空間の方が適している場合があります。
cv::Mat imgLab;
cv::cvtColor(img, imgLab, cv::COLOR_BGR2Lab);
- ピクセルデータの整形
画像のピクセルをクラスタリング用のデータに変換します。
cv::Mat data;
imgLab.reshape(1, img.rows * img.cols).convertTo(data, CV_32F);
クラスタリング適用例
次に、cv::kmeans
を用いて色のクラスタリングを行う例を示します。
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
// 画像の読み込みと前処理
cv::Mat img = cv::imread("sample.jpg");
cv::resize(img, img, cv::Size(256, 256));
cv::Mat imgLab;
cv::cvtColor(img, imgLab, cv::COLOR_BGR2Lab);
// ピクセルデータの整形
cv::Mat data = imgLab.reshape(1, img.rows * img.cols);
data.convertTo(data, CV_32F);
// k-meansクラスタリングの実行
int clusterCount = 4; // 色数の削減
cv::Mat labels, centers;
cv::kmeans(data, clusterCount, labels,
cv::TermCriteria(cv::TermCriteria::EPS + cv::TermCriteria::COUNT, 10, 1.0),
3, cv::KMEANS_PP_CENTERS, centers);
// クラスタ中心を画像に反映
cv::Mat newImage(img.size(), img.type());
for (int i = 0; i < img.rows * img.cols; ++i) {
int clusterIdx = labels.at<int>(i);
cv::Vec3f color = centers.at<cv::Vec3f>(clusterIdx);
newImage.at<cv::Vec3b>(i / img.cols, i % img.cols) = cv::Vec3b(color[0], color[1], color[2]);
}
// 色空間をBGRに戻す
cv::cvtColor(newImage, newImage, cv::COLOR_Lab2BGR);
// 結果の表示
cv::imshow("Color Quantized Image", newImage);
cv::waitKey(0);
return 0;
}


この例では、画像の色を4色に減らし、視覚的にわかりやすくしています。
セグメンテーション
画像のセグメンテーションは、画像内の異なる領域を識別し、分離する技術です。
ピクセルクラスタリングを用いることで、色や輝度の類似性に基づいて画像を分割できます。
マスク作成と可視化
クラスタリング結果をもとに、特定のクラスタに属するピクセルだけを抽出し、マスク画像を作成します。
// 特定のクラスタのピクセルを抽出
int targetCluster = 2; // 例:クラスタ番号2
cv::Mat mask = cv::Mat::zeros(img.size(), CV_8UC1);
for (int i = 0; i < labels.rows; ++i) {
if (labels.at<int>(i) == targetCluster) {
int row = i / img.cols;
int col = i % img.cols;
mask.at<uchar>(row, col) = 255;
}
}
// マスクを使った画像の抽出
cv::Mat segmented;
img.copyTo(segmented, mask);
// 結果の表示
cv::imshow("Segmented Region", segmented);
cv::waitKey(0);
この方法により、特定の色や領域を強調した画像を作成できます。
応用シナリオ
ピクセルクラスタリングは、多くの応用シナリオに適用可能です。
- 画像圧縮:色数を制限し、ファイルサイズを削減
- 画像編集:特定の色域を抽出・操作
- 物体検出:色や輝度のクラスタリングを用いた物体の抽出
- 医用画像処理:組織や異常部位の識別
これらの応用例は、クラスタリングの結果をもとにした画像の解析や処理の基盤となります。
適切なクラスタ数やパラメータ設定により、さまざまな画像処理タスクに対応可能です。
特徴量ベースのクラスタリング
画像の内容を理解し、分類やセグメンテーションを行うためには、ピクセルの色や輝度だけでなく、画像内の局所的な特徴を抽出することが重要です。
特徴抽出手法を用いることで、画像の構造やパターンを数値化し、より高度なクラスタリングを実現します。
特徴抽出手法
SIFT/SURF/ORBの選択
局所特徴点の抽出には、SIFT(Scale-Invariant Feature Transform)、SURF(Speeded-Up Robust Features)、ORB(Oriented FAST and Rotated BRIEF)といった代表的なアルゴリズムがあります。
- SIFT
スケールと回転に不変な特徴点を抽出し、画像のスケールや角度に対してロバストです。
高い識別能力を持ちますが、特許の関係で商用利用には制約があります。
#include <opencv2/xfeatures2d.hpp>
auto detector = cv::xfeatures2d::SIFT::create();
- SURF
SIFTと同様にスケールと回転に不変で、計算速度も比較的速いです。
ただし、こちらも特許の関係で制約があります。
auto detector = cv::xfeatures2d::SURF::create();
- ORB
特許の制約がなく、計算コストも低いため、リアルタイム処理やモバイル端末に適しています。
BRIEFの特徴記述子を用いて高速に特徴点を抽出します。
auto detector = cv::ORB::create();
これらの特徴抽出手法は、画像の局所的なパターンを捉えるのに適しており、クラスタリングの入力として用いることで、画像の内容をより詳細に分類できます。
Bag‑of‑Wordsモデル
クラスタリングとの連携
Bag‑of‑Words(BoW)モデルは、画像の局所特徴点の記述子をクラスタリングし、各画像を「単語」の出現頻度ベクトルに変換する手法です。
これにより、画像間の類似度計算や分類が容易になります。
具体的な流れは以下の通りです。
- 特徴点の抽出
SIFT、SURF、ORBなどを用いて、画像から局所特徴点と記述子を抽出します。
- クラスタリングによる辞書の作成
すべての画像から抽出した記述子を集めて、k-meansなどのクラスタリングを行います。
クラスタの中心点を「辞書の単語」とします。
- 画像の表現
各画像の特徴記述子を、最も近いクラスタ中心に割り当て、その出現頻度をカウントします。
これにより、画像は固定長のベクトル(ヒストグラム)に変換されます。
- 類似度計算とクラスタリング
画像のヒストグラムを用いて、コサイン類似度やユークリッド距離などで比較し、クラスタリングや分類を行います。
// 例:特徴記述子のクラスタリングとBoWの構築
#include <opencv2/opencv.hpp>
#include <vector>
int main() {
// 画像から特徴点と記述子を抽出
cv::Ptr<cv::ORB> detector = cv::ORB::create();
std::vector<cv::KeyPoint> keypoints;
cv::Mat descriptors;
cv::Mat image = cv::imread("sample.jpg");
detector->detectAndCompute(image, cv::noArray(), keypoints, descriptors);
// すべての画像から得た記述子を集めてクラスタリング
// 例:k-meansを用いて辞書作成
int dictionarySize = 50; // 単語数
cv::Mat labels, centers;
cv::kmeans(descriptors, dictionarySize, labels,
cv::TermCriteria(cv::TermCriteria::EPS + cv::TermCriteria::COUNT, 10, 1.0),
3, cv::KMEANS_PP_CENTERS, centers);
// 画像のヒストグラム作成
cv::Mat bowHistogram = cv::Mat::zeros(1, dictionarySize, CV_32F);
for (int i = 0; i < descriptors.rows; ++i) {
int clusterIdx = labels.at<int>(i);
bowHistogram.at<float>(0, clusterIdx) += 1.0f;
}
// 正規化
cv::normalize(bowHistogram, bowHistogram, 1, 0, cv::NORM_L1);
// これを複数画像に対して繰り返し、画像間の類似度計算やクラスタリングに利用
return 0;
}
このように、特徴抽出とクラスタリングを組み合わせることで、画像の内容を高次元の特徴空間にマッピングし、より精度の高い分類や検索を実現します。
BoWモデルは、画像認識やコンテンツベースの画像検索システムにおいて広く利用されています。
パフォーマンス最適化
クラスタリングの処理は計算負荷が高く、大規模な画像データや高次元の特徴量を扱う場合には、処理速度やメモリ使用量の最適化が重要となります。
ここでは、データ型の選択、メモリ管理、並列処理、GPUの活用、そしてパラメータの調整について詳しく解説します。
データ型とメモリ管理
適切なデータ型の選択は、メモリ使用量の削減と計算速度の向上に直結します。
例えば、画像のピクセル値や特徴記述子をfloat
型(32ビット浮動小数点)で扱うと、メモリ消費が大きくなります。
一方、CV_16U
やCV_8U
といった低ビット深度の型を用いることで、メモリ負荷を軽減できます。
- 例:
特徴記述子の値が0から255の範囲に収まる場合はCV_8U
を選択し、メモリを節約します。
ただし、計算精度やクラスタリングの結果に影響を与えるため、適切な型を選ぶ必要があります。
- メモリ管理のポイント
- 不要なコピーを避けるために、
cv::Mat
のrefcount
を理解し、必要に応じてclone()
やcopyTo()
を使い分けます - 大規模データはストリーミング処理やバッチ処理を行い、一度にメモリに載せるデータ量を制御します
- 不要なコピーを避けるために、
並列処理とGPU活用
クラスタリングの計算は、並列処理やGPUを活用することで大幅に高速化できます。
- OpenCVの並列化
OpenCVは内部でOpenMPやTBB(Threading Building Blocks)を利用しており、多くの関数は自動的に並列化されます。
cv::parallel_for_
を用いて自作のループ処理も並列化可能です。
- GPUの利用
CUDAやOpenCLを用いたGPUアクセラレーションは、特に大規模なデータや高次元の特徴量に対して効果的です。
OpenCVのcv::cuda
モジュールは、画像処理や特徴抽出、クラスタリングの一部処理をGPU上で高速に行うことができます。
#include <opencv2/cudaimgproc.hpp>
#include <opencv2/cudaarithm.hpp>
cv::cuda::GpuMat gpuData;
gpuData.upload(data); // CPUからGPUへデータ転送
// 例:GPU上でのクラスタリング処理(実装例はライブラリに依存)
- 注意点
GPUを利用する場合は、データの転送コストやGPUメモリの容量を考慮し、最適なバッチサイズや処理フローを設計します。
パラメータチューニング
クラスタリングの結果は、パラメータ設定に大きく依存します。
最適なパラメータを見つけるためには、以下のポイントに注意します。
- クラスタ数(k)
適切なクラスタ数は、エルボー法やシルエット分析などの評価指標を用いて決定します。
過剰なクラスタ数は過学習や計算負荷増加を招き、少なすぎると情報の損失につながります。
- 反復回数と収束条件
cv::TermCriteria
の設定を調整し、早すぎず遅すぎず適切な停止条件を選びます。
反復回数を増やすと精度は向上しますが、計算時間も増加します。
- 初期値の設定
cv::KMEANS_PP_CENTERS
を用いると、より良い初期値を自動的に選び、結果の安定性と収束速度が向上します。
- クラスタリングの評価
クラスタリングの結果を定量的に評価し、パラメータの調整を繰り返すことが重要です。
シルエット係数やDavies-Bouldin指数などの評価指標を活用します。
cv::TermCriteria criteria(cv::TermCriteria::EPS + cv::TermCriteria::COUNT, 50, 0.01);
int attempts = 5; // 複数回試行して最良結果を選択
cv::kmeans(data, k, labels, criteria, attempts, cv::KMEANS_PP_CENTERS, centers);
これらの最適化手法を組み合わせることで、クラスタリングの速度と精度を向上させ、より効率的な画像処理を実現します。
トラブルシューティング
クラスタリングを実施する際には、さまざまな問題に直面することがあります。
ここでは、収束しない場合の対処法、適切なクラスタ数の選び方、そしてアウトライア(外れ値)への対応策について詳しく解説します。
収束しない場合の対処
クラスタリングのアルゴリズムが収束しない、または非常に遅い場合には、いくつかの原因と対策があります。
- 原因
- 初期クラスタ中心の選択が悪い
- データのスケールや分布が偏っている
- パラメータ設定(例:反復回数や収束閾値)が適切でない
- データにノイズや外れ値が多い
- 対策
- 初期値の工夫:
cv::KMEANS_PP_CENTERS
を使用して、より良い初期中心を自動選択させます。複数回試行attempts
を増やす - データの正規化:各特徴量を標準化(平均0、分散1)や正規化(0〜1の範囲)により、スケールの偏りを解消します
- パラメータの調整:
cv::TermCriteria
の反復回数や閾値を緩める - ノイズ除去:外れ値やノイズを除去したり、前処理で平滑化を行います
- 初期値の工夫:
- 例:正規化と反復回数の増加
// 正規化例
cv::normalize(data, data, 1, 0, cv::NORM_MINMAX);
// 反復回数増加
cv::TermCriteria criteria(cv::TermCriteria::EPS + cv::TermCriteria::COUNT, 100, 0.001);
cv::kmeans(data, k, labels, criteria, attempts, cv::KMEANS_PP_CENTERS, centers);
クラスタ数選定のコツ
適切なクラスタ数を選ぶことは、クラスタリングの成功にとって非常に重要です。
以下の方法やポイントを参考にします。
- エルボー法(Elbow Method)
クラスタ数を変化させながら、クラスタ内誤差の総和(SSE)を計算し、誤差の減少が鈍くなる点(肘の部分)を見つける。
// SSEの計算例(擬似コード)
for (int k = 1; k <= maxK; ++k) {
// k-means実行
// SSEを計算
// 結果を保存
}
- シルエット分析
各データポイントのクラスタ内の一貫性とクラスタ間の分離度を評価し、最も高い平均シルエット係数を示すクラスタ数を選択。
- ドメイン知識の活用
画像の内容や目的に応じて、経験的に適切なクラスタ数を設定します。
- 複数の指標を併用
複数の評価指標を比較し、総合的に判断します。
アウトライアへの対応
外れ値やノイズは、クラスタリングの結果に悪影響を及ぼすことがあります。
適切な対策を講じることで、より安定した結果を得られます。
- 原因
- センサーの誤差や画像のノイズ
- 異常な特徴値や極端な値
- 対策
- 前処理での除去:閾値を設けて極端な値を除去します。例:平均±3標準偏差を超える値を除外
- ロバストクラスタリング:DBSCANやMean Shiftのような密度に基づく手法は、外れ値に対してロバストです
- 重み付け:外れ値に低い重みを設定し、クラスタリングに影響を与えにくくします
- 例:標準偏差を用いた外れ値除去
// 1次元データの例
cv::Scalar mean, stddev;
cv::meanStdDev(data, mean, stddev);
double lowerBound = mean[0] - 3 * stddev[0];
double upperBound = mean[0] + 3 * stddev[0];
cv::Mat filteredData;
for (int i = 0; i < data.rows; ++i) {
float val = data.at<float>(i);
if (val >= lowerBound && val <= upperBound) {
filteredData.push_back(data.row(i));
}
}
これらの対策を適用することで、クラスタリングの安定性と精度を向上させ、外れ値の影響を最小限に抑えることが可能です。
まとめ
この記事では、画像クラスタリングの基本手法や実装方法、最適化のポイント、トラブル時の対処法について詳しく解説しました。
k-meansや階層型クラスタリングの仕組みや、特徴量抽出、パフォーマンス向上の工夫、問題解決のコツを理解することで、効率的かつ正確な画像解析が可能になります。
これらの知識を活用し、実際の画像処理や分析に役立ててください。