duanjianjun 2 years ago
parent
commit
98c6d32158

+ 4 - 4
MFCApplication1/MFCApplication1.vcxproj

@@ -94,11 +94,11 @@
       <SDLCheck>true</SDLCheck>
       <PreprocessorDefinitions>WIN32;_WINDOWS;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
-      <AdditionalIncludeDirectories>..\thridparty\opencv\Include;%(AdditionalIncludeDirectories);./libpdf</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>..\thridparty\opencv\Include;%(AdditionalIncludeDirectories);./libpdf;./scan</AdditionalIncludeDirectories>
     </ClCompile>
     <Link>
       <SubSystem>Windows</SubSystem>
-      <AdditionalLibraryDirectories>..\thridparty\opencv\Lib;.\libpdf\debug</AdditionalLibraryDirectories>
+      <AdditionalLibraryDirectories>..\thridparty\opencv\Lib;.\libpdf\debug;.\scan\debug;</AdditionalLibraryDirectories>
     </Link>
     <Midl>
       <MkTypLibCompatible>false</MkTypLibCompatible>
@@ -144,13 +144,13 @@
       <SDLCheck>true</SDLCheck>
       <PreprocessorDefinitions>WIN32;_WINDOWS;NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
       <PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
-      <AdditionalIncludeDirectories>..\thridparty\opencv\Include;%(AdditionalIncludeDirectories);./libpdf</AdditionalIncludeDirectories>
+      <AdditionalIncludeDirectories>..\thridparty\opencv\Include;%(AdditionalIncludeDirectories);./libpdf;;./scan</AdditionalIncludeDirectories>
     </ClCompile>
     <Link>
       <SubSystem>Windows</SubSystem>
       <EnableCOMDATFolding>true</EnableCOMDATFolding>
       <OptimizeReferences>true</OptimizeReferences>
-      <AdditionalLibraryDirectories>..\thridparty\opencv\Lib;.\libpdf\release</AdditionalLibraryDirectories>
+      <AdditionalLibraryDirectories>..\thridparty\opencv\Lib;.\libpdf\release;.\scan\release</AdditionalLibraryDirectories>
     </Link>
     <Midl>
       <MkTypLibCompatible>false</MkTypLibCompatible>

+ 2 - 0
MFCApplication1/MFCApplication1Dlg.cpp

@@ -22,8 +22,10 @@
 #include <json/json.h>
 #ifdef _DEBUG
 #pragma comment(lib, "libpdfd.lib")
+#pragma comment(lib, "lib_common_depence_d.lib")
 #else
 #pragma comment(lib, "libpdf.lib")
+#pragma comment(lib, "lib_common_depence.lib")
 #endif
 
 

BIN
MFCApplication1/scan/debug/lib_common_depence_d.dll


BIN
MFCApplication1/scan/debug/lib_common_depence_d.lib


+ 223 - 0
MFCApplication1/scan/error_api_define.h

@@ -0,0 +1,223 @@
+#pragma once
+/********************************************************
+	* @file    : error_api_define.h
+	* @brief   : 异常算法接口2的 公用的定义属性
+	* @details :
+	* @author  : qqm
+	* @date    : 2022.3.10
+*********************************************************/
+#include <iostream>
+#include <string>
+#include <vector>
+#include <map>
+#include <unordered_map>
+
+#include "public_define.h"
+
+#if defined(_MSC_VER) || defined(_WIN32) || defined(_WIN64)
+#define dll_export __declspec(dllexport)
+#define dll_import __declspec(dllimport)
+#else //  __linux__
+#define dll_export __attribute__ ((visibility("default")))
+#include <memory>
+#endif
+#define MAX_SOCRE_DEFINE 1000
+
+/////////////////////////////异常接口2返回的结构体设计////////////////////////////////
+namespace eapi {
+/// 条码/二维码
+	struct ErCodeQ {
+		preinfo::BOX_TYPE type;				/// 类型 条码还是二维码
+		std::string result;					/// 识别结果
+		preinfo::PaperRect<double> box;		/// 整个大的区域坐标
+		ErCodeQ() :type(preinfo::BOX_TYPE::BOX_EXAM_BARCODE), result("") {};
+	};
+	/// 填涂判断通用结果存放结构体
+	struct ErSinTt :preinfo::PaperRect<double> {
+		bool filled;			/// 本填涂框是否是填涂
+		int  fildedNumber;		/// 本区域内有效的判断依据数量(红色点数) 
+		ErSinTt() :filled(false),fildedNumber(0) {};
+	};
+	/// 单个填涂序列(单个选择题,或者单个考号填涂列)
+	struct ErTtRes {
+		std::vector<ErSinTt> smallBoxes;
+		int id;					/// 题号 或 序号之类
+		int uuid;				/// uuid
+		ErTtRes() :id(0) { smallBoxes.clear(); };
+	};
+	/// 学号填涂框
+	struct ErFillNumber {
+		bool effective;						/// 是否有效
+		preinfo::PaperRect<double> box;		/// 整个大的区域坐标
+		std::string fillNumber;				/// 填涂识别结果
+		std::vector<ErTtRes> smallItems;	/// 每一列或行的填涂信息	
+		ErFillNumber() :effective(false), fillNumber("") { smallItems.clear(); };
+	};
+	/// 选择题 可以考虑删除
+	struct ErChoise {
+		std::vector<ErTtRes> sinChoise;		/// 单个选择题
+		ErChoise() { sinChoise.clear(); };
+	};
+	/// Score区域
+	struct ErScore {
+		bool effective;						/// 是否有效 - 无效情况下 下面三个不需要读取
+		preinfo::SCORE_TYPE type;			/// 打分框的类型
+		bool withHalf;						/// 是否含有.5分
+		int	limit;							/// 上限分值 数据来自模板json中"limit"字段
+		double maxSocre;                    /// 小问最大分值
+		int cols;							/// 个数
+		preinfo::PaperRect<double> box;		/// 打分框区域
+		std::vector<ErSinTt> smallBoxes;	/// 单个打分框
+		std::vector<double> vecSocreValue;	/// 单个打分框内分数值
+		ErScore() :effective(true) { smallBoxes.clear(); vecSocreValue.clear(); };
+		double GetSocre()                   /// 获取该打分框分数
+		{
+			double ret = 0.0;
+			if (effective)
+			{
+				if ( smallBoxes.size() == 1)
+				{
+					//单个空,并且没有分数
+					if (maxSocre == -1)
+					{
+						//没有小问给选中最大分值,界面自己综合调整显示
+						ret = smallBoxes[0].fildedNumber > 0 ? MAX_SOCRE_DEFINE : 0;
+					}
+					else
+					{
+						//小问,给小问最大分
+						ret = smallBoxes[0].fildedNumber > 0 ? maxSocre : 0;
+					}
+				}
+				else if (type == preinfo::SCORE_TYPE::NORMAL)
+				{
+					int selectred = -1;
+					int maxred = 0;
+					if (withHalf)
+					{
+						ret += smallBoxes[smallBoxes.size() - 1].fildedNumber > 0 ? 0.5 : 0;
+						for (int m = 0; m < smallBoxes.size() - 1; m++)
+						{
+							if (maxred < smallBoxes[m].fildedNumber)
+							{
+								maxred = smallBoxes[m].fildedNumber;
+								selectred = m;
+							}
+						}
+					}
+					else
+					{
+						for (int m = 0; m < smallBoxes.size(); m++)
+						{
+							if (maxred < smallBoxes[m].fildedNumber)
+							{
+								maxred = smallBoxes[m].fildedNumber;
+								selectred = m;
+							}
+						}
+					}
+					if (selectred > -1)
+						ret += vecSocreValue[selectred];
+				}
+				else if (type == preinfo::SCORE_TYPE::COMBIN)
+				{
+					int selectred = -1;
+					int maxred = 0;
+					//十位
+					int tw = limit / 10;
+					for (int m = 1; m <= tw; m++)
+					{
+						if (maxred < smallBoxes[m].fildedNumber)
+						{
+							maxred = smallBoxes[m].fildedNumber;
+							selectred = m;
+						}
+					}
+					if (selectred > -1)
+						ret += 10 * vecSocreValue[selectred];
+
+					selectred = -1;
+					maxred = 0;
+
+					if (withHalf)
+					{
+						if (smallBoxes[smallBoxes.size() - 1].fildedNumber > 0)
+							ret += 0.5;
+						for (int m = tw + 1; m < smallBoxes.size() - 1; m++)
+						{
+							if (maxred < smallBoxes[m].fildedNumber)
+							{
+								maxred = smallBoxes[m].fildedNumber;
+								selectred = m;
+							}
+						}
+					}
+					else
+					{
+						for (int m = tw + 1; m < smallBoxes.size(); m++)
+						{
+							if (maxred < smallBoxes[m].fildedNumber)
+							{
+								maxred = smallBoxes[m].fildedNumber;
+								selectred = m;
+							}
+						}
+					}
+					if (selectred > -1)
+						ret += vecSocreValue[selectred];
+				}
+				else
+				{
+					int selectred = -1;
+					int maxred = 0;
+					for (int m = 0; m < smallBoxes.size(); m++)
+					{
+						if (maxred < smallBoxes[m].fildedNumber)
+						{
+							maxred = smallBoxes[m].fildedNumber;
+							selectred = m;
+						}
+					}
+					if (selectred > -1)
+						ret += vecSocreValue[selectred];
+				}
+			}
+			else
+			{
+				ret = -1.0;
+			}
+			return ret;
+		};
+	};
+	/// 通用主观题
+	struct ErBox {
+		int id;								/// 模板内的ID
+		int uuid;							/// 自增的UUID
+		int multiple;						/// 是否是多块
+		int orderNumber;					/// 多块的块号 
+		int marktype;						/// markType
+		preinfo::PaperRect<double> box;		/// 大区域
+		preinfo::BOX_TYPE type;				/// 类型
+		ErScore score;						/// 打分框
+		/// 选做题填涂框 选做题时候才有效
+		ErTtRes	xzTt;						/// 选做填涂
+		ErBox() :id(0), type(preinfo::BOX_TYPE::BOX_NONE) {};
+	};
+
+	/// 新增的异常处理接口交互结构体  
+	typedef struct SingleErrorPaperInfo
+	{
+		/**
+			包含各种每一张答题卡上面的模板信息,坐标信息,题目信息,识别结果信息
+		*/
+		int					pageId;				/// 本页页号
+		int					status;				/// 本页状态
+		std::string			strPath;			/// 矫正后图像绝对路径
+
+		ErFillNumber		fillNumber;			/// 填涂考号
+		std::vector<ErCodeQ> codeResult;		/// 条码/二维码识别结果
+		ErChoise			choice;				/// 选择题
+		std::vector<ErBox>	boxes;				/// 通用主观题
+		SingleErrorPaperInfo() :pageId(-1), status(PAPER_SUCC), strPath("") {};
+	}speinfo;
+}

+ 137 - 0
MFCApplication1/scan/lib_common_depence.h

@@ -0,0 +1,137 @@
+// lib_common_depence.h: 标准系统包含文件的包含文件
+// 或项目特定的包含文件。
+
+/************************************************************************/
+/* 2021.08.13 上线第一版												*/
+/************************************************************************/
+
+#pragma once
+
+#include "public_define.h"
+#include "error_api_define.h"
+
+////////////////////////////////// 测试接口 ////////////////////////////////////////
+
+/// 测试输出 win/linux  msvc/g++
+dll_export void api_cout_system_compiler();
+
+/// 测试opencv是否有效
+dll_export int api_mat_test(std::string strPath);
+
+/// 测试boost 
+dll_export int api_boost_test(std::string strCreateDir);
+
+///////////////////////////////// 交互接口  /////////////////////////////////////////
+
+/**
+  * @brief: 获取版本号 格式:N.X.Y 1.0.1 2.2.0
+  * @param :  
+  * @return const char * : string.c_str()  
+  * @author : qqm  2021/03/09 14:24
+*/
+dll_export const char * api_get_version();
+
+/**
+  * @brief: 解析json 数据到模板中 方便测试
+  * @param type: 见枚举
+  * @param path: json path
+  * @param qtMap: 全学科逻辑题型与识别题型表
+  * @param tpInfos: 接受数据 内部会改变内存 所以不同版本的编译器要注意
+  * @param szInfo:	选做题信息组合
+  * @param socringMap: 切块和分数框业务表
+  * @return int : 0 succ  -1 some error
+  * @author : qqm  2021/03/09 14:24
+*/
+dll_export int api_param_json(CARD_TYPE type, const char * path,std::map<int ,int> &qtMap,
+	preinfo::templatesInfo & tpInfos, preinfo::SubjChiInfo &zxInfo, std::map<int, preinfo::SocringArea> &socringMap, std::map<int, preinfo::QtNumInfo> *cutingMap = NULL);
+
+/**
+  * @brief: 批次图像处理接口
+  * @param strBatchUUID:			本批次的UUID,每个批次号都是唯一的,每个批次号对应一个模板映射,但是每个模板可以对应N个批次号
+  * @param strTempSavePath:			临时中间文件存储路径
+  * @param vecPtInfo:				批次号对应的模板映射的解析结果,由调用者解析好
+  * @param XzInfo;					选做题信息 调用者提供
+  * @param vecProcessedImagesList:	待识别图像绝对路径
+  * @param iLogLeave:				日志等级 使用默认值就好 主要用来线上问题分析调试
+  * @param bCutRect:				是否切图
+  * @param type:					参照CARD_TYPE枚举
+  * @param data_interaction:		回调函数
+  * @param autoParam:				预留参数
+  * @return int:					0:succ  -1:
+  * @author : qqm  2021/03/05 13:19
+*/
+dll_export int api_processing_images(
+	const char *	strBatchUUID,
+	const char *	strTempSavePath,
+	const			preinfo::templatesInfo & vecPtInfo,
+	const			preinfo::SubjChiInfo & XzInfo,
+	const			std::vector<std::string> & vecProcessedImagesList,
+	const LOG_LEAVE	iLogLeave /*= LOG_AUTO*/,
+	const bool		bCutRect,
+	const CARD_TYPE	type,
+	int(*data_interaction)(result::spinfo & pinfo,void * param),
+	void * param
+);
+
+
+/**
+  * @brief: 异常图像再次识别接口1 传入的答题卡 默认与模板同方向 同顺序,算法不需要做分页和方向判断,直接按照模板一致处理即可
+  * @param strBatchUUID:			本批次的UUID,每个批次号都是唯一的,每个批次号对应一个模板映射,但是每个模板可以对应N个批次号
+  * @param strTempSavePath:			临时中间文件存储路径
+  * @param vecPtInfo:				批次号对应的模板映射的解析结果,由调用者解析好
+  * @param XzInfo;					选做题信息 调用者提供
+  * @param vecProcessedImagesList:	待识别图像绝对路径
+  * @param iLogLeave:				日志等级 使用默认值就好 主要用来线上问题分析调试
+  * @param bCutRect:				是否切图
+  * @param type:					参照CARD_TYPE枚举
+  * @param data_interaction:		回调函数
+  * @param autoParam:				预留参数
+  * @return int:					参见新增异常接口返回值状态码
+  * @author : qqm  2022/02/17 
+*/
+dll_export int api_processing_error_images_self(
+	const char *	strBatchUUID,
+	const char *	strTempSavePath,
+	const			preinfo::templatesInfo & vecPtInfo,
+	const			preinfo::SubjChiInfo & XzInfo,
+	const			std::vector<std::string> & vecProcessedImagesList,
+	const LOG_LEAVE	iLogLeave /*= LOG_AUTO*/,
+	const bool		bCutRect,
+	const CARD_TYPE	type,
+	int(*data_interaction)(result::spinfo & pinfo, void * param),
+	void * param
+);
+
+
+
+/**
+  * @brief: 异常图像再次识别接口2   传入模板和答题卡上对应的定位信息序列,手工指定矫正信息
+  * @param strBatchUUID:			本批次的UUID,每个批次号都是唯一的,每个批次号对应一个模板映射,但是每个模板可以对应N个批次号
+  * @param strTempSavePath:			临时中间文件存储路径
+  * @param vecPtInfo:				批次号对应的模板映射的解析结果,由调用者解析好
+  * @param XzInfo;					选做题信息 调用者提供
+  * @param vecProcessedImagesList:	待识别图像绝对路径
+  * @param vecLocationInfoMb:		手工的模板定位信息  不能加const 因为在在线答题卡 需要内部解析定位点
+  * @param vecLocationInfoMb:		手工的答题卡定位信息
+  * @param iLogLeave:				日志等级 使用默认值就好 主要用来线上问题分析调试
+  * @param bCutRect:				是否切图
+  * @param type:					参照CARD_TYPE&API_PARAM枚举 !!! 注意本参数 为组合参数 传入的类型应该为 CARD_TYPE | ERROR_CARD  !!!
+  * @param resultInfo:				结果交互结构体 返回值=RT_OK
+  * @param autoParam:				预留参数
+  * @return int:					参见新增异常接口返回值状态码
+  * @author : qqm  2022/02/17
+*/
+dll_export int api_processing_error_images_hand(
+	const char *	strBatchUUID,
+	const char *	strTempSavePath,
+	const			preinfo::templatesInfo & vecPtInfo,
+	const			preinfo::SubjChiInfo & XzInfo,
+	const			std::vector<std::string> & vecProcessedImagesList,
+					std::vector<analysis::LocationResult> & vecLocationInfoMb,
+	const			std::vector<analysis::LocationResult> & vecLocationInfoPt,
+	const LOG_LEAVE	iLogLeave /*= LOG_AUTO*/,
+	const bool		bCutRect,
+	const CARD_TYPE	type,
+	std::vector<eapi::SingleErrorPaperInfo> & resultInfo, 
+	void * param
+);

+ 563 - 0
MFCApplication1/scan/public_define.h

@@ -0,0 +1,563 @@
+#pragma once
+/******************************************************** 
+	* @file    : public_define.h
+	* @brief   : 公用的定义属性
+	* @details : 
+	* @author  : qqm
+	* @date    : 2021-2-4
+*********************************************************/
+
+
+#include <iostream>
+#include <string>
+#include <vector>
+#include <map>
+#include <unordered_map>
+
+#if defined(_MSC_VER) || defined(_WIN32) || defined(_WIN64)
+#define dll_export __declspec(dllexport)
+#define dll_import __declspec(dllimport)
+#else //  __linux__
+#define dll_export __attribute__ ((visibility("default")))
+#include <memory>
+#endif
+
+
+/// dll return value
+enum RETURN_VALUE {
+	RT_OK					= 0,	/// 正常
+	RT_ERROR				= 1,	/// 内部error
+	RT_UUID_EMPTY			= 2,	/// 批次号异常
+	RT_SAVE_PATH_EMPTY		= 3,	/// 保存路径异常
+	RT_TEMPLATE_EMPTY		= 4,	/// 模板为空
+	RT_HIMGS_LIST_EMPTY		= 5,	/// 图像列表异常
+	RT_HIMGS_LIST_NOT_DIV	= 6,	/// 图像列表不是模板整数被
+
+	/// 异常算法新增的异常类型
+	RT_ERROR_IMAGES_NOT_EQUAL_TPS	= 7,	/// 传入的答题卡个数跟模板个数不一致
+	RT_CHECK_ERROR					= 8,	/// 单份答题卡安全校验未通过
+	RT_CHECK_LCPTS_NOT_EQUAL_TPS	= 9,	/// 异常接口2传入的模板定位信息、答题卡定位信息、模板size不相等
+	RT_CHECK_LCPTINFO_ERROR			= 10,	/// 传入的定位信息不能满足矫正需求,成对数量<3
+	RT_CHECK_ONLINECARD_LCPT_ERROR	= 11,	/// 在线答题卡传入的定位点数量<3
+	RT_CHECK_LCPTINFO_XS_ERROR		= 12,	/// 传入的定位信息不能满足矫正需求,分布象限<3
+
+	/// 新增模板坐标僬侥
+	RT_TEMPLATE_BOX_ERROR	= 20,	/// 模板内的坐标越界或者无效
+};
+
+/// debug log leave 主要调式使用
+enum LOG_LEAVE{
+	LOG_AUTO		= 0, /// log默认输出,正常接口默认值
+	LOG_FUN			= 1, /// log所有流程中调用的函数  fun_xxx() in! ... fun_xxx() return!
+	LOG_KEYSTEP		= 2, /// log所有的关键点步骤信息 
+	LOG_DETAIL		= 3, /// log关键点的具体值各个细节
+};
+
+/// 接口参数类型
+enum API_PARAM{
+	TYPE_ONLINE_CARD = 1,	/// 在线答题卡
+	TYPE_THRID_CARD  = 2,	/// 第三方答题卡
+	NEED_OCR_SOCRE   = 4,	/// 需要识别分数
+	NO_NEED_OCR_SCORE= 8,	/// 不需要识别分数
+	ERROR_CARD		 = 16,	/// 异常答题卡 api_processing_error_images_hand 接口使用
+};
+
+/// 答题卡类型
+enum CARD_TYPE {
+	OLWS	= TYPE_ONLINE_CARD | NEED_OCR_SOCRE,	/// 在线 识别分数
+	OLNS	= TYPE_ONLINE_CARD | NO_NEED_OCR_SCORE,	/// 在线 不识别分数
+	TDWS	= TYPE_THRID_CARD | NEED_OCR_SOCRE,		/// 第三方 识别分数
+	TDNS	= TYPE_THRID_CARD | NO_NEED_OCR_SCORE,	/// 第三方 不识别分数
+	OLWSE	= OLWS | ERROR_CARD,					/// api_processing_error_images_hand接口类型
+	OLNSE	= OLNS | ERROR_CARD,
+	TDWSE	= TDWS | ERROR_CARD,
+	TDNSE	= TDNS | ERROR_CARD,
+};
+
+/// 答题卡可能的状态信息  !!!有待完善设计
+enum PAPER_STATUS {
+	PAPER_SUCC					= 0, /// SUCC
+	MISSED_LOCATION_POINT		= 1, /// 定位点缺失
+	MISSED_LOCATION_AREA		= 2, /// 定位区缺失
+	MISSED_LOCATION_CROSSPOINT	= 4, /// 交叉点缺失
+	MATCH_FAILED				= 8, /// 分页失败 模板不匹配
+	PROGRAM_ERROR				= 16,/// 程序异常导致的失败
+	PAGEDISMISS					= 32,/// 本份缺页
+	PAGEDATAERROR				= 64,/// 本份图像异常
+};
+
+namespace preinfo {
+	/// 图像方向 逆时针方向
+	enum IMG_ROTATION { 
+		ROTATION_0		= 1,
+		ROTATION_90		= 2,
+		ROTATION_180	= 4,
+		ROTATION_270	= 8,
+	};
+
+	/// 图像&试题类型
+	enum BOX_TYPE
+	{
+		BOX_NONE,				/// UNKNOW
+		BOX_ABSENCE_MARK,		/// 缺考标记
+		BOX_BREAK_PRECEDENT,	/// 违纪标记
+		BOX_EXAM_TT_NUMBER,		/// 填涂考号
+		BOX_EXAM_QRCODE,		/// 试卷二维码
+		BOX_EXAM_BARCODE,		/// 试卷条码
+		BOX_QUESTION_CHOICE_S,	///	单项选择题
+		BOX_QUESTION_CHOICE_M,	///	不定项选择题
+		BOX_QUESTION_COMPLETION,/// 填空题
+		BOX_QUESETION_TF,		/// 判断题
+		BOX_SUBJECTIVE_NORMAL,  /// 普通主观题
+		BOX_SCORING_BOX,		/// 线下阅卷的打分区
+		BOX_SUBJECTIVE_CHOOSE,			/// 选做题
+		BOX_SUBJECTIVE_CHOOSE_COMBIN,	/// 选做题-合并填涂
+		BOX_SUBJECTIVE_CHOOSE_SPLITE,	/// 选做题-分离填涂
+		BOX_WRITE_CHINESE_COMPOSITION,	/// 写作-语文作文
+		BOX_WRITE_ENGLISH_WRITING,		/// 写作-英语写作
+
+		BOX_LOCATION_POINT,		/// 定位点
+		BOX_LOCATION_AREA,		/// 定位区
+		BOX_LOCATION_CROSS,		/// 定位交叉点
+	};
+
+	/// 方向
+	enum DIR_FLAG {
+		DIR_UNKNOW = -1,
+		DIR_UP = 1,
+		DIR_DOWN = 2,
+		DIR_LEFT = 4,
+		DIR_RIGHT = 8
+	};
+
+	/// 填涂区域的方向
+	enum TT_DIR_FLAG { 
+		TT_Y = 0, /// 垂直
+		TT_X = 1, /// 水平
+	};
+
+	template<typename T> struct TtSize{ /// 
+		T width;
+		T height;
+		TtSize() :width(T(0)), height(T(0)) {};
+	};
+	template<typename T> struct PaperRect {
+		T centerx;
+		T centery;
+		T width;
+		T height;
+		T getX() const { return centerx - width / 2.0;}
+		T getY() const { return centery - height/ 2.0; }
+		PaperRect() :centerx(T(-1)), centery(T(-1)), width(T(0)), height(T(0)) {};
+	};
+	struct Field :PaperRect<double> {
+		int id;		///	一个唯一标识符 不可能有重复的
+		std::vector<int> vecTiHao;	/// 整数题号 可以有重复
+		Field() :id(-1) {};
+	};
+
+	/// 定位点
+	struct LocationPoint :Field {
+		BOX_TYPE type;
+		LocationPoint() :type(BOX_LOCATION_POINT) {};
+	};
+
+	/// 定位区
+	struct LocationArea :Field {
+		BOX_TYPE type;
+		bool head;	/// 是否是试卷名称区域
+		LocationArea() :head(false), type(BOX_LOCATION_AREA) {};
+	};
+
+	/// 交叉点
+	struct LocationCrossPoint :Field {
+		BOX_TYPE type;
+		int sign;		/// 交叉点开口方向
+		double angle;
+		LocationCrossPoint() :sign(DIR_UNKNOW), angle(0.0), type(BOX_LOCATION_CROSS) {};
+	};
+
+	/// 条码/二维码
+	struct CodeInfo :Field {
+		BOX_TYPE type; /// BOX_EXAM_QRCODE or BOX_EXAM_BARCODE
+		TT_DIR_FLAG way;
+		CodeInfo() :type(BOX_EXAM_BARCODE), way(TT_X) {};
+	};
+
+	/// 缺考标记
+	struct AbsenceMark:Field {
+		BOX_TYPE type;
+		AbsenceMark() :type(BOX_ABSENCE_MARK) {};
+	};
+
+	/// 填涂考号
+	struct FillStuNumber:Field {
+		bool			useable;	/// 是否可用
+		TT_DIR_FLAG		way;		/// 水平/垂直
+		int				cols;		/// 列数
+		int				rows;		/// 行数
+		TtSize<double>  itemSize;	/// 单个小项宽高
+		double			disx;		/// 横向相邻的两个小项的x坐标的距离差
+		double			disy;		/// 纵向的........
+		FillStuNumber() :useable(false), way(TT_Y), cols(-1), rows(-1), disx(0.0), disy(0.0) {};
+		void init_dis() {
+			disx = (cols == 1) ? 0.0 : (width - itemSize.width) * 1.0 / (cols - 1) * 1.0;
+			disy = (rows == 1) ? 0.0 : (height - itemSize.height) * 1.0 / (rows - 1) * 1.0;
+		}
+	};
+
+	
+	/// 填涂考号 拆解到小项坐标
+	/// 填涂小项结构信息
+	struct TtItem :PaperRect<double> {
+		std::string optName; /// 0 ~ 9 A B C D .... 填涂
+	};
+
+	/// 新逻辑 - 填涂考号
+	struct _FillStuNumber{
+		bool		useable;	/// 是否可用
+		int			cols;		/// 列数
+		int			rows;		/// 行数
+		std::map<int,std::vector<TtItem>> groups; /// pair<列序号(0开始),小项合集(从上到下)>
+		_FillStuNumber() : useable(false), cols(-1), rows(-1) { groups.clear(); };
+	};
+
+	/// 新逻辑 - 填涂选做题 or 选择题
+	struct _QuestionChoice:Field{
+		BOX_TYPE				type;		/// BOX_QUESTION_CHOICE_S / M
+		int						number;		/// 填涂个数
+		TT_DIR_FLAG				way;		/// 方向
+		TtSize<double>			itemSize;	/// 单个小项宽高
+		std::vector<TtItem>		groups;		/// 组内的各个小项
+		_QuestionChoice():number(0),way(TT_X) {};
+	};
+
+	/// 判断题
+	struct QuesetionTF:Field{ 
+		;
+	};
+
+	
+	/// 单个选择题
+	struct QuestionChoice:Field{ 
+		BOX_TYPE		type;		/// BOX_QUESTION_CHOICE_S / M
+		TT_DIR_FLAG		way;		/// 水平/垂直
+		TtSize<double>  itemSize;	/// 单个小项宽高
+		int				number;		/// 填涂项个数
+		std::vector<TtItem>		groups;		/// 组内的各个小项
+		QuestionChoice() :type(BOX_QUESTION_CHOICE_M), way(TT_X), number(0) {};
+	};
+
+	/// 主观题的区域信息
+	struct SubjectiveField:Field{ 
+		BOX_TYPE type;
+		bool	multiple;		/// 是否多块 
+		int		orderNumber;	/// 如果是多块 本块序号 从1开始
+		bool	needConbin;		/// 是否需要合并一起
+		int		marktype;		/// 如果等于4或者10则需要保留小图			2021.6.11 确认如果试题 如此切图
+		TT_DIR_FLAG way;		/// 左右拼接 or 上下拼接
+		std::string name;		/// 落地保存的文件名 e: "17" "22"
+		SubjectiveField() :type(BOX_SUBJECTIVE_NORMAL), multiple(false), orderNumber(1), needConbin(true), way(TT_Y), name("") {};
+	};
+
+	/// 打分区
+	enum SCORE_TYPE	{
+		NONE   = 0, /// 
+		NORMAL = 1, /// 一般的
+		COMBIN = 2, /// 组合型 个位&十位
+		QtCom  = 3, /// 填空题
+	};
+	struct SocringArea:PaperRect<double> { /// 关联在 boxInfo下面 多块的只有ordernumber=0需要识别分数
+		bool		ocr;		/// 是否需要识别分数
+		int			cols;		/// 列数
+		SCORE_TYPE	type;	
+		bool		withHalf;	/// 是否含有.5分
+		int			limit;		/// 上限分值
+		double      maxSocre;   /// 小问最大分值
+		std::vector<double> vecSocreValue;	/// 每个格子对应的数据 小于零的项不可用(个位&十位格子)
+		SocringArea() :cols(0), type(NONE),withHalf(false),ocr(false),limit(-1), maxSocre(-1.0){};
+	};
+
+	/// 单个填空题
+	struct QuestionCompletion :SubjectiveField {
+		SocringArea scoreBox;
+	};
+
+	/// 其他类型的box 只需要切割区域不需要额外操作的
+	struct BoxInfo :SubjectiveField { 
+		SocringArea scoreBox;
+		BoxInfo() {};
+	};
+
+	/// 选做题的标识符 每个选做题&选做题填涂 都有唯一的Key 
+	struct SubjectiveChooseKey	{ 
+		std::vector<int> vecIds;			/// 选做题的id列表
+		std::vector<std::string> vecNames;	/// 别名列表
+		int idx;	/// 本块的序号 0 1 2 3 ...多选多的N序号
+		bool hbtt;	/// 是否是合并填涂
+		int m;		/// m选n
+		int n;		/// m选n
+		bool operator == (const SubjectiveChooseKey & _k) {
+			return (_k.vecIds == vecIds) && (_k.idx == idx);
+		}
+		SubjectiveChooseKey() :idx(-1),hbtt(true) {};
+	};
+
+	/// 选做题的区域信息 
+	struct SubjectiveChooseField:SubjectiveField{ 
+		SubjectiveChooseKey key;
+		SocringArea scoreBox;
+	};
+
+	/// 选做题的填涂判断信息
+	struct SubjectiveChooseTt:QuestionChoice{ 
+		SubjectiveChooseKey key;
+	};
+
+	/// 新逻辑的填涂信息
+	struct _SubjectiveChooseTt : _QuestionChoice {
+		SubjectiveChooseKey key;
+	};
+
+	/// 待定... 
+	struct SubjectiveChoose	{ /// 选做题 分离&合并填涂  M选N 
+		int		m;		/// 总数
+		int		n;		/// 选做个数
+		std::vector<int>	ids;		/// 题号列表
+		std::vector<SubjectiveChooseField>	vecBoxes;	/// 图像区域列表
+		std::vector<SubjectiveChooseTt>		vecTt;		/// 填涂区域列表
+		
+		virtual void push_back(const SubjectiveField & sjtField) = 0;
+	};
+	struct Combin:public SubjectiveChoose{ /// 合并填涂
+
+	};
+	struct Splite:public SubjectiveChoose{ /// 分离填涂
+		Splite(const int & _m, const int & _n) {
+			;
+		}
+	protected:
+		 
+	};
+
+	/// 答题卡模板
+	typedef struct PaperTemplateInfo {
+		int index;	/// 模板页的序号 从0开始
+		int width;	/// 
+		int height;	/// 
+
+		std::string						strJsonPath;		/// 模板json文件绝对路径
+		std::string						strImgPath;			/// 本张模板图像路径/URL
+		std::vector<BoxInfo>			vecBoxes;			/// 本张模板内主观题题目信息
+		std::vector<LocationPoint>		vecLocaltionPoints;	/// 定位点信息
+		std::vector<LocationArea>		vecLocaltionAreas;	/// 定位区信息
+		std::vector<LocationCrossPoint> vecLocationCross;	/// 交叉点信息
+		std::vector<QuestionChoice>		vecQtChoices;		/// 选择题
+		std::vector<_QuestionChoice>	_vecQtChoices;		/// _选择题
+		std::vector<QuesetionTF>		vecQtTf;			/// 判断题
+		std::vector<QuestionCompletion> vecQtCompletion;	/// 填空题
+		std::vector<AbsenceMark>		vecAbsenceMark;		/// 缺考&违纪
+		std::vector<CodeInfo>			vecCodeBQ;			/// 条码/ 二维码
+		std::vector<FillStuNumber>		vecFillNumber;		/// 填涂考号
+		std::vector<_FillStuNumber>		_vecFillNumber;		/// _填涂考号
+		std::vector<SubjectiveChooseField>  vecXzBoxes;		/// 选做题题目信息
+		std::vector<SubjectiveChooseTt>		vecXzTtBoxes;	/// 选做题填涂信息
+		std::vector<_SubjectiveChooseTt>	_vecXzTtBoxes;	/// 选做题填涂信息
+		void * ptrAreaFeatures;	/// 定位区特征
+	} ptinfo;
+	typedef std::vector<PaperTemplateInfo> templatesInfo;
+
+	/// 选做题信息
+	struct XX {
+		std::vector<int> vecTiHao; ///! 必须是按照题号 升序排列
+		int m;
+		int n;
+		/**
+			2022.1.19 题组实现逻辑
+			问题:题组的结构是数量的N倍,与现有的逻辑冲突,但是如果把题组的题号拆分成两个
+			选做题就可以实现,但是问题就是 第二块没有填涂区域,如何解决这个问题 ,
+			方案:
+			设定一个标识 判断是否使用其他题号的填涂判断结果 
+			stealVecRes = true 则本题偷取 vecStealTiHao 的填涂结果
+		*/
+		bool stealVecRes;
+		std::vector<int> vecStealTiHao;
+		XX() :m(0), n(0), stealVecRes(false) { vecStealTiHao.clear(); };
+	};
+
+	struct SubjChiInfo	{
+		bool hasSplitTT;
+		bool hasCombinTT;
+		std::vector<XX> vecSptt;
+		std::vector<XX> vecCbtt;
+		SubjChiInfo() :hasSplitTT(false), hasCombinTT(false) {};
+	};
+
+	struct QtNumInfo {
+		std::vector<int> qtIds;
+		int              qtNum;
+	};
+}
+
+namespace analysis {
+
+	/// 记录定位点 定位区 交叉点的查找情况
+	struct LocationResult
+	{
+		std::vector<preinfo::LocationPoint> vecLcpts;		/// 图像找到的定位点
+		std::vector<preinfo::LocationArea> vecAreas;		/// 图像找到的上的定位区
+		std::vector<preinfo::LocationCrossPoint> vecCrosses;/// 图像找到的上的交叉点
+		
+		LocationResult() { vecLcpts.clear(); vecAreas.clear(); vecCrosses.clear(); }
+	};
+
+	/// 每张答题卡的匹配结果
+	struct MatchResult {
+		bool matched;				/// 是否匹配到了
+		PAPER_STATUS matchedStatus;	/// 匹配过程中的状态记录
+		int matchedCount;			/// 匹配到点数
+		int totalCount;				/// 参与匹配的总数
+		int schemaIndex;			/// 匹配到模板ID 从0开始
+		double scale;				/// 与模板的比例系数
+		int dir;					/// 方向
+		LocationResult lcRes;		/// 定位信息结果
+		double transfrom[6];		/// 第三方的3*2矫正举证		模板-图像
+		double transfrom_[6];		/// 在线&第三方的3*2举证    图像-模板
+		bool nineUse;				/// 是否优先使用3*3矩阵
+		double olctransfrom_[9];	/// 在线答题卡的3*3映射矩阵 图像-模板
+		MatchResult() { nineUse = false;  matched = false; matchedStatus = PAPER_SUCC; matchedCount = 0; totalCount = 0; schemaIndex = -1; scale = -1; dir = 0; }
+	};
+	struct PageRes {
+		int idx;
+		preinfo::DIR_FLAG dir;
+		PageRes() { idx = -1; dir = preinfo::DIR_UNKNOW; }
+	};
+}
+
+namespace result {
+	/**
+	  !所有填涂判断的起始值都是 -1 标识未填涂
+	*/
+	
+	enum ABSENT_MARK {
+		MARK_NORMAL = 0,			/// 正常
+		MARK_ABSENCE = 1,			/// 缺考
+		MARK_BREAK_PRECEDENT =2,	/// 违纪
+		CHOICE_TOO_MUCH = 4,		/// 单选题 填涂了全部
+		CHOICE_NONE		= 8,		/// 本题未填涂
+	};
+
+	/// 填涂题目结果
+	struct ChoiceResult	{
+		struct element	{
+			int		index;		/// 按照从左到右/从上到下排列小项的下标 从-1开始 标识未填涂
+			double	confidence;	/// 置信度
+			element() :index(-1), confidence(0.0) {};
+		};
+		int id;					/// 与模板的ID对应
+		int tiHao;				/// 题号
+		ABSENT_MARK status;		/// 本题的状态
+		int number;				/// 本题小项个数
+		int mostCredible;		/// 单选情况下 最可信的ID下标
+		std::vector<std::string> vecOpts;	/// 本题结果选项是哪一个
+		preinfo::BOX_TYPE type;	/// 多选还是单选
+		std::vector<bool> vecRes;/// _看需求 是否启用 true:填涂了 false:未填涂
+		std::vector<element> vecIdx;/// 备选方案 
+		ChoiceResult() :id(-1),tiHao(-1), status(MARK_NORMAL), number(0), type(preinfo::BOX_QUESTION_CHOICE_M) {};
+	};
+
+	/// 判断题
+	struct QTFRes {
+		int		id;
+		bool	fill;
+		QTFRes() :id(-1), fill(false) {};
+	};
+
+	/// 主观题题目
+	struct SubjectiveResult	{
+		struct IMG_INFO {
+			std::vector<unsigned char> imgData;
+			bool vaild;
+			int cols;
+			int rows;
+			int channel;
+			IMG_INFO() :vaild(false), cols(0), rows(0), channel(0) {};
+		};
+		int		id;				/// 不是题号 是一个UUID雷士的东西
+		std::vector<int> uuids;	/// 多块合并的图像,是可能有多个UUID的 2022.1.19 新增
+		int		th;				/// 本题的题号
+		std::vector<int> tiHao;	/// 选做题适用
+		bool	multiple;		/// 是否多块 
+		int		orderNumber;	/// 如果是多块 本块序号  默认从1开始 如果是合并得到图则 ordernumber=-1
+		double	score;			/// 分数
+		preinfo::BOX_TYPE	type;	/// BOX TYPE
+		int					marktype;	/// 是否保存分块的小图 只有4和10保留
+		int					idx;	/// 只有type=选做题时候有效
+		IMG_INFO			img;	/// 线下阅卷问题 预留方案
+		/*
+			2022.1.19 改动 多个小块的图像有可能有多个打分框 所以要做支持
+			改动记录:
+			由 vector<int> 改动到 unordered_map<int, std::vector<int>> ,int = uuid 作为唯一关联key值
+		**/
+		std::vector<int>    sr;		/// 分数判断结果 内容:红色批改的框的红色值 个数:所有小框数量和 
+		std::unordered_map<int, std::vector<int>> mapSr; /// 记录每个uuid对应的打分框的识别结果
+
+		std::string			path;	/// 绝对路径
+		std::string			name;	/// 落地文件名 
+		int					width;	/// 2021.4.24 需求需要提供主观题的宽高信息
+		int					height;	/// ...
+		SubjectiveResult() :id(-1), multiple(false), orderNumber(1),marktype(0),
+			score(0.0),idx(-1), type(preinfo::BOX_NONE), path(""), name(""), width(0),height(0) {};
+	};
+
+	/// 选做题
+	struct SubjectiveChooseResult{
+		preinfo::SubjectiveChooseKey key;
+		preinfo::BOX_TYPE	type;
+		int id;
+		int tIdx;	/// 选中的下标 0:可能是未填涂默认值  也有可能是填涂了第一个
+		std::string			path;	/// 绝对路径
+		std::string			name;	/// 文件名
+		double				score;	/// 分数
+		SubjectiveChooseResult() :type(preinfo::BOX_SUBJECTIVE_CHOOSE), id(-1), tIdx(-1), path(""), name(""), score(0.0) {};
+	};
+
+	/// 选做题结果简单记录
+	struct SubjChResRecord {
+		std::vector<int> vecTiHaos;
+		int m; int n;
+		preinfo::BOX_TYPE	type;
+		std::vector<int> vecRes;
+	};
+
+	/// 识别的单份答题卡结果
+	typedef struct SinglePaperInfo {
+		int				orderNumber;		/// 本份的序号			备注:orderNumber = totalNumbers/mb.size()
+		bool			last;				/// 是否本批次最后一份
+		int				idx;				/// 如果状态是异常,标明那一张答题卡出现了异常,下标0开始 备注:首先判断status的状态
+		int				numbers;			/// 本份页数			备注:理论上等于模板的张数
+		int				choiceNumbers;		/// 所有填涂题目个数	备注:所有选择题总数量
+		PAPER_STATUS	status;				/// 本份试卷状态标志	备注:见枚举定义
+		ABSENT_MARK		absent;				/// 缺考 or 违纪		备注:见枚举定义
+		std::string		barCode;			/// 条码识别结果		备注:条形码识别结果 仅仅是条形码结果
+		std::string		qrCode;				/// 二维码识别结果		备注:二维码识别结果 空值只能标明未识别到或者没有条码 条码也适用
+		std::string		fillNumber;			/// 填涂考号			备注:长度=模板设定考号列数,未填涂位置用' '空格填充 !!!重要
+		std::string		strSavePath;		/// 本份的保存路径		备注:备份的保存路径下面会有 xx.jpg xx_.jpg等等图像
+		std::vector<std::string>			vecUrlPath;			/// 图像的本地路径					备注:客户端需求添加
+		std::vector<std::string>			vecUrlAPath;		/// 矫正后的原图路径				备注:矫正后的图,顺序为 img_1.jpg img_2.jpg ... 不超过模板张数
+		std::vector<QTFRes>					tfRes;				/// 判断题							备注:该类型未实现
+		std::vector<ChoiceResult>			choiceRes;			/// 所有填涂题目结果				备注:使用tiHao(int) 和 vecOpts(判断结果)即可 ,单选题vecOpts可能是一个或者全部数量(对应全部填涂错误)多选择是真实个数
+		std::shared_ptr<ChoiceResult>		ptrChoiceRes;		/// 所有填涂题目结果				备注:未启用 请忽略
+		std::vector<SubjectiveResult>		subjectiveRes;		/// 主观题							备注:所有主观题(填空,解答,选做等等)都在这里面 th标识本题最终输出结果 
+		std::shared_ptr<SubjectiveResult>	ptrSubjective;		/// ......							备注:未启用 请忽略
+		std::vector<SubjChResRecord>		subjectiveCRes;		/// 选做题							备注:存放选择题判断结果 内部使用请忽略
+		std::shared_ptr<SubjChResRecord>	ptrSubjectiveC;		///									备注:未启用 请忽略
+		std::vector<analysis::LocationResult>	vecLcRes;		///  定位信息的结果保存				备注:与 vecUrlPath 一一对应
+		SinglePaperInfo() :orderNumber(-1), last(false), idx(-1), numbers(0), choiceNumbers(0), status(PAPER_SUCC), absent(MARK_NORMAL),
+			barCode(""), qrCode(""), fillNumber(""), strSavePath("") {};
+	}spinfo;
+
+
+
+}
+

BIN
MFCApplication1/scan/release/lib_common_depence.dll


BIN
MFCApplication1/scan/release/lib_common_depence.lib