🎴 自动识别扑克牌点数
© 版权所有 Conmajia 2012
原文地址
作者
Nazmi Altun
介绍
用机器人配上扑克牌识别系统
本文涉及到的
需要注意的是
这里有一个视频演示
© 版权所有 野比 2012
扑克检测
我们需要检测图像
第一步
我们把彩色图像转为灰度图像后
1 Bitmap temp = source.Clone() as Bitmap; // 复制原始图像 2 3 FiltersSequence seq = new FiltersSequence(); 4 seq.Add(Grayscale.CommonAlgorithms.BT709); // 添加灰度滤镜 5 seq.Add(new OtsuThreshold()); // 添加二值化滤镜 6 temp = seq.Apply(source); // 应用滤镜
有了二值图像后
1 // 从图像中提取宽度和高度大于150 的 blob 2 BlobCounter extractor = new BlobCounter(); 3 extractor.FilterBlobs = true; 4 extractor.MinWidth = extractor.MinHeight = 150; 5 extractor.MaxWidth = extractor.MaxHeight = 350; 6 extractor.ProcessImage(temp);
执行完上述代码后BlobCounter
现在extractor.GetObjectsInformation()
1 foreach (Blob blob in extractor.GetObjectsInformation()) 2 { 3 // 获取扑克牌的边缘点 4 List< IntPoint > edgePoints = extractor.GetBlobsEdgePoints(blob); 5 // 利用边缘点,在原始图像上找到四角 6 List< IntPoint > corners = PointsCloud.FindQuadrilateralCorners(edgePoints); 7 }
找到扑克牌的四角后
注意
1 // 用于从原始图像提取扑克牌 2 QuadrilateralTransformation quadTransformer = new QuadrilateralTransformation(); 3 // 用于调整扑克牌大小 4 ResizeBilinear resizer = new ResizeBilinear(CardWidth, CardHeight); 5 6 foreach (Blob blob in extractor.GetObjectsInformation()) 7 { 8 // 获取扑克牌边缘点 9 List<IntPoint> edgePoints = extractor.GetBlobsEdgePoints(blob); 10 // 利用边缘点,在原始图像上找到四角 11 List<IntPoint> corners = PointsCloud.FindQuadrilateralCorners(edgePoints); 12 Bitmap cardImg = quadTransformer.Apply(source); // 提取扑克牌图像 13 14 if (cardImg.Width > cardImg.Height) // 如果扑克牌横放 15 cardImg.RotateFlip(RotateFlipType.Rotate90FlipNone); // 旋转之 16 cardImg = resizer.Apply(cardImg); // 归一化(重设大小)扑克牌 17 ..... 18 }
到目前为止
© 版权所有 野比 2012
识别扑克牌
有好几种用于识别的技术用于识别扑克牌
1 public enum Rank 2 { 3 NOT_RECOGNIZED = 0, 4 Ace = 1, 5 Two, 6 Three, 7 Four, 8 Five, 9 Six, 10 Seven, 11 Eight, 12 Nine, 13 Ten, 14 Jack, 15 Queen, 16 King 17 } 18 public enum Suit 19 { 20 NOT_RECOGNIZED = 0, 21 Hearts, 22 Diamonds, 23 Spades, 24 Clubs 25 }
我们还将创建如下的
1 public class Card 2 { 3 // 变量 4 private Rank rank; // 大小 5 private Suit suit; // 花色 6 private Bitmap image; // 提取出的图像 7 private Point[] corners ;// 四角点 8 9 // 属性 10 public Point[] Corners 11 { 12 get { return this.corners; } 13 } 14 public Rank Rank 15 { 16 set { this.rank = value; } 17 } 18 public Suit Suit 19 { 20 set { this.suit = value; } 21 } 22 public Bitmap Image 23 { 24 get { return this.image; } 25 } 26 // 构造函数 27 public Card(Bitmap cardImg, IntPoint[] cornerIntPoints) 28 { 29 this.image = cardImg; 30 31 // 将AForge.IntPoint 数组转化为 System.Drawing.Point 数组 32 int total = cornerIntPoints.Length; 33 corners = new Point[total]; 34 35 for(int i = 0 ; i < total ; i++) 36 { 37 this.corners[i].X = cornerIntPoints[i].X; 38 this.corners[i].Y = cornerIntPoints[i].Y; 39 } 40 } 41 }
© 版权所有 野比 2012
识别花色
标准的扑克牌花色有四种
识别颜色
首先
1 public Bitmap GetTopRightPart() 2 { 3 if (image == null) 4 return null; 5 Crop crop = new Crop(new Rectangle(image.Width - 37, 10, 30, 60)); 6 7 return crop.Apply(image); 8 }
裁剪了扑克牌右上角后
现在
1 char color = 'B'; 2 // 开始,锁像素 3 BitmapData imageData = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), 4 ImageLockMode.ReadOnly, bmp.PixelFormat); 5 int totalRed = 0; 6 int totalBlack = 0; 7 8 unsafe 9 { 10 // 统计红与黑 11 try 12 { 13 UnmanagedImage img = new UnmanagedImage(imageData); 14 15 int height = img.Height; 16 int width = img.Width; 17 int pixelSize = (img.PixelFormat == PixelFormat.Format24bppRgb) ? 3 : 4; 18 byte* p = (byte*)img.ImageData.ToPointer(); 19 20 // 逐行 21 for (int y = 0; y < height; y++) 22 { 23 // 逐像素 24 for (int x = 0; x < width; x++, p += pixelSize) 25 { 26 int r = (int)p[RGB.R]; // 红 27 int g = (int)p[RGB.G]; // 绿 28 int b = (int)p[RGB.B]; // 蓝 29 30 if (r > g + b) // 红 > 绿 + 蓝 31 totalRed++; // 认为是红色 32 33 if (r <= g + b && r < 50 && g < 50 && b < 50) // 红绿蓝均小于50 34 totalBlack++; // 认为是黑色 35 } 36 } 37 } 38 finally 39 { 40 bmp.UnlockBits(imageData); // 解锁 41 } 42 } 43 if (totalRed > totalBlack) // 红色占优 44 color = 'R'; // 设置颜色为红,否则默认黑色 45 return color;
注意.NETBitmap.GetPixel()
区分人物牌和数字牌
识别了颜色后
为了找出一张扑克牌到底是人物牌还是数字牌非常简单
1 private bool IsFaceCard(Bitmap bmp) 2 { 3 FiltersSequence commonSeq = new FiltersSequence(); 4 commonSeq.Add(Grayscale.CommonAlgorithms.BT709); 5 commonSeq.Add(new BradleyLocalThresholding()); 6 commonSeq.Add(new DifferenceEdgeDetector()); 7 8 Bitmap temp = this.commonSeq.Apply(bmp); 9 ExtractBiggestBlob extractor = new ExtractBiggestBlob(); 10 temp = extractor.Apply(temp); // 提取最大图块 11 12 if (temp.Width > bmp.Width / 2) // 如果宽度大于整个牌的一般宽 13 return true; // 人物牌 14 15 return false; // 数字牌 16 }
所以我们不断的对扑克牌图像进行灰度变换
正如你所看到的
前面提到过
1 private Suit ScanSuit(Bitmap suitBmp, char color) 2 { 3 Bitmap temp = commonSeq.Apply(suitBmp); 4 //Extract biggest blob on card 5 ExtractBiggestBlob extractor = new ExtractBiggestBlob(); 6 temp = extractor.Apply(temp); //Biggest blob is suit blob so extract it 7 Suit suit = Suit.NOT_RECOGNIZED; 8 9 //Determine type of suit according to its color and width 10 if (color == 'R') 11 suit = temp.Width >= 55 ? Suit.Diamonds : Suit.Hearts; 12 if (color == 'B') 13 suit = temp.Width <= 48 ? Suit.Spades : Suit.Clubs; 14 15 return suit; 16 }
上述测试最大误差
人物牌牌面上没有类似数字牌的最大花色图像
在项目资源文件中有二值化模板图像
AForge.NET
1 private Suit ScanFaceSuit(Bitmap bmp, char color) 2 { 3 Bitmap clubs, diamonds, spades, hearts; // 花色模板 4 5 // 载入模板资源 6 clubs = PlayingCardRecognition.Properties.Resources.Clubs; 7 diamonds = PlayingCardRecognition.Properties.Resources.Diamonds; 8 spades = PlayingCardRecognition.Properties.Resources.Spades; 9 hearts = PlayingCardRecognition.Properties.Resources.Hearts; 10 11 // 用0.8 的相似度阈值初始化模板匹配类 12 ExhaustiveTemplateMatching templateMatching = new ExhaustiveTemplateMatching(0.8f); 13 Suit suit = Suit.NOT_RECOGNIZED; 14 15 if (color == 'R') // 如果是红色 16 { 17 if (templateMatching.ProcessImage(bmp, hearts).Length > 0) 18 suit = Suit.Hearts; //匹配红桃 19 if (templateMatching.ProcessImage(bmp, diamonds).Length > 0) 20 suit = Suit.Diamonds; // 匹配方块 21 } 22 else // 如果是黑色 23 { 24 if (templateMatching.ProcessImage(bmp,spades).Length > 0) 25 suit = Suit.Spades; // 匹配黑桃 26 if (templateMatching.ProcessImage(bmp, clubs).Length > 0) 27 suit = Suit.Clubs; // 匹配梅花 28 } 29 return suit; 30 }
当然
识别大小
识别大小和识别花色类似
下面所示的
1 private Rank ScanRank(Bitmap cardImage) 2 { 3 Rank rank = Rank.NOT_RECOGNIZED; 4 5 int total = 0; 6 Bitmap temp = commonSeq.Apply(cardImage); // 应用滤镜 7 BlobCounter blobCounter = new BlobCounter(); 8 blobCounter.FilterBlobs = true; 9 // 过滤小图块 10 blobCounter.MinHeight = blobCounter.MinWidth = 30; 11 blobCounter.ProcessImage(temp); 12 13 total = blobCounter.GetObjectsInformation().Length; // 获取总数 14 rank = (Rank)total; // 转换成大小(枚举类型) 15 16 return rank; 17 }
所以
1 private Rank ScanFaceRank(Bitmap bmp) 2 { 3 Bitmap j, k, q; // 人物牌人物模板 4 // 载入资源 5 j = PlayingCardRecognition.Properties.Resources.J; 6 k = PlayingCardRecognition.Properties.Resources.K; 7 q = PlayingCardRecognition.Properties.Resources.Q; 8 9 10 // 用0.75 进行初始化 11 ExhaustiveTemplateMatching templateMatchin = 12 new ExhaustiveTemplateMatching(0.75f); 13 Rank rank = Rank.NOT_RECOGNIZED; 14 15 if (templateMatchin.ProcessImage(bmp, j).Length > 0) // J 16 rank = Rank.Jack; 17 if (templateMatchin.ProcessImage(bmp, k).Length > 0)// K 18 rank = Rank.King; 19 if (templateMatchin.ProcessImage(bmp, q).Length > 0)// Q 20 rank = Rank.Queen; 21 22 return rank; 23 }
由于识别难度较大
已知问题
本文的实现
© 版权所有 野比 2012
结论
本文用到的图像用例来自
本文还可提高
历史
* 7th, Oct., 2011: 初稿
许可
本文及附带的源文件代码和文件
关于作者
Nazmi Altun
Softeare Developer
Turkey
© 版权所有 Conmajia 2012
if(jQuery('#no-reward').text() == 'true') jQuery('.bottom-reward').addClass('hidden');
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?