EMGU.CV进阶 (一、银行卡识别)

22 篇文章 27 订阅
21 篇文章 0 订阅

一、效果

识别出银行卡上的数字,并显示
注:本文所用所有知识,均在入门系列提到过
原图:
在这里插入图片描述
效果:
在这里插入图片描述

二、模板制作

目的,将10个数分成10个模板

2.1 加载模板

在这里插入图片描述

var imgTemplate = new Mat("NumberTemplate.png");
CvInvoke.Imshow("template", imgTemplate);

2.2 转为灰度

在这里插入图片描述

var imgTemplateGray = new Mat();
CvInvoke.CvtColor(imgTemplate, imgTemplateGray, ColorConversion.Bgr2Gray);
CvInvoke.Imshow("template Gray", imgTemplateGray);

2.3 阈值

在这里插入图片描述

var imgThreshold = new Mat();
CvInvoke.Threshold(imgTemplateGray, imgThreshold, 10, 255, ThresholdType.BinaryInv);
CvInvoke.Imshow("imgThreshold", imgThreshold);

2.4 轮廓检测

在这里插入图片描述

VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint();
var hierarchy = new Mat();
CvInvoke.FindContours(imgThreshold, contours, hierarchy, RetrType.External, ChainApproxMethod.ChainApproxSimple);
//RetrType.External 只检测外轮廓, 2. ChainApproxMethod.ChainApproxSimple 只保留终点坐标
var img0 = imgTemplate.Clone();
CvInvoke.DrawContours(img0, contours, -1, new MCvScalar(0, 0, 255), 2);
CvInvoke.Imshow("imgContours", img0);

2.5 轮廓排序

一个简单的冒泡排序,根据其X坐标的大小,将轮廓从左到右一一对应:

var array = contours.ToArrayOfArray();
for (int i = 0; i < contours.Size - 1; i++)
{
    for (int j = 0; j < contours.Size - i - 1; j++)
        if (array[j][0].X > array[j + 1][0].X)
        {
            var temp = array[j + 1];
            array[j + 1] = array[j];
            array[j] = temp;
        }
}
var img1 = imgTemplate.Clone();
VectorOfVectorOfPoint contoursSorted = new VectorOfVectorOfPoint(array);
CvInvoke.DrawContours(img1, contoursSorted, -1, new MCvScalar(0, 0, 255), 2);
CvInvoke.Imshow("imgContours2", img1);

2.6 计算外接矩形

在这里插入图片描述

 Rectangle[] rects = new Rectangle[contours.Size];
 for (int i = 0; i < contoursSorted.Size; i++)
 {
     rects[i] = CvInvoke.BoundingRectangle(contoursSorted[i]);
 }
 var img2 = imgTemplate.Clone();
 CvInvoke.Rectangle(img2, rects[3], new MCvScalar(0, 0, 255), 2);
 CvInvoke.Rectangle(img2, rects[4], new MCvScalar(0, 0, 255), 2);
 CvInvoke.Imshow("imgContours3", img2);

2.7 模板裁剪

将模板裁剪为10个类似下图的小模板
在这里插入图片描述

 VectorOfMat imgTemplates = new VectorOfMat();
 foreach (var r in rects)
  {
      var mat = new Mat(imgTemplateGray, r);
      imgTemplates.Push(mat);
  }
 CvInvoke.Imshow("imgTemplate3", imgTemplates[3]);

三、银行卡操作

3.1 读取银行卡

在这里插入图片描述

3.2 转为灰度

在这里插入图片描述

var imgCardGray = new Mat();
CvInvoke.CvtColor(imgCard, imgCardGray, ColorConversion.Bgr2Gray);
CvInvoke.Imshow("CardGray", imgCardGray);

3.3 阈值

在这里插入图片描述

 var imgCardThreshold = new Mat();
CvInvoke.Threshold(imgCardGray, imgCardThreshold, 0, 255, ThresholdType.Otsu | ThresholdType.Binary);
CvInvoke.Imshow("imgCardThreshold", imgCardThreshold);

3.4 顶帽

在这里插入图片描述

var rectKernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(9, 3), new Point(-1, -1));
var imgTophat = new Mat();
CvInvoke.MorphologyEx(imgCardThreshold, imgTophat, MorphOp.Tophat, rectKernel, new Point(-1, -1), 1, BorderType.Default, new MCvScalar());
CvInvoke.Imshow("tophat", imgTophat);

3.5 sobel

在这里插入图片描述

var imgSobel = new Mat();
CvInvoke.Sobel(imgTophat, imgSobel, DepthType.Cv32F, 1, 0, -1);
CvInvoke.ConvertScaleAbs(imgSobel, imgSobel, 1, 0);
CvInvoke.Imshow("sobel", imgSobel);

3.6 闭运算

在这里插入图片描述

var imgClose = new Mat();
CvInvoke.MorphologyEx(imgSobel, imgClose, MorphOp.Close, rectKernel, new Point(-1, -1), 4, BorderType.Default, new MCvScalar());
CvInvoke.Imshow("Close", imgClose);

3.7 查找轮廓

在这里插入图片描述

VectorOfVectorOfPoint cardContours = new VectorOfVectorOfPoint();
var cardHierarchy = new Mat();
CvInvoke.FindContours(imgClose, cardContours, cardHierarchy, RetrType.External, ChainApproxMethod.ChainApproxSimple);
// RetrType.External 只检测外轮廓, 2. ChainApproxMethod.ChainApproxSimple 只保留终点坐标
var imgCardContours = imgCard.Clone();
CvInvoke.DrawContours(imgCardContours, cardContours, -1, new MCvScalar(0, 0, 255), 2);
CvInvoke.Imshow("imgCardContours", imgCardContours);

3.8 过滤轮廓

在这里插入图片描述

int CardRectCount = 4;
Rectangle[] cardRects = new Rectangle[CardRectCount];
CardRectCount = 0;
var imgCardTest = imgCard.Clone();
for (int i = 0; i < cardContours.Size; i++)
{
  var rect = CvInvoke.BoundingRectangle(cardContours[i]);
  var radio = rect.Width / (double)rect.Height;
  if (radio > 2.5 && radio < 4.0 && rect.Height > 20)
  {
      cardRects[CardRectCount] = rect;
      CvInvoke.Rectangle(imgCardTest, rect, new MCvScalar(0, 0, 255), 2);
      CvInvoke.Imshow("imgCardTest", imgCardTest);
      CardRectCount++;
  }
}

3.9 轮廓排序

在这里插入图片描述

for (int i = 0; i < cardRects.Length - 1; i++)
{
   for (int j = 0; j < cardRects.Length - i - 1; j++)
       if (cardRects[j].X > cardRects[j + 1].X)
       {
           var temp = cardRects[j + 1];
           cardRects[j + 1] = cardRects[j];
           cardRects[j] = temp;
       }
}
var imgcard1 = imgCard.Clone();
CvInvoke.Rectangle(imgcard1, cardRects[0], new MCvScalar(0, 0, 255), 2);
CvInvoke.Rectangle(imgcard1, cardRects[1], new MCvScalar(0, 0, 255), 2);
CvInvoke.Imshow("imgContours2", imgcard1);

四、模板匹配

4.1 裁剪轮廓

在这里插入图片描述

VectorOfMat cardResizes = new VectorOfMat();
foreach(var rect in cardRects) { 
  // 裁剪四个轮廓
  var mat= new Mat(imgCardGray,new Rectangle(rect.X-5,rect.Y-5,rect.Width+10,rect.Height+10) );
  cardResizes.Push(mat);
}
CvInvoke.Imshow("cardResizes[2]", cardResizes[2]);

4.2 裁剪数字

在这里插入图片描述

VectorOfMat NumberVector =  new VectorOfMat();
for(int i = 0; i < cardResizes.Size; i++)
{
  // 二值化阈值
  var th = new Mat();
  CvInvoke.Threshold(cardResizes[i], th, 0, 255, ThresholdType.Binary | ThresholdType.Otsu);
 // CvInvoke.Imshow("t", th);
  // 查找轮廓
  VectorOfVectorOfPoint c = new VectorOfVectorOfPoint();
  var h = new Mat();
  CvInvoke.FindContours(th, c, h, RetrType.External, ChainApproxMethod.ChainApproxSimple);
  // RetrType.External 只检测外轮廓, 2. ChainApproxMethod.ChainApproxSimple 只保留终点坐标
  var imgTemp = cardResizes[i].Clone();
  CvInvoke.DrawContours(imgTemp, c, -1, new MCvScalar(0, 0, 0), 2);
 // CvInvoke.Imshow("c", imgTemp);
  // 计算外接矩形
  Rectangle[] rs = new Rectangle[4];
  for (int j = 0; j < c.Size; j++)
  {
      rs[j] = CvInvoke.BoundingRectangle(c[j]);
  }
  // 排序外接矩形
  for (int x = 0; x < rs.Length - 1; x++)
  {
      for (int y = 0; y < rs.Length - x - 1; y++)
          if (rs[y].X > rs[y + 1].X)
          {
              var temp = rs[y + 1];
              rs[y + 1] = rs[y];
              rs[y] = temp;
          }
  }
  // 裁剪数字
  foreach (var r in rs) {
      NumberVector.Push(new Mat(cardResizes[i],r));
  }
}
CvInvoke.Imshow("NumberVector[2]", NumberVector[2]);

4.3 匹配数字

double minLoc = 0, maxLoc = 0;
Point minPoint = new Point();
Point maxPoint = new Point();
int[] result = new int[16];
double[] score = new double[16];
for (int i = 0; i < 16; i++)
{
    CvInvoke.Threshold(NumberVector[i], NumberVector[i], 0, 255, ThresholdType.BinaryInv | ThresholdType.Otsu);
    CvInvoke.Resize(NumberVector[i], NumberVector[i], new Size(20, 40));
    for (int j = 0; j < imgTemplates.Size; j++) {
        var res = new Mat();
        CvInvoke.Resize(imgTemplates[j], imgTemplates[j], new Size(20, 40));
        CvInvoke.MatchTemplate(NumberVector[i], imgTemplates[j], res, TemplateMatchingType.Ccoeff);
        CvInvoke.MinMaxLoc(res, ref minLoc, ref maxLoc, ref minPoint, ref maxPoint);
        if (score[i] < maxLoc) {
            score[i] = maxLoc;
            result[i] = j;
        }
    }
    
}

五、最终显示

5.1 绘制选择框

在这里插入图片描述

var display = imgCard.Clone();
foreach (var r in cardRects)
{
   CvInvoke.Rectangle(display, r, new MCvScalar(0, 0, 255), 2);
  
}

5.2 显示数字

在这里插入图片描述

for (int i = 0; i < 16; i++) {
   CvInvoke.PutText(display, result[i].ToString(), new Point(cardRects[0].X+i*20+i/4*50+10,    cardRects[0].Y-10), FontFace.HersheyTriplex, 1, new MCvScalar(0, 0, 255),2);
}
   
CvInvoke.Imshow("result", display);

六、完整代码

// 一、模板操作
// 1. 读取模板
var imgTemplate = new Mat("NumberTemplate.png");
//CvInvoke.Imshow("template", imgTemplate);

// 2. 转换为灰度
var imgTemplateGray = new Mat();
CvInvoke.CvtColor(imgTemplate, imgTemplateGray, ColorConversion.Bgr2Gray);
//CvInvoke.Imshow("template Gray", imgTemplateGray);

// 3. 阈值
var imgThreshold = new Mat();
CvInvoke.Threshold(imgTemplateGray, imgThreshold, 10, 255, ThresholdType.BinaryInv);
//CvInvoke.Imshow("imgThreshold", imgThreshold);

// 4. 轮廓检测
VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint();
var hierarchy = new Mat();
CvInvoke.FindContours(imgThreshold, contours, hierarchy, RetrType.External, ChainApproxMethod.ChainApproxSimple);
//RetrType.External 只检测外轮廓, 2. ChainApproxMethod.ChainApproxSimple 只保留终点坐标
var img0 = imgTemplate.Clone();
CvInvoke.DrawContours(img0, contours, -1, new MCvScalar(0, 0, 255), 2);
// CvInvoke.Imshow("imgContours", img0);

// 5. 轮廓排序
var array = contours.ToArrayOfArray();
for (int i = 0; i < contours.Size - 1; i++)
{
 for (int j = 0; j < contours.Size - i - 1; j++)
     if (array[j][0].X > array[j + 1][0].X)
     {
         var temp = array[j + 1];
         array[j + 1] = array[j];
         array[j] = temp;
     }
}
var img1 = imgTemplate.Clone();
VectorOfVectorOfPoint contoursSorted = new VectorOfVectorOfPoint(array);
CvInvoke.DrawContours(img1, contoursSorted, -1, new MCvScalar(0, 0, 255), 2);
// CvInvoke.Imshow("imgContours2", img1);

// 6. 计算外接矩形
Rectangle[] rects = new Rectangle[contours.Size];
for (int i = 0; i < contoursSorted.Size; i++)
{
 rects[i] = CvInvoke.BoundingRectangle(contoursSorted[i]);
}
var img2 = imgTemplate.Clone();
CvInvoke.Rectangle(img2, rects[3], new MCvScalar(0, 0, 255), 2);
CvInvoke.Rectangle(img2, rects[4], new MCvScalar(0, 0, 255), 2);
//CvInvoke.Imshow("imgContours3", img2);

// 7. 模板裁剪
VectorOfMat imgTemplates = new VectorOfMat();
foreach (var r in rects)
{
 var mat = new Mat(imgTemplateGray, r);
 imgTemplates.Push(mat);
}
//CvInvoke.Imshow("imgTemplate3", imgTemplates[3]);
// CvInvoke.Imshow("imgTemplate5", imgTemplates[5]);

// 二、银行卡操作
// 2.1 读取银行卡
var imgCard = new Mat("Card1.png");
CvInvoke.Imshow("card", imgCard);

// 2.2 转为灰度
var imgCardGray = new Mat();
CvInvoke.CvtColor(imgCard, imgCardGray, ColorConversion.Bgr2Gray);
CvInvoke.Imshow("CardGray", imgCardGray);

// 2.3 阈值
var imgCardThreshold = new Mat();
CvInvoke.Threshold(imgCardGray, imgCardThreshold, 0, 255, ThresholdType.Otsu | ThresholdType.Binary);
CvInvoke.Imshow("imgCardThreshold", imgCardThreshold);

// 2.4 顶帽操作
var rectKernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(9, 3), new Point(-1, -1));
var imgTophat = new Mat();
CvInvoke.MorphologyEx(imgCardThreshold, imgTophat, MorphOp.Tophat, rectKernel, new Point(-1, -1), 1, BorderType.Default, new MCvScalar());
CvInvoke.Imshow("tophat", imgTophat);

// 2.5 sobel
var imgSobel = new Mat();
CvInvoke.Sobel(imgTophat, imgSobel, DepthType.Cv32F, 1, 0, -1);
CvInvoke.ConvertScaleAbs(imgSobel, imgSobel, 1, 0);
CvInvoke.Imshow("sobel", imgSobel);

// 2.6 闭运算
var imgClose = new Mat();
CvInvoke.MorphologyEx(imgSobel, imgClose, MorphOp.Close, rectKernel, new Point(-1, -1), 4, BorderType.Default, new MCvScalar());
CvInvoke.Imshow("Close", imgClose);

// 2.7 查找轮廓
VectorOfVectorOfPoint cardContours = new VectorOfVectorOfPoint();
var cardHierarchy = new Mat();
CvInvoke.FindContours(imgClose, cardContours, cardHierarchy, RetrType.External, ChainApproxMethod.ChainApproxSimple);
// RetrType.External 只检测外轮廓, 2. ChainApproxMethod.ChainApproxSimple 只保留终点坐标
var imgCardContours = imgCard.Clone();
CvInvoke.DrawContours(imgCardContours, cardContours, -1, new MCvScalar(0, 0, 255), 2);
CvInvoke.Imshow("imgCardContours", imgCardContours);


// 2.8 过滤轮廓
int CardRectCount = 4;
Rectangle[] cardRects = new Rectangle[CardRectCount];
CardRectCount = 0;
var imgCardTest = imgCard.Clone();
for (int i = 0; i < cardContours.Size; i++)
{
 var rect = CvInvoke.BoundingRectangle(cardContours[i]);
 var radio = rect.Width / (double)rect.Height;
 if (radio > 2.5 && radio < 4.0 && rect.Height > 20)
 {
     cardRects[CardRectCount] = rect;
     CvInvoke.Rectangle(imgCardTest, rect, new MCvScalar(0, 0, 255), 2);
     CvInvoke.Imshow("imgCardTest", imgCardTest);
     CardRectCount++;
 }
}

// 2.9 轮廓排序
for (int i = 0; i < cardRects.Length - 1; i++)
{
 for (int j = 0; j < cardRects.Length - i - 1; j++)
     if (cardRects[j].X > cardRects[j + 1].X)
     {
         var temp = cardRects[j + 1];
         cardRects[j + 1] = cardRects[j];
         cardRects[j] = temp;
     }
}
var imgcard1 = imgCard.Clone();
CvInvoke.Rectangle(imgcard1, cardRects[0], new MCvScalar(0, 0, 255), 2);
CvInvoke.Rectangle(imgcard1, cardRects[1], new MCvScalar(0, 0, 255), 2);
CvInvoke.Imshow("imgContours2", imgcard1);

// 三、轮廓匹配
// 3.1 裁剪轮廓
VectorOfMat cardResizes = new VectorOfMat();
foreach(var rect in cardRects) { 
 // 裁剪四个轮廓
 var mat= new Mat(imgCardGray,new Rectangle(rect.X-5,rect.Y-5,rect.Width+10,rect.Height+10) );
 cardResizes.Push(mat);
// CvInvoke.Imshow("c",mat);

}
CvInvoke.Imshow("cardResizes[2]", cardResizes[2]);
// 3.2 裁剪每一个数字
VectorOfMat NumberVector =  new VectorOfMat();
for(int i = 0; i < cardResizes.Size; i++)
{
 // 二值化阈值
 var th = new Mat();
 CvInvoke.Threshold(cardResizes[i], th, 0, 255, ThresholdType.Binary | ThresholdType.Otsu);
// CvInvoke.Imshow("t", th);
 // 查找轮廓
 VectorOfVectorOfPoint c = new VectorOfVectorOfPoint();
 var h = new Mat();
 CvInvoke.FindContours(th, c, h, RetrType.External, ChainApproxMethod.ChainApproxSimple);
 // RetrType.External 只检测外轮廓, 2. ChainApproxMethod.ChainApproxSimple 只保留终点坐标
 var imgTemp = cardResizes[i].Clone();
 CvInvoke.DrawContours(imgTemp, c, -1, new MCvScalar(0, 0, 0), 2);
// CvInvoke.Imshow("c", imgTemp);
 // 计算外接矩形
 Rectangle[] rs = new Rectangle[4];
 for (int j = 0; j < c.Size; j++)
 {
     rs[j] = CvInvoke.BoundingRectangle(c[j]);
 }
 // 排序外接矩形
 for (int x = 0; x < rs.Length - 1; x++)
 {
     for (int y = 0; y < rs.Length - x - 1; y++)
         if (rs[y].X > rs[y + 1].X)
         {
             var temp = rs[y + 1];
             rs[y + 1] = rs[y];
             rs[y] = temp;
         }
 }
 // 裁剪数字
 foreach (var r in rs) {
     NumberVector.Push(new Mat(cardResizes[i],r));
 }
}
CvInvoke.Imshow("NumberVector[2]", NumberVector[2]);
// 3.3 匹配数字
double minLoc = 0, maxLoc = 0;
Point minPoint = new Point();
Point maxPoint = new Point();
int[] result = new int[16];
double[] score = new double[16];
for (int i = 0; i < 16; i++)
{
 CvInvoke.Threshold(NumberVector[i], NumberVector[i], 0, 255, ThresholdType.BinaryInv | ThresholdType.Otsu);
 CvInvoke.Resize(NumberVector[i], NumberVector[i], new Size(20, 40));
 NumberVector[i].Save($"0{i}.jpg");
 for (int j = 0; j < imgTemplates.Size; j++) {
     var res = new Mat();
     CvInvoke.Resize(imgTemplates[j], imgTemplates[j], new Size(20, 40));
     imgTemplates[j].Save($"1_{j}.jpg");
     CvInvoke.MatchTemplate(NumberVector[i], imgTemplates[j], res, TemplateMatchingType.Ccoeff);
     CvInvoke.MinMaxLoc(res, ref minLoc, ref maxLoc, ref minPoint, ref maxPoint);
     if (score[i] < maxLoc) {
         score[i] = maxLoc;
         result[i] = j;
     }
 }
 
}

// 四、 最终显示
var display = imgCard.Clone();
// 4.1 绘制选择框
foreach (var r in cardRects)
{
 CvInvoke.Rectangle(display, r, new MCvScalar(0, 0, 255), 2);

}
CvInvoke.Imshow("result1", display);
// 4.2 显示数字
for (int i = 0; i < 16; i++) {
 CvInvoke.PutText(display, result[i].ToString(), new Point(cardRects[0].X+i*20+i/4*50+10, cardRects[0].Y-10), FontFace.HersheyTriplex, 1, new MCvScalar(0, 0, 255),2);
}
 
CvInvoke.Imshow("result", display);

CvInvoke.WaitKey(0);
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值