「玩一玩」翻译:基于AForge.Net的扑克牌视频识别程序
看到有趣的东西,忍不住翻译过来了。
----------------
© 版权所有 野比 2012
原文地址:点击查看
作者:Nazmi Altun「土耳其」
源码下载:点我
demo下载:点我
介绍
(图片上的字:方块4,方块J,黑桃2)
用机器人配上扑克牌识别系统,就可以在二十一点一类的扑克游戏中扮演荷官或是人类玩家的角色。实现这样的程序同样也是学习计算机视觉和模式识别的好途径。
本文涉及到的AForge.NET框架技术有二值化、边缘检测、仿射变换、BLOB处理和模板匹配算法等。
需要注意的是,这篇文章和文中介绍的系统是针对英美扑克设计的,可能不适用于其他种类的扑克。然而,本文描述了扑克的检测和识别的基本方法。因此,具体的识别算法需要根据扑克牌型特点而加以变化。
这里有一个视频演示。
YouTube
直接访问 通过代理访问(在代理页面输入http://www.youtube.com/watch?v=dui3ftwsuhM然后访问)
(话说我传到优酷上了怎么总是「发布中」呢?)
扑克检测
我们需要检测图像(指采集到的视频画面,下同——野比注)上的扑克对象,以便能进行下一步的识别。为了完成检测,我们会用一些图像滤镜对视频画面进行处理。
第一步,将图像去色(即灰度化——野比注)。去色是将彩色图像转换成8bit图像的一种操作。我们需要将彩色图像转换为灰度图像以便对其进行二值化。
我们把彩色图像转为灰度图像后,对其进行二值化。二值化(阈值化)是将灰度图像转换为黑白图像的过程。本文使用Otsu的方法进行全局阈值化。
Bitmap temp = source.Clone() as Bitmap; // 复制原始图像 FiltersSequence seq = new FiltersSequence(); seq.Add(Grayscale.CommonAlgorithms.BT709); // 添加灰度滤镜 seq.Add(new OtsuThreshold()); // 添加二值化滤镜 temp = seq.Apply(source); // 应用滤镜
// 从图像中提取宽度和高度大于150的blob BlobCounter extractor = new BlobCounter(); extractor.FilterBlobs = true; extractor.MinWidth = extractor.MinHeight = 150; extractor.MaxWidth = extractor.MaxHeight = 350; extractor.ProcessImage(temp);
foreach (Blob blob in extractor.GetObjectsInformation()) { // 获取扑克牌的边缘点 List< IntPoint > edgePoints = extractor.GetBlobsEdgePoints(blob); // 利用边缘点,在原始图像上找到四角 List< IntPoint > corners = PointsCloud.FindQuadrilateralCorners(edgePoints); }
// 用于从原始图像提取扑克牌 QuadrilateralTransformation quadTransformer = new QuadrilateralTransformation(); // 用于调整扑克牌大小 ResizeBilinear resizer = new ResizeBilinear(CardWidth, CardHeight); foreach (Blob blob in extractor.GetObjectsInformation()) { // 获取扑克牌边缘点 List<IntPoint> edgePoints = extractor.GetBlobsEdgePoints(blob); // 利用边缘点,在原始图像上找到四角 List<IntPoint> corners = PointsCloud.FindQuadrilateralCorners(edgePoints); Bitmap cardImg = quadTransformer.Apply(source); // 提取扑克牌图像 if (cardImg.Width > cardImg.Height) // 如果扑克牌横放 cardImg.RotateFlip(RotateFlipType.Rotate90FlipNone); // 旋转之 cardImg = resizer.Apply(cardImg); // 归一化(重设大小)扑克牌 ..... }
public enum Rank { NOT_RECOGNIZED = 0, Ace = 1, Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten, Jack, Queen, King } public enum Suit { NOT_RECOGNIZED = 0, Hearts, Diamonds, Spades, Clubs }
public class Card { // 变量 private Rank rank; // 大小 private Suit suit; // 花色 private Bitmap image; // 提取出的图像 private Point[] corners ;// 四角点 // 属性 public Point[] Corners { get { return this.corners; } } public Rank Rank { set { this.rank = value; } } public Suit Suit { set { this.suit = value; } } public Bitmap Image { get { return this.image; } } // 构造函数 public Card(Bitmap cardImg, IntPoint[] cornerIntPoints) { this.image = cardImg; // 将AForge.IntPoint数组转化为System.Drawing.Point数组 int total = cornerIntPoints.Length; corners = new Point[total]; for(int i = 0 ; i < total ; i++) { this.corners[i].X = cornerIntPoints[i].X; this.corners[i].Y = cornerIntPoints[i].Y; } } }
public Bitmap GetTopRightPart() { if (image == null) return null; Crop crop = new Crop(new Rectangle(image.Width - 37, 10, 30, 60)); return crop.Apply(image); }
char color = 'B'; // 开始,锁像素 BitmapData imageData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, bmp.PixelFormat); int totalRed = 0; int totalBlack = 0; unsafe { // 统计红与黑 try { UnmanagedImage img = new UnmanagedImage(imageData); int height = img.Height; int width = img.Width; int pixelSize = (img.PixelFormat == PixelFormat.Format24bppRgb) ? 3 : 4; byte* p = (byte*)img.ImageData.ToPointer(); // 逐行 for (int y = 0; y < height; y++) { // 逐像素 for (int x = 0; x < width; x++, p += pixelSize) { int r = (int)p[RGB.R]; // 红 int g = (int)p[RGB.G]; // 绿 int b = (int)p[RGB.B]; // 蓝 if (r > g + b) // 红 > 绿 + 蓝 totalRed++; // 认为是红色 if (r <= g + b && r < 50 && g < 50 && b < 50) // 红绿蓝均小于50 totalBlack++; // 认为是黑色 } } } finally { bmp.UnlockBits(imageData); // 解锁 } } if (totalRed > totalBlack) // 红色占优 color = 'R'; // 设置颜色为红,否则默认黑色 return color;
private bool IsFaceCard(Bitmap bmp) { FiltersSequence commonSeq = new FiltersSequence(); commonSeq.Add(Grayscale.CommonAlgorithms.BT709); commonSeq.Add(new BradleyLocalThresholding()); commonSeq.Add(new DifferenceEdgeDetector()); Bitmap temp = this.commonSeq.Apply(bmp); ExtractBiggestBlob extractor = new ExtractBiggestBlob(); temp = extractor.Apply(temp); // 提取最大图块 if (temp.Width > bmp.Width / 2) // 如果宽度大于整个牌的一般宽 return true; // 人物牌 return false; // 数字牌 }
private Suit ScanSuit(Bitmap suitBmp, char color) { Bitmap temp = commonSeq.Apply(suitBmp); //Extract biggest blob on card ExtractBiggestBlob extractor = new ExtractBiggestBlob(); temp = extractor.Apply(temp); //Biggest blob is suit blob so extract it Suit suit = Suit.NOT_RECOGNIZED; //Determine type of suit according to its color and width if (color == 'R') suit = temp.Width >= 55 ? Suit.Diamonds : Suit.Hearts; if (color == 'B') suit = temp.Width <= 48 ? Suit.Spades : Suit.Clubs; return suit; }