提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
导师让干的活,要传给别人,记录一下自己的付出和成果
一、开发目的
由于某些企业的仪表盘界面,还需进行人工读取和记录,所以要使用程序来解放人民的双手。且由于电脑系统还是上古时代的产品,所以代码占用内存要非常小。
二、项目框架
1.仪表制作: 此小项目需要用qt制作界面,模拟某企业的仪表盘进行识别。
2.识别输入:采用微软提供的一组C++模板库来对qt界面进行截图,并作为识别的输入。
3.识别:采用opencv模板匹配进行识别,模板匹配简单易用,但对于旋转、缩放、光照变化等情况的鲁棒性较差。对于这些复杂情况,可能需要结合其他更高级的图像处理技术,如轮廓检测或深度学习等方法。
三、qt界面制作
ui->setupUi(this);
//this->resize(1440,900);
ui->lcdNumber_1->setDigitCount(4);
ui->lcdNumber_1->setStyleSheet("background:black;color:#4dff00;");
ui->lcdNumber_2->setDigitCount(4);
ui->lcdNumber_2->setStyleSheet("background:black;color:#4dff00;");
ui->lcdNumber_3->setDigitCount(4);
ui->lcdNumber_3->setStyleSheet("background:black;color:#4dff00;");
ui->lcdNumber_4->setDigitCount(4);
ui->lcdNumber_4->setStyleSheet("background:black;color:#4dff00;");
ui->lcdNumber_5->setDigitCount(4);
ui->lcdNumber_5->setStyleSheet("background:black;color:#ff0022;");
ui->lcdNumber_6->setDigitCount(4);
ui->lcdNumber_6->setStyleSheet("background:black;color:#ff0022;");
ui->lcdNumber_7->setDigitCount(4);
ui->lcdNumber_7->setStyleSheet("background:black;color:#ff0022;");
ui->lcdNumber_8->setDigitCount(4);
ui->lcdNumber_8->setStyleSheet("background:black;color:#ff0022;");
ui->lcdNumber_9->setDigitCount(4);
ui->lcdNumber_9->setStyleSheet("background:black;color:#4dff00;");
ui->lcdNumber_10->setDigitCount(4);
ui->lcdNumber_10->setStyleSheet("background:black;color:#4dff00;");
ui->lcdNumber_11->setDigitCount(4);
ui->lcdNumber_11->setStyleSheet("background:black;color:#4dff00;");
ui->lcdNumber_12->setDigitCount(4);
ui->lcdNumber_12->setStyleSheet("background:black;color:#4dff00;");
float num1,num2,num3,num4,num5,num6,num7,num8,num9,num10,num11,num12;
num1 = (qrand()%(40-10)+10)*0.4;
num2 = (qrand()%(40-10)+10)*0.4;
num3 = (qrand()%(40-10)+10)*0.4;
num4 = (qrand()%(40-10)+10)*0.4;
num5 = (qrand()%(40-10)+10)*0.4;
num6 = (qrand()%(40-10)+10)*0.4;
num7 = (qrand()%(40-10)+10)*0.4;
num8 = (qrand()%(40-10)+10)*0.4;
num9 = (qrand()%(40-10)+10)*0.4;
num10 = (qrand()%(40-10)+10)*0.4;
num11 = (qrand()%(40-10)+10)*0.4;
num12 = (qrand()%(40-10)+10)*0.4;
num1 = (qrand()%(40-10)+10)*0.4;
num2 = (qrand()%(40-10)+10)*0.4;
num3 = (qrand()%(40-10)+10)*0.4;
num4 = (qrand()%(40-10)+10)*0.4;
num5 = (qrand()%(40-10)+10)*0.4;
num6 = (qrand()%(40-10)+10)*0.4;
num7 = (qrand()%(40-10)+10)*0.4;
num8 = (qrand()%(40-10)+10)*0.4;
num9 = (qrand()%(40-10)+10)*0.4;
num10 = (qrand()%(40-10)+10)*0.4;
num11 = (qrand()%(40-10)+10)*0.4;
num12 = (qrand()%(40-10)+10)*0.4;
ui->lcdNumber_1->display(num1);
ui->lcdNumber_2->display(num2);
ui->lcdNumber_3->display(num3);
ui->lcdNumber_4->display(num4);
ui->lcdNumber_5->display(num5);
ui->lcdNumber_6->display(num6);
ui->lcdNumber_7->display(num7);
ui->lcdNumber_8->display(num8);
ui->lcdNumber_9->display(num9);
ui->lcdNumber_10->display(num10);
ui->lcdNumber_11->display(num11);
ui->lcdNumber_12->display(num12);
界面制作比较简单,只需拉几个仪表图再用函数显示随机数,并用定时器设置固定时间变换一次就ok。
四、截取界面
bool SavePic(wstring name, HWND hWnd) {
HDC hDc = NULL;
hWnd = (hWnd == NULL) ? GetDesktopWindow() : hWnd;
hDc = GetDC(hWnd); //获取DC
if (hDc == NULL) return false;
int bitOfPix = GetDeviceCaps(hDc, BITSPIXEL); //获取DC像素的大小
int width = 1440; //获取DC宽度
int hight = 900; //获取DC高度
UINT dpi = GetDpiForWindow(hWnd); //获取dpi
float fold; //根据dpi计算放大倍数
switch (dpi) {
case 96:
fold = 1;
break;
case 120:
fold = 1.25;
break;
case 144:
fold = 1.5;
break;
case 192:
fold = 2;
break;
case 216:
fold = 2.25;
break;
default:
fold = 1;
break;
}
width *= fold; //复原宽度
hight *= fold; //复原高度
CImage image;
image.Create(width, hight, bitOfPix); //为图像类创建与窗口DC相同大小的DC
BitBlt(image.GetDC(), 0, 0, width, hight, hDc, 0, 0, SRCCOPY); //将窗口DC图像复制到image
//name = to_wstring(i) + name;
image.Save(name.data(), Gdiplus::ImageFormatPNG); //保存为png格式图片文件
image.ReleaseDC(); //释放DC
ReleaseDC(hWnd, hDc); //释放DC资源
}
五、模板匹配
vector<Mat> getcontour(Mat imgDil, Mat imgThre)//判断轮廓
{
vector<vector<Point>> contour;
vector<Vec4i> hierarchy;
findContours(imgDil, contour, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_TC89_KCOS);
vector<vector<Point>> conPoly(contour.size());
vector<Rect> boundRect(contour.size());
vector<Mat> imgCrop;
for (int i = 0; i < contour.size(); i++)
{
int area = contourArea(contour[i]);//轮廓面积用于排除黑点
cout << area << endl;
string objectType;//形状
if (area > 1000)//排除小黑圆点的干扰
{
float peri = arcLength(contour[i], true);
approxPolyDP(contour[i], conPoly[i], 0.02 * peri, true);//把一个连续光滑曲线折线化
cout << conPoly[i].size() << endl;//边数
boundRect[i] = boundingRect(conPoly[i]);
int objCor = (int)conPoly[i].size();
if (objCor == 3) { objectType = "Tri"; }
else if (objCor == 4) {
float aspRatio = (float)boundRect[i].width / boundRect[i].height;//长宽比来区别正方形与矩形
cout << aspRatio << endl;
if (aspRatio > 0.95 && aspRatio < 1.05) { objectType = "Square"; }
else { objectType = "Rect"; }
}
else if (objCor > 4) { objectType = "Circle"; }
drawContours(imgThre, conPoly, i, Scalar(255, 0, 255), 2);
rectangle(imgThre, boundRect[i].tl(), boundRect[i].br(), Scalar(0.255, 0), 5);//框出图形
putText(imgThre, objectType, { boundRect[i].x,boundRect[i].y - 5 }, 1, FONT_HERSHEY_PLAIN, Scalar(0, 69, 255), 1);
Rect roi(boundRect[i].x, boundRect[i].y, boundRect[i].width, boundRect[i].height);
imgCrop.push_back(imgThre(roi));
/*imshow("Image dila", imgThre(roi));
waitKey(0);*/
}
}
reverse(imgCrop.begin(), imgCrop.end());
return imgCrop;
}
double compare(Mat& img, Mat& temp)
{
Mat my_temp;
resize(temp, my_temp, img.size());
//imshow("pre", my_temp);
//waitKey(0);
//
//imshow("pre", img);
//waitKey(0);
//imwrite("600temp.jpg", img);
int rows, cols;
uchar* img_point, * temp_point; //像素类型uchar
rows = my_temp.rows;
cols = my_temp.cols * img.channels();
double result, same = 0.0, different = 0.0;
for (int i = 0; i < rows; i++) //遍历图像像素
{
//获取像素值
img_point = img.ptr<uchar>(i);
temp_point = my_temp.ptr<uchar>(i);
for (int j = 0; j < cols; j++)
{
if (img_point[j] == temp_point[j])
same++; //记录像素相同的个数
else
different++; //记录像素不同的个数
}
}
result = same / (same + different);
return result; //返回匹配结果
}
float imgDetect(Mat img) {
Mat clone_img = img.clone();
cv::GaussianBlur(img, img, cv::Size(3, 3), 0);
cv::dilate(img, img, cv::getStructuringElement(cv::MORPH_RECT, cv::Size(3, 3)));
vector<vector<Point>> contours; //存储原图轮廓
vector<Vec4i> hierarcy;
findContours(img, contours, hierarcy, RETR_EXTERNAL, CHAIN_APPROX_NONE);//查找轮廓
drawContours(clone_img, contours, -1, Scalar(0, 255, 0), 1, 8);//绘制轮廓
vector<Rect> sort_rect(contours.size()); //外接矩形
vector<Mat> img_mat;//存储原图中的有效数字区域
for (int i = 0; i < contours.size(); i++)
{
sort_rect[i] = boundingRect(contours[i]); //外接矩形
Mat roi = clone_img(sort_rect[i]);
Mat dstroi;
resize(roi, dstroi, Size(40, 50), 0, 0); //重设大小
img_mat.push_back(dstroi);
//绘制外接矩形
rectangle(clone_img, Point(sort_rect[i].x, sort_rect[i].y),
Point(sort_rect[i].x + sort_rect[i].width, sort_rect[i].y + sort_rect[i].height),
Scalar(0, 0, 255), 1, 8);
}
//imshow("pre", clone_img);
//waitKey(0);
//对矩形进行排序,因为轮廓的顺序不一定是数字真正的顺序
for (int i = 0; i < sort_rect.size() - 1; i++)
{
for (int j = 0; j < sort_rect.size() - i - 1; j++)
{
int j_x = sort_rect[j].x;
int j1_x = sort_rect[j + 1].x;
if (j_x > j1_x)
{
cv::Rect_<int> tempSize = sort_rect[j];
sort_rect[j] = sort_rect[j + 1];
sort_rect[j + 1] = tempSize;
Mat temps = img_mat[j];
img_mat[j] = img_mat[j + 1];
img_mat[j + 1] = temps;
}
}
}
vector<Mat> myTemplate; //模板容器
for (int i = 0; i < 11; i++)
{
string name = format("img/%d.PNG", i);
Mat temp = imread(name);
cvtColor(temp, temp, COLOR_BGR2GRAY);
threshold(temp, temp, 0, 255, THRESH_BINARY);
myTemplate.push_back(temp);
/*imshow("pre", temp);
waitKey(0);*/
}
vector<int> seq;//顺序存放识别结果
for (int i = 0; i < img_mat.size(); i++)
{
double com = 0;
double min = 0;
int min_seq = 0;//记录识别结果
for (int j = 0; j < myTemplate.size(); j++)
{
/*imshow("pre", img_mat[i]);
waitKey(0);
imshow("pre", myTemplate[j]);
waitKey(0);*/
com = compare(img_mat[i], myTemplate[j]);
if (com > min)
{
min = com;
min_seq = j;
}
com = 0;
}
seq.push_back(min_seq);
}
//输出结果
float result = 0;
int j = 1;
int point = 0;
for (int i = seq.size() - 1; i >= 0; i--) {
if (seq[i] < 10) {
result += seq[i] * j;
j = j * 10;
}
else {
point = i;
}
}
if (point != 0)result = result * pow(0.1, seq.size() - point - 1);
return result;
}
bool compareVectors(const std::vector<float>& vec1, const std::vector<float>& vec2) {
// 如果两个向量大小不同,则它们不相等
if (vec1.size() != vec2.size()) {
return false;
}
// 逐个比较向量中的元素
for (size_t i = 0; i < vec1.size(); ++i) {
if (vec1[i] != vec2[i]) {
return false;
}
}
// 如果所有元素都相等,则两个向量相等
return true;
}
六、主函数
void main() {
int i = 1;
vector<float> vecResult;
while (i++) {
clock_t start, stop;
HWND hq = FindWindow(NULL, TEXT("MainWindow"));
SavePic(L"1.png", hq);
Sleep(2640);
//cv::Mat image2 = cv::imread("4.png");
/*cv::imshow("meinv", image2);
cv::waitKey(0);*/
//string path = to_string(i) + ".png";
string path = "1.png";
Mat img = imread(path);//读取图片
Mat imgGray, imgBlur, imgCanny, imgDila, imgErode, imgThre;
Mat kernel = getStructuringElement(MORPH_RECT, Size(3, 3));
//preprocessing
cvtColor(img, imgGray, COLOR_BGR2GRAY, 0);
threshold(imgGray, imgThre, 0, 255, THRESH_BINARY);
GaussianBlur(imgThre, imgBlur, Size(65, 65), 1, 1);
Canny(imgBlur, imgCanny, 40, 120);
dilate(imgCanny, imgDila, kernel);
//灰度->高斯滤波->Canny边缘算法->膨胀
vector<Mat> imgFinal = getcontour(imgDila, imgThre);
/*imshow("Image", img);
imshow("Image Gray", imgGray);
imshow("Image Blur", imgBlur);
imshow("Image Canny", imgCanny);
imshow("Image dila", imgDila);
waitKey(0);*/
/*clock_t start, stop;
start = clock();*/
float result = 0;
start = clock();
vector<float> vecResult2(vecResult.begin(), vecResult.end());
vecResult.clear();
for (int i = 0; i < imgFinal.size(); i++) {
result = imgDetect(imgFinal[i]);
cout << "识别结果为:" << result << endl;
vecResult.push_back(result);
}
if (!compareVectors(vecResult, vecResult2)) {
for (int i = 0; i < vecResult.size(); i++) {
string filePath = "result.txt";
ofstream outfile(filePath,ios::app);
outfile.is_open();
if (!outfile)
{
cout << "打开文件失败" << endl;
}
outfile << vecResult[i] << endl;
}
}
stop = clock();
cout << "识别时间:" << stop - start << "ms" << endl;
}
}
七. 结果展示
测试数据约10000个,准确率高达100%。
总结
希望后生能好好为老师服务,别当一个不负责的人