轮廓的性质
本章节内容是博主网上收集的。当前系列所有demo下载地址:
https://github.com/GaoRenBao/OpenCv4-Demo
当前demo来源:
OpenCV-Python-Tutorial-中文版.pdf(P111 轮廓的性质) 在线预览
不同编程语言对应的OpenCv版本以及开发环境信息如下:
语言 | OpenCv版本 | IDE |
C# | OpenCvSharp4.4.8.0.20230708 | Visual Studio 2022 |
C++ | OpenCv-4.5.5-vc14_vc15 | Visual Studio 2022 |
Python | OpenCv-Python (4.6.0.66) | PyCharm Community Edition 2022.1.3 |
当前demo主要演示轮廓的一些基本性质。
测试原图:

C#版本运行效果及代码如下:

源码如下:
using OpenCvSharp;
using System;
using System.Linq;
namespace ConsoleApp
{
internal class Program
{
static void Main(string[] args)
{
Mat srcImage = Cv2.ImRead("../../../images/star3.jpg");
Mat img = new Mat();
Cv2.CvtColor(srcImage, img, ColorConversionCodes.BGR2GRAY);
Mat thresh = new Mat();
Cv2.Threshold(img, thresh, 127, 255, ThresholdTypes.Binary);
Point[][] contours = new Point[][] { };
HierarchyIndex[] hierarcy;
Cv2.FindContours(thresh, out contours, out hierarcy, RetrievalModes.Tree, ContourApproximationModes.ApproxSimple, null);
Console.WriteLine($"contours len:{contours.Length}");
Point[] cnt = contours[0]; // contours 有三个,挑个最大的,方便演示
test1(cnt);
test2(cnt);
test3(cnt);
test4(cnt);
test5(cnt);
test6(img, cnt);
test7(srcImage, cnt);
}
static void test1(Point[] cnt)
{
// 边界矩形的宽高比
Rect rect = Cv2.BoundingRect(cnt);
float aspect_ratio = (float)rect.Width / (float)rect.Height;
Console.WriteLine($"边界矩形的宽高比:{aspect_ratio}");
}
static void test2(Point[] cnt)
{
// Extent轮廓面积与边界矩形面积的比
double area = Cv2.ContourArea(cnt);
Rect rect = Cv2.BoundingRect(cnt);
double rect_area = rect.Width * rect.Height;
double extent = area / rect_area;
Console.WriteLine($"Extent轮廓面积与边界矩形面积的比:{extent}");
}
static void test3(Point[] cnt)
{
// Solidity轮廓面积与凸包面积的比。
double area = Cv2.ContourArea(cnt);
Point[] hull = Cv2.ConvexHull(cnt);
double hull_area = Cv2.ContourArea(hull);
double solidity = area / hull_area;
Console.WriteLine($"Solidity轮廓面积与凸包面积的比:{solidity}");
}
static void test4(Point[] cnt)
{
// Equivalent Diameter与轮廓面积相等的圆形的直径
double area = Cv2.ContourArea(cnt);
double equi_diameter = Math.Sqrt(4 * area / Math.PI);
Console.WriteLine($"Equivalent Diameter与轮廓面积相等的圆形的直径:{equi_diameter}");
}
static void test5(Point[] cnt)
{
// Orientation对象的方向下的方法会返回长轴和短轴的长度
RotatedRect rect = Cv2.FitEllipse(cnt);
Console.WriteLine($"Orientation对象的方向下的方法会返回长轴和短轴的长度:{rect.Center},{rect.Size},{rect.Angle}");
}
static void test6(Mat imgray, Point[] cnt)
{
// Mask and Pixel Points掩模和像素点
Mat mask = new Mat(imgray.Size(), MatType.CV_8U, new Scalar(0, 0, 0));
Point[][] cnts = new Point[][] { cnt };
Cv2.DrawContours(mask, cnts, 0, 255, -1);
// 最大值和最小值及它们的位置
Cv2.MinMaxLoc(imgray, out double minVal, out double maxVal, out Point minLoc, out Point maxLoc, mask);
Console.WriteLine($"最大值和最小值及它们的位置:{minVal},{maxVal},{minLoc},{maxLoc}");
// 我们也可以使用相同的掩模求一个对象的平均颜色或平均灰度
Scalar mean_val = Cv2.Mean(imgray, mask);
Console.WriteLine($"平均色:{mean_val}");
}
static void test7(Mat img, Point[] cnt)
{
Point topmost = cnt.FirstOrDefault(x => x.Y == cnt.Min(p => p.Y)); // 最上面
Point bottommost = cnt.FirstOrDefault(x => x.Y == cnt.Max(p => p.Y)); // 最下面
Point leftmost = cnt.FirstOrDefault(x => x.X == cnt.Min(p => p.X)); // 最左面
Point rightmost = cnt.FirstOrDefault(x => x.X == cnt.Max(p => p.X)); // 最右面
Console.WriteLine($"最上面:{topmost}");
Console.WriteLine($"最下面:{bottommost}");
Console.WriteLine($"最左:{leftmost}");
Console.WriteLine($"最右:{rightmost}");
Cv2.Circle(img, topmost, 5, new Scalar(0, 0, 255), -1);
Cv2.Circle(img, bottommost, 5, new Scalar(0, 0, 255), -1);
Cv2.Circle(img, leftmost, 5, new Scalar(0, 0, 255), -1);
Cv2.Circle(img, rightmost, 5, new Scalar(0, 0, 255), -1);
Cv2.ImShow("img", img);
Cv2.WaitKey(0);
}
}
}
C++版本运行效果及代码如下:

源码如下:
#include <opencv2/opencv.hpp>
#include <opencv2/cvconfig.h>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <string>
#include <cmath>
#include <iostream>
using namespace cv;
using namespace std;
void test1(vector<Point> cnt)
{
// 边界矩形的宽高比
Rect rect = cv::boundingRect(cnt);
float aspect_ratio = (float)rect.width / (float)rect.height;
cout << "边界矩形的宽高比:" << aspect_ratio << endl;
}
void test2(vector<Point> cnt)
{
// Extent轮廓面积与边界矩形面积的比
double area = cv::contourArea(cnt);
Rect rect = cv::boundingRect(cnt);
double rect_area = rect.width * rect.height;
double extent = area / rect_area;
cout << "Extent轮廓面积与边界矩形面积的比:" << extent << endl;
}
void test3(vector<Point> cnt)
{
// Solidity轮廓面积与凸包面积的比。
double area = cv::contourArea(cnt);
vector<Point> hull;
cv::convexHull(cnt, hull);
double hull_area = cv::contourArea(hull);
double solidity = area / hull_area;
cout << "Solidity轮廓面积与凸包面积的比:" << solidity << endl;
}
void test4(vector<Point> cnt)
{
// Equivalent Diameter与轮廓面积相等的圆形的直径
double area = cv::contourArea(cnt);
double equi_diameter = sqrt(4 * area / CV_PI);
cout << "Equivalent Diameter与轮廓面积相等的圆形的直径:" << equi_diameter << endl;
}
void test5(vector<Point> cnt)
{
// Orientation对象的方向下的方法会返回长轴和短轴的长度
RotatedRect rect = cv::fitEllipse(cnt);
cout << "Orientation对象的方向下的方法会返回长轴和短轴的长度:" << rect.center << "," << rect.size << "," << rect.angle << endl;
}
void test6(Mat imgray, vector<Point> cnt)
{
// Mask and Pixel Points掩模和像素点
Mat mask(imgray.size(), CV_8U);
mask = Scalar::all(0);
vector<vector<Point>> cnts;
cnts.push_back(cnt);
cv::drawContours(mask, cnts, 0, 255, -1);
// 最大值和最小值及它们的位置
double minValue;
double maxValue;
Point minLocation;
Point maxLocation;
minMaxLoc(imgray, &minValue, &maxValue, &minLocation, &maxLocation, Mat());
cout << "最大值和最小值及它们的位置:" << minValue << "," << maxValue << "," << minLocation << "," << maxLocation << endl;
// 我们也可以使用相同的掩模求一个对象的平均颜色或平均灰度
Scalar mean_val = cv::mean(imgray, mask);
cout << "平均色:" << mean_val << endl;
}
void test7(Mat img, vector<Point> cnt)
{
Point topmost = *std::min_element(cnt.begin(), cnt.end(), [](const Point& a, const Point& b) { return a.y < b.y; });// 最上面
Point bottommost = *std::min_element(cnt.begin(), cnt.end(), [](const Point& a, const Point& b) { return a.y > b.y; }); // 最下面
Point leftmost = *std::min_element(cnt.begin(), cnt.end(), [](const Point& a, const Point& b) { return a.x < b.x; });// 最左面
Point rightmost = *std::min_element(cnt.begin(), cnt.end(), [](const Point& a, const Point& b) { return a.x > b.x;}); // 最右面
cout << "最上面:" << topmost << endl;
cout << "最下面:" << bottommost << endl;
cout << "最左:" << leftmost << endl;
cout << "最右:" << rightmost << endl;
cv::circle(img, topmost, 5, Scalar(0, 0, 255), -1);
cv::circle(img, bottommost, 5, Scalar(0, 0, 255), -1);
cv::circle(img, leftmost, 5, Scalar(0, 0, 255), -1);
cv::circle(img, rightmost, 5, Scalar(0, 0, 255), -1);
cv::imshow("img", img);
cv::waitKey(0);
}
int main()
{
Mat srcImage = cv::imread("../images/star3.jpg");
Mat img;
cv::cvtColor(srcImage, img, COLOR_BGR2GRAY);
Mat thresh;
cv::threshold(img, thresh, 127, 255, THRESH_BINARY);
vector<vector<Point>> contours;
vector<Vec4i> hierarcy;
findContours(thresh, contours, hierarcy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));
cout << "contours len:" << contours.size() << endl;
vector<Point> cnt = contours[0]; // contours 有三个,挑个最大的,方便演示
test1(cnt);
test2(cnt);
test3(cnt);
test4(cnt);
test5(cnt);
test6(img, cnt);
test7(srcImage, cnt);
}
Python版本运行效果及代码如下:
运行结果:

源码如下:
import cv2
import numpy as np
def test1(cnt):
# 边界矩形的宽高比
x, y, w, h = cv2.boundingRect(cnt)
aspect_ratio = float(w) / h
print('边界矩形的宽高比:', aspect_ratio)
def test2(cnt):
# Extent轮廓面积与边界矩形面积的比
area = cv2.contourArea(cnt)
x, y, w, h = cv2.boundingRect(cnt)
rect_area = w * h
extent = float(area) / rect_area
print('Extent轮廓面积与边界矩形面积的比:', extent)
def test3(cnt):
# Solidity轮廓面积与凸包面积的比。
area = cv2.contourArea(cnt)
hull = cv2.convexHull(cnt)
hull_area = cv2.contourArea(hull)
solidity = float(area) / hull_area
print('Solidity轮廓面积与凸包面积的比:', solidity)
def test4(cnt):
# Equivalent Diameter与轮廓面积相等的圆形的直径
area = cv2.contourArea(cnt)
equi_diameter = np.sqrt(4 * area / np.pi)
print('Equivalent Diameter与轮廓面积相等的圆形的直径:', equi_diameter)
def test5(cnt):
# Orientation对象的方向下的方法会返回长轴和短轴的长度(椭圆拟合)
(x, y), (MA, ma), angle = cv2.fitEllipse(cnt)
print('Orientation对象的方向下的方法会返回长轴和短轴的长度:',(x, y), (MA, ma), angle)
def test6(imgray):
# Mask and Pixel Points掩模和像素点
mask = np.zeros(imgray.shape, np.uint8)
cv2.drawContours(mask, [cnt], 0, 255, -1)
pixelpoints = np.transpose(np.nonzero(mask))
# 最大值和最小值及它们的位置
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(imgray, mask=mask)
print('最大值和最小值及它们的位置:',min_val, max_val, min_loc, max_loc)
# 我们也可以使用相同的掩模求一个对象的平均颜色或平均灰度
mean_val = cv2.mean(imgray, mask=mask)
print('平均色:',mean_val)
def test7(img ,cnt):
# 极点 一个对象最上面、最下面、最左、最右,的点。
leftmost = tuple(cnt[cnt[:, :, 0].argmin()][0])
rightmost = tuple(cnt[cnt[:, :, 0].argmax()][0])
topmost = tuple(cnt[cnt[:, :, 1].argmin()][0])
bottommost = tuple(cnt[cnt[:, :, 1].argmax()][0])
print('最上面:',topmost)
print('最下面:',bottommost)
print('最左:',leftmost)
print('最右:',rightmost)
cv2.circle(img, topmost, 5, (0, 0, 255), -1)
cv2.circle(img, bottommost, 5, (0, 0, 255), -1)
cv2.circle(img, leftmost, 5, (0, 0, 255), -1)
cv2.circle(img, rightmost, 5, (0, 0, 255), -1)
cv2.imshow("img", img)
srcImage = cv2.imread('../images/star3.jpg')
img = cv2.cvtColor(srcImage,cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(img, 127, 255, 0)
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
print('contours len:', len(contours))
cnt = contours[0]
test1(cnt)
test2(cnt)
test3(cnt)
test4(cnt)
test5(cnt)
test6(img)
test7(srcImage,cnt)
cv2.waitKey(0)
cv2.destroyAllWindows()