概要の基本的な考え方
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
対応する属性 (トポロジ情報など)、輪郭の検索モード、輪郭を見つけるときmode
にmethod
使用される近似アルゴリズム (輪郭cv::CHAIN_APPROX_NONE
上のすべての点を保存しcv::CHAIN_APPROX_SIMPLE
ます。輪郭上の変曲点のみを保存します)。たとえば、長方形には 4 つの頂点のみが保存され、cv::CHAIN_APPROX_TC89_L1
「cv::CHAIN_APPROX_TC89_KCOS
teh-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() );
例:image
contours
cv::findContours()
contourIdx
color
thickness
-1
lineType
hierarchy
maxLevel
offset
#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.000000
第1个轮廓的面积为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}={
M00M10、M00M01}。
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.000000
第1个轮廓的面积为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;
}
効果: