凸包检测
视频讲解如下:
本章节给大家演示在opencv下如果进行凸包检测,当前代码同样的是在毛星云的代码基础上进行扩展优化的,主要进行了三种效果演示,方便大家学习。
C#、C++、Python三种版本的三种效果其实是一样的,我这里就拿C#的版本来做个演示。
当前系列所有demo下载地址:
https://github.com/GaoRenBao/OpenCv4-Demo
不同编程语言对应的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 |
首先我们需要准备一张测试图片:

第一种效果演示:

第二种效果演示:

第三种效果演示:

C#版本代码如下:
C#版本需要安装“OpenCvSharp4”、“OpenCvSharp4.runtime.win”两个库才行。不然会报错。
如果需要使用“ BitmapConverter.ToBitmap”操作,则需要追加安装“OpenCvSharp4.Extensions”库。
using OpenCvSharp;
using System;
using System.Collections.Generic;
namespace demo
{
internal class Program
{
static void Main(string[] args)
{
demo1();
//demo2();
//demo3();
Cv2.WaitKey(0);
}
#region 演示1
static void demo1()
{
Mat srcImage = Cv2.ImRead("../../../images/kele.jpg");
//图像灰度图转化并平滑滤波
Mat grayImage = new Mat();
Cv2.CvtColor(srcImage, grayImage, ColorConversionCodes.BGR2GRAY);
// 均值滤波
Cv2.Blur(grayImage, grayImage, new OpenCvSharp.Size() { Width = 3, Height = 3 });
Cv2.ImShow("原图像", grayImage);
Mat threshold_output = new Mat();
// 使用Threshold检测图像边缘
Cv2.Threshold(grayImage, threshold_output, 200, 255, ThresholdTypes.Binary);
// 寻找图像轮廓
Random rd = new Random();
Point[][] contours = new Point[][] { };
HierarchyIndex[] hierarcy;
Cv2.FindContours(threshold_output, out contours, out hierarcy, RetrievalModes.Tree, ContourApproximationModes.ApproxSimple, null);
//寻找图像凸包
Point[][] hull = new Point[contours.Length][];
for (int i = 0; i < contours.Length; i++)
{
hull[i] = Cv2.ConvexHull(contours[i], false);
}
//绘制轮廓和凸包
RNG rng = new RNG(12345);
Mat drawing = Mat.Zeros(threshold_output.Size(), MatType.CV_8UC3);
for (int i = 0; i < hull.Length; i++)
{
Scalar color = new Scalar(rng.Uniform(0, 255), rng.Uniform(0, 255), rng.Uniform(0, 255));
// 绘制轮廓
Cv2.DrawContours(drawing, contours, (int)i, color, 1, LineTypes.Link8);
// 绘制凸包
Cv2.DrawContours(drawing, hull, (int)i, color, 5, LineTypes.Link8);
}
Cv2.ImShow("凸包", drawing);
}
#endregion
#region 演示2
private static Mat grayImage = new Mat();
private static Mat drawing = new Mat();
private static Mat threshold_output = new Mat();
private static RNG rng = new RNG(12345);
private static int temp = 100;
static void demo2()
{
Mat srcImage = Cv2.ImRead("../../../images/kele.jpg");
//图像灰度图转化并平滑滤波
Cv2.CvtColor(srcImage, grayImage, ColorConversionCodes.BGR2GRAY);
// 均值滤波
Cv2.Blur(grayImage, grayImage, new Size() { Width = 3, Height = 3 });
Cv2.NamedWindow("凸包", WindowFlags.AutoSize);
Cv2.CreateTrackbar("阈值", "凸包", 255, null);
Cv2.SetMouseCallback("凸包", new MouseCallback(on_Mouse));
while (true)
{
if (Cv2.WaitKey(10) == 'q')
{
break;
}
}
}
public static void on_Mouse(MouseEventTypes @event, int x, int y, MouseEventFlags flags, IntPtr userData)
{
int thresh = Cv2.GetTrackbarPos("阈值", "凸包");
// 滑动条滑动一次,回调会执行N次,为避免不必要的运算,这里加个过滤
if (temp == thresh) return;
temp = thresh;
Console.WriteLine(thresh);
// 使用Threshold检测图像边缘
Cv2.Threshold(grayImage, threshold_output, thresh, 255, ThresholdTypes.Binary);
// 寻找图像轮廓
Point[][] contours = new Point[][] { };
HierarchyIndex[] hierarcy;
Cv2.FindContours(threshold_output, out contours, out hierarcy, RetrievalModes.Tree, ContourApproximationModes.ApproxSimple, null);
//寻找图像凸包
Point[][] hull = new Point[contours.Length][];
for (int i = 0; i < contours.Length; i++)
{
hull[i] = Cv2.ConvexHull(contours[i], false);
}
//绘制轮廓和凸包
drawing = Mat.Zeros(threshold_output.Size(), MatType.CV_8UC3);
for (int i = 0; i < hull.Length; i++) //contours.Length
{
Scalar color = new Scalar(rng.Uniform(0, 255), rng.Uniform(0, 255), rng.Uniform(0, 255));
// 绘制轮廓
Cv2.DrawContours(drawing, contours, (int)i, color, 1, LineTypes.Link8);
// 绘制凸包
Cv2.DrawContours(drawing, hull, (int)i, color, 5, LineTypes.Link8);
}
Cv2.ImShow("凸包", drawing);
Cv2.WaitKey(1);
}
#endregion
#region 演示3
static void demo3()
{
RNG rng = new RNG(12345);
Mat image = Mat.Zeros(600, 600, MatType.CV_8UC3);
while (true)
{
List<Point> points = new List<Point>(); //点值
//参数初始化
int count = rng.Uniform(20, 50);//随机生成点的数量
points.Clear();
//随机生成点坐标
for (int i = 0; i < count; i++)
{
Point point;
point.X = rng.Uniform(image.Cols / 4, image.Cols * 3 / 4);
point.Y = rng.Uniform(image.Rows / 4, image.Rows * 3 / 4);
points.Add(point);
}
//寻找图像凸包
Point[] hull = Cv2.ConvexHull(points, false);
//绘制出随机颜色的点
image = Mat.Zeros(600, 600, MatType.CV_8UC3);
for (int i = 0; i < count; i++)
Cv2.Circle(image, points[i], 3, new Scalar(rng.Uniform(0, 255), rng.Uniform(0, 255), rng.Uniform(0, 255)), -1, LineTypes.AntiAlias);
//准备参数
int hullcount = (int)hull.Length;//凸包的边数
Point point0 = hull[hullcount - 1];//连接凸包边的坐标点
//绘制凸包的边
for (int i = 0; i < hullcount; i++)
{
Point point = hull[i];
Cv2.Line(image, point0, point, new Scalar(255, 255, 255), 2, LineTypes.AntiAlias);
point0 = point;
}
//显示效果图
Cv2.ImShow("凸包检测示例", image);
Cv2.WaitKey(0);
}
}
#endregion
}
}
C++版本代码如下:
这里要注意了,C++版本中,我们通过控制宏定义OBJ的值来控制当前演示的模型。
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
using namespace cv;
using namespace std;
#define OBJ 3
#if OBJ == 1
// 演示1
Mat srcImage, grayImage;
RNG rng(12345);
int main()
{
srcImage = imread("../images/kele.jpg");
//图像灰度图转化并平滑滤波
cvtColor(srcImage, grayImage, COLOR_BGR2GRAY);
blur(grayImage, grayImage, Size(3, 3));
namedWindow("原图像", WINDOW_AUTOSIZE);
imshow("原图像", grayImage);
Mat threshold_output;
Mat src_copy = srcImage.clone();
vector<vector<Point>>contours;
vector<Vec4i>hierarchy;
int thresh = 100;
//使用Threshold检测图像边缘
threshold(grayImage, threshold_output, 200, 255, THRESH_BINARY);
//寻找图像轮廓
findContours(threshold_output, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));
//寻找图像凸包
vector<vector<Point>>hull(contours.size());
for (int i = 0; i < contours.size(); i++) {
convexHull(Mat(contours[i]), hull[i], false);
}
//绘制轮廓和凸包
Mat drawing = Mat::zeros(threshold_output.size(), CV_8UC3);
for (int i = 0; i < contours.size(); i++)
{
Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
// 绘制轮廓
drawContours(drawing, contours, i, color, 1, 8, vector<Vec4i>(), 0, Point());
// 绘制凸包
drawContours(drawing, hull, i, color, 1, 8, vector<Vec4i>(), 0, Point());
}
namedWindow("凸包", WINDOW_AUTOSIZE);
imshow("凸包", drawing);
waitKey(0);
return 0;
}
#endif
#if OBJ == 2
// 演示2
Mat srcImage, grayImage;
int thresh = 100;
const int threshMaxValue = 255;
RNG rng(12345);
//定义回调函数
void thresh_callback(int, void*);
int main()
{
srcImage = imread("../images/kele.jpg");
//图像灰度图转化并平滑滤波
cvtColor(srcImage, grayImage, COLOR_BGR2GRAY);
blur(grayImage, grayImage, Size(3, 3));
namedWindow("原图像", WINDOW_AUTOSIZE);
imshow("原图像", grayImage);
//创建轨迹条
createTrackbar("Threshold:", "原图像", &thresh, threshMaxValue, thresh_callback);
thresh_callback(thresh, 0);
waitKey(0);
return 0;
}
void thresh_callback(int, void*)
{
Mat src_copy = srcImage.clone();
Mat threshold_output;
vector<vector<Point>>contours;
vector<Vec4i>hierarchy;
//使用Threshold检测图像边缘
threshold(grayImage, threshold_output, thresh, 255, THRESH_BINARY);
//寻找图像轮廓
findContours(threshold_output, contours, hierarchy, RETR_TREE, CHAIN_APPROX_SIMPLE, Point(0, 0));
//寻找图像凸包
vector<vector<Point>>hull(contours.size());
for (int i = 0; i < contours.size(); i++)
{
convexHull(Mat(contours[i]), hull[i], false);
}
//绘制轮廓和凸包
Mat drawing = Mat::zeros(threshold_output.size(), CV_8UC3);
for (int i = 0; i < contours.size(); i++)
{
Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
drawContours(drawing, contours, i, color, 1, 8, vector<Vec4i>(), 0, Point());
drawContours(drawing, hull, i, color, 1, 8, vector<Vec4i>(), 0, Point());
}
namedWindow("凸包", WINDOW_AUTOSIZE);
imshow("凸包", drawing);
}
#endif
#if OBJ == 3
// 演示3
//键盘按键【ESC】、【Q】、【q】- 退出程序
//键盘按键任意键 - 重新生成随机点,并进行凸包检测
int main()
{
//改变console字体颜色
system("color 1F");
//初始化变量和随机值
Mat image(600, 600, CV_8UC3);
RNG& rng = theRNG();
//循环,按下ESC,Q,q键程序退出,否则有键按下便一直更新
while (1)
{
//参数初始化
char key;//键值
int count = (unsigned)rng % 100 + 3;//随机生成点的数量
vector<Point> points; //点值
//随机生成点坐标
for (int i = 0; i < count; i++)
{
Point point;
point.x = rng.uniform(image.cols / 4, image.cols * 3 / 4);
point.y = rng.uniform(image.rows / 4, image.rows * 3 / 4);
points.push_back(point);
}
//检测凸包
vector<int> hull;
convexHull(Mat(points), hull, true);
//绘制出随机颜色的点
image = Scalar::all(0);
for (int i = 0; i < count; i++)
circle(image, points[i], 3, Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255)), FILLED, LINE_AA);
//准备参数
int hullcount = (int)hull.size();//凸包的边数
Point point0 = points[hull[hullcount - 1]];//连接凸包边的坐标点
//绘制凸包的边
for (int i = 0; i < hullcount; i++)
{
Point point = points[hull[i]];
line(image, point0, point, Scalar(255, 255, 255), 2, LINE_AA); // 低版本是:CV_AA
point0 = point;
}
//显示效果图
imshow("凸包检测示例", image);
//按下ESC,Q,或者q,程序退出
key = (char)waitKey();
if (key == 27 || key == 'q' || key == 'Q')
break;
}
return 0;
}
#endif
Python版本代码如下:
demo1
import cv2
import numpy as np
def zh_ch(string):
return string.encode("gbk").decode(errors="ignore")
srcImage = cv2.imread("../images/kele.jpg")
# 图像灰度图转化并平滑滤波
grayImage = cv2.cvtColor(srcImage, cv2.COLOR_BGR2GRAY)
# 均值滤波
grayImage = cv2.blur(grayImage, (3, 3))
cv2.imshow(zh_ch("原始图"), grayImage)
# 二值化
ret, threshold_output = cv2.threshold(grayImage, 200, 255, cv2.THRESH_BINARY)
# 寻找图像轮廓
contours, hierarchy = cv2.findContours(threshold_output, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
hull = []
# 寻找图像凸包
for i in range(len(contours)):
hull.append(cv2.convexHull(contours[i], False))
# 初始化一个空白图像
drawing = np.zeros((threshold_output.shape[0], threshold_output.shape[1], 3), np.uint8)
# 绘制轮廓和凸包
for i in range(len(contours)):
# 绘制轮廓
cv2.drawContours(drawing, contours, i, (0, 255, 0), 1, 8, hierarchy)
# 绘制凸包
cv2.drawContours(drawing, hull, i, (255, 0, 0), 1, 8)
# 显示图片
cv2.imshow("drawing", drawing)
cv2.waitKey(0)
demo2
# OpenCv版本 OpenCvSharp4.6.0.66
# 内容:凸包检测
# 博客:/Course?id=3741165000202
# 作者:高仁宝
# 时间:2023.11
import cv2
import numpy as np
def zh_ch(string):
return string.encode("gbk").decode(errors="ignore")
thresh = 80
def on_ThreshChange(x):
global thresh, grayImage, drawing
# 获取滑动条的值
thresh = cv2.getTrackbarPos('thresh', 'drawing')
# 二值化
ret, threshold_output = cv2.threshold(grayImage, thresh, 255, cv2.THRESH_BINARY)
# 寻找图像轮廓
contours, hierarchy = cv2.findContours(threshold_output, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
# 寻找图像凸包
hull = []
for i in range(len(contours)):
hull.append(cv2.convexHull(contours[i], False))
# 初始化一个空白图像
drawing = np.zeros((threshold_output.shape[0], threshold_output.shape[1], 3), np.uint8)
# 绘制轮廓和凸包
for i in range(len(contours)):
# 绘制轮廓
cv2.drawContours(drawing, contours, i, (0, 255, 0), 1, 8, hierarchy)
# 绘制凸包
cv2.drawContours(drawing, hull, i, (255, 0, 0), 1, 8)
srcImage = cv2.imread("../images/kele.jpg")
# 图像灰度图转化并平滑滤波
grayImage = cv2.cvtColor(srcImage, cv2.COLOR_BGR2GRAY)
# 初始化一个空白图像
drawing = np.zeros((srcImage.shape[0], srcImage.shape[1], 3), np.uint8)
# 均值滤波
grayImage = cv2.blur(grayImage, (3, 3))
cv2.imshow(zh_ch("原始图"), grayImage)
# 创建窗口
cv2.namedWindow('drawing')
cv2.createTrackbar('thresh', 'drawing', 0, 255, on_ThreshChange)
cv2.setTrackbarPos('thresh', 'drawing', 80)
while True:
cv2.imshow("drawing", drawing)
if cv2.waitKey(10) == ord('q'):
break
cv2.destroyAllWindows()
demo3
# OpenCv版本 OpenCvSharp4.6.0.66
# 内容:凸包检测
# 博客:/Course?id=3741165000202
# 作者:高仁宝
# 时间:2023.11
import cv2
import numpy as np
import random
def zh_ch(string):
return string.encode("gbk").decode(errors="ignore")
while (True):
# 随机生成点的数量
count = random.randint(30, 50)
point = []
for i in range(count):
x = random.randint(600 / 4, 600 * 3 / 4)
y = random.randint(600 / 4, 600 * 3 / 4)
point.append([x, y])
points = np.array(point, np.int32) # 点值
# 寻找图像凸包
hull = cv2.convexHull(points, False)
# 绘制出随机颜色的点
image = np.zeros((600, 600, 3), np.uint8)
for i in range(count):
cv2.circle(image, (points[i][0], points[i][1]), 3,
(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)), -1)
# 准备参数
point0 = hull[len(hull) - 1] # 连接凸包边的坐标点
# 绘制轮廓和凸包
for i in range(len(hull)):
point = hull[i]
image = cv2.line(image, (point0[0][0], point0[0][1]),
(point[0][0], point[0][1]),
(random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)), 2)
point0 = point
cv2.imshow("image", image)
cv2.waitKey(0)