1. 多角度模板匹配测试效果如下图:
图1-1
图1-2
图1-3
正负角度均可正常识别,识别角度偏差<1°
2. 下面分享一下开发过程:
a). ROI区域的生成,基于GDI+完成图形绘制,如图
绘制模板设置区域,用来生成需要的模板特征。
ROI区域绘制代码如下:
///
/// 区域绘制
///
///
///
///
public static void drawRegion(this Graphics graphics, RegionEx regionEx,float sizeratio=1)
{
if (regionEx?.Region is RectangleF)
{
RectangleF rect = (RectangleF)regionEx.Region;
graphics.DrawRectangle(new Pen(regionEx.Color, regionEx.Size), rect.X / sizeratio, rect.Y / sizeratio,
rect.Width / sizeratio, rect.Height / sizeratio);
}
else if(regionEx?.Region is RotatedRectF)
{
RotatedRectF rrect = (RotatedRectF)regionEx.Region;
using (var graph = new GraphicsPath())
{
PointF Center = new PointF(rrect.cx / sizeratio, rrect.cy / sizeratio);
graph.AddRectangle(new RectangleF( rrect.getrectofangleEqualZero().X / sizeratio,
rrect.getrectofangleEqualZero().Y / sizeratio,
rrect.getrectofangleEqualZero().Width / sizeratio,
rrect.getrectofangleEqualZero().Height / sizeratio));
graph.AddLine(new PointF((rrect.cx - rrect.Width / 2) / sizeratio, rrect.cy / sizeratio),
new PointF((rrect.cx + rrect.Width/2) / sizeratio, rrect.cy / sizeratio));
/
RotatedRectF rotatedRectF = new RotatedRectF((rrect.cx + rrect.Width / 2) / sizeratio,
rrect.cy / sizeratio,20 / sizeratio, 10 / sizeratio, 0);
PointF[] point2Fs = rotatedRectF.getPointF();
graph.AddLine(new PointF((rrect.cx + rrect.Width / 2) / sizeratio,
rrect.cy / sizeratio), new PointF(point2Fs[0].X, point2Fs[0].Y));
graph.AddLine(new PointF((rrect.cx + rrect.Width / 2) / sizeratio,
rrect.cy / sizeratio), new PointF(point2Fs[3].X, point2Fs[3].Y));
/
var a = rrect.angle * (Math.PI / 180);
var n1 = (float)Math.Cos(a);
var n2 = (float)Math.Sin(a);
var n3 = -(float)Math.Sin(a);
var n4 = (float)Math.Cos(a);
var n5 = (float)(Center.X * (1 - Math.Cos(a)) + Center.Y * Math.Sin(a));
var n6 = (float)(Center.Y * (1 - Math.Cos(a)) - Center.X * Math.Sin(a));
graph.Transform(new Matrix(n1, n2, n3, n4, n5, n6));
graphics.DrawPath(new Pen(regionEx.Color, regionEx.Size), graph);
}
}
else if (regionEx?.Region is RotatedCaliperRectF)
{
RotatedCaliperRectF rrect = (RotatedCaliperRectF)regionEx.Region;
using (var graph = new GraphicsPath())
{
PointF Center = new PointF(rrect.cx / sizeratio, rrect.cy / sizeratio);
graph.AddRectangle(new RectangleF(rrect.getrectofangleEqualZero().X / sizeratio,
rrect.getrectofangleEqualZero().Y / sizeratio,
rrect.getrectofangleEqualZero().Width / sizeratio,
rrect.getrectofangleEqualZero().Height / sizeratio));
graph.AddLine(new PointF((rrect.cx - rrect.Width / 2) / sizeratio, rrect.cy / sizeratio),
new PointF((rrect.cx + rrect.Width / 2) / sizeratio, rrect.cy / sizeratio));
/
RotatedCaliperRectF rotatedRectF = new RotatedCaliperRectF((rrect.cx + rrect.Width / 2) / sizeratio,
rrect.cy / sizeratio, 20 / sizeratio, 10 / sizeratio, 0);
PointF[] point2Fs = rotatedRectF.getPointF();
graph.AddLine(new PointF((rrect.cx + rrect.Width / 2) / sizeratio,
rrect.cy / sizeratio), new PointF(point2Fs[0].X, point2Fs[0].Y));
graph.AddLine(new PointF((rrect.cx + rrect.Width / 2) / sizeratio,
rrect.cy / sizeratio), new PointF(point2Fs[3].X, point2Fs[3].Y));
/
var a = rrect.angle * (Math.PI / 180);
var n1 = (float)Math.Cos(a);
var n2 = (float)Math.Sin(a);
var n3 = -(float)Math.Sin(a);
var n4 = (float)Math.Cos(a);
var n5 = (float)(Center.X * (1 - Math.Cos(a)) + Center.Y * Math.Sin(a));
var n6 = (float)(Center.Y * (1 - Math.Cos(a)) - Center.X * Math.Sin(a));
graph.Transform(new Matrix(n1, n2, n3, n4, n5, n6));
graphics.DrawPath(new Pen(regionEx.Color, regionEx.Size), graph);
}
}
else if (regionEx?.Region is CircleF)
{
CircleF circle = (CircleF)regionEx.Region;
graphics.DrawEllipse(new Pen(regionEx.Color, regionEx.Size), (circle.Centerx - circle.Radius) / sizeratio,
(circle.Centery - circle.Radius) / sizeratio, 2 * circle.Radius / sizeratio, 2 * circle.Radius / sizeratio);
}
else if (regionEx?.Region is PointF)
{
PointF point = (PointF)regionEx.Region;
graphics.DrawPolygon(new Pen(regionEx.Color, regionEx.Size), new PointF[] { new PointF (
point.X/sizeratio,point.Y/sizeratio
)});
}
else if (regionEx?.Region is PolygonF)
{
PolygonF polygon = (PolygonF)regionEx.Region;
List temlist = new List();
foreach (var s in polygon.Points)
temlist.Add(new PointF(s.X / sizeratio, s.Y / sizeratio));
graphics.DrawPolygon(new Pen(regionEx.Color, regionEx.Size), temlist.ToArray());
}
else if (regionEx?.Region is LineF)
{
LineF line = (LineF)regionEx.Region;
graphics.DrawLine(new Pen(regionEx.Color, regionEx.Size), line.x1/ sizeratio, line.y1/ sizeratio,
line.x2/ sizeratio, line.y2/ sizeratio);
}
else if (regionEx?.Region is CrossF)
{
CrossF cross = (CrossF)regionEx.Region;
graphics.DrawLine(new Pen(regionEx.Color, regionEx.Size), (cross.x1- cross.width/2) / sizeratio, cross.y1 / sizeratio,
(cross.x1 + cross.width / 2) / sizeratio, cross.y1 / sizeratio);
graphics.DrawLine(new Pen(regionEx.Color, regionEx.Size), cross.x1 / sizeratio, (cross.y1- cross.height/2) / sizeratio,
cross.x1 / sizeratio, (cross.y1 + cross.height / 2) / sizeratio);
graphics.DrawEllipse(new Pen(regionEx.Color, regionEx.Size), (cross.x1 - cross.radius) / sizeratio,
(cross.y1 - cross.radius) / sizeratio, 2 * cross.radius / sizeratio, 2 * cross.radius / sizeratio);
}
else if(regionEx?.Region is SectorF)
{
SectorF sectorF=(SectorF)regionEx.Region;
//graphics.DrawEllipse(MyPens.assist, sectorF.x / sizeratio, sectorF.y / sizeratio,
// sectorF.width / sizeratio, sectorF.height / sizeratio);
graphics.DrawPie(new Pen(regionEx.Color, regionEx.Size),
sectorF.x / sizeratio, sectorF.y / sizeratio,
sectorF.width / sizeratio, sectorF.height / sizeratio,
sectorF.startAngle, sectorF.sweepAngle);
}
else if (regionEx?.Region is Region)
{
Region unionRegion = (Region)regionEx?.Region;
//RectangleF rectangleF = unionRegion.GetBounds(graphics);
//Matrix matrix = new Matrix();
//matrix.Scale(1/sizeratio, 1/sizeratio);
//unionRegion.Transform(matrix);
//RectangleF rectangleF2= unionRegion.GetBounds(graphics);
graphics.FillRegion(Brushes.Orange, unionRegion);
}
else
;
}
b). 模板创建
选择稳定唯一的形状特征,设置合适的参数,用来生成模板,此基础版生成的特征为闭合的轮廓,后期版本会推出非闭合的多轮廓形状匹配算法。
模板创建代码如下:
//创建模板
private void btncreateModel_Click(object sender, EventArgs e)
{
if (GrabImg == null || GrabImg.Width <= 0)
{
MessageBox.Show("未获取图像");
return;
}
List roiList = currvisiontool.getRoiList();
if (roiList.Count <= 0)
{
MessageBox.Show("请设置模板创建区域{矩形}");
return;
}
if (MessageBox.Show("确认创建新模板?", "Info", MessageBoxButtons.YesNo, MessageBoxIcon.Question)
== DialogResult.Yes)
{
CVRect cVRect = new CVRect((int)roiList[0].X, (int)roiList[0].Y, (int)roiList[0].Width, (int)roiList[0].Height);
Mat tp = MatExtension.Crop_Mask_Mat(GrabImg, cVRect);
templateContour = null;
coutourLen = 100;
NumMincoutourLen.Value=100;
contourArea = 100;
NumMinContourArea.Value=100;
double modelx = 0, modely = 0;
runTool = new ShapeMatchTool();
parmaData = new ShapeMatchData();
(parmaData as ShapeMatchData).Segthreshold = (double)NumSegthreshold.Value;
modeltp = (runTool as ShapeMatchTool).CreateTemplateContours(tp,
(parmaData as ShapeMatchData).Segthreshold, cVRect,
ref templateContour,
ref coutourLen, ref contourArea, ref modelx, ref modely, ref modelangle);
picTemplate.Image = BitmapConverter.ToBitmap(modeltp);
if (templateContour == null)
{
MessageBox.Show("模板创建失败!");
return;
}
modelx += cVRect.X;
modely += cVRect.Y;
lIstModelInfo.Items.Clear();
lIstModelInfo.Items.Add(new ListViewItem(
new string[] { "BaseX", modelx.ToString("f3") }));
lIstModelInfo.Items.Add(new ListViewItem(
new string[] { "BaseY", modely.ToString("f3") }));
lIstModelInfo.Items.Add(new ListViewItem(
new string[] { "BaseAngle", modelangle.ToString("f3") }));
lIstModelInfo.Items.Add(new ListViewItem(
new string[] { "ContourLength", coutourLen.ToString("f3") }));
lIstModelInfo.Items.Add(new ListViewItem(
new string[] { "ContourArea", contourArea.ToString("f3") }));
modelOrigion = string.Format("{0},{1},{2}",
modelx.ToString("f3"),
modely.ToString("f3"),
modelangle.ToString("f3"));
if(coutourLen * 0.8> (double)NumMincoutourLen.Maximum||
contourArea * 0.8> (double)NumMinContourArea.Maximum)
{
MessageBox.Show("模板创建完成失败,模板区域过大!");
return;
}
NumMincoutourLen.Value = (decimal)(coutourLen *0.8);
NumMaxcoutourLen.Value = (decimal)(coutourLen *1.2);
NumMinContourArea.Value = (decimal)(contourArea * 0.8);
NumMaxContourArea.Value = (decimal)(contourArea * 1.2);
NumMatchValue.Value = (decimal)0.5;
MessageBox.Show("模板创建完成!");
}
}
c). 模板匹配
多角度轮廓匹配算法,同时通过钜来获取中心,和角度
//模板匹配
void TestModelMatch()
{
if (GrabImg == null || GrabImg.Width <= 0)
{
stuModelMatchData.runFlag = false;
MessageBox.Show("未获取图像");
return;
}
if (templateContour == null)
{
stuModelMatchData.runFlag = false;
MessageBox.Show("模板不存在,请先创建模板!");
return;
}
runTool = new ShapeMatchTool();
parmaData = new ShapeMatchData();
(parmaData as ShapeMatchData).tpContour = templateContour;
(parmaData as ShapeMatchData).Segthreshold = (double)NumSegthreshold.Value;
(parmaData as ShapeMatchData).MatchValue = (double)NumMatchValue.Value;
(parmaData as ShapeMatchData).MincoutourLen = (int)NumMincoutourLen.Value;
(parmaData as ShapeMatchData).MaxcoutourLen = (int)NumMaxcoutourLen.Value;
(parmaData as ShapeMatchData).MinContourArea = (int)NumMinContourArea.Value;
(parmaData as ShapeMatchData).MaxContourArea = (int)NumMaxContourArea.Value;
(parmaData as ShapeMatchData).baseAngle = modelangle;
ResultOfToolRun = runTool.Run(GrabImg, parmaData as ShapeMatchData);
currvisiontool.clearAll();
currvisiontool.dispImage(ResultOfToolRun.resultToShow);
ShapeMatchResult shapeMatchResult = ResultOfToolRun as ShapeMatchResult;
if (shapeMatchResult.scores.Count <= 0)
{
currvisiontool.DrawText(new TextEx("模板匹配失败!") {x=1000,y=10, brush = new SolidBrush(Color.Red) });
currvisiontool.AddTextBuffer(new TextEx("模板匹配失败!") { x = 1000, y = 10, brush = new SolidBrush(Color.Red) });
stuModelMatchData.runFlag = false;
return;
}
currvisiontool.DrawText(new TextEx("得分:" + shapeMatchResult.scores[0].ToString("f3")));
currvisiontool.AddTextBuffer(new TextEx("得分:" + shapeMatchResult.scores[0].ToString("f3")));
currvisiontool.DrawText(new TextEx("偏转角度:" + shapeMatchResult.rotations[0].ToString("f3")) { x = 10, y = 100 });
currvisiontool.AddTextBuffer(new TextEx("偏转角度:" + shapeMatchResult.rotations[0].ToString("f3")) { x = 10, y = 100 });
currvisiontool.DrawText(new TextEx(string.Format("匹配点位X:{0},Y:{1}", shapeMatchResult.positions[0].X.ToString("f3"),
shapeMatchResult.positions[0].Y.ToString("f3")))
{ x = 10, y = 200 });
currvisiontool.AddTextBuffer(new TextEx(string.Format("匹配点位X:{0},Y:{1}", shapeMatchResult.positions[0].X.ToString("f3"),
shapeMatchResult.positions[0].Y.ToString("f3")))
{ x = 10, y = 200 });
stuModelMatchData.matchPoint = shapeMatchResult.positions[0];
stuModelMatchData.matchOffsetAngle = shapeMatchResult.rotations[0];
stuModelMatchData.matchScore = shapeMatchResult.scores[0];
stuModelMatchData.runFlag = true;
}
3. 关键部位代码如下,包含模板创建,模板多角度匹配等
a)创建形状轮廓模板核心代码如下:
///
/// 创建形状轮廓模板
///
/// 模板图像
/// 分割阈值
/// 模板轮廓
/// 模板轮廓长度
/// 模板轮廓面积
/// 模板轮廓X
/// 模板轮廓Y
/// 模板轮廓角度
/// 返回绘制图
public Mat CreateTemplateContours(Mat img_template,double Segthreshold, CVRect boundingRect,
ref CVPoint[] templateContour, ref double coutourLen, ref double contourArea,
ref double modelx,ref double modely,ref double modelangle)
{
//灰度化
//Mat gray_img_template = new Mat();
//Cv2.CvtColor(img_template, gray_img_template, ColorConversionCodes.BGR2GRAY);
//阈值分割
Mat thresh_img_template = new Mat();
Cv2.Threshold(img_template, thresh_img_template, Segthreshold, 255, ThresholdTypes.Binary);
//开运算处理,提出白色噪点
Mat ellipse = Cv2.GetStructuringElement(MorphShapes.Ellipse, new Size(3, 3));
Cv2.MorphologyEx(thresh_img_template, thresh_img_template, MorphTypes.Open, ellipse);
//Mat cannyMat = new Mat();
//Cv2.Canny(thresh_img_template, cannyMat, Segthreshold, 255);
//寻找边界
CVPoint[][] contours_template;
//Vector> contours_template=new Vector>();
//Vector hierarchy=new Vector();
// HierarchyIndex[] hierarchy;
Cv2.FindContours(thresh_img_template, out contours_template, out _, RetrievalModes.Tree,
ContourApproximationModes.ApproxNone);
CVPoint[][] ExceptContours = ContourOperate.ExceptBoundPoints(img_template.BoundingRect(), contours_template);
int count = ExceptContours.ToList().Count;
List ModelContours=new List();
for (int i=0;i< count; i++)
{
if(Cv2.ContourArea(ExceptContours[i])>= contourArea&&
Cv2.ArcLength(ExceptContours[i],false)>= coutourLen)
//if (ExceptContours[i].Length > 30)//至少30点有效
ModelContours.Add(ExceptContours[i]);
}
ModelContours = ModelContours.OrderByDescending(s => s.Length).ToList();
//绘制边界
Mat dst = new Mat();
Cv2.CvtColor(img_template, dst, ColorConversionCodes.GRAY2BGR);
if(ModelContours.Count>0)
{
Cv2.DrawContours(dst, ModelContours, 0, new Scalar(0, 0, 255));
//获取重心点
Moments M;
M = Cv2.Moments(ModelContours[0]);
double cX = (M.M10 / M.M00);
double cY = (M.M01 / M.M00);
float a = (float)(M.M20 / M.M00 - cX * cX);
float b = (float)(M.M11 / M.M00 - cX * cY);
float c = (float)(M.M02 / M.M00 - cY * cY);
//计算角度(0~180)
// double tanAngle = Cv2.FastAtan2(2 * b, (a - c)) / 2;
//计算角度2(-90~90)
// double ang = (Math.Atan2(2 * b, (a - c)) * 180 / Math.PI) / 2;
//double ang2= Cv2.MinAreaRect(ModelContours[0]).Angle;
//if (tanAngle > 90)
// tanAngle -= 180;
//当前轮廓旋转矩
RotatedRect currrect = Cv2.MinAreaRect(ModelContours[0]);
//绘制旋转矩形
dst.DrawRotatedRect(currrect, Scalar.Lime);
//绘制目标边界
Cv2.DrawContours(dst, ModelContours, 0, new Scalar(0, 0, 255));
//显示目标中心
dst.drawCross(new CVPoint((int)cX, (int)cY),
new Scalar(0, 255, 0), 10, 2);
//
//CVPoint[] HullP = Cv2.ConvexHull(ModelContours[0], true);//顺时针方向
//List HullPList = new List();
//HullPList.Add(HullP);
Cv2.Polylines(dst, HullPList, true, Scalar.Red);
//Point2f cVPoint = CalBestDisP(new Point2d(cX, cY), HullP);
//double ang3 = ang;
//if(!(cVPoint.X==0&& cVPoint.Y == 0))
// {
// //计算角度2(-180~180)
// ang3 = calAngleOfLx(cX, cY, cVPoint.X, cVPoint.Y);
// Cv2.Line(dst, (int)cX, (int)cY, (int)cVPoint.X, (int)cVPoint.Y, Scalar.DarkOrange);
//}
//轮廓点位
modelx = cX;
modely = cY;
modelangle = currrect.Angle;
//轮廓长度
coutourLen = Cv2.ArcLength(ModelContours[0],false);
contourArea = Cv2.ContourArea(ModelContours[0]);
templateContour = ModelContours[0];
}
else
{
//轮廓点位
modelx = 0;
modely = 0;
modelangle = 0;
//轮廓长度
coutourLen = 0;
contourArea = 0;
templateContour =null;
}
return dst;
}
b)形状多角度匹配核心算法如下:
///
/// 形状匹配
///
/// 输入图像
/// 模板轮廓
/// 分割阈值
/// 匹配值
/// 轮廓最小长度
/// 轮廓最大长度
/// 轮廓最小面积
/// 轮廓最大面积
/// 匹配结果
/// 是否使用多模板
/// 返回绘制图
bool ShapeTemplateMatch(Mat image, CVPoint[] imgTemplatecontours, double Segthreshold,
double MatchValue, int MincoutourLen, int MaxcoutourLen,
double MinContourArea, double MaxContourArea, double baseAngle,
ref ShapeMatchResult shapeMatchResult,
bool isMultipleTemplates=false)
{
//List image_coordinates = new List();
//灰度化
//Mat gray_img=new Mat();
//Cv2.CvtColor(image, gray_img, ColorConversionCodes.BGR2GRAY);
Mat dst = new Mat();
Cv2.CvtColor(image, dst, ColorConversionCodes.GRAY2BGR);
//阈值分割
Mat thresh_img = new Mat();
Cv2.Threshold(image, thresh_img, Segthreshold, 255, ThresholdTypes.Binary);
#region------此处增加与模板创建时候同样的图像处理--------
//开运算处理,提出白色噪点
Mat ellipse = Cv2.GetStructuringElement(MorphShapes.Ellipse, new Size(3, 3));
Cv2.MorphologyEx(thresh_img, thresh_img, MorphTypes.Open, ellipse);
#endregion
//Mat cannyMat = new Mat();
//Cv2.Canny(thresh_img, cannyMat, Segthreshold, 255);
//寻找边界
CVPoint[][] contours_img;
//HierarchyIndex[] hierarchy;
Cv2.FindContours(thresh_img, out contours_img, out _, RetrievalModes.Tree,
ContourApproximationModes.ApproxNone);
//根据形状模板进行匹配
int min_pos = -1;
double min_value = MatchValue;//匹配分值
List points = contours_img.ToList();
//List angleList = new List();
for (int i = 0; i < points.Count; i++)
{
//计算轮廓面积,筛选掉一些没必要的小轮廓
if (Cv2.ContourArea(contours_img[i]) < MinContourArea ||
Cv2.ContourArea(contours_img[i]) > MaxContourArea)
continue;
//轮廓长度不达标
if (Cv2.ArcLength(contours_img[i], false) < MincoutourLen ||
Cv2.ArcLength(contours_img[i], false) > MaxcoutourLen)
continue;
//得到匹配分值 ,值越小相似度越高
double value = Cv2.MatchShapes(contours_img[i], imgTemplatecontours,
ShapeMatchModes.I3, 0.0);
value = 1 - value;
//将匹配分值与设定分值进行比较
if (value >= min_value)
{
min_pos = i;
//将目标的得分都存在数组中
shapeMatchResult.scores.Add(value);
//匹配到的轮廓
shapeMatchResult.contours.Add(contours_img[min_pos]);
/*----------------*/
}
}
/*----------------*/
int count = shapeMatchResult.scores.Count;
if(count<=0)
{
shapeMatchResult.resultToShow = dst;
shapeMatchResult.exceptionInfo = "模板匹配失败!";
return false;
}
if (isMultipleTemplates)
{
for (int j = 0; j < count; j++)
{
//绘制目标边界
Cv2.DrawContours(dst, shapeMatchResult.contours, j, new Scalar(0, 0, 255));
//得分绘制
Cv2.PutText(dst,
string.Format("Score:{0};Angle:{1}", shapeMatchResult.scores[j].ToString("F3"),
shapeMatchResult.rotations[j].ToString("F3")),
//anglebuf[j].ToString("F3")),
new CVPoint(shapeMatchResult.contours[j][0].X + 10, shapeMatchResult.contours[j][0].Y - 10),
HersheyFonts.HersheyDuplex, 1, Scalar.Yellow);
//显示目标中心并提取坐标点
dst.drawCross(new CVPoint((int)shapeMatchResult.positions[j].X, (int)shapeMatchResult.positions[j].Y),
new Scalar(0, 255, 0), 10, 2);
//当前轮廓旋转矩
RotatedRect currrect = Cv2.MinAreaRect(shapeMatchResult.contours[j]);
dst.DrawRotatedRect(currrect, Scalar.Lime);
}
}
else
{
double bestScore= shapeMatchResult.scores.Max(); //最佳得分
int index = shapeMatchResult.scores.FindIndex(s=>s== bestScore);
// double bestangle = shapeMatchResult.rotations[index]; //最佳角度
// Point2d bestpos = shapeMatchResult.positions[index]; //最佳点位
CVPoint[] bestcontour= shapeMatchResult.contours[index]; //最佳轮廓
//绘制目标边界
Cv2.DrawContours(dst, shapeMatchResult.contours, index, new Scalar(0, 0, 255));
//获取重心点
Moments M = Cv2.Moments(bestcontour);
double cX = (M.M10 / M.M00);
double cY = (M.M01 / M.M00);
float a = (float)(M.M20 / M.M00 - cX * cX);
float b = (float)(M.M11 / M.M00 - cX * cY);
float c = (float)(M.M02 / M.M00 - cY * cY);
//计算角度(0~180)
// double tanAngle = Cv2.FastAtan2(2 * b, (a - c)) / 2;
//angleList.Add(tanAngle);
//计算角度2(-90~90)
//double ang = (Math.Atan2(2 * b, (a - c)) * 180 / Math.PI) / 2;
#region----角度计算方式2---
//-90~90度
//由于先验目标最小包围矩形是长方形
//因此最小包围矩形的中心和重心的向量夹角为旋转
RotatedRect rect_template = Cv2.MinAreaRect(imgTemplatecontours);
RotatedRect rect_search = Cv2.MinAreaRect(bestcontour);
//两个旋转矩阵是否同向
float sign = (rect_template.Size.Width - rect_template.Size.Height) *
(rect_search.Size.Width - rect_search.Size.Height);
float angle=0;
if (sign > 0)
// 可以直接相减
angle = rect_search.Angle - rect_template.Angle;
else
angle = (90 + rect_search.Angle) - rect_template.Angle;
if (angle > 90)
angle -= 180;
#endregion
//显示目标中心并提取坐标点
dst.drawCross(new CVPoint((int)cX, (int)cY),
new Scalar(0, 255, 0), 10, 2);
//当前轮廓旋转矩
RotatedRect currrect = Cv2.MinAreaRect(bestcontour);
//绘制旋转矩形
dst.DrawRotatedRect(currrect, Scalar.Lime);
//CVPoint[] HullP = Cv2.ConvexHull(bestcontour, true);//顺时针方向
//List HullPList = new List();
//HullPList.Add(HullP);
//Cv2.Polylines(dst, HullPList, true, Scalar.Red);
//Point2f cVPoint = CalBestDisP(new Point2d(cX, cY), HullP);
//double ang3 = ang;
//if (!(cVPoint.X == 0 && cVPoint.Y == 0))
//{
// //计算角度2(-180~180)
// ang3 = calAngleOfLx(cX, cY, cVPoint.X, cVPoint.Y);
// Cv2.Line(dst, (int)cX, (int)cY, (int)cVPoint.X, (int)cVPoint.Y, Scalar.DarkOrange);
//}
//double offsetA = ang3 - baseAngle;//偏转角
//if (offsetA < -180)
// offsetA += 360;
//else if (offsetA > 180)
// offsetA -= 360;
//得分绘制
//Cv2.PutText(dst,
// string.Format("Score:{0};Angle:{1}", bestScore.ToString("F3"),
// ang3.ToString("F3")),
// new CVPoint(shapeMatchResult.contours[index][0].X + 10, shapeMatchResult.contours[index][0].Y - 10),
// HersheyFonts.HersheyDuplex, 1, Scalar.Yellow);
shapeMatchResult.positions.Clear();
shapeMatchResult.rotations.Clear();
shapeMatchResult.scores.Clear();
shapeMatchResult.contours.Clear();
//将目标的重心坐标都存在数组中
shapeMatchResult.positions.Add(new Point2d(cX, cY));//向数组中存放点的坐标
shapeMatchResult.rotations.Add(angle);//将偏转角度都存在数组中
shapeMatchResult.scores.Add(bestScore);//将目标的得分都存在数组中
shapeMatchResult.contours.Add(bestcontour); //匹配到的轮廓
/*----------------*/
}
shapeMatchResult.resultToShow = dst;
return true;
}
PS:众人拾柴火焰高,大家共同进步!