ApproxPolyDP轮廓逼近
函数说明:使用道格拉斯-普克算法(Douglas–Peucker algorithm)获取一个小于或等于指定精度(epsilon)的更少坐标集的轮廓(或曲线)。图片来自(道格拉斯普克算法)
![](https://img-blog.csdnimg.cn/img_convert/7ae8bc0e454644500aa9002e0af52a98.gif)
//函数原型1
void ApproxPolyDP(InputArray curve,
OutputArray approxCurve,
double epsilon,
bool closed)
//函数原型2
Point[] ApproxPolyDP(IEnumerable<Point> curve,
double epsilon,
bool closed)
//函数原型3
Point2f[] ApproxPolyDP(IEnumerable<Point2f> curve,
double epsilon,
bool closed)
参数 | 说明 |
InputArray curve IEnumerable<Point> curve IEnumerable<Point2f> curve | 待计算的轮廓(曲线) |
double epsilon | 近似精度,为逼近结果与原轮廓(曲线)的最大差值 |
bool closed | 轮廓(曲线)是否闭合 |
返回值OutputArray approxCurve | 返回逼近后的轮廓(曲线)坐标集 |
图像示例
原图
![](https://img-blog.csdnimg.cn/img_convert/7e23f65b87894515a4c89dfdf5ff96e6.jpeg)
逼近并矫正后结果
通过轮廓发现的轮廓个数是2864(蓝色边框),轮廓逼近后个数只剩4个(红点)
![](https://img-blog.csdnimg.cn/img_convert/d7f0165b75c249148390ee31b6b0c6b7.png)
源代码示例
public void Run()
{
using (var src = Cv2.ImRead(ImagePath.Book, ImreadModes.Color))
{
if (src.Empty()) throw new Exception("图像读取有误");
var srcWinName = "src";
ResizeWin(srcWinName, src);
Cv2.ImShow("src", src);
using (var srcGray = new Mat())
{
Cv2.CvtColor(src, srcGray, ColorConversionCodes.BGR2GRAY);
//中值模糊
Cv2.MedianBlur(srcGray, srcGray, 15);
//Otsu二值化
Cv2.Threshold(srcGray, srcGray, 0, 255, ThresholdTypes.Otsu | ThresholdTypes.BinaryInv);
var srcGrayWinName = "srcGray";
ResizeWin(srcGrayWinName, srcGray);
Cv2.ImShow(srcGrayWinName, srcGray);
//轮廓发现
Cv2.FindContours(srcGray, out Point[][] contours, out HierarchyIndex[] hierarchy, RetrievalModes.External, ContourApproximationModes.ApproxSimple);
//找到最大轮廓
var maxIndex = -1;
double maxContourArea = 0;
for (int index = 0; index < contours.Length; index++)
{
var area = Cv2.ContourArea(contours[index]);
if (maxContourArea > area) continue;
maxIndex = index;
maxContourArea = area;
}
if (maxIndex > -1)
{
//绘制未逼近前的轮廓
Cv2.DrawContours(src, new Point[][] { contours[maxIndex] }, -1, Scalar.Blue, 5);
//轮廓逼近参数
var epsilon = Cv2.ArcLength(contours[maxIndex], true) * 0.1;
//轮廓逼近
var approxContour = Cv2.ApproxPolyDP(contours[maxIndex], epsilon, true);
//标记逼近后轮廓点
foreach (var point in approxContour)
{
Cv2.Circle(src, point, 10, Scalar.Red, -1);
}
//获取轮廓最小外接矩形(图像中书不是平行四边形,应该用霍夫变换的,但还没研究,先用这个简单处理吧)
var RotatedRect = Cv2.MinAreaRect(approxContour);
//获取旋转矩形,Angle为正数时是逆时针
var rotMat = Cv2.GetRotationMatrix2D(RotatedRect.Center, RotatedRect.Angle, 1);
//每个通道分开仿射变换
var splits = Cv2.Split(src);
for (int i = 0; i < splits.Length; i++)
{
var dst = new Mat();
//仿射变换
Cv2.WarpAffine(splits[i], dst, rotMat, srcGray.Size());
splits[i] = dst;
}
Cv2.Merge(splits, src);
//转化率逼近前后的个数
Cv2.PutText(src, $"Before ApproxPolyDP Contour count={contours[maxIndex].Length},after ={approxContour.Length}", new Point(50, 50), HersheyFonts.HersheySimplex, 1.5, Scalar.Red,5);
var srcResult = "ApproxPolyDP";
ResizeWin(srcResult, src);
Cv2.ImShow(srcResult, src);
}
}
}
Cv2.WaitKey();
Cv2.DestroyAllWindows();
}
/// <summary>
/// 缩放窗口
/// </summary>
/// <param name="winName"></param>
/// <param name="mat"></param>
/// <param name="newWidth"></param>
private void ResizeWin(string winName, Mat mat, int newWidth = 600)
{
Cv2.NamedWindow(winName, WindowFlags.KeepRatio);
Cv2.ResizeWindow(winName, newWidth, (int)(mat.Height * newWidth / mat.Width));
}
今天是女神节,祝天下女神们节日快乐!
参考