写一个识别扑克牌花色和点数的小程序(二)

rotation.cpp

上一篇文章整个程序的基本框架已经搭建好,现在完善一下相关的函数。旋转操作在rotation.cpp中完成。包括一下两个函数
计算旋转的角度

void calculationAngle(vector<Point> porker,float &angle) {
    //以扑克牌第二个顶点为旋转中心计算旋转的角度
    float dx, dy;
    dx = porker[0].x - porker[1].x;
    dy = porker[0].y - porker[1].y;
    float width;
    float length;
    length = fabs(sqrt((porker[3].x - porker[0].x) * (porker[3].x - porker[0].x) + (porker[3].y - porker[0].y) * (porker[3].y - porker[0].y)));
    width = fabs(sqrt((porker[0].x - porker[1].x) * (porker[0].x - porker[1].x) + (porker[0].y - porker[1].y) * (porker[0].y - porker[1].y)));
    //根据长短边旋转矩形
    //cout << dx << " " << dy << endl;
    float tan = dy / dx;
        angle=atan(tan);
        //cout << angle << endl;
        if (width < length) {
            angle = (float)atan2(dy,dx) * 180 / PI;
        }
        else {
            angle = (float)atan2(dy,dx) * 180 / PI;
            if (angle > 0)
                angle = angle - 90;
            else
                angle = angle + 90;
        }
    //cout << angle<<endl;
}

旋转图片

void rotate_arbitrarily_angle(Mat src, Mat& dst, float angle) {
    //以图像中心点为旋转点进行旋转
    float radian = (float)(angle / 180.0 * CV_PI);

    //填充图像
    int maxBorder = (int)(max(src.cols, src.rows) * 1.414); //即为sqrt(2)*max
    int dx = (maxBorder - src.cols) / 2;
    int dy = (maxBorder - src.rows) / 2;
    //边界增加
    copyMakeBorder(src, dst, dy, dy, dx, dx, BORDER_CONSTANT);

    //旋转
    Point2f center((float)(dst.cols / 2), (float)(dst.rows / 2));
    Mat affine_matrix = getRotationMatrix2D(center, angle, 1.0);//求得旋转矩阵
    warpAffine(dst, dst, affine_matrix, dst.size());

    //计算图像旋转之后包含图像的最大的矩形
    float sinVal = abs(sin(radian));
    float cosVal = abs(cos(radian));
    Size targetSize((int)(src.cols * cosVal + src.rows * sinVal),
        (int)(src.cols * sinVal + src.rows * cosVal));

}

templateMatching.cpp

对扑克牌识别的区域进行模板匹配,因为数字图像处理的老师不让用机器学习等方法,所以用了这个最基础的方法。
首先先设两个数组

string numID[] ={"10","2","3","4","5","6","7","8","9","A","J","K","Q"};
string suitID[] = { "clubs","diamonds","hearts","spades" };

加载模板图片

void loadImage(vector<Mat> &models,vector<string>files,string path) {
    getFiles(path,files);

    for (int i = 0; i < files.size(); i++) {
        if(path=="num")
            files[i][3] = '/';
        else if (path == "suits")
            files[i][5] = '/';
    }
    //加载模板图片
    for (int i = 0; i < files.size(); i++) {
        models.push_back(imread(files[i]));
    }
}

模板是我自己做的图片。数字是2020像素大小,花色是3030大小

2.jpg
3.jpg
4.jpg
5.jpg
6.jpg
7.jpg
8.jpg
9.jpg
A.jpg
10.jpg
J.jpg
K.jpg
Q.jpg
clubs.jpg
diamonds.jpg
hearts.jpg
spades.jpg

对要识别的花色和数字区域进行处理,这里以花色为例

Mat boxDetectSuit(Mat m) {
    Mat binary;
    //腐蚀膨胀
    binary = m;
    erode(m, binary, 2);
    dilate(binary, binary, 2);
    bitwise_not(binary, binary);
    vector<vector<Point>>contours;
    contours.clear();
    findContours(m, contours, RETR_EXTERNAL, CHAIN_APPROX_NONE);
    int max = 0;
    Rect maxBoundRect;
    for (int i = 0; i < contours.size(); i++) {
        Rect boundRect = boundingRect(contours[i]);
        if (boundRect.area() > max) {
            maxBoundRect = boundRect;
            max = boundRect.area();
        }
    }
    Mat imageROI = m(maxBoundRect);
    resize(imageROI, imageROI, Size(30, 30), 1);
    return imageROI;
}

处理后的效果如下


微信图片_20191209102014.png

微信图片_20191208223042.png

接下来在模板中查找模板与要识别的数字或花色差值最小的模板。

//矩阵内元素求和
int matSum(Mat m1,Mat m2) {
    int sum = 0;
    for (size_t i = 0; i < m1.rows; i++) {
        for (size_t j = 0; j < m1.cols;j++) {
            int value1 = (int)*(m1.data+m1.step[0]*i+m1.step[1]*j);
            //cout << value1<<endl;
            int value2 = (int)*(m2.data + m2.step[0] * i + m2.step[1] * j);
            //cout << value2 << endl;
            int diff=fabs(value1-value2);
            sum += diff;
        }
    }
    return sum;
}
void returnNum(Mat numImage, vector<Mat>& models, string& number) {
    resize(numImage, numImage, Size(20, 20), 1);
    Mat binarizedImage;//完美切割出来的数字,开心:)
    //二值化图像
    cvtColor(numImage, binarizedImage,COLOR_RGB2GRAY);
    /*string name1 = "binaryg" + to_string(i++);
    imshow(name1, binarizedImage);*/
    //大津法找阈值
    threshold(binarizedImage, binarizedImage,0,255, THRESH_BINARY | THRESH_OTSU);
    bitwise_not(binarizedImage,binarizedImage);
    binarizedImage=boxDetect(binarizedImage);
    //string name = "binary" + to_string(i++);
    //imshow(name, binarizedImage);
    int minDiff =1000000000;
    int index;
    for (int i = 0; i < models.size(); i++) {
        int diff;
        Mat detectModel;
        diff=matSum(models[i], binarizedImage);
        if (minDiff > diff) {
            minDiff = diff;
            index = i;
        }
    }
    number = numID[index];
    //cout << numID[index]<<endl;
}
void returnSuit(Mat suitImage, vector<Mat>& models, string &suit) {
    resize(suitImage, suitImage, Size(30, 30), 1);
    Mat binarizedImage;//完美切割出来的数字,开心:)
    //二值化图像
    cvtColor(suitImage, binarizedImage, COLOR_RGB2GRAY);
    /*string name1 = "binaryg" + to_string(i++);
    imshow(name1, binarizedImage);*/
    //大津法找阈值
    threshold(binarizedImage, binarizedImage, 0, 255, THRESH_BINARY | THRESH_OTSU);
    //bitwise_not(binarizedImage, binarizedImage);
    binarizedImage = boxDetectSuit(binarizedImage);
    //string name = "binary2" + to_string(i++);
    //imshow(name, binarizedImage);
    int minDiff = 1000000000;
    int index;
    for (int i = 0; i < models.size(); i++) {
        int diff;
        Mat detectModel;
        diff = matSum(models[i], binarizedImage);
        if (minDiff > diff) {
            minDiff = diff;
            index = i;
        }
    }
    suit = suitID[index];
    //cout << suitID[index] << endl;
}

此时已经可以得出识别扑克牌的结果。
但是界面不够美观,识别结果是由控制台显示的,于是再给为这个程序写一个QT界面。具体实现在写一个识别扑克牌花色和点数的小程序(三)中。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容