Browse Source

:new: qqm create for physics circult fucking code

maoyehu 1 year ago
parent
commit
f0fdcb9d59

+ 422 - 8
MFCApplication1/CvxText.cpp

@@ -5,6 +5,10 @@
 #include <iostream>
 #include <fstream>
 
+#if QQM_ADD
+#include "cvUtil.h"
+#endif
+
 using namespace std;
 using namespace cv;
 
@@ -368,6 +372,410 @@ std::string JsonToString(const Json::Value & root)
 	return stream.str();
 }
 
+
+
+int dataCollectionPaperPhysics(int cols, int index, int fontSize, int lineGrayPix, bool engShow,
+	std::vector<tuple<string, string>>& vecLines, CString dir, std::string& strPngPath, std::string tips) {
+	/* 这里要做一些栏数 和 vecLines的对应值确认 */
+	if (2 == cols && 14 * 2 == vecLines.size())
+		; // ok 
+	if (3 == cols && 14 * 3 == vecLines.size())
+		; // ok 
+	if (4 == cols && 14 * 4 == vecLines.size())
+		; // ok 
+
+	// 生成画布图像 
+	cv::Mat img(cv::Size(1654, 2344), CV_8UC3, cv::Scalar(255, 255, 255));
+	int ptw = 80; int pth = 40;
+	cv::Size fSize(0, 0);
+	int leftPos = 100, rightPos = 1450;
+	int topPos = 90;
+	int fontW = rightPos - leftPos + ptw;
+	vector<cv::Rect> vecPts = {
+		cv::Rect(100,90,ptw,pth),
+		cv::Rect(580,90,ptw,pth),
+		cv::Rect(1450,90,ptw,pth),
+		cv::Rect(100,2176,ptw,pth),
+		cv::Rect(1450,2176,ptw,pth),
+	};
+
+
+	// 画定位点
+	for (size_t i = 0; i < vecPts.size(); i++)
+	{
+		rectangle(img, vecPts[i], cv::Scalar(0, 0, 0), -1);
+	}
+
+	// 画示例框
+	topPos += pth; topPos += 30;
+	rectangle(img, cv::Rect(leftPos, topPos, fontW, 200), cv::Scalar(0, 0, 0), 2, 8, 0);
+
+	// 画页码框
+	cv::Rect boxPageNumber(leftPos + fontW - 300, topPos, 300, 200);
+	rectangle(img, boxPageNumber, cv::Scalar(0, 0, 0), 2, 8, 0);
+
+	// 生成二进制码流
+	/* 二进制码流的坐标在下面函数里面实现,需要用书写获取 */
+	vector<cv::Rect> vecBoxPages;
+	createBinString(img, index, ptw, pth, cv::Point(boxPageNumber.x, boxPageNumber.y), vecBoxPages);
+	string strPageNumInfo = "关联ID: " + to_string(index);
+	putTextZH(img, fSize, strPageNumInfo.c_str(), cv::Point(boxPageNumber.x + 100, boxPageNumber.y + 50 * 2 + 20 + 15 + 15 + 15), Scalar(0), 20, "宋体");
+
+	// 生成page.json
+	//generatePageJson(vecPts, vecBoxPages);
+
+	// 画警示信息
+	string strMesInfo = "1、请在对应的区域内临摹左侧电路图,每个方框内一个!\
+						\n\n2、可以使用铅笔和黑色笔绘图。☆\
+						\n\n3、如果本框内有绘画错误,进行了涂抹修改等操作,请将左上方的填涂框用任意笔进行填涂!☆☆\n";
+	if (tips.length() > 0)
+	{
+		strMesInfo = tips;
+	}
+	putTextZH(img, fSize, strMesInfo.c_str(), cv::Point(leftPos + 20, topPos + 20), Scalar(0), 25, "宋体");
+	int lineTop1 = topPos + 20 + fSize.height + 20;
+	cv::line(img, cv::Point(leftPos, lineTop1), cv::Point(rightPos + ptw - 300, topPos + 20 + fSize.height + 20), Scalar(0), 2, 8, 0);
+
+	// 正确示例
+	string strEgInfo = "注\n意\n事\n项";
+	putTextZH(img, fSize, strEgInfo.c_str(), cv::Point(leftPos + 20, topPos + 20 + fSize.height + 20 + 3), Scalar(0), 20, "宋体");
+	cv::line(img, cv::Point(leftPos + 20 + fSize.width + 10, lineTop1), cv::Point(leftPos + 20 + fSize.width + 10, topPos + 200), Scalar(0, 0, 0), 2, 8, 0);
+
+	// 填涂示例
+	string strtt = "尽量保持手绘内容与示例图相对位置一致!即保持整体相对居中!";
+	cv::Point ptEgInfo(leftPos + 70, topPos + 20 + fSize.height + 20 + 30);
+	putTextZH(img, fSize, strtt.c_str(), ptEgInfo, Scalar(0), 20, "宋体");
+	
+	//cv::Rect rcttbox(ptEgInfo.x + 100, ptEgInfo.y - 5, 30, 30);
+	//rectangle(img, rcttbox, cv::Scalar(0), -1);
+	//rcttbox.x += 1;
+	//rcttbox.y += 1;
+	//rcttbox.width -= 2;
+	//rcttbox.height -= 2;
+	//rectangle(img, rcttbox, cv::Scalar(100, 100, 100), -1);
+	
+
+	// 贴图区域
+	/* 暂不实现 */
+
+	topPos += 200;
+
+
+	///////////////////////////////////////// 主干区域: y 起始坐标:360/////////////////////////////////////////////
+	Json::Value root(Json::arrayValue);
+
+	// +指定像素
+	auto yhPadding = [&](cv::Rect & rcBox,int pd) {
+		rcBox.x += pd;
+		rcBox.y += pd;
+		rcBox.width -= (pd * 2);
+		rcBox.height -= (pd * 2);
+	};
+
+	// json valule
+	auto addValueToJson = [&](Json::Value & root, cv::Rect rcbox, cv::Rect rcNum, cv::Rect rcQues, cv::Rect rcAns, 
+		int iQNum, std::string strTgInfo, std::string strSolution) {
+		//生成识别信息
+		Json::Value ttbox;
+		ttbox["x"] = rcbox.x;
+		ttbox["y"] = rcbox.y;
+		ttbox["width"] = rcbox.width;
+		ttbox["height"] = rcbox.height;
+
+		Json::Value queNum;
+		queNum["x"] = rcNum.x;
+		queNum["y"] = rcNum.y;
+		queNum["width"] = rcNum.width;
+		queNum["height"] = rcNum.height;
+
+		Json::Value queInfo;
+		queInfo["x"] = rcQues.x;
+		queInfo["y"] = rcQues.y;
+		queInfo["width"] = rcQues.width;
+		queInfo["height"] = rcQues.height;
+		queInfo["info"] = strTgInfo.c_str();
+		queInfo["solution"] = strSolution.c_str();
+
+		Json::Value ansInfo;
+		ansInfo["x"] = rcAns.x;
+		ansInfo["y"] = rcAns.y;
+		ansInfo["width"] = rcAns.width;
+		ansInfo["height"] = rcAns.height;
+
+		Json::Value item;
+		item["id"] = iQNum;
+		item["ttbox"] = ttbox;
+		item["queNum"] = queNum;
+		item["queInfo"] = queInfo;
+		item["ansInfo"] = ansInfo;
+
+		root.append(item);
+	};
+
+
+#if QQM_ADD
+	topPos += 50; // 间隔预留
+	int pd = 3;
+	cv::Size exampleSize(512+ pd *2, 512+ pd *2);	// 示例图size
+	int exdis = 20;	// 示例图之间水平方向间隔
+	int eydis = 50; //
+	int bigDrawBoxNum = 2;
+	int smlDrawBoxNum = 2 * 4;
+	int iQNum = 1;
+	/*
+	---------------------------------------------
+		|			|			|			|
+		|示例大图1	|绘画区域1	|绘画区域2	|
+		|			|			|			|
+	---------------------------------------------
+		|			|			|			|
+		|示例大图2	|...		|			|
+		|			|			|			|
+	---------------------------------------------
+		|			|1	  |2	|1	  |2	|
+		|示例小图3	|-----------|-----------|
+		|			|3	  |4	|3	  |4	|
+	----------------------------------------------
+	*/
+	int eleft = max(0, (img.cols - (exampleSize.width + 20) * max_line) / 2);
+	for (auto ex = 0; ex < max_line; ex++) {
+		//int _left = eleft + (exampleSize.width + 20)*ex;
+		// 基础信息准备
+		int _left = eleft;
+		int _top = topPos + (exampleSize.height + eydis)*ex;
+		cv::Rect box(_left, _top, exampleSize.width, exampleSize.height);
+		rectangle(img, box, cv::Scalar(0), 2, 8, 0);
+		string strTgInfo = std::get<0>(vecLines[ex]);
+		string strSolution = std::get<1>(vecLines[ex]);
+		int _type = atoi(strSolution.c_str()); // 1 big 0 small
+
+		// 示例图贴图
+		cv::Mat _img = cv::imread(strTgInfo, cv::IMREAD_ANYCOLOR);
+		CV_Assert(!_img.empty());
+		cv::Mat _rsImg;
+		if (_type) 
+			_rsImg = paddingResize(_img, exampleSize.width, exampleSize.height);
+		else {
+			cv::Mat _rsImg4  = paddingNoResize(_img, exampleSize.width/2, exampleSize.height/2);
+			_rsImg = cv::Mat(exampleSize, _img.type(), cv::Scalar(255, 255, 255));
+			_rsImg = splitAndInsertImages(_rsImg4, _rsImg);
+			cv::line(_rsImg, cv::Point(0, exampleSize.height / 2), cv::Point(exampleSize.width - 1, exampleSize.height / 2), cv::Scalar(0), 1, 8, 0);
+			cv::line(_rsImg, cv::Point(exampleSize.width / 2, 0), cv::Point(exampleSize.width / 2, exampleSize.height - 1), cv::Scalar(0), 1, 8, 0);
+
+		}
+
+		cv::Mat _bin; 
+		cv::cvtColor(_rsImg, _bin, cv::COLOR_BGR2GRAY);
+		cv::threshold(_bin, _bin, 200, 255, THRESH_BINARY);
+		cv::cvtColor(_bin, _rsImg, cv::COLOR_GRAY2BGR);
+		_rsImg.copyTo(img(box));
+		
+
+
+		// 绘图区域
+		int ttW = 20; int ttH = 20; // 填涂框
+		int __left = _left + box.width + exdis;
+		int __top = _top;
+		int __w = exampleSize.width * 2 + exdis;
+		int __h = exampleSize.height;
+		if (_type) {
+			for (auto n = 0; n < bigDrawBoxNum; n++) {
+				cv::Rect __box(__left+n*(exampleSize.width+exdis), __top, exampleSize.width, exampleSize.height);
+				cv::rectangle(img, __box, cv::Scalar(0), 2, 8, 0);
+				// 填涂框子
+				cv::Rect __ttBox(__box.x, __box.y - ttH, ttW, ttH);
+				cv::rectangle(img, __ttBox, cv::Scalar(0), 1, 8, 0);
+				cv::Rect __thBox(__ttBox);
+				cv::Rect __qBox(__ttBox);
+				yhPadding(__box, pd);
+				addValueToJson(root, __ttBox, __ttBox, __ttBox, __box, iQNum++, strTgInfo, strSolution);
+			}
+		}
+		else {
+			int __col = 4; int __row = 2;
+			int ___disx = 20;
+			int ___disy = 20;
+			int ___w = (__w - ___disx * (__col - 1)) / __col;
+			int ___h = (__h - ___disy * (__row - 1)) / __row;
+			for (auto row = 0; row < __row; row++) {
+				int ___top = __top + row*(___h + ___disy);
+				for (auto col = 0; col < __col; col++) {
+					int ___left = __left + col * (___w + ___disx);
+					cv::Rect ___box(___left, ___top, ___w, ___h);
+					cv::rectangle(img, ___box, cv::Scalar(0), 2, 8, 0);
+					// 填涂框子
+					cv::Rect __ttBox(___box.x, ___box.y - ttH, ttW, ttH);
+					cv::rectangle(img, __ttBox, cv::Scalar(0), 1, 8, 0);
+					cv::Rect __thBox(__ttBox);
+					cv::Rect __qBox(__ttBox);
+					yhPadding(___box, pd);
+					addValueToJson(root, __ttBox, __ttBox, __ttBox, ___box, iQNum++, strTgInfo, strSolution);
+				}
+			}
+		}
+
+		// fuick
+		int x = 0;
+		
+	}
+
+
+#else
+	// 文字区域 
+	static int maxLineNum = 14;	// 最大行数14
+	static int ttBoxWidth = 30;  // 填涂框的边长
+	static int tiSl = 10; // 题号和题干间的距离
+	static int lineDis = 20;	// 两行之间的距离
+	int colWidth = fontW / cols; // 单栏的宽度
+	fSize.width = 0; fSize.height = 0;
+
+	//RNG rng;
+	for (int col = 0; col < cols; col++)
+	{ // 逐栏画题
+		int colTopPos = topPos;
+		int colLeftPos = leftPos + col * colWidth;
+		for (size_t i = 0; i < maxLineNum; i++)
+		{
+			size_t vecIndex = i + col * maxLineNum;
+			if (vecIndex >= vecLines.size())
+			{
+				break;
+			}
+			colTopPos += lineDis;
+			int lineLen = ttBoxWidth + tiSl;
+			// 画填涂框 30*30 大小
+			cv::Rect rcbox(colLeftPos, colTopPos, ttBoxWidth, ttBoxWidth);
+			rectangle(img, rcbox, cv::Scalar(0), 1, 8, 0);
+
+			int iQNum = i + 1 + col * maxLineNum;
+			string strStinfo = to_string(iQNum); strStinfo += ".";
+			cv::Point ptNum(colLeftPos + ttBoxWidth + tiSl, colTopPos);
+			putTextZH(img, fSize, strStinfo.c_str(), ptNum, Scalar(0, 0, 0), fontSize, "宋体"); // 跟填涂框保持10px距离
+			lineLen += fSize.width;
+			cv::Rect rcNum(ptNum.x, ptNum.y, fSize.width, fSize.height);
+
+			strStinfo.clear();
+			string strTgInfo = std::get<0>(vecLines[vecIndex]);
+			string strSolution = std::get<1>(vecLines[vecIndex]);
+			if (!engShow)
+				; /*  移除词性   */
+			//char szTemp[2048];
+			//memset(szTemp, 0, sizeof(char)*(2048));
+			//UTF82ANSI(strTgInfo.c_str(), strTgInfo.length(), szTemp, 2048);
+			//strTgInfo = szTemp;
+			cv::Point ptQues(colLeftPos + ttBoxWidth + tiSl + fSize.width, colTopPos);
+			putTextZH(img, fSize, strTgInfo.c_str(), ptQues, Scalar(0, 0, 0), fontSize, "宋体"); // 跟填涂框保持10px距离
+			lineLen += fSize.width;
+			cv::Rect rcQues(ptQues.x, ptQues.y, fSize.width, fSize.height);
+			cv::Rect rcAns(colLeftPos + 30, colTopPos + fSize.height, colWidth - ttBoxWidth, 1);
+
+			colTopPos = colTopPos + fSize.height * 3.5; //两行之间两倍的距离用于书写
+			//lineGrayPix = rng.operator ()(220);
+			line(img, cv::Point(colLeftPos + 30, colTopPos), cv::Point(colLeftPos + colWidth - 1, colTopPos), Scalar(lineGrayPix, lineGrayPix, lineGrayPix), 1, 8, 0);
+			rcAns.height = colTopPos - rcAns.y + lineDis;
+
+
+
+
+
+			//生成识别信息
+			Json::Value ttbox;
+			ttbox["x"] = rcbox.x;
+			ttbox["y"] = rcbox.y;
+			ttbox["width"] = rcbox.width;
+			ttbox["height"] = rcbox.height;
+	#if QQM_ADD
+			cv::rectangle(img, rcbox, cv::Scalar(0, 0, 244), 1, 8, 0);
+	#endif // QQM_ADD
+
+
+			Json::Value queNum;
+			queNum["x"] = rcNum.x;
+			queNum["y"] = rcNum.y;
+			queNum["width"] = rcNum.width;
+			queNum["height"] = rcNum.height;
+	#if QQM_ADD
+			cv::rectangle(img, rcNum, cv::Scalar(0, 255, 244), 1, 8, 0);
+	#endif // QQM_ADD
+
+			Json::Value queInfo;
+			queInfo["x"] = rcQues.x;
+			queInfo["y"] = rcQues.y;
+			queInfo["width"] = rcQues.width;
+			queInfo["height"] = rcQues.height;
+			queInfo["info"] = strTgInfo.c_str();
+			queInfo["solution"] = strSolution.c_str();
+	#if QQM_ADD
+			cv::rectangle(img, rcQues, cv::Scalar(215, 0, 244), 1, 8, 0);
+	#endif // QQM_ADD
+
+			Json::Value ansInfo;
+			ansInfo["x"] = rcAns.x;
+			ansInfo["y"] = rcAns.y;
+			ansInfo["width"] = rcAns.width;
+			ansInfo["height"] = rcAns.height;
+	#if QQM_ADD
+			cv::rectangle(img, rcAns, cv::Scalar(225, 120, 244), 1, 8, 0);
+	#endif // QQM_ADD
+
+			Json::Value item;
+			item["id"] = iQNum;
+			item["ttbox"] = ttbox;
+			item["queNum"] = queNum;
+			item["queInfo"] = queInfo;
+			item["ansInfo"] = ansInfo;
+
+			root.append(item);
+
+			// test
+			/*rectangle(img, rcbox, cv::Scalar(0, 0, 200), 1, 8, 0);
+			rectangle(img, rcNum, cv::Scalar(0, 200, 0), 1, 8, 0);
+			rectangle(img, rcQues, cv::Scalar(100, 0, 0), 1, 8, 0);
+			rectangle(img, rcAns, cv::Scalar(200, 0, 0), 1, 8, 0);*/
+		}
+	}
+
+#endif // QQM_ADD
+
+	
+	std::string strJson = JsonToString(root); 
+	
+	CString strTemplatePath;
+	strTemplatePath.Format(L"%s/json/%d.json", dir, index);
+	CFile zip;
+	zip.Open(strTemplatePath, CFile::modeCreate | CFile::modeWrite);
+	zip.Write((void*)strJson.c_str(), strJson.length());
+	zip.Close();
+
+	wchar_t tempPath[MAX_PATH];
+	DWORD dwSize = MAX_PATH;
+	GetTempPath(dwSize, tempPath);//获取临时文件夹路径
+	static int tmp_index = 1;
+	wchar_t szPath[MAX_PATH];
+	_stprintf(szPath, L"%stmp_%d.png", tempPath, tmp_index);
+	tmp_index++;
+	if (tmp_index > 10)
+	{
+		tmp_index = 1;
+	}
+	
+	strPngPath = TstringToGB2312(szPath);
+	cv::imwrite(strPngPath, img);
+
+	
+	/*cv::namedWindow("fuck", 1);
+	cv::imshow("fuck", img);
+	cv::waitKey(0);*/
+
+
+
+
+
+	
+
+	return 0;
+}
+
 int dataCollectionPaper(int cols, int index, int fontSize, int lineGrayPix, bool engShow, std::vector<tuple<string, string>>& vecLines, CString dir, std::string& strPngPath, std::string tips) {
 	/* 这里要做一些栏数 和 vecLines的对应值确认 */
 	if (2 == cols && 14 * 2 == vecLines.size())
@@ -445,7 +853,7 @@ int dataCollectionPaper(int cols, int index, int fontSize, int lineGrayPix, bool
 	rcttbox.width -= 2;
 	rcttbox.height -= 2;
 	rectangle(img, rcttbox, cv::Scalar(100, 100, 100), -1);
-	
+
 	// 贴图区域
 	/* 暂不实现 */
 
@@ -498,12 +906,13 @@ int dataCollectionPaper(int cols, int index, int fontSize, int lineGrayPix, bool
 			lineLen += fSize.width;
 			cv::Rect rcQues(ptQues.x, ptQues.y, fSize.width, fSize.height);
 			cv::Rect rcAns(colLeftPos + 30, colTopPos + fSize.height, colWidth - ttBoxWidth, 1);
-			
+
 			colTopPos = colTopPos + fSize.height * 3.5; //两行之间两倍的距离用于书写
 			//lineGrayPix = rng.operator ()(220);
 			line(img, cv::Point(colLeftPos + 30, colTopPos), cv::Point(colLeftPos + colWidth - 1, colTopPos), Scalar(lineGrayPix, lineGrayPix, lineGrayPix), 1, 8, 0);
 			rcAns.height = colTopPos - rcAns.y + lineDis;
 
+
 			//生成识别信息
 			Json::Value ttbox;
 			ttbox["x"] = rcbox.x;
@@ -511,12 +920,15 @@ int dataCollectionPaper(int cols, int index, int fontSize, int lineGrayPix, bool
 			ttbox["width"] = rcbox.width;
 			ttbox["height"] = rcbox.height;
 
+
 			Json::Value queNum;
 			queNum["x"] = rcNum.x;
 			queNum["y"] = rcNum.y;
 			queNum["width"] = rcNum.width;
 			queNum["height"] = rcNum.height;
 
+
+
 			Json::Value queInfo;
 			queInfo["x"] = rcQues.x;
 			queInfo["y"] = rcQues.y;
@@ -525,19 +937,21 @@ int dataCollectionPaper(int cols, int index, int fontSize, int lineGrayPix, bool
 			queInfo["info"] = strTgInfo.c_str();
 			queInfo["solution"] = strSolution.c_str();
 
+
 			Json::Value ansInfo;
 			ansInfo["x"] = rcAns.x;
 			ansInfo["y"] = rcAns.y;
 			ansInfo["width"] = rcAns.width;
 			ansInfo["height"] = rcAns.height;
 
+
 			Json::Value item;
 			item["id"] = iQNum;
 			item["ttbox"] = ttbox;
 			item["queNum"] = queNum;
 			item["queInfo"] = queInfo;
 			item["ansInfo"] = ansInfo;
-			
+
 			root.append(item);
 
 			// test
@@ -548,8 +962,8 @@ int dataCollectionPaper(int cols, int index, int fontSize, int lineGrayPix, bool
 		}
 	}
 
-	std::string strJson = JsonToString(root); 
-	
+	std::string strJson = JsonToString(root);
+
 	CString strTemplatePath;
 	strTemplatePath.Format(L"%s/json/%d.json", dir, index);
 	CFile zip;
@@ -568,11 +982,11 @@ int dataCollectionPaper(int cols, int index, int fontSize, int lineGrayPix, bool
 	{
 		tmp_index = 1;
 	}
-	
+
 	strPngPath = TstringToGB2312(szPath);
 	cv::imwrite(strPngPath, img);
 
-	
+
 	/*cv::namedWindow("fuck", 1);
 	cv::imshow("fuck", img);
 	cv::waitKey(0);*/
@@ -581,7 +995,7 @@ int dataCollectionPaper(int cols, int index, int fontSize, int lineGrayPix, bool
 
 
 
-	
+
 
 	return 0;
 }

+ 10 - 0
MFCApplication1/CvxText.h

@@ -11,7 +11,14 @@ using namespace std;
 #define font_size   30
 #define chk_width   30
 #define main_wdith  1430
+
+
+#if QQM_ADD
+#define max_line	3 // 每张三个示例图
+#else
 #define max_line	14
+#endif // QQM_ADD
+
 
 void GetStringSize(HDC hDC, const char* str, int* w, int* h);
 //获取字符串的宽高
@@ -50,6 +57,9 @@ int getLineStrWidth(string str, int fontSize, int ttBoxW, int tiSl, int backPix)
 *********************************************************/
 int dataCollectionPaper(int cols, int index, int fontSize, int lineGrayPix, bool engShow, std::vector<tuple<string, string>>& vecLines, CString dir, std::string& strPngPath, std::string tips);
 
+// just for physics
+int dataCollectionPaperPhysics(int cols, int index, int fontSize, int lineGrayPix, bool engShow, std::vector<tuple<string, string>>& vecLines, CString dir, std::string& strPngPath, std::string tips);
+
 /********************************************************
 	*  @function :  答题卡切割
 	*  @brief    :  brief

+ 2 - 0
MFCApplication1/MFCApplication1.vcxproj

@@ -198,6 +198,7 @@
     <ClInclude Include="BaseUtility.h" />
     <ClInclude Include="Buffers.h" />
     <ClInclude Include="Config.h" />
+    <ClInclude Include="cvUtil.h" />
     <ClInclude Include="CvxText.h" />
     <ClInclude Include="framework.h" />
     <ClInclude Include="Instance.h" />
@@ -215,6 +216,7 @@
     <ClCompile Include="BaseUtility.cpp" />
     <ClCompile Include="Buffers.cpp" />
     <ClCompile Include="Config.cpp" />
+    <ClCompile Include="cvUtil.cpp" />
     <ClCompile Include="CvxText.cpp" />
     <ClCompile Include="Log.cpp" />
     <ClCompile Include="md5.cpp" />

+ 6 - 0
MFCApplication1/MFCApplication1.vcxproj.filters

@@ -60,6 +60,9 @@
     <ClInclude Include="md5.h">
       <Filter>头文件</Filter>
     </ClInclude>
+    <ClInclude Include="cvUtil.h">
+      <Filter>头文件</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="MFCApplication1.cpp">
@@ -95,6 +98,9 @@
     <ClCompile Include="md5.cpp">
       <Filter>源文件</Filter>
     </ClCompile>
+    <ClCompile Include="cvUtil.cpp">
+      <Filter>源文件</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="MFCApplication1.rc">

+ 5 - 1
MFCApplication1/MFCApplication1Dlg.cpp

@@ -165,7 +165,8 @@ DWORD WINAPI ProcessThread(void *param)
 			vecSmall.insert(vecSmall.begin(), vctFiles.begin() + i, vctFiles.begin() + last);
 
 			std::string strPagePath;
-			dataCollectionPaper(col, pageNum, 30, nLineGrayPix, false, vecSmall, pWnd->m_strPdfDir, strPagePath, strTips);
+			dataCollectionPaperPhysics(col, pageNum, 30, nLineGrayPix, false, vecSmall, pWnd->m_strPdfDir, strPagePath, strTips);
+			//dataCollectionPaper(col, pageNum, 30, nLineGrayPix, false, vecSmall, pWnd->m_strPdfDir, strPagePath, strTips);
 			if (strPage1.length() == 0)
 			{
 				strPage1 = strPagePath;
@@ -597,6 +598,9 @@ BOOL CMFCApplication1Dlg::OnInitDialog()
 	GetDlgItem(IDC_EDIT_CSVFILEDIR)->SetWindowText(CConfig::Instance()->m_strCsvFileDir);
 
 	GetDlgItem(IDC_STATIC_CSV_PROCESS)->SetWindowText(L"--/--");
+
+	m_strPageNumber = L"1";
+	((CEdit*)GetDlgItem(IDC_EDIT_PAGE_NUMBER))->SetWindowText(m_strPageNumber);
 	
 
 	return TRUE;  // 除非将焦点设置到控件,否则返回 TRUE

+ 316 - 0
MFCApplication1/cvUtil.cpp

@@ -0,0 +1,316 @@
+#include "pch.h"
+#include "cvUtil.h"
+
+
+int getWhitePixNumber(cv::Mat& img) {
+    int w = img.cols;
+    int h = img.rows;
+    int total = 0;
+    for (int i = 0; i < h; i++) {
+        uchar* p2 = img.ptr<uchar>(i);
+        for (int j = 0; j < w; j++) {
+            uchar& pix = *p2++;
+            if (pix == 255)
+                total++;
+        }
+    }
+    return total;
+}
+
+// 按照指定长款缩放图像
+cv::Mat scaleImage(int w, int h, cv::Mat& image)
+{
+    // 获取原始图像的宽度和高度
+    int originalWidth = image.cols;
+    int originalHeight = image.rows;
+
+    // 计算缩放比例
+    double scaleRatio = 1.0;
+    if (w > 0 && h > 0)
+    {
+        // 如果指定了宽度和高度,计算宽度和高度的缩放比例并选择较小值作为最终缩放比例
+        double widthRatio = static_cast<double>(w) / originalWidth;
+        double heightRatio = static_cast<double>(h) / originalHeight;
+        scaleRatio = std::min(widthRatio, heightRatio);
+    }
+    else if (w > 0)
+    {
+        // 如果指定了宽度,根据宽度计算缩放比例
+        scaleRatio = static_cast<double>(w) / originalWidth;
+    }
+    else if (h > 0)
+    {
+        // 如果指定了高度,根据高度计算缩放比例
+        scaleRatio = static_cast<double>(h) / originalHeight;
+    }
+
+    // 计算缩放后的新宽度和新高度
+    int newWidth = static_cast<int>(originalWidth * scaleRatio);
+    int newHeight = static_cast<int>(originalHeight * scaleRatio);
+
+    // 执行缩放操作
+    cv::Mat scaledImage;
+    cv::resize(image, scaledImage, cv::Size(newWidth, newHeight));
+
+    return scaledImage;
+}
+
+
+cv::Mat paddingResize(cv::Mat& image, int w, int h) {
+	int originalWidth = image.cols;
+	int originalHeight = image.rows;
+
+	// 计算缩放比例
+	float scale = std::min(static_cast<float>(w) / originalWidth, static_cast<float>(h) / originalHeight);
+	int newWidth = static_cast<int>(originalWidth * scale);
+	int newHeight = static_cast<int>(originalHeight * scale);
+
+	// 创建一个新的画布,大小为指定的宽度和高度,填充为白色
+	cv::Mat paddedImage(h, w, image.type(), cv::Scalar(255, 255, 255));
+
+	// 计算填充后的图像在新画布上的位置
+	int x = (w - newWidth) / 2;
+	int y = (h - newHeight) / 2;
+
+	// 调整图像大小并将其放置在新画布上
+	cv::resize(image, image, cv::Size(newWidth, newHeight));
+	image.copyTo(paddedImage(cv::Rect(x, y, newWidth, newHeight)));
+
+	return paddedImage;
+}
+
+
+cv::Mat paddingNoResize(cv::Mat& image, int w, int h) {
+	int originalWidth = image.cols;
+	int originalHeight = image.rows;
+
+	// 创建一个新的画布,大小为指定的宽度和高度,填充为白色
+	cv::Mat paddedImage(h, w, image.type(), cv::Scalar(255, 255, 255));
+
+	// 计算填充后的图像在新画布上的位置
+	int x = (w - originalWidth) / 2;
+	int y = (h - originalHeight) / 2;
+
+	// 调整图像大小并将其放置在新画布上
+	image.copyTo(paddedImage(cv::Rect(x, y, originalWidth, originalHeight)));
+
+	return paddedImage;
+}
+
+cv::Mat splitAndInsertImages(const cv::Mat& imageA, const cv::Mat& imageB) {
+	int w = imageB.cols;
+	int h = imageB.rows;
+
+	// 创建目标图像,大小为imageB
+	cv::Mat targetImage = imageB.clone();
+
+	// 计算imageB四等分的大小
+	int quarterW = w / 2;
+	int quarterH = h / 2;
+
+	// 将imageA放入四等分的每一块内
+	cv::Mat roi1 = targetImage(cv::Rect(0, 0, quarterW, quarterH));
+	cv::Mat roi2 = targetImage(cv::Rect(quarterW, 0, quarterW, quarterH));
+	cv::Mat roi3 = targetImage(cv::Rect(0, quarterH, quarterW, quarterH));
+	cv::Mat roi4 = targetImage(cv::Rect(quarterW, quarterH, quarterW, quarterH));
+
+	imageA.copyTo(roi1);
+	imageA.copyTo(roi2);
+	imageA.copyTo(roi3);
+	imageA.copyTo(roi4);
+
+	return targetImage;
+}
+
+std::vector<cv::Rect> getAllBoundingBoxes(const cv::Mat& img)
+{
+    std::vector<cv::Rect> vecBoxes;
+    cv::Mat  labels, stats, centroids, img_color, img_gray;
+    int nccomps = cv::connectedComponentsWithStats(img, labels, stats, centroids);
+    for (int i = 1; i < nccomps; i++) {
+        int l = stats.at<int>(i, cv::CC_STAT_LEFT);
+        int t = stats.at<int>(i, cv::CC_STAT_TOP);
+        int w = stats.at<int>(i, cv::CC_STAT_WIDTH);
+        int h = stats.at<int>(i, cv::CC_STAT_HEIGHT);
+        vecBoxes.push_back(cv::Rect(l, t, w, h));
+    }
+    return vecBoxes;
+}
+
+std::vector<Cluster> clusterRectangles(const std::vector<cv::Rect>& rectangles)
+{
+    std::vector<Cluster> clusters;
+    std::vector<bool> processed(rectangles.size(), false);
+
+    for (int i = 0; i < rectangles.size(); ++i)
+    {
+        if (processed[i])
+            continue;
+
+        Cluster cluster;
+        cluster.rect = rectangles[i];
+        cluster.indices.push_back(i);
+        processed[i] = true;
+
+        for (int j = i + 1; j < rectangles.size(); ++j)
+        {
+            if (processed[j])
+                continue;
+
+            int overlapHeight = std::min(cluster.rect.y + cluster.rect.height, rectangles[j].y + rectangles[j].height) - std::max(cluster.rect.y, rectangles[j].y);
+            if (overlapHeight > 0)
+            {
+                cluster.rect |= rectangles[j];
+                cluster.indices.push_back(j);
+                processed[j] = true;
+            }
+        }
+
+        clusters.push_back(cluster);
+    }
+
+    return clusters;
+}
+
+// 闭运算 丢弃  输入 黑底白字图像
+void colseImg(const cv::Mat& lpImgSrc, cv::Mat& lpImgDes, cv::Size sizeErode)
+{
+    sizeErode.width = sizeErode.width - sizeErode.width % 3;
+    sizeErode.height = sizeErode.height - sizeErode.height % 3;
+    if (0 == sizeErode.width % 2)
+        sizeErode.width += 3;
+    if (0 == sizeErode.height % 2)
+        sizeErode.height += 3;
+    cv::Mat element = getStructuringElement(cv::MORPH_RECT, sizeErode);
+    morphologyEx(lpImgSrc, lpImgDes, cv::MORPH_OPEN, element);
+}
+
+// 开运算 连通白色块  输入 黑底白字图像  这个运算内核必须能被2和3整除 否则会有像素平移情况发生
+void openImg(const cv::Mat& lpImgSrc, cv::Mat& lpImgDes, cv::Size sizeErode)
+{
+    sizeErode.width = sizeErode.width - sizeErode.width % 3;
+    sizeErode.height = sizeErode.height - sizeErode.height % 3;
+    if (0 == sizeErode.width % 2)
+        sizeErode.width += 3;
+    if (0 == sizeErode.height % 2)
+        sizeErode.height += 3;
+    cv::Mat element = getStructuringElement(cv::MORPH_RECT, sizeErode);
+    morphologyEx(lpImgSrc, lpImgDes, cv::MORPH_CLOSE, element);
+}
+
+void rectangleMask(cv::Mat& image, const cv::Rect& box, int alpha, bool useRandColor /*= true*/, const cv::Scalar& color /* = cv::Scalar()*/)
+{
+    if (image.empty())    {
+        std::cerr << "Input image is empty." << std::endl;
+        return;
+    }
+
+    if (alpha < 0 || alpha > 100)    {
+        std::cerr << "Alpha value should be between 0 and 100." << std::endl;
+        return;
+    }
+
+    // 生成随机颜色
+    cv::Scalar maskColor;
+    if (useRandColor){
+        maskColor = cv::Scalar(rand() % 256, rand() % 256, rand() % 256);
+    }
+    else {
+        maskColor = color;
+    }
+
+    cv::Mat mask(box.size(), CV_8UC3, maskColor);
+    cv::Mat roi = image(box);
+    cv::addWeighted(roi, (100 - alpha) / 100.0, mask, alpha / 100.0, 0, roi);
+
+
+}
+
+void adjustBoundingBox(int imageWidth, int imageHeight, cv::Rect& box) 
+{
+    // 获取图像的宽度和高度
+    //int imageWidth = image.cols;
+    //int imageHeight = image.rows;
+
+    // 确保矩形框的left在图像内
+    if (box.x < 0)
+        box.x = 0;
+
+    // 确保矩形框的right在图像内
+    if (box.x + box.width > imageWidth)
+        box.width = imageWidth - box.x;
+
+    // 确保矩形框的top在图像内
+    if (box.y < 0)
+        box.y = 0;
+
+    // 确保矩形框的bottom在图像内
+    if (box.y + box.height > imageHeight)
+        box.height = imageHeight - box.y;
+}
+void adjustBoundingBox(const cv::Mat& image, cv::Rect& box) {
+    int imageWidth = image.cols;
+    int imageHeight = image.rows;
+    adjustBoundingBox(imageWidth, imageHeight, box);
+}
+
+
+void printText(const cv::Mat& image, const cv::Point& pos, const std::string& text, int height, int width )
+{
+    // 设置字体类型和大小
+    int fontFace = cv::FONT_HERSHEY_SIMPLEX;
+    double fontScale = height / 40.0;
+
+    // 根据给定的宽度调整字体大小
+    if (width != 0)
+    {
+        int textWidth = 0;
+        int textHeight = 0;
+
+        // 测量文字的宽度和高度
+        cv::Size textSize = cv::getTextSize(text, fontFace, fontScale, 1, &textHeight);
+
+        // 根据给定的宽度调整字体大小
+        while (textSize.width > width && fontScale > 0)
+        {
+            fontScale -= 0.1;
+            textSize = cv::getTextSize(text, fontFace, fontScale, 1, &textHeight);
+        }
+    }
+
+    // 在图像上绘制文字
+    cv::putText(image, text, pos, fontFace, fontScale, cv::Scalar(0, 0, 255), 2, cv::LINE_AA);
+}
+
+double boxSimIOU(cv::Rect boxA, cv::Rect boxB)
+{
+    cv::Rect intersection = boxA & boxB;
+    double intersectionArea = intersection.area();
+    double boxAArea = boxA.area();
+
+    if (boxAArea == 0.0)
+        // Handle the case where boxA has zero area
+        return 0.0;
+    else {
+        double iou = intersectionArea / boxAArea;
+        return iou;
+    }
+}
+
+
+double boxVerProjectionSimIOU(cv::Rect boxA, cv::Rect boxB) {
+    // 计算两个矩形的 Y 轴投影的重叠区域
+    int startY = std::max(boxA.y, boxB.y);
+    int endY = std::min(boxA.y + boxA.height-1, boxB.y + boxB.height-1);
+
+    // 计算两个区域的高度(重叠部分)
+    int intersectionHeight = std::max(0, endY - startY);
+    if (intersectionHeight <= 0)
+        return 0.0;
+
+    // 计算两个矩形的总高度(并集部分)
+    int unionHeight = boxA.height + boxB.height - intersectionHeight;
+    double iou = static_cast<double>(intersectionHeight) / unionHeight;
+
+    return iou;
+}

+ 97 - 0
MFCApplication1/cvUtil.h

@@ -0,0 +1,97 @@
+#pragma once
+#include "pch.h"
+#include <opencv2/opencv.hpp>
+#include <random>
+
+
+/**
+ * 获取二值化图像(黑底白字)白色点个数
+ * @param image 原图
+ * @return 个数
+ */
+int getWhitePixNumber(cv::Mat& img);
+
+/**
+ * 开运算&闭运算
+ * @param image 原图
+ * @return 个数
+ */
+void openImg(const cv::Mat& lpImgSrc, cv::Mat& lpImgDes, cv::Size sizeErode);
+void colseImg(const cv::Mat& lpImgSrc, cv::Mat& lpImgDes, cv::Size sizeErode);
+
+/**
+ * 保持长宽比不变缩放图像
+ * @param w,h w=0,则按照h计算新的宽度,反之依然
+ * @param image 原图
+ * @return 缩放后的图像
+ */
+cv::Mat scaleImage(int w, int h, cv::Mat& image);
+cv::Mat paddingResize(cv::Mat& image, int w, int h);
+cv::Mat paddingNoResize(cv::Mat& image, int w, int h);
+// 四等分后放入
+cv::Mat splitAndInsertImages(const cv::Mat& imageA, const cv::Mat& imageB);
+
+/**
+ * 获取全部的bbx
+ * @param img
+ * @return vector<结果>
+ */
+std::vector<cv::Rect> getAllBoundingBoxes(const cv::Mat& img);
+
+struct Cluster {
+    cv::Rect rect;
+    std::vector<int> indices;
+};
+/**
+ * 对给定的矩形框进行聚类,按照指定的规则将在高度方向有重叠的矩形框合并为一个。
+ * @param rectangles 待聚类的矩形框集合
+ * @return 聚类结果,包含聚类矩形框和对应的矩形框索引
+ */
+std::vector<Cluster> clusterRectangles(const std::vector<cv::Rect>& rectangles);
+
+
+/**
+ * 在指定的矩形区域内创建mask蒙版并覆盖到图像上。
+ * @param image 待操作的图像 (BGR格式)
+ * @param box 需要蒙版覆盖的区域
+ * @param alpha 透明度 (0-100之间的值,0为完全透明,100为完全不透明)
+ * @param useRandColor 是否使用随机颜色 (默认为true)
+ * @param color 传入指定的颜色 (仅在useRandColor为false时有效)
+ * @return 覆盖蒙版后的图像
+ */
+void rectangleMask(cv::Mat& image, const cv::Rect& box, int alpha, bool useRandColor = true, const cv::Scalar& color = cv::Scalar());
+
+/**
+ * 检查并调整矩形框的合法性,确保其在图像内部。
+ * @param image 输入图像
+ * @param box 待调整的矩形框,会被修改为在图像内部的合法位置
+ */
+void adjustBoundingBox(const cv::Mat& image, cv::Rect& box);
+void adjustBoundingBox(int w,int h, cv::Rect& box);
+
+
+/**
+ * 使用opencv自带的函数实现 在指定位置打印要显示的文字,##不支持汉字##
+ * @param image 输入图像
+ * @param pos 要显示的位置的左上角顶点
+ * @param text 要显示的文字内容
+ * @param width 显示文字的最大宽度,=0则忽略该参数
+ * @param height 显示文字的高度,函数内根据该参数动态计算字体大小
+ */
+void printText(const cv::Mat& image, const cv::Point &pos,const std::string & text,int height, int width=0 );
+
+/**
+ * 计算两个cv::Rect的类IOU,返回值 = boxA和boxB的重合面积/boxA面积
+ * @param boxA 
+ * @param boxB 
+ * @return (boxA∩boxB).area()/boxA.area()
+ */
+double boxSimIOU(cv::Rect boxA, cv::Rect boxB);
+
+/**
+ * 计算两个cv::Rect在y轴投影的IOU,用于mrege横向重叠的box
+ * @param boxA
+ * @param boxB
+ * @return boxA.width∩boxB.width/boxA.width∪boxB.width
+ */
+double boxVerProjectionSimIOU(cv::Rect boxA, cv::Rect boxB);

+ 4 - 0
MFCApplication1/pch.h

@@ -10,4 +10,8 @@
 // 添加要在此处预编译的标头
 #include "framework.h"
 
+
+// qqm add for physics
+#define QQM_ADD 1
+
 #endif //PCH_H