本程序只对扑克牌的花色进行训练和识别,对扑克牌上的数字的识别在以后的学习中再进行完善。
本次只是简单的提取了扑克牌的RGB均值、HSV均值、7 个不变矩以及长宽比等14个简单的特征,其中,长宽比为了防止图像的位置等因素的影响,提取了目标区域的最小外接矩形。
部分图像如下图所示:
特征提取的部分代码如下所示:
void CPokeAlgorithmDlg::CollectCharacter(IplImage* img, CvMat* mat, int rows)
{
if (img != nullptr)
{
showImage(img, IDC_PIC1); //显示图像
IplImage* bitImage = nullptr, *grayImage = nullptr, *hsvImage = nullptr;
bitImage = cvCreateImage(cvGetSize(img), IPL_DEPTH_8U, 1);
grayImage = cvCreateImage(cvGetSize(img), IPL_DEPTH_8U, 1);
hsvImage = cvCreateImage(cvGetSize(img), IPL_DEPTH_8U, 3);
cvCvtColor(img, hsvImage, CV_RGB2HSV);
cvCvtColor(img, grayImage, CV_RGB2GRAY);
cvSmooth(grayImage, grayImage, CV_MEDIAN);
cvThreshold(grayImage, bitImage, 128, 255.0, CV_THRESH_BINARY);
cvNot(bitImage, bitImage);
IplConvKernel* element = cvCreateStructuringElementEx(5, 5, 2, 2, CV_SHAPE_ELLIPSE);
cvSmooth(bitImage, bitImage, CV_MEDIAN);
cvErode(bitImage, bitImage, element, 1);
cvDilate(bitImage, bitImage, element, 1);
cvReleaseStructuringElement(&element);
element = NULL;
CvMemStorage* storage = cvCreateMemStorage(0);
CvSeq* contour = 0;
cvFindContours(bitImage, storage, &contour, sizeof(CvContour), CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE); //轮廓检索
for (; contour != 0; contour = contour->h_next)
{
double area = fabs(cvContourArea(contour, CV_WHOLE_SEQ));
if (area > 2000) //此处阈值需重新调节
{
cvDrawContours(bitImage, contour, cvScalarAll(255), cvScalarAll(255), -1, CV_FILLED, 8);
CvRect rect = cvBoundingRect(contour, 0);
CvBox2D minRect = cvMinAreaRect2(contour, storage);
CvPoint2D32f rectPts[4] = { 0 };
cvBoxPoints(minRect, rectPts);
int nPts = 4; // 4 个顶点
CvPoint minRectPts[4] = { 0 };
for (int i = 0; i < 4; ++i)
{
minRectPts[i] = cvPointFrom32f(rectPts[i]); //将 cvPoint2D32f 转化为 CvPoint
}
CvPoint *pt = minRectPts;
//在图像中绘制矩形框
cvPolyLine(bitImage, &pt, &nPts, 1, 1, cvScalarAll(255), 1);
int l1 = sqrtf((pt[0].x - pt[1].x)*(pt[0].x - pt[1].x) + (pt[0].y - pt[1].y)*(pt[0].y - pt[1].y));
int l2 = sqrtf((pt[2].x - pt[1].x)*(pt[2].x - pt[1].x) + (pt[2].y - pt[1].y)*(pt[2].y - pt[1].y));
int length = l1 > l2 ? l1 : l2; //取较长边为图形的长
int width = l1 > l2 ? l2 : l1; //取较短边为图形的宽
double r = (width * 1.0) / length; //长宽比
cvSetReal2D(mat, rows, 0, r);
double RMean = 0, GMean = 0, BMean = 0;
double HMean = 0, SMean = 0, VMean = 0;
int nCount = 0;
for (int imgRow = rect.y; imgRow < rect.y + rect.height; ++imgRow)
{
for (int imgCol = rect.x; imgCol < rect.x + rect.width; ++imgCol)
{
CvScalar s = cvGet2D(bitImage, imgRow, imgCol);
if (s.val[0] == 255)
{
s = cvGet2D(img, imgRow, imgCol);
RMean += s.val[2];
GMean += s.val[1];
BMean += s.val[0];
s = cvGet2D(hsvImage, imgRow, imgCol);
HMean += s.val[0];
SMean += s.val[1];
VMean += s.val[2];
++nCount;
}
}
}// end RGB,HSV for
RMean /= nCount;
GMean /= nCount;
BMean /= nCount;
HMean /= nCount;
SMean /= nCount;
VMean /= nCount;
cvSetReal2D(mat, rows, 1, RMean);
cvSetReal2D(mat, rows, 2, GMean);
cvSetReal2D(mat, rows, 3, BMean);
cvSetReal2D(mat, rows, 4, HMean);
cvSetReal2D(mat, rows, 5, SMean);
cvSetReal2D(mat, rows, 6, VMean);
//7个不变矩
CvMoments moments;
cvMoments(contour, &moments, 1);
CvHuMoments huMoments;
cvGetHuMoments(&moments, &huMoments);
double hu1 = huMoments.hu1;
double hu2 = huMoments.hu2;
double hu3 = huMoments.hu3;
double hu4 = huMoments.hu4;
double hu5 = huMoments.hu5;
double hu6 = huMoments.hu6;
double hu7 = huMoments.hu7;
cvSetReal2D(mat, rows, 7, hu1);
cvSetReal2D(mat, rows, 8, hu2);
cvSetReal2D(mat, rows, 9, hu3);
cvSetReal2D(mat, rows, 10, hu4);
cvSetReal2D(mat, rows, 11, hu5);
cvSetReal2D(mat, rows, 12, hu6);
cvSetReal2D(mat, rows, 13, hu7);
}// end if
}
showImage(hsvImage, IDC_PIC3);
showImage(bitImage, IDC_PIC2);
//释放内存
cvReleaseMemStorage(&storage);
storage = nullptr;
cvReleaseImage(&bitImage);
bitImage = nullptr;
cvReleaseImage(&grayImage);
grayImage = nullptr;
cvReleaseImage(&hsvImage);
hsvImage = nullptr;
}
//释放内存
cvReleaseImage(&img);
img = nullptr;
}
Bayes训练代码:
Book* book = xlCreateXMLBookW();
CvMat* dataMat = NULL;
if (book->load(L"Data.xlsx"))
{
Sheet *sheet = book->getSheet(0);
int myrow = sheet->lastRow();
int mycol = sheet->lastCol();
if (sheet)
{
CvMat* importMat = cvCreateMat(myrow, mycol, CV_32FC1); //存储导入数据
for (auto i = 0; i < myrow; ++i)
{
for (auto j = 0; j < mycol; j++)
{
double temp = sheet->readNum(i, j);
cvSetReal2D(importMat, i, j, temp);
}
}// end for
dataMat = cvCloneMat(importMat);
}// end if
}
book->release();
MessageBox(L"数据导入完成");
CvMat* lableMat = cvCreateMat(dataMat->rows, 1, CV_32FC1); //构建样本的分类标签
cvZero(lableMat);
for (int i = 0; i < 4; ++i) //共分了 20 个不同的种类
{
for (int j = 0; j < 10; ++j) //每个品种共50个籽粒
{
cvSetReal2D(lableMat, i * 10 + j, 0, i + 1);
}
}
CvNormalBayesClassifier nbc;
nbc.train(dataMat, lableMat);
nbc.save("bayes.txt");
MessageBox(L"数据训练完成");
CvMat* nbcResult = cvCreateMat(dataMat->rows, 1, CV_32FC1);
CvMat* nbcRow = NULL;
for (int i = 0; i < dataMat->rows; ++i)
{
nbcRow = cvCreateMat(1, dataMat->cols, CV_32FC1);
for (int j = 0; j < dataMat->cols; ++j)
{
float temp = cvGetReal2D(dataMat, i, j);
cvSetReal2D(nbcRow, 0, j, temp);
}
unsigned int ret = 0;
ret = nbc.predict(nbcRow);
cvSetReal2D(nbcResult, i, 0, ret);
cvReleaseMat(&nbcRow);
nbcRow = NULL;
}
int nCount = 0;
for (int i = 0; i < 4; ++i)
{
for (int j = 0; j < 10; ++j)
{
int ret = cvGetReal2D(nbcResult, i * 10 + j, 0);
if (ret == (i + 1))
{
++nCount;
}
}
}
float recognize = 100 * nCount / 10 / 4;
CString str;
str.Format(L"朴素贝叶斯 识别率为: %f", recognize);
str = str + L"%";
MessageBox(str);
识别代码如下所示:
CvNormalBayesClassifier nbc;
nbc.load("bayes.txt");
CFileDialog dlg(TRUE, NULL, NULL, 0, L"图片文件(*.jpg)|*.jpg||");
if (dlg.DoModal() == IDOK)
{
USES_CONVERSION;
const char* loadPath = W2A(dlg.GetPathName());
IplImage* testImage = cvLoadImage(loadPath);
CvMat* mat = cvCreateMat(1, 14, CV_32FC1);
CollectCharacter(testImage, mat, 0);
int ret = nbc.predict(mat);
CString str;
switch (ret)
{
case 1:
str = L"黑桃";
break;
case 2:
str = "红桃";
break;
case 3:
str = "梅花";
break;
case 4:
str = "方块";
break;
}
AfxMessageBox(str);
cvReleaseMat(&mat);
mat = NULL;
}//end if