mirror of
https://github.com/bdnugget/Magic-Vision.git
synced 2026-11-07 09:17:36 +00:00
407 lines
14 KiB
C#
407 lines
14 KiB
C#
/* Magic Vision
|
|
* Created by Peter Simard
|
|
* You are free to use this source code any way you wish, all I ask for is an attribution
|
|
*/
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Data;
|
|
using System.Drawing;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Windows.Forms;
|
|
using System.Drawing.Imaging;
|
|
using DirectX.Capture;
|
|
using Microsoft.VisualBasic;
|
|
using AForge;
|
|
using AForge.Imaging;
|
|
using AForge.Imaging.Filters;
|
|
using AForge.Math.Geometry;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Runtime.InteropServices;
|
|
|
|
namespace PoolVision
|
|
{
|
|
public partial class Form1 : Form
|
|
{
|
|
private Bitmap cameraBitmap;
|
|
private Bitmap cameraBitmapLive;
|
|
private Bitmap filteredBitmap;
|
|
private Bitmap cardBitmap;
|
|
private Bitmap cardArtBitmap;
|
|
private String refCardDir = @"C:\Users\Pete\Pictures\New Phyrexia\Crops\";
|
|
private Capture capture = null;
|
|
private Filters cameraFilters = new Filters();
|
|
private List<MagicCard> magicCards = new List<MagicCard>();
|
|
private List<MagicCard> magicCardsLastFrame = new List<MagicCard>();
|
|
private List<ReferenceCard> referenceCards = new List<ReferenceCard>();
|
|
static readonly object _locker = new object();
|
|
|
|
public static string SqlConString = "SERVER=127.0.0.1;" +
|
|
"DATABASE=magiccards;" +
|
|
"UID=root;" +
|
|
"Allow Zero Datetime=true";
|
|
|
|
public MySqlClient sql = new MySqlClient(SqlConString);
|
|
|
|
public Form1()
|
|
{
|
|
InitializeComponent();
|
|
}
|
|
|
|
private void button1_Click(object sender, EventArgs e)
|
|
{
|
|
foreach (ReferenceCard card in referenceCards)
|
|
{
|
|
Phash.ph_dct_imagehash(refCardDir + (String)card.dataRow["Set"] + "\\" + card.cardId + ".jpg", ref card.pHash);
|
|
sql.dbNone("UPDATE cards SET pHash=" + card.pHash.ToString() + " WHERE id=" + card.cardId);
|
|
}
|
|
}
|
|
|
|
double GetDeterminant(double x1, double y1, double x2, double y2)
|
|
{
|
|
return x1 * y2 - x2 * y1;
|
|
}
|
|
|
|
double GetArea(IList<IntPoint> vertices)
|
|
{
|
|
if (vertices.Count < 3)
|
|
{
|
|
return 0;
|
|
}
|
|
double area = GetDeterminant(vertices[vertices.Count - 1].X, vertices[vertices.Count - 1].Y, vertices[0].X, vertices[0].Y);
|
|
for (int i = 1; i < vertices.Count; i++)
|
|
{
|
|
area += GetDeterminant(vertices[i - 1].X, vertices[i - 1].Y, vertices[i].X, vertices[i].Y);
|
|
}
|
|
return area / 2;
|
|
}
|
|
|
|
private void detectQuads(Bitmap bitmap)
|
|
{
|
|
// Greyscale
|
|
filteredBitmap = Grayscale.CommonAlgorithms.BT709.Apply(bitmap);
|
|
|
|
// edge filter
|
|
SobelEdgeDetector edgeFilter = new SobelEdgeDetector();
|
|
edgeFilter.ApplyInPlace(filteredBitmap);
|
|
|
|
// Threshhold filter
|
|
Threshold threshholdFilter = new Threshold(190);
|
|
threshholdFilter.ApplyInPlace(filteredBitmap);
|
|
|
|
BitmapData bitmapData = filteredBitmap.LockBits(
|
|
new Rectangle(0, 0, filteredBitmap.Width, filteredBitmap.Height),
|
|
ImageLockMode.ReadWrite, filteredBitmap.PixelFormat);
|
|
|
|
|
|
BlobCounter blobCounter = new BlobCounter();
|
|
|
|
blobCounter.FilterBlobs = true;
|
|
blobCounter.MinHeight = 125;
|
|
blobCounter.MinWidth = 125;
|
|
|
|
blobCounter.ProcessImage(bitmapData);
|
|
Blob[] blobs = blobCounter.GetObjectsInformation();
|
|
filteredBitmap.UnlockBits(bitmapData);
|
|
|
|
SimpleShapeChecker shapeChecker = new SimpleShapeChecker();
|
|
|
|
Bitmap bm = new Bitmap(filteredBitmap.Width, filteredBitmap.Height, PixelFormat.Format24bppRgb);
|
|
|
|
Graphics g = Graphics.FromImage(bm);
|
|
g.DrawImage(filteredBitmap, 0, 0);
|
|
|
|
Pen pen = new Pen(Color.Red, 5);
|
|
List<IntPoint> cardPositions = new List<IntPoint>();
|
|
|
|
|
|
// Loop through detected shapes
|
|
for (int i = 0, n = blobs.Length; i < n; i++)
|
|
{
|
|
List<IntPoint> edgePoints = blobCounter.GetBlobsEdgePoints(blobs[i]);
|
|
List<IntPoint> corners;
|
|
bool sameCard = false;
|
|
|
|
// is triangle or quadrilateral
|
|
if (shapeChecker.IsConvexPolygon(edgePoints, out corners))
|
|
{
|
|
// get sub-type
|
|
PolygonSubType subType = shapeChecker.CheckPolygonSubType(corners);
|
|
|
|
// Only return 4 corner rectanges
|
|
if ((subType == PolygonSubType.Parallelogram || subType == PolygonSubType.Rectangle) && corners.Count == 4)
|
|
{
|
|
// Check if its sideways, if so rearrange the corners so it's veritcal
|
|
rearrangeCorners(corners);
|
|
|
|
// Prevent it from detecting the same card twice
|
|
foreach (IntPoint point in cardPositions)
|
|
{
|
|
if (corners[0].DistanceTo(point) < 40)
|
|
sameCard = true;
|
|
}
|
|
|
|
if (sameCard)
|
|
continue;
|
|
|
|
// Hack to prevent it from detecting smaller sections of the card instead of the whole card
|
|
if (GetArea(corners) < 20000)
|
|
continue;
|
|
|
|
cardPositions.Add(corners[0]);
|
|
|
|
g.DrawPolygon(pen, ToPointsArray(corners));
|
|
|
|
// Extract the card bitmap
|
|
QuadrilateralTransformation transformFilter = new QuadrilateralTransformation(corners, 211, 298);
|
|
cardBitmap = transformFilter.Apply(cameraBitmap);
|
|
|
|
List<IntPoint> artCorners = new List<IntPoint>();
|
|
artCorners.Add(new IntPoint(14, 35));
|
|
artCorners.Add(new IntPoint(193, 35));
|
|
artCorners.Add(new IntPoint(193, 168));
|
|
artCorners.Add(new IntPoint(14, 168));
|
|
|
|
// Extract the art bitmap
|
|
QuadrilateralTransformation cartArtFilter = new QuadrilateralTransformation(artCorners, 183, 133);
|
|
cardArtBitmap = cartArtFilter.Apply(cardBitmap);
|
|
|
|
MagicCard card = new MagicCard();
|
|
card.corners = corners;
|
|
card.cardBitmap = cardBitmap;
|
|
card.cardArtBitmap = cardArtBitmap;
|
|
|
|
magicCards.Add(card);
|
|
}
|
|
}
|
|
}
|
|
|
|
pen.Dispose();
|
|
g.Dispose();
|
|
|
|
filteredBitmap = bm;
|
|
}
|
|
|
|
// Move the corners a fixed amount
|
|
private void shiftCorners(List<IntPoint> corners, IntPoint point)
|
|
{
|
|
int xOffset = point.X - corners[0].X;
|
|
int yOffset = point.Y - corners[0].Y;
|
|
|
|
for (int x = 0; x < corners.Count; x++)
|
|
{
|
|
IntPoint point2 = corners[x];
|
|
|
|
point2.X += xOffset;
|
|
point2.Y += yOffset;
|
|
|
|
corners[x] = point2;
|
|
}
|
|
}
|
|
|
|
|
|
private void rearrangeCorners(List<IntPoint> corners)
|
|
{
|
|
float[] pointDistances = new float[4];
|
|
|
|
for (int x = 0; x < corners.Count; x++)
|
|
{
|
|
IntPoint point = corners[x];
|
|
|
|
pointDistances[x] = point.DistanceTo( (x == (corners.Count - 1) ? corners[0] : corners[x + 1]) ) ;
|
|
}
|
|
|
|
float shortestDist = float.MaxValue;
|
|
Int32 shortestSide = Int32.MaxValue;
|
|
|
|
for (int x = 0; x < corners.Count; x++)
|
|
{
|
|
if (pointDistances[x] < shortestDist)
|
|
{
|
|
shortestSide = x;
|
|
shortestDist = pointDistances[x];
|
|
}
|
|
}
|
|
|
|
if (shortestSide != 0 && shortestSide != 2)
|
|
{
|
|
IntPoint endPoint = corners[0];
|
|
corners.RemoveAt(0);
|
|
corners.Add(endPoint);
|
|
}
|
|
}
|
|
|
|
|
|
private void Form1_Load(object sender, EventArgs e)
|
|
{
|
|
cameraBitmap = new Bitmap(640, 480);
|
|
capture = new Capture(cameraFilters.VideoInputDevices[0], cameraFilters.AudioInputDevices[0]);
|
|
VideoCapabilities vc = capture.VideoCaps;
|
|
capture.FrameSize = new Size(640, 480);
|
|
capture.PreviewWindow = cam;
|
|
capture.FrameEvent2 += new Capture.HeFrame(CaptureDone);
|
|
capture.GrapImg();
|
|
|
|
loadSourceCards();
|
|
}
|
|
|
|
private void loadSourceCards()
|
|
{
|
|
using (DataTable Reader = sql.dbResult("SELECT * FROM cards"))
|
|
{
|
|
foreach (DataRow r in Reader.Rows)
|
|
{
|
|
ReferenceCard card = new ReferenceCard();
|
|
card.cardId = (String)r["id"];
|
|
card.name = (String)r["Name"];
|
|
card.pHash = (UInt64)r["pHash"];
|
|
card.dataRow = r;
|
|
|
|
referenceCards.Add(card);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void CaptureDone(System.Drawing.Bitmap e)
|
|
{
|
|
lock (_locker)
|
|
{
|
|
magicCardsLastFrame = new List<MagicCard>(magicCards);
|
|
magicCards.Clear();
|
|
cameraBitmap = e;
|
|
cameraBitmapLive = (Bitmap)cameraBitmap.Clone();
|
|
detectQuads(cameraBitmap);
|
|
matchCard();
|
|
|
|
image_output.Image = filteredBitmap;
|
|
camWindow.Image = cameraBitmap;
|
|
}
|
|
}
|
|
|
|
private void matchCard()
|
|
{
|
|
int cardTempId = 0;
|
|
foreach(MagicCard card in magicCards)
|
|
{
|
|
cardTempId++;
|
|
// Write the image to disk to be read by the pHash library.. should really find
|
|
// a way to pass a pointer to image data directly
|
|
card.cardArtBitmap.Save("tempCard" + cardTempId + ".jpg", ImageFormat.Jpeg);
|
|
|
|
|
|
// Calculate art bitmap hash
|
|
UInt64 cardHash = 0;
|
|
Phash.ph_dct_imagehash("tempCard" + cardTempId + ".jpg", ref cardHash);
|
|
|
|
int lowestHamming = int.MaxValue;
|
|
ReferenceCard bestMatch = null;
|
|
|
|
foreach (ReferenceCard referenceCard in referenceCards)
|
|
{
|
|
int hamming = Phash.HammingDistance(referenceCard.pHash, cardHash);
|
|
if (hamming < lowestHamming)
|
|
{
|
|
lowestHamming = hamming;
|
|
bestMatch = referenceCard;
|
|
}
|
|
}
|
|
|
|
if (bestMatch != null)
|
|
{
|
|
card.referenceCard = bestMatch;
|
|
//Debug.WriteLine("Highest Similarity: " + bestMatch.name + " ID: " + bestMatch.cardId.ToString());
|
|
|
|
Graphics g = Graphics.FromImage(cameraBitmap);
|
|
g.DrawString(bestMatch.name, new Font("Tahoma", 25), Brushes.Black, new PointF(card.corners[0].X - 29, card.corners[0].Y - 39));
|
|
g.DrawString(bestMatch.name, new Font("Tahoma", 25), Brushes.Yellow, new PointF(card.corners[0].X - 30, card.corners[0].Y - 40));
|
|
g.Dispose();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Conver list of AForge.NET's points to array of .NET points
|
|
private System.Drawing.Point[] ToPointsArray(List<IntPoint> points)
|
|
{
|
|
System.Drawing.Point[] array = new System.Drawing.Point[points.Count];
|
|
|
|
for (int i = 0, n = points.Count; i < n; i++)
|
|
{
|
|
array[i] = new System.Drawing.Point(points[i].X, points[i].Y);
|
|
}
|
|
|
|
return array;
|
|
}
|
|
|
|
private void camWindow_MouseClick(object sender, MouseEventArgs e)
|
|
{
|
|
lock (_locker)
|
|
{
|
|
foreach (MagicCard card in magicCards)
|
|
{
|
|
Rectangle rect = new Rectangle(card.corners[0].X, card.corners[0].Y, (card.corners[1].X - card.corners[0].X), (card.corners[2].Y - card.corners[1].Y));
|
|
if (rect.Contains(e.Location))
|
|
{
|
|
Debug.WriteLine(card.referenceCard.name);
|
|
cardArtImage.Image = card.cardArtBitmap;
|
|
cardImage.Image = card.cardBitmap;
|
|
|
|
cardInfo.Text = "Card Name: " + card.referenceCard.name + Environment.NewLine +
|
|
"Set: " + (String)card.referenceCard.dataRow["Set"] + Environment.NewLine +
|
|
"Type: " + (String)card.referenceCard.dataRow["Type"] + Environment.NewLine +
|
|
"Casting Cost: " + (String)card.referenceCard.dataRow["Cost"] + Environment.NewLine +
|
|
"Rarity: " + (String)card.referenceCard.dataRow["Rarity"] + Environment.NewLine;
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class ReferenceCard
|
|
{
|
|
public string cardId;
|
|
public string name;
|
|
public UInt64 pHash;
|
|
public DataRow dataRow;
|
|
}
|
|
|
|
class MagicCard
|
|
{
|
|
public ReferenceCard referenceCard;
|
|
public List<IntPoint> corners;
|
|
public Bitmap cardBitmap;
|
|
public Bitmap cardArtBitmap;
|
|
}
|
|
|
|
public class Phash
|
|
{
|
|
|
|
[DllImport("pHash.dll", CallingConvention = CallingConvention.Cdecl)]
|
|
public static extern int ph_dct_imagehash(string file_name, ref UInt64 Hash);
|
|
|
|
private static UInt64 m1 = 0x5555555555555555;
|
|
private static UInt64 m2 = 0x3333333333333333;
|
|
private static UInt64 h01 = 0x0101010101010101;
|
|
private static UInt64 m4 = 0x0f0f0f0f0f0f0f0f;
|
|
|
|
// Calculate the similarity between two hashes
|
|
public static int HammingDistance(UInt64 hash1, UInt64 hash2)
|
|
{
|
|
UInt64 x = hash1 ^ hash2;
|
|
|
|
|
|
x -= (x >> 1) & m1;
|
|
x = (x & m2) + ((x >> 2) & m2);
|
|
x = (x + (x >> 4)) & m4;
|
|
UInt64 res = (x * h01) >> 56;
|
|
|
|
return (int)res;
|
|
}
|
|
}
|
|
|
|
}
|