提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
提示:项目需要识别实时采集图片,识别图片中浮点数
想法如下,采取CV来处理图片,处理的图片进行轮廓分割(这里采用了两种方式)一种为mat数据主动切割识别,一种为CV自带轮廓分割。
一、两种分割思想
- 首先图片进行灰度处理
- 然后图片进行二值化
- 开始图片分割
- 采集出图片的Mat数据,按行列来读取,读取mat中最左边row数据都是0的最上行序号,读取mat中row数据为空的最下行数据,然后读取column不为0的咧数,直到column为0 这样就采集到了一个长方形,里面就是要识别的数字。
!!!!!但是这种方式有问题,比如图片二值化后,部分数据断开, 或者两个数字存在连通,比如77二值化后的数据最顶行可能中间没有空隙,就会出问题 - 第二种方式,直接采用cv自带的函数来分割(后来才发现)
!!!!! 存在相同问题。
有解决方式,就是对比的图片,将特殊的数全部穷举处理,比如0-9和.的图片为left0 -left10.png
对于77你可以创建一个11.png去比,如果为11就是77, 过于麻烦最后采用了OCR的处理方式(可看我另外一篇文章Tesseract C++使用)
二、使用步骤
源码
注释掉的就是原来采用的第一种分割方式。还有对比图片生成,需要你自己先用最后一个函数的注释代码生成0-9和.的图片。有时间再做详细修改最近有些忙嘻嘻。
代码如下(示例):
/********************************************************
* @file : cvstract.h
* @brief : 图像分割识别文件
* @details :
* @author : liuyapeng5
* @date : 2021-8-11
*********************************************************/
#pragma once
#include "include/opencv2/highgui.hpp"
#include "include/opencv2/core.hpp"
#include "include/opencv2/opencv.hpp"
#include "include/opencv2/imgproc.hpp"
#include "include/opencv2/imgcodecs.hpp"
#include "include/opencv2/opencv_modules.hpp"
#include <QMessageBox>
#include <QTextCodec>
#include <QDir>
#include <QDebug>
#include <stack>
using namespace cv;
static bool isDian = false;
int getColSum(Mat src, int col)
{
int sum = 0;
int height = src.rows;
int width = src.cols;
for (int i = 0; i < height; i++)
{
sum = sum + src.at <uchar>(i, col);
}
return sum;
}
int getRowSum(Mat src, int row)
{
int sum = 0;
int height = src.rows;
int width = src.cols;
for (int i = 0; i < width; i++)
{
sum += src.at <uchar>(row, i);
}
return sum;
}
void cutTop(Mat& src, Mat& dstImg)//上下切割
{
int top, bottom;
top = 0;
bottom = src.rows;
int i;
for (i = 0; i < src.rows; i++)
{
int colValue = getRowSum(src, i);
//cout <<i<<" th "<< colValue << endl;
if (colValue > 0)
{
top = i;
break;
}
}
for (int j = src.rows-1; j > i; j--)
{
int colValue = getRowSum(src, j);
//cout << i << " th " << colValue << endl;
if (colValue != 0)
{
bottom = j;
break;
}
}
int height = bottom - top+3;
Rect rect(0, top-1, src.cols, height);
dstImg = src(rect).clone();
}
int cutLeft(Mat& src, Mat& leftImg, Mat& rightImg)//左右切割
{
int left, right;
left = 0;
right = src.cols;
int i;
for (i = 0; i < src.cols; i++)
{
int colValue = getColSum(src, i);
if (colValue > 0)
{
left = i;
//qDebug() <<i<<" th1 "<< colValue << endl;
if (colValue == 510)
{
int Value = getColSum(src, i + 1);
if (Value == 510)
{
Rect rect(left, 0, 2, src.rows);
leftImg = src(rect).clone();
cutTop(leftImg, leftImg);
if (leftImg.rows == 2 && leftImg.cols == 2)
{
right = left + 2;
Rect rectRight(right, 0, src.cols - right, src.rows);
rightImg = src(rectRight).clone();
isDian = true;
return 0;
}
}
}
break;
}
}
if (left == 0 && !isDian)
{
return 1;
}
isDian = false;
for (; i < src.cols; i++)
{
int colValue = getColSum(src, i);
if (colValue == 0)
{
right = i;
//qDebug() << i << " th2 " << colValue << endl;
break;
}
}
int width = right - left;
Rect rect(left, 0, width, src.rows);
leftImg = src(rect).clone();
Rect rectRight(right, 0, src.cols - right, src.rows);
rightImg = src(rectRight).clone();
cutTop(leftImg, leftImg);
return 0;
}
void getPXSum(Mat& src, int& a)//获取所有像素点和
{
threshold(src, src, 100, 255, THRESH_BINARY);
a = 0;
for (int i = 0; i < src.rows; i++)
{
for (int j = 0; j < src.cols; j++)
{
a += src.at <uchar>(i, j);
}
}
}
int getSubtract(Mat src, int TemplateNum) //两张图片相减
{
Mat img_result;
int min = 1000000;
int serieNum = 0;
for (int i = 0; i < TemplateNum; i++) {
QString str = QDir::currentPath() + QDir::separator() + "src" + QDir::separator() + QString("%1Left.png").arg(i);
QTextCodec* code = QTextCodec::codecForName("gb18030");
std::string name = code->fromUnicode(str).data(); // 解决中文乱码以及imread输入为string
Mat Template = imread(name, COLOR_BGR2GRAY);
Mat dst(src);
threshold(Template, Template, 100, 255, THRESH_BINARY);
/*threshold(src, dst, 100, 255, THRESH_BINARY);*/
resize(dst, dst, Size(16, 16), 0, 0, INTER_CUBIC);
resize(Template, Template, Size(16, 16), 0, 0, INTER_CUBIC);
//imshow(name, Template);
//std::cout << "\n" << dst;
//std::cout << "\n" << Template;
absdiff(Template, dst, img_result);
int diff = 0;
getPXSum(img_result, diff);
if (diff < min)
{
min = diff;
serieNum = i;
}
}
//std::cout << "i =========" << min << "\n";
//std::cout << "i--------------" << serieNum << " " << serieNum << "\n";
return serieNum;
}
//对每条连通域上的各点根据y从小到大进行排序
void SortContourPoint(std::vector<std::vector<Point> > &inputContours)
{
std::vector<Point> tempContoursPoint;
for (int i = 0; i < inputContours.size(); i++)
{
tempContoursPoint.clear(); //每次循环注意清空
for (int j = i+1; j < inputContours.size(); j++)
{
if (inputContours[i][0].x > inputContours[j][0].x)
{
swap(inputContours[i], inputContours[j]);
}
}
}
}
//src:待分割的二值图,最大值为255
//segMat:分割好的每一个图片
//算法:判断连通域,有几个连通域就会分割成几个子图片
//用途:手写数字识别中进行无黏连数字的分割
void getConnectedDomain(cv::Mat& src, std::vector<cv::Mat>& segMat)//segMat为最终结果,存放分割好的每一个数字
{
int img_row = src.rows;
int img_col = src.cols;
cv::Mat flag = cv::Mat::zeros(cv::Size(img_col, img_row), CV_8UC1);//标志矩阵,为0则当前像素点未访问过
for (int i = 0; i < img_row; i++)
{
for (int j = 0; j < img_col; j++)
{
if (src.ptr<uchar>(i)[j] == 255 && flag.ptr<uchar>(i)[j] == 0)
{
cv::Mat subMat = cv::Mat::zeros(cv::Size(img_col, img_row), CV_8UC1);//代表子图
std::stack<cv::Point2f> cd;
cd.push(cv::Point2f(j, i));
flag.ptr<uchar>(i)[j] = 1;
subMat.ptr<uchar>(i)[j] = 255;
while (!cd.empty())
{
cv::Point2f tmp = cd.top();
cd.pop();
cv::Point2f p[4];//邻域像素点,这里用的四邻域
p[0] = cv::Point2f(tmp.x - 1 > 0 ? tmp.x - 1 : 0, tmp.y);
p[1] = cv::Point2f(tmp.x + 1 < img_col - 1 ? tmp.x + 1 : img_row - 1, tmp.y);
p[2] = cv::Point2f(tmp.x, tmp.y - 1 > 0 ? tmp.y - 1 : 0);
p[3] = cv::Point2f(tmp.x, tmp.y + 1 < img_row - 1 ? tmp.y + 1 : img_row - 1);
for (int m = 0; m < 4; m++)
{
int x = p[m].y;
int y = p[m].x;
if (src.ptr<uchar>(x)[y] == 255 && flag.ptr<uchar>(x)[y] == 0)//若是未访问,则入栈,并标记访问过该点
{
cd.push(p[m]);
flag.ptr<uchar>(x)[y] = 1;
subMat.ptr<uchar>(x)[y] = 255;
}
}
}
segMat.push_back(subMat);
}
}
}
}
QString cvrun(cv::Mat src, int model)
{
//QString str = QDir::currentPath() + QDir::separator() + "cc.png";
//QTextCodec* code = QTextCodec::codecForName("gb18030");
//std::string name = code->fromUnicode(str).data(); // 解决中文乱码以及imread输入为string
//src = imread(name );
//数据判空
if (!src.data)
{
qDebug() << "error";
return 0;
}
cvtColor(src, src, COLOR_BGR2GRAY); //灰度显示
threshold(src, src, 160, 255, THRESH_BINARY); //二值化
//imshow("原始", src);
腐蚀
//erode(src, src, NULL );
//
//imshow("腐蚀_cv", src);
膨胀
//Mat element = getStructuringElement(MORPH_RECT, Size(3, 3));
//dilate(src, src, element, Point(-1, -1),
// 2,
// BORDER_CONSTANT); //iterations=6,6是表示膨胀的次数,表示操作进行了6次膨胀
//imshow("膨胀_my", src);
//waitKey(0);
if (model==0)
{
bitwise_not(src, src);//颜色反转
}
//std::vector<cv::Mat> segMat;
// getConnectedDomain(src, segMat);
std::vector<std::vector<Point>> contours;
Mat hierarchy;
//切割原始图像
findContours(src, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_NONE);
//drawContours(src, contours, 2, Scalar(255, 0, 255), 1);
//imshow(" g",src);
waitKey(0);
//std::cout << src <<"\n";
//对切出来的数据排序
SortContourPoint(contours);
std::vector<std::vector<Point>>::const_iterator iter = contours.begin();
std::vector<Mat> vecMat;
int i = 0;
while (iter != contours.end())
{
//获取rect值
Rect rc = boundingRect(*iter);
//获取原始图像该位置的数据
Mat leftImg = src(rc).clone();
//std::cout << leftImg<<"\n";
vecMat.push_back(leftImg);
//char nameLeft[10];
//sprintf(nameLeft, "%dLeft", i);
//char nameRight[10];
//sprintf(nameRight, "%dRight", i);
//i++;
imshow(nameLeft, leftImg);
std::cout << leftImg;
//std::stringstream ss;
//ss << nameLeft;
//imwrite("D:\\" + ss.str() + ".png", leftImg);
iter++;
}
QString strnum = "";
for (auto it: vecMat )
{
//对比数字数据
int number = getSubtract(it, 11);
if (number == 10)
{
strnum += ".";
}
else
{
strnum += QString::number(number);
}
}
return strnum;
// imshow("origin", src);
//std::cout<<src;
//Mat leftImg, rightImg;
//int res = cutLeft(src, leftImg, rightImg);
int i = 0;
//QString strnum = "";
//while (res == 0)
//{
// char nameLeft[10];
// sprintf(nameLeft, "%dLeft", i);
// char nameRight[10];
// sprintf(nameRight, "%dRight", i);
// i++;
// //imshow(nameLeft, leftImg);
// std::cout << leftImg;
// std::stringstream ss;
// ss << nameLeft;
// imwrite("D:\\" + ss.str() + ".png", leftImg);
// Mat srcTmp = rightImg;
// // QString str = "D:\\1Left.png";
// // QTextCodec *code = QTextCodec::codecForName("gb18030");
// // std::string name = code->fromUnicode(str).data(); // 解决中文乱码以及imread输入为string
// // Mat Template = imread(name, COLOR_BGR2GRAY);
// // // resize(Template, Template, Size(Template.cols, Template.rows), 0, 0, THRESH_BINARY);
// // std::cout<<"\n"<<Template.cols<< " " <<Template.rows;
// // std::cout<<"\n"<<Template;
// // std::cout<<"\n"<<leftImg;
// int number = getSubtract(leftImg, 11);
// if (number == 10)
// {
// strnum += ".";
// }
// else
// {
// strnum += QString::number(number);
// }
//
// res = cutLeft(srcTmp, leftImg, rightImg);
//}
return strnum;
}
该处使用的url网络请求的数据。
总结
提示:这里对文章进行总结:
以上就是今天要讲的内容,本文仅仅简单介绍简单数字识别的CV的使用