OpenCV 輪郭関連操作 C++

参照
参照

概要の基本的な考え方

OpenCV では、cv::findContours()関数を使用してグレースケール画像内の輪郭を見つけることができます。

関数プロトタイプ

void findContours( InputArray image, OutputArrayOfArrays contours,
                              OutputArray hierarchy, int mode,
                              int method, Point offset = Point());

void findContours( InputArray image, OutputArrayOfArrays contours,
                              int mode, int method, Point offset = Point());

パラメータimageは、入力グレースケール画像 (背景が黒の場合、オブジェクトは白であることに注意してください)、検出contoursされた輪郭 (各輪郭は点セットで表されます)、そのタイプは通常 です、各輪郭std::vector<std::vector<Point> >hierarchy対応する属性 (トポロジ情報など)、輪郭の検索モード、輪郭を見つけるときmodemethod使用される近似アルゴリズム (輪郭cv::CHAIN_APPROX_NONE上のすべての点を保存しcv::CHAIN_APPROX_SIMPLEます。輪郭上の変曲点のみを保存します)。たとえば、長方形には 4 つの頂点のみが保存され、cv::CHAIN_APPROX_TC89_L1cv::CHAIN_APPROX_TC89_KCOSteh-Chinl チェーン」近似アルゴリズムが使用されます。

検索モードの種類:
元の画像は、ここに画像の説明を挿入
cv::RETR_EXTERNAL子の輪郭を除くすべての外側の輪郭のみを取得します。
その結果、
cv::RETR_LIST親子関係を作成せずにすべての輪郭が取得されます。
結果は次のとおりです。ここに画像の説明を挿入
cv::RETR_CCOMPすべての輪郭を取得し、外側の輪郭をすべてレベル 1、子輪郭をすべてレベル 2 として、2 レベルの階層に配置します。
その結果、ここに画像の説明を挿入
cv::RETR_TREEすべての輪郭を取得しても、親、子、孫などの完全な階層リストは作成されません。
効果は次のとおりです。ここに画像の説明を挿入
一般的に使用される iscv::RETR_EXTERNALおよびcv::RETR_TREEオプションです。

輪郭関連の操作

概要
cv::drawContours()関数を使用して輪郭を描くことができます。
関数プロトタイプ:

void drawContours( InputOutputArray image, InputArrayOfArrays contours,
                              int contourIdx, const Scalar& color,
                              int thickness = 1, int lineType = LINE_8,
                              InputArray hierarchy = noArray(),
                              int maxLevel = INT_MAX, Point offset = Point() );

例:imagecontourscv::findContours()contourIdxcolorthickness-1lineTypehierarchymaxLeveloffset

#include <iostream>
#include <vector>

#include <opencv2\opencv.hpp>

int main() {
    
    
	cv::Mat src = cv::imread("1_0.png", -1);

	std::vector<std::vector<cv::Point>> contours;
	std::vector<cv::Vec4i> hierarchy;
	cv::findContours(src, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE);  //只找最外层轮廓

	cv::Mat dst;
	cv::cvtColor(src, dst, cv::COLOR_GRAY2BGR);

	for (int i = 0; i < contours.size(); ++i) {
    
      //绘制所有轮廓
		cv::drawContours(dst, contours, i, cv::Scalar(0, 255, 0), -1);  //thickness为-1时为填充整个轮廓
	}

	cv::imshow("dst", dst);
	cv::waitKey();

	cv::destroyAllWindows();
	return 0;
}

効果は次のとおりです。
輪郭の長さと面積を計算します
cv::contourArea()輪郭の面積はによって取得でき、輪郭cv::arcLength()の長さは によって取得できます。
関数プロトタイプ:

double contourArea( InputArray contour, bool oriented = false );
double arcLength( InputArray curve, bool closed );

contour単一の輪郭から構成される点集合を示し、oriented領域の方向性を表します。true は方向性があることを意味し、false は方向性がないことを意味します。
curve単一の輪郭または任意の 2 次元の点セットで構成される点セットを示します。closed点セットが閉じているかどうかを示し、この値は見つかった輪郭に対して true である必要があります。

例:

#include <iostream>
#include <vector>

#include <opencv2\opencv.hpp>

int main() {
    
    
	cv::Mat src = cv::imread("1_0.png", -1);

	std::vector<std::vector<cv::Point>> contours;
	std::vector<cv::Vec4i> hierarchy;
	cv::findContours(src, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE);  //只找最外层轮廓

	for (int i = 0; i < contours.size(); ++i) {
    
      //遍历所有轮廓
		printf("第%d个轮廓的面积为%lf 长度为%lf\n", i,
			cv::contourArea(contours[i]), cv::arcLength(contours[i], true));
	}

	return 0;
}

输出:0个轮廓的面积为31110.000000 长度为706.0000001个轮廓的面积为32569.000000 长度为767.910811

輪郭の最小外接長方形を取得します。
cv::minAreaRect()輪郭の最小外接長方形は次のようにして取得できます。
関数プロトタイプ:

RotatedRect minAreaRect( InputArray points );

戻り値は四角形 (四角形には四隅の情報と x 軸に対する回転角度の情報が含まれます) で、パラメーターはpoints単一の輪郭です。

例:

#include <iostream>
#include <vector>

#include <opencv2\opencv.hpp>

int main() {
    
    
	cv::Mat src = cv::imread("1_0.png", -1);

	std::vector<std::vector<cv::Point>> contours;
	std::vector<cv::Vec4i> hierarchy;
	cv::findContours(src, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE);  //只找最外层轮廓

	cv::Mat dst;  //用于绘制结果
	cv::cvtColor(src, dst, cv::COLOR_GRAY2BGR);

	std::vector<cv::RotatedRect> minAreaRects(contours.size());  //存储轮廓的最小外接矩形

	cv::Point2f ps[4];  //外接矩形四个端点的集合
	for (int i = 0; i < contours.size(); ++i) {
    
      //遍历所有轮廓
		minAreaRects[i] = cv::minAreaRect(contours[i]);  //获取轮廓的最小外接矩形
		minAreaRects[i].points(ps);  //将最小外接矩形的四个端点复制给ps数组

		for (int j = 0; j < 4; j++) {
    
      //绘制最小外接轮廓的四条边
			line(dst, cv::Point(ps[j]), cv::Point(ps[(j + 1) % 4]), cv::Scalar(0, 255, 0), 2);
			cv::putText(dst, std::to_string(j), ps[j], 0, 1, cv::Scalar(0, 0, 255), 2);  //将点集按顺序显示在图上
		}

		printf("minAreaRects[%d]的旋转角度为%f\n", i, minAreaRects[i].angle);  //输出旋转角度
	}

	cv::imshow("dst", dst);
	cv::waitKey();

	cv::destroyAllWindows();
	return 0;
}

输出:
minAreaRects[0]的旋转角度为90.000000
minAreaRects[1]的旋转角度为52.678967

効果は次のとおりです。ここに画像の説明を挿入
輪郭の最小の外接長方形を取得します
一般的な画像操作は正方形の画像に対して行う必要があるため、一般に輪郭の最小の外接長方形が必要となります。輪郭の最小の外接長方形は
次のようにして取得できます。関数プロトタイプ:cv::boundingRect()

Rect boundingRect( InputArray array );

戻り値は四角形で、引数はarray単一の輪郭です。

例:

#include <iostream>
#include <vector>

#include <opencv2\opencv.hpp>

int main() {
    
    
	cv::Mat src = cv::imread("1_0.png", -1);

	std::vector<std::vector<cv::Point>> contours;
	std::vector<cv::Vec4i> hierarchy;
	cv::findContours(src, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE);  //只找最外层轮廓

	cv::Mat dst;  //用于绘制结果
	cv::cvtColor(src, dst, cv::COLOR_GRAY2BGR);

	std::vector<cv::RotatedRect> minAreaRects(contours.size());  //存储轮廓的最小外接矩形
	std::vector<cv::Rect> boundingRect(contours.size());  //存储轮廓的最小外接矩形

	cv::Point2f ps[4];  //外接矩形四个端点的集合
	for (int i = 0; i < contours.size(); ++i) {
    
      //遍历所有轮廓
		minAreaRects[i] = cv::minAreaRect(contours[i]);  //获取轮廓的最小外接矩形
		minAreaRects[i].points(ps);  //将最小外接矩形的四个端点复制给rect数组

		for (int j = 0; j < 4; j++) {
    
      //绘制最小外接轮廓的四条边
			line(dst, cv::Point(ps[j]), cv::Point(ps[(j + 1) % 4]), cv::Scalar(0, 255, 0), 8);
		}

		boundingRect[i] = cv::boundingRect(contours[i]);  //获取轮廓的最小外接长方形
		cv::rectangle(dst, boundingRect[i], cv::Scalar(255, 0, 0), 2);  //绘制最小外接长方形
	}

	cv::imshow("dst", dst);
	cv::waitKey();

	cv::destroyAllWindows();
	return 0;
}

効果は次のとおりです。ここに画像の説明を挿入
輪郭の画像瞬間を取得する
輪郭モーメントは、画像の特定のピクセル グレー レベルの加重平均、または同様の機能や意味を持つ画像の属性を指します。面積 (または全体の明るさ) を含む画像の部分的なプロパティと、その幾何学的中心と方向に関する情報は、その瞬間を通じて取得できます。これは、特定の変換 (平行移動、スケーリング、回転の不変性) に関する不変性を取得するために使用できます。
輪郭の面積と幾何学的中心は、輪郭モーメントを通じて取得できます。
バイナリ画像エリアまたは、グレースケール画像のピクセルの合計。M 00 \mathrm{M}_{00}として表現できます。M00
グラフィック幾何学的な中心{ x ‾ , y ‾ } = { M 10 M 00 , M 01 M 00 } \ {\overline{\mathrm{x}}, \overline{\mathrm{y}}\}=\left\{\frac{\mathrm{M}_{10}}{\mathrm{M}_{00}}\right\}{ バツy}={ M00M10M00M01}

cv::moments()輪郭の輪郭モーメントは、によって求めることができます。
関数プロトタイプ:

Moments moments( InputArray array, bool binaryImage = false );

array輪郭点セットであるか任意の 2 次元点セットであるか、binaryImage画像がバイナリ イメージであるかにかかわらず、バイナリ イメージとは 0 または 255 の 2 つのグレースケール値のみを持つ画像を指します。

例:

#include <iostream>
#include <vector>

#include <opencv2\opencv.hpp>

int main() {
    
    
	cv::Mat src = cv::imread("1_0.png", -1);

	std::vector<std::vector<cv::Point>> contours;
	std::vector<cv::Vec4i> hierarchy;
	cv::findContours(src, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE);  //只找最外层轮廓

	cv::Mat dst;  //用于绘制结果
	cv::cvtColor(src, dst, cv::COLOR_GRAY2BGR);

	std::vector<cv::Moments> mv(contours.size());  //存储所以轮廓的轮廓矩
	for (int i = 0; i < contours.size(); ++i) {
    
      //遍历所有轮廓
		mv[i] = cv::moments(contours[i]);  //求轮廓矩
		cv::circle(dst, cv::Point(mv[i].m10 / mv[i].m00, mv[i].m01 / mv[i].m00), 5, cv::Scalar(0, 255, 0), -1);  //绘制轮廓几何中心
		printf("第%d个轮廓的面积为%lf   %lf\n", i, mv[i].m00, cv::contourArea(contours[i]));  //计算轮廓的面积,可知cv::contourArea()其实就是通过轮廓矩获得的
	}

	cv::imshow("dst", dst);
	cv::waitKey();

	cv::destroyAllWindows();
	return 0;
}

输出:0个轮廓的面积为31110.000000   31110.0000001个轮廓的面积为32569.000000   32569.000000

効果は次のとおりです。ここに画像の説明を挿入
輪郭の最小外接円を取得します
cv::minEnclosingCircle()輪郭の最小外接長方形は次のようにして取得できます。
関数プロトタイプ:

void minEnclosingCircle( InputArray points,
                                      CV_OUT Point2f& center, CV_OUT float& radius );

pointsこれは入力輪郭点セットまたは任意の 2 次元点セットであり、center返された円の中心であり、radius返された円の半径です。

例:

#include <iostream>
#include <vector>

#include <opencv2\opencv.hpp>

int main() {
    
    
	cv::Mat src = cv::imread("1_0.png", -1);

	std::vector<std::vector<cv::Point>> contours;
	std::vector<cv::Vec4i> hierarchy;
	cv::findContours(src, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE);  //只找最外层轮廓

	cv::Mat dst;  //用于绘制结果
	cv::cvtColor(src, dst, cv::COLOR_GRAY2BGR);

	for (int i = 0; i < contours.size(); ++i) {
    
      //遍历所有轮廓
		cv::Point2f center;
		float radius = 0.0f;
		cv::minEnclosingCircle(contours[i], center, radius);  //获取轮廓的最小外接圆

		cv::circle(dst, center, radius, cv::Scalar(0, 0, 255), 2);
	}

	cv::imshow("dst", dst);
	cv::waitKey();

	cv::destroyAllWindows();
	return 0;
}

効果は次のとおりです。ここに画像の説明を挿入
輪郭の凸包を取得します
輪郭の凸包は次のようにして取得できますcv::convexHull()
凸包は、当てはめられた最小外接多角形です。
効果は次のとおりです。
関数プロトタイプ:

void convexHull( InputArray points, OutputArray hull,
                              bool clockwise = false, bool returnPoints = true );

pointsつまり、入力 2 次元点セットです。hullこれは出力 2 次元点セットです。clockwise輪郭が時計回りかどうかを決定し、true の場合は時計回りです。returnPointsフラグは、true の場合はhull点セットを返し、この時点ではstd::vector<cv::Point>タイプであり、false の場合はhullシェル ポイントに対応する輪郭点のインデックスを返します。

例:

#include <iostream>
#include <vector>

#include <opencv2\opencv.hpp>

int main() {
    
    
	cv::Mat src = cv::imread("1_0.png", -1);

	std::vector<std::vector<cv::Point>> contours;
	std::vector<cv::Vec4i> hierarchy;
	cv::findContours(src, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE);  //只找最外层轮廓

	cv::Mat dst;  //用于绘制结果
	cv::cvtColor(src, dst, cv::COLOR_GRAY2BGR);

	std::vector<std::vector<cv::Point>> Hulls(contours.size());

	for (int i = 0; i < contours.size(); ++i) {
    
      //遍历所有轮廓
		cv::convexHull(contours[i], Hulls[i], true, true);  //寻找轮廓的凸包,输出点集为顺时针

		cv::drawContours(dst, Hulls, i, cv::Scalar(0, 255, 0), 2);
	}

	cv::imshow("dst", dst);
	cv::waitKey();

	cv::destroyAllWindows();
	return 0;
}

近似輪郭を取得します
cv::approxPolyDP()近似輪郭はによって得られます。

関数プロトタイプ:

void approxPolyDP( InputArray curve,
                                OutputArray approxCurve,
                                double epsilon, bool closed );

curveこれは入力ポイント セットです。approxCurve近似後の出力ポイント セットです。epsilon近似精度です。値が大きいほど、近似後に出力されるポイントは少なくなります。closed閉じているかどうか、閉じている場合、値は true です。

例:

#include <iostream>
#include <vector>

#include <opencv2\opencv.hpp>

int main() {
    
    
	cv::Mat src = cv::imread("1_11.png", -1);

	std::vector<std::vector<cv::Point>> contours;
	std::vector<cv::Vec4i> hierarchy;
	cv::findContours(src, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE);  //只找最外层轮廓

	cv::Mat dst;  //用于绘制结果
	cv::cvtColor(src, dst, cv::COLOR_GRAY2BGR);

	std::vector<std::vector<cv::Point>> approxCurves(contours.size());
	for (int i = 0; i < contours.size(); ++i) {
    
      //绘制逼近后的轮廓
		double epsilon = 0.1 * cv::arcLength(contours[i], true);
		cv::approxPolyDP(contours[i], approxCurves[i], epsilon, true);

		cv::drawContours(dst, approxCurves, i, cv::Scalar(0, 255, 0), 2);
	}

	cv::imshow("dst", dst);
	cv::waitKey();

	cv::destroyAllWindows();
	return 0;
}

元の画像は です
ここに画像の説明を挿入
元の輪郭は です
ここに画像の説明を挿入
近似された輪郭は です
ここに画像の説明を挿入
点と輪郭の関係を判断し、輪郭の最大の内接円を求める
点と輪郭の関係には、輪郭の外側、輪郭の内側、輪郭上の 3 種類があります。
この関数を使用して、点と多角形の間の距離を計算することで、点と輪郭の関係を取得できますcv::pointPolygonTest()。点が輪郭点であるか、輪郭多角形上の点に属する場合、距離は 0 になります。多角形の内側の点である場合、正の数になります。負の数の場合、点が外側にあることを返します。

関数プロトタイプ:

double pointPolygonTest( InputArray contour, Point2f pt, bool measureDist );

戻り値は、点から輪郭までの距離を表します。
contour入力ポイント セットです。pt入力ポイントです。measureDistフラグが true の場合は各ポイントから輪郭までの距離を返し、false の場合は +1、0、-1 の 3 つの値を返します。+1 は点が輪郭の内側にあることを示し、0 は点が輪郭上にあることを示し、-1 は点が輪郭の外側にあることを示します。

例:

#include <iostream>
#include <vector>

#include <opencv2\opencv.hpp>

int main() {
    
    
	cv::Mat src = cv::imread("1_11.png", -1);

	std::vector<std::vector<cv::Point>> contours;
	std::vector<cv::Vec4i> hierarchy;
	cv::findContours(src, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE);  //只找最外层轮廓

	cv::Mat dst;  //用于绘制结果
	cv::cvtColor(src, dst, cv::COLOR_GRAY2BGR);

	cv::Point pt(100, 50);
	int res = cv::pointPolygonTest(contours[0], pt, false);  //只和第0个轮廓进行比较
	if (res == 0) {
    
    
		std::cout << "点在轮廓上" << std::endl;
	}
	else if (res == -1) {
    
    
		std::cout << "点在轮廓外" << std::endl;
	}
	else if (res == 1) {
    
    
		std::cout << "点在轮廓内" << std::endl;
	}

	cv::drawContours(dst, contours, 0, cv::Scalar(0, 0, 255), 2);
	cv::circle(dst, pt, 5, cv::Scalar(0, 255, 0), -1);
	cv::imshow("dst", dst);
	cv::waitKey();

	cv::destroyAllWindows();
	return 0;
}

输出:
点在轮廓内

効果:
ここに画像の説明を挿入
この判断により、輪郭の最大内接円を取得できます。アイデアは、すべての点を横断し (もちろん、ROI をカスタマイズできます)、すべての点から輪郭までの距離を計算し、輪郭内の点と輪郭の間の距離のみを比較することです。距離が最大の輪郭内の点が、最大の内接円の中心になります

#include <iostream>
#include <vector>

#include <opencv2\opencv.hpp>

int main() {
    
    
	cv::Mat src = cv::imread("1_11.png", -1);

	std::vector<std::vector<cv::Point>> contours;
	std::vector<cv::Vec4i> hierarchy;
	cv::findContours(src, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE);  //只找最外层轮廓

	cv::Mat dst;  //用于绘制结果
	cv::cvtColor(src, dst, cv::COLOR_GRAY2BGR);

	cv::Point center;  //记录最大内接圆的圆心
	float radius = 0.0f;  //记录最大内接圆的半径

	//遍历所有点
	for (int i = 0; i < src.cols; ++i) {
    
    
		for (int j = 0; j < src.rows; ++j) {
    
    
			double res = cv::pointPolygonTest(contours[0], cv::Point(i, j), true);  //在轮廓内的点,返回的距离是正数
			if (res > 0.0f) {
    
      //只关注轮廓内的点
				if (res > radius) {
    
      //当当前点距离大于记录的点时,更新
					radius = res;
					center = {
    
     i, j };
				}
			}
		}
	}

	cv::drawContours(dst, contours, 0, cv::Scalar(0, 0, 255), 2);  //绘制轮廓
	cv::circle(dst, center, radius, cv::Scalar(0, 255, 0), -1);  //绘制最大内接圆
	cv::imshow("dst", dst);
	cv::waitKey();

	cv::destroyAllWindows();
	return 0;
}

効果:
ここに画像の説明を挿入
輪郭をズームアウト、ズームインする
この機能は完璧ではありませんが、試してみてください。
輪郭を使用する前に凸包に変換することをお勧めします。また、cv::findContours()の最後のパラメーターは として選択する必要があり cv::CHAIN_APPROX_SIMPLE、輪郭は時計回りである必要があります。

#include <iostream>
#include <vector>

#include <opencv2\opencv.hpp>

//函数功能:对cv::findContours(...,cv::CHAIN_APPROX_SIMPLE)找到的轮廓进行放大、缩小处理(注意最后的参数必须为cv::CHAIN_APPROX_SIMPLE)
//in为输入轮廓;out为输出轮廓;scalar负数为内缩,正数为外扩
static void contours_handle(std::vector<cv::Point>& in, std::vector<cv::Point>& out, const float scalar) {
    
    
	float SAFELINE = scalar;

	std::vector<cv::Point2f> dpList, ndpList;
	int count = in.size();
	for (int i = 0; i < count; ++i) {
    
    
		int next = (i == (count - 1) ? 0 : (i + 1));
		dpList.emplace_back(in.at(next) - in.at(i));
		float unitLen = 1.0f / sqrt(dpList.at(i).dot(dpList.at(i)));
		ndpList.emplace_back(dpList.at(i) * unitLen);
	}

	for (int i = 0; i < count; ++i) {
    
    
		int startIndex = (i == 0 ? (count - 1) : (i - 1));
		int endIndex = i;
		float sinTheta = ndpList.at(startIndex).cross(ndpList.at(endIndex));
		cv::Point2f orientVector = ndpList.at(endIndex) - ndpList.at(startIndex);  //i.e. PV2-V1P=PV2+PV1
		if (std::isinf(SAFELINE / sinTheta * orientVector.x) || std::isinf(SAFELINE / sinTheta * orientVector.y)) {
    
      //过滤掉离谱数据
			continue;
		}
		out.emplace_back(cv::Point2f(in.at(i).x + SAFELINE / sinTheta * orientVector.x, in.at(i).y + SAFELINE / sinTheta * orientVector.y));
	}

	return;
}

int main() {
    
    
	cv::Mat src = cv::imread("1_11.png", -1);

	std::vector<std::vector<cv::Point>> contours;
	std::vector<cv::Vec4i> hierarchy;
	cv::findContours(src, contours, hierarchy, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_SIMPLE);  //只找最外层轮廓

	cv::Mat dst;  //用于绘制结果
	cv::cvtColor(src, dst, cv::COLOR_GRAY2BGR);

	std::vector<std::vector<cv::Point>> Hulls(contours.size());  //保存凸包
	cv::convexHull(contours[0], Hulls[0], true, true);  //寻找轮廓的凸包,输出点集为顺时针

	std::vector <std::vector<cv::Point>> outs(contours.size());  //保存放大、缩小后的轮廓
	contours_handle(Hulls[0], outs[0], -15.0f);  //将轮廓缩小15个像素

	cv::drawContours(dst, contours, 0, cv::Scalar(0, 0, 255), 2);  //绘制原始轮廓
	cv::drawContours(dst, Hulls, 0, cv::Scalar(0, 255, 0), 2);  //绘制轮廓的凸包
	cv::drawContours(dst, outs, 0, cv::Scalar(255, 0, 0), 2);  //绘制缩小后的轮廓

	cv::imshow("dst", dst);
	cv::waitKey();

	cv::destroyAllWindows();
	return 0;
}

効果:
ここに画像の説明を挿入

おすすめ

転載: blog.csdn.net/weixin_43003108/article/details/127104352