原模板匹配方法中,是无法进行任意角度和旋转匹配的,但我们在实际的使用中,模板图像的位置和方向都是不确定的,因此,就需要我们对模板图像进行预处理,处理后在进行模板匹配。
基本处理方法如下:
- 模板图像读取
- 进行模糊
- 绘制轮廓
- 形态学处理
- 提取轮廓
- 获取当前位置角度
- 图像旋转。
- 原图读取
- 从4个角度进行模板匹配(0°,90°,180°,270°)
- 记录每个角度的匹配值
- 进行后续操作。
1:原图如下所示:
2:模板图像如下所示:
3:思路:在模板图像中,抠出图像,并且找到当前的旋转角度,将其恢复为正向0°,在进行对其模板匹配,给出匹配结果。处理过程图像如下:
分别是将其调整为正向后,0°,90°,180°,270°的旋转匹配,同时记录匹配度的值。
源码图下:
public List<Point2d> GetResult(Mat tempImage, string srcImgPath, out double robotRotateAngel, out double arcVal)
{
List<Point2d> point2Ds = new List<Point2d>();
robotRotateAngel = 999;
arcVal = 0;
try
{
double rotAngel;
Point2f centerPoint;
Rect rect;
Mat tempMat = SetTemplateImage1(tempImage, out rotAngel, out rect, out centerPoint);
bool matchRet = GetMatchRet1(srcImgPath, tempMat, rotAngel, out robotRotateAngel, out arcVal);
if (!matchRet)
{
//图像识别失败
return null;
}
}
catch (Exception ex)
{
}
return point2Ds;
}
public Mat SetTemplateImage1(Mat image, out double rotAngel, out Rect rect, out Point2f centerPoint)
{
rotAngel = -1;
rect = new Rect();
centerPoint = new Point2f();
Mat blurImg = GaussBlurImage(image);//ip.MedBlurImage(image);//
Mat cannyImg = new Mat();
Cv2.Canny(blurImg, cannyImg, 30, 90);
Mat morphImg = MorphImage(cannyImg);
//Cv2.ImShow("morphImg1", morphImg);
Mat roiImage = GetRioOutline(morphImg, image, out rotAngel, out rect, out centerPoint);
//Cv2.ImShow("roiImage", roiImage);
Mat rotImg = ImageRotate(roiImage, rotAngel, rect);
//Cv2.ImShow("rotImg", rotImg);
Mat mat = GetRoi(rotImg, rect);
Cv2.ImShow("mat", mat);
return mat;
}
/// <summary>
/// 高斯 模糊
/// </summary>
/// <param name="img"></param>
/// <returns></returns>
public Mat GaussBlurImage(Mat image)
{
Mat gaussianimage = new Mat();
Cv2.GaussianBlur(image, gaussianimage, new OpenCvSharp.Size(3, 3), 3, 3);//尽可能多的去除杂质
return gaussianimage;
}
/// <summary>
/// 图像形态学操作
/// </summary>
/// <param name="image"></param>
/// <returns></returns>
public Mat MorphImage(Mat image)
{
Mat morphImage = new Mat();
Mat kernel = Cv2.GetStructuringElement(MorphShapes.Rect, new OpenCvSharp.Size(3, 3), new Point(-1, -1));
Cv2.MorphologyEx(image, morphImage, MorphTypes.Gradient, kernel, new Point(-1, -1), 2);
return morphImage;
}
/// <summary>
/// 提取图像轮廓
/// </summary>
/// <param name="morphImage">形态学图片</param>
/// <param name="srcImage">原图</param>
/// <param name="rotateAngel">返回旋转角度</param>
/// <param name="rect">返回获取的矩形边框</param>
/// <returns></returns>
public Mat GetRioOutline(Mat morphImage, Mat srcImage, out double rotateAngel, out Rect rect, out Point2f minRectcenterPoint)
{
OpenCvSharp.Point[][] contours;
HierarchyIndex[] hierarchies;
//Cv2.ImShow(",mmmm", morphImage);
Cv2.FindContours(morphImage, out contours, out hierarchies, RetrievalModes.External, ContourApproximationModes.ApproxTC89KCOS, new Point());
Mat connImg = Mat.Zeros(morphImage.Size(), MatType.CV_8UC3);
rotateAngel = 0;
//Point2f[] vertices = new Point2f[4];
minRectcenterPoint = new Point2f();
rect = new Rect();
int index = 0;
int count = -1;
//寻找最大轮廓
for (int i = 0; i < contours.Length; i++)
{
for (int j = 0; j < contours[i].Length; j++)
{
if (count < contours[i].Length)
{
count = contours[i].Length;
index = i;
}
}
}
//获取轮廓点的矩形区域
rect = Cv2.BoundingRect(contours[index]);
//minAreaRect():以X轴正方形为起点,顺时针为正,逆时针为负
//绘制Rio区域最小矩形
#region 绘制Rio区域最小矩形
RotatedRect minRect = Cv2.MinAreaRect(contours[index]);
rotateAngel = minRect.Angle;
minRectcenterPoint = minRect.Center;
//vertices = minRect.Points();
#endregion
//绘制最小矩形
#region 绘制最小矩形
//Cv2.Line(srcImage, Convert.ToInt32(vertices[0].X), Convert.ToInt32(vertices[0].Y), Convert.ToInt32(vertices[1].X), Convert.ToInt32(vertices[1].Y), new Scalar(0, 255, 255));
//Cv2.Line(srcImage, Convert.ToInt32(vertices[0].X), Convert.ToInt32(vertices[0].Y), Convert.ToInt32(vertices[3].X), Convert.ToInt32(vertices[3].Y), new Scalar(0, 255, 255));
//Cv2.Line(srcImage, Convert.ToInt32(vertices[1].X), Convert.ToInt32(vertices[1].Y), Convert.ToInt32(vertices[2].X), Convert.ToInt32(vertices[2].Y), new Scalar(0, 255, 255));
//Cv2.Line(srcImage, Convert.ToInt32(vertices[2].X), Convert.ToInt32(vertices[2].Y), Convert.ToInt32(vertices[3].X), Convert.ToInt32(vertices[3].Y), new Scalar(0, 255, 255));
//Console.WriteLine("AngleRect_angle: {0}", minRect.Angle);
#endregion
//Cv2.ImShow("srcImage", srcImage);
return srcImage;
}
/// <summary>
/// 图像旋转
/// </summary>
/// <param name="image">旋转图像</param>
/// <param name="rotateAngel">旋转角度</param>
/// <returns></returns>
public Mat ImageRotate(Mat image, double rotateAngel, Rect rect)
{
if (rotateAngel > -90 && rotateAngel <= -45)
{
rotateAngel += 90;
}
else if (!(rotateAngel > -45 && rotateAngel < 0))
{
//degree = degree;
rotateAngel = 0;
}
//OpenCvSharp.Point center = new OpenCvSharp.Point(image.Cols / 2, image.Rows / 2);
//以被截取的区域中心位置旋转
//边缘放大截取
OpenCvSharp.Point center = new OpenCvSharp.Point(rect.TopLeft.X + (rect.Width / 2), rect.TopLeft.Y + (rect.Height / 2));
//截取最小矩形
//OpenCvSharp.Point center = new OpenCvSharp.Point(image.Cols / 2, image.Rows / 2);
double angle = rotateAngel;
double scale = 1;
Mat rotate = new Mat(2, 3, MatType.CV_32FC1);
rotate = Cv2.GetRotationMatrix2D(center, angle, scale);//getRotationMatrix2D():以X轴正方形为起点,顺时针为负,逆时针为正
Mat rotImage = new Mat();
Cv2.WarpAffine(image, rotImage, rotate, image.Size(), InterpolationFlags.Linear, BorderTypes.Wrap);
int winSize = 0;
if (image.Cols > image.Rows)
{
winSize = image.Cols;
}
else
{
winSize = image.Rows;
}
return rotImage;
}
/// <summary>
/// 获取感兴趣区域
/// </summary>
/// <param name="image">图像</param>
/// <param name="rect">面积</param>
/// <returns></returns>
public Mat GetRoi(Mat image, Rect rect)
{
//Mat imageRoi = new Mat(image, new Rect(new Point(rect.TopLeft.X - rect.Width / 2, rect.TopLeft.Y), new Size(rect.Height, rect.Height)));
Mat imageRoi = new Mat(image, new Rect(new Point(rect.TopLeft.X, rect.TopLeft.Y), new Size(rect.Width, rect.Height)));
return imageRoi;
}
Dictionary<int, double> angelDic1;
/// <summary>
/// 模板匹配是否成功
/// </summary>
/// <param name="srcBmp">原图</param>
/// <param name="templateMat">模板图像</param>
/// <param name="rotAngel">初始旋转角度</param>
/// <param name="robotRotateAngel">机器人需要旋转的角度</param>
/// <returns></returns>
public bool GetMatchRet1(string srcImgFilePath, Mat templateMat, double rotAngel, out double robotRotateAngel, out double arcValue)
{
double matchVal = 0;
//最大旋转角度360
robotRotateAngel = 999;
arcValue = 0;
try
{
Mat src = Cv2.ImRead(@"E:\SoftwarePackage\CodePackage\ImgPro\Img\T1.bmp");
//src.ConvertTo(src, MatType.CV_8UC4);
if (src.Empty())
{
//Console.WriteLine("src could not load image...\n");
return false;
}
Cv2.ImShow("src", src);
//Mat templateMat = Cv2.ImRead(@"E:\SoftwarePackage\CodePackage\ImgPro\Img\T21.bmp");
if (templateMat.Empty())
{
//Console.WriteLine("src could not load image...\n");
return false;
}
angelDic1 = new Dictionary<int, double>(); ;
//记录角度
int rotateAngel = 0;
//第一张调正之后的图像
//Cv2.ImShow(0.ToString(), mat);
Point p;
Bitmap rotateBmp;
//旋转图像4个角度
for (int i = 0; i < 4; i++)
{
if (templateMat.Rows > src.Rows || templateMat.Cols > src.Cols)
{
continue;
}
Bitmap bmp = MatchTemplate(templateMat, src, out p, out matchVal);
if (matchVal > TemplateMatchThreshold)
{
angelDic1.Add(rotateAngel, matchVal);
}
//Mat rotImg1 = ip.ImageRotate(mat, 90);
rotateBmp = BitmapConverter.ToBitmap(templateMat);
rotateBmp.RotateFlip(RotateFlipType.Rotate90FlipXY);
rotateAngel += 90;
templateMat = BitmapConverter.ToMat(rotateBmp);
if (1 + i > 3)
{
break;
}
Cv2.ImShow((i + 1).ToString(), templateMat);
}
//若匹配值没有符合条件
if (angelDic1.Count < 1)
{
return false;
}
List<double> matchRet = new List<double>();
foreach (KeyValuePair<int, double> kvp in angelDic1)
{
matchRet.Add(kvp.Value);
}
//判断匹配最大值是否大于设定阈值 否则 匹配失败
//if (matchRet.Max() < TemplateMatchThreshold)
//{
// return false;
//}
//抠出模板图像 与原图进行模板匹配,并且记住最大匹配度的角度
var ag = angelDic1.Where(q => q.Value == matchRet.Max()).Select(q => q.Key);
robotRotateAngel = CalcRotateAngel(rotAngel, ag.Max());
//角度转弧度
arcValue = robotRotateAngel * arc;
}
catch (Exception ex)
{
//LogHelper.WriteLog(ex.Message, ex);
}
return true;
}
private double arc1 = Math.PI / 180;
/// <summary>
/// 模板匹配
/// </summary>
/// <param name="tempalte">模板图片像</param>
/// <param name="srcPic">被匹配图像</param>
/// <returns>标注匹配区域的图像</returns>
public Bitmap MatchTemplate(Mat tempalte, Mat srcPic, out OpenCvSharp.Point tempPoint, out double matchValue)
{
using (Mat result = new Mat()) //匹配结果
{ //模板匹配
Double minVul;
Double maxVul;
OpenCvSharp.Point minLoc = new OpenCvSharp.Point(0, 0);
OpenCvSharp.Point maxLoc = new OpenCvSharp.Point(0, 0);
OpenCvSharp.Point matchLoc = new OpenCvSharp.Point(0, 0);
Cv2.MatchTemplate(srcPic, tempalte, result, TemplateMatchModes.CCoeffNormed);//CCoeffNormed 最好匹配为1,值越小匹配越差 xxxNormed的算法无需归一化
//Cv2.Normalize(result, result, 0, 1, NormTypes.MinMax, -1);//归一化
Cv2.MinMaxLoc(result, out minVul, out maxVul, out minLoc, out maxLoc);//查找极值
//Cv2.MinMaxLoc(result, out minVul, out maxVul, out minLoc, out maxLoc, null);//查找极值
matchLoc = maxLoc;//最大值坐标
//result.Set(matchLoc.Y, matchLoc.X, 0);//改变最大值为最小值
Mat mask = srcPic.Clone();//复制整个矩阵
//Console.WriteLine("最大值:{0},X:{1},Y:{2}", maxVul, matchLoc.Y, matchLoc.X);
matchValue = maxVul;
tempPoint.X = matchLoc.X;
tempPoint.Y = matchLoc.Y;
//画框显示 :对角线画框,起点和终点,都用Point,线宽
if (matchValue > TemplateMatchThreshold)
{
Cv2.Rectangle(mask, matchLoc, new OpenCvSharp.Point(matchLoc.X + tempalte.Cols, matchLoc.Y + tempalte.Rows), Scalar.Green, 2);//2代表画的线条的宽细程度
}
return OpenCvSharp.Extensions.BitmapConverter.ToBitmap(mask);
}
}
/// <summary>
/// 计算角度
/// </summary>
/// <param name="srcAngel"></param>
/// <param name="rotateAngle"></param>
/// <returns></returns>
public double CalcRotateAngel(double srcAngel, int rotateAngle)
{
//逆时针旋转为负 顺时针旋转为正
double tempSrcAngel = Math.Abs(srcAngel);
double resultAngel = -1;
if (tempSrcAngel >= 45)
{
switch (rotateAngle)
{
case 0:
resultAngel = 90 - tempSrcAngel;
break;
case 90:
resultAngel = 180 - tempSrcAngel;
break;
case 180:
//逆时针
resultAngel = -(90 + tempSrcAngel);
break;
case 270:
//逆时针
resultAngel = -tempSrcAngel;
break;
}
}
else if (tempSrcAngel < 45)
{
switch (rotateAngle)
{
case 0:
//逆时针
resultAngel = -tempSrcAngel;
break;
case 90:
resultAngel = 90 - tempSrcAngel;
break;
case 180:
resultAngel = 90 + tempSrcAngel;
break;
case 270:
//逆时针
resultAngel = -(90 + tempSrcAngel);
break;
}
}
return resultAngel;
}
完整代码都在上面了,感兴趣的小伙伴可以在优化优化。下个案例见。