功能实现:1.采集图片 2.预处理 3.具体问题具体分析
CMakeLists.txt
cmake_minimum_required(VERSION 3.1)
project(OMR VERSION 1.0.0)
add_executable(OMR main.cpp)
find_package(OpenCV REQUIRED)
target_link_libraries(OMR ${OpenCV_LIBS})
main.cpp
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
Mat img, imgGray, imgBlur, imgCanny, imgContours, imgWarp, imgWarpGrade, imgThre, imgWarpGray, imgWarpGradeGray, imgGradeThre;
vector<Point> docPoint_max, docPoint_grade;
vector<Mat> boxes;
/*获取所需轮廓区域*/
vector<vector<Point>> getContours(Mat image)
{
//四个角点,闭合的轮廓
//让涂卡去从众多矩阵中脱颖而出(面积,周长)
//两个ROI:1.涂卡区 2.成绩区
vector<vector<Point>> contours;
vector<Vec4i> hierarchy;
findContours(image, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_NONE);
vector<vector<Point>> coatedArea;
vector<vector<Point>> gradeArea;
vector<vector<Point>> recCnt(contours.size()); //轮廓的角点坐标
int maxArea = 0;
for (int i = 0; i < contours.size(); i++)
{
//遍历每个轮廓
double area = contourArea(contours[i], false); //面积绝对值
//cout << area << endl;
if (area > 300)
{
double peri = arcLength(contours[i], true); //闭合曲线的长度
approxPolyDP(contours[i], recCnt[i], 0.1 * peri, true);
if (recCnt[i].size() == 4 && area > maxArea)
{
//drawContours(img, recCnt, i, Scalar(0, 255, 0));
//imshow("drawContours", img);
maxArea = area;
coatedArea.push_back(recCnt[i]);
}
}
}
return coatedArea;
}
/*画点*/
void drawPoints(Mat image, vector<Point> cPoint, Scalar color)
{
for (int i = 0; i < cPoint.size(); i++)
{
circle(image, cPoint[i], 5, color, FILLED);
putText(image, to_string(i), cPoint[i], FONT_HERSHEY_PLAIN, 5, color, 3);
}
}
/*给点重新排序*/
vector<Point> reorder(vector<Point> points)
{
vector<Point> newPoints;
vector<int> sumPoints, subPoints;
for (int i = 0; i < points.size(); i++)
{
sumPoints.push_back(points[i].x + points[i].y);
subPoints.push_back(points[i].x - points[i].y);
}
newPoints.push_back(points[min_element(sumPoints.begin(), sumPoints.end()) - sumPoints.begin()]);
newPoints.push_back(points[max_element(subPoints.begin(), subPoints.end()) - subPoints.begin()]);
newPoints.push_back(points[min_element(subPoints.begin(), subPoints.end()) - subPoints.begin()]);
newPoints.push_back(points[max_element(sumPoints.begin(), sumPoints.end()) - sumPoints.begin()]);
return newPoints;
}
/*放射变换*/
Mat getWrap(Mat image, vector<Point> points, float w, float h)
{
Mat imgWrap;
Point2f src[4] = {points[0], points[1], points[2], points[3]};
Point2f dst[4] = {{0.0f, 0.0f}, {w, 0.0f}, {0.0f, h}, {w, h}};
Mat matrix = getPerspectiveTransform(src, dst);
warpPerspective(img, imgWrap, matrix, Point(w, h));
return imgWrap;
}
/*图形分割*/
vector<Mat> splitBox(Mat img)
{
vector<Mat> boxes;
Mat img_cut, roi_img;
int width = img.cols / 5;
int height = img.rows / 5;
//cout << "width:" << width << " height: " << height << endl;
for (int i = 0; i < 5; i++)
{
for (int j = 0; j < 5; j++)
{
Rect rect(i * height, j * height, 60, 60);
img_cut = Mat(img, rect);
roi_img = img_cut.clone();
boxes.push_back(roi_img);
}
}
for (int i = 0; i < boxes.size(); i++)
{
string num = to_string(i);
//imshow(num, boxes[i]);
}
return boxes;
}
/*答案显示*/
Mat showAnswer(Mat img, vector<int> index, vector<int> grading, vector<int> ans, int question, int choice)
{
int secW = img.cols / choice;
int secH = img.rows / question;
Scalar color;
for (int i = 0; i < question; i++)
{
int sx = (index[i] * secW) + (secW / 2);
int sy = (i * secH) + (secH / 2);
if (grading[i] == 1)
{
color = Scalar(0, 255, 0);
}
else
{
color = Scalar(0, 0, 255);
circle(img, Point((ans[i] * secW) + (secW / 2), sy), 10, Scalar(0, 255, 0), FILLED);
}
circle(img, Point(sx, sy), 20, color, FILLED);
}
return img;
}
/*反向仿射变换*/
Mat getreWrap(Mat image, vector<Point> points, float w, float h, float sizeW, float sizeH)
{
Mat imgWrap;
Point2f src[4] = {points[0], points[1], points[2], points[3]};
Point2f dst[4] = {{0.0f, 0.0f}, {w, 0.0f}, {0.0f, h}, {w, h}};
Mat matrix = getPerspectiveTransform(dst, src);
warpPerspective(image, imgWrap, matrix, Point(sizeW, sizeH));
return imgWrap;
}
int main(int argc, char const *argv[])
{
img = imread("../omr1.JPG");
cout << img.size << endl;
resize(img, img, Size(500, 500));
//imshow("img", img);
cvtColor(img, imgGray, COLOR_RGB2GRAY);
//imshow("imgGray", imgGray);
GaussianBlur(imgGray, imgBlur, Size(5, 5), 0, 0);
//imshow("imgBlur", imgBlur);
Canny(imgBlur, imgCanny, 50, 25, 3);
imshow("imgCanny", imgCanny);
//ROI: resgion of interest
vector<vector<Point>> contours;
vector<vector<Point>> cPoint;
vector<Vec4i> hierarchy;
//深拷贝不会影响原图
imgContours = img.clone();
//findContours(imgCanny, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_NONE);
//drawContours(imgContours, contours, -1, Scalar(0, 255, 0), 4);
//imshow("imgContours", imgContours);
//找到涂卡去的矩形轮廓
cPoint = getContours(imgCanny);
drawContours(imgContours, cPoint, -1, Scalar(0, 255, 0), 2);
imshow("imgContours", imgContours);
//1.重新排序四个角点
cout << cPoint[0] << endl;
cout << cPoint[1] << endl;
// drawPoints(img, cPoint[0], Scalar(0, 0, 200));
// drawPoints(img, cPoint[1], Scalar(0, 0, 200));
// imshow("img", img);
docPoint_grade = reorder(cPoint[0]);
docPoint_max = reorder(cPoint[1]);
drawPoints(imgContours, docPoint_max, Scalar(0, 0, 200));
drawPoints(imgContours, docPoint_grade, Scalar(0, 0, 200));
imshow("imgContours", imgContours);
//2.仿射变换
imgWarp = getWrap(img, docPoint_max, 300, 300);
//imshow("imgWrap", imgWarp);
imgWarpGrade = getWrap(img, docPoint_grade, 180, 100);
//imshow("imgWarpGrade", imgWarpGrade);
//3.图像二值化
cvtColor(imgWarp, imgWarpGray, COLOR_RGB2GRAY);
threshold(imgWarpGray, imgThre, 180, 255, THRESH_BINARY_INV);
imshow("imgThre", imgThre);
cvtColor(imgWarpGrade, imgWarpGradeGray, COLOR_RGB2GRAY);
threshold(imgWarpGradeGray, imgGradeThre, 180, 255, THRESH_BINARY_INV);
imshow("imgGradeThre", imgGradeThre);
//4.图形分割
boxes = splitBox(imgThre);
//5.计算答案下标,对比正确答案
int totalPixels;
int myPixelVal[5][5] = {0};
for (int i = 0; i < 25; i++)
{
totalPixels = countNonZero(boxes[i]);
myPixelVal[i % 5][i / 5] = totalPixels;
}
for (int i = 0; i < 5; i++)
{
for (int j = 0; j < 5; j++)
{
cout << "[" << i << "," << j << "] " << myPixelVal[i][j] << " ";
}
cout << endl;
}
vector<vector<int>> brr;
for (int i = 0; i < 5; i++)
{
vector<int> arr;
for (int j = 0; j < 5; j++)
{
arr.push_back(myPixelVal[i][j]);
}
brr.push_back(arr);
}
vector<int> index;
for (int i = 0; i < brr.size(); i++)
{
auto location = max_element(brr[i].begin(), brr[i].end());
index.push_back((int)(location - brr[i].begin()));
cout << *location << " " << index[i] << endl;
}
//标准答案
vector<int> ans = {1, 2, 0, 1, 4};
vector<int> grading;
int rightnum = 0;
for (int i = 0; i < 5; i++)
{
if (ans[i] == index[i])
{
rightnum++;
grading.push_back(1);
}
else
{
grading.push_back(0);
}
cout << grading[i] << endl;
}
//计算分数
int score = (double)rightnum / 5 * 100;
cout << "score: " << score << endl;
//6.显示答案及分数
Mat imgAnswser = imgWarp.clone();
imgAnswser = showAnswer(imgAnswser, index, grading, ans, 5, 5);
imshow("imgAnswser", imgAnswser);
Mat imgDrawing(imgAnswser.size(), imgAnswser.type(), Scalar(0, 0, 0));
imgDrawing = showAnswer(imgDrawing, index, grading, ans, 5, 5);
imshow("imgDrawing", imgDrawing);
Mat imgGradeDrawing(imgWarpGrade.size(), imgWarpGrade.type(), Scalar(0, 0, 0));
putText(imgGradeDrawing, to_string(score) + "%", Point(20, 80), FONT_HERSHEY_PLAIN, 5, Scalar(0, 0, 255), 4);
imshow("imgGradeDrawing", imgGradeDrawing);
//7.反向仿射变换
Mat imgReWarp = getreWrap(imgDrawing, docPoint_max, 300, 300, 500, 500);
Mat imgGradeReWarp = getreWrap(imgGradeDrawing, docPoint_grade, 180, 100, 500, 500);
imshow("imgReWarp", imgReWarp);
imshow("imgGradeReWarp", imgGradeReWarp);
//8.融合
Mat imgFinal;
addWeighted(img, 1, imgReWarp, 1, 0, imgFinal);
addWeighted(imgFinal, 1, imgGradeReWarp, 1, 0, imgFinal);
imshow("imgFinal", imgFinal);
waitKey();
return 0;
}
结果展示: