Browse Source

Merge remote-tracking branch 'origin/wn' into ywx

ywx
雍文秀 2 weeks ago
parent
commit
fbeb5c9f40

+ 6
- 0
smarking/pom.xml View File

@@ -31,6 +31,12 @@
31 31
             <artifactId>poi</artifactId>
32 32
             <version>3.17</version>
33 33
         </dependency>
34
+        <!--答题卡识别-->
35
+        <dependency>
36
+            <groupId>org.opencv</groupId>
37
+            <artifactId>opencv</artifactId>
38
+            <version>4.6.0</version>
39
+        </dependency>
34 40
 
35 41
     </dependencies>
36 42
     <dependencyManagement>

+ 61
- 14
smarking/src/main/java/com/xhkjedu/smarking/controller/exam/MsExamController.java View File

@@ -190,20 +190,6 @@ public class MsExamController {
190 190
         return new ResultVo(0, "查询成功", msExamService.findById(examid));
191 191
     }
192 192
 
193
-    /**
194
-     * @Description 发布成绩
195
-     * @Date 2024/11/26 9:10
196
-     * @Author YWX
197
-     * @Param [msExam]
198
-     * @Return com.xhkjedu.vo.ResultVo
199
-     **/
200
-    @PostMapping("/fb_cj")
201
-    public ResultVo fbCj(@RequestBody MsExam msExam) {
202
-        Integer examid = msExam.getExamid();
203
-        N_Utils.validation(new Object[]{examid, "考试id", 1});
204
-        msExamService.updateExamState(examid,4);
205
-        return new ResultVo(0, "发布成绩成功");
206
-    }
207 193
 
208 194
     /**
209 195
      * @Description 获取考试关联学校
@@ -246,4 +232,65 @@ public class MsExamController {
246 232
         N_Utils.validation(new Object[]{examid, "考试id", 1});
247 233
         return new ResultVo(0, "查询成功", msExamService.getExamstate(examid));
248 234
     }
235
+
236
+    /*
237
+     * @Description 一键统分
238
+     * @Date 2025/1/8 16:49:38
239
+     * @Author WN
240
+     * @Param [msExam]
241
+     * @Return com.xhkjedu.vo.ResultVo
242
+     **/
243
+    @PostMapping("/yjtf")
244
+    public ResultVo updateExamStateForYjtf(@RequestBody MsExam msExam) {
245
+        Integer examid = msExam.getExamid();
246
+        N_Utils.validation(new Object[]{examid, "考试id", 1});
247
+        msExamService.updateExamStateForYjtf(examid);
248
+        return new ResultVo(0, "保存成功");
249
+    }
250
+    /*
251
+     * @Description 取消一键统分
252
+     * @Date 2025/1/8 16:49:38
253
+     * @Author WN
254
+     * @Param [msExam]
255
+     * @Return com.xhkjedu.vo.ResultVo
256
+     **/
257
+    @PostMapping("/unyjtf")
258
+    public ResultVo updateExamStateForUnYjtf(@RequestBody MsExam msExam) {
259
+        Integer examid = msExam.getExamid();
260
+        N_Utils.validation(new Object[]{examid, "考试id", 1});
261
+        msExamService.updateExamStateForUnYjtf(examid);
262
+        return new ResultVo(0, "取消成功");
263
+    }
264
+
265
+    /*
266
+     * @Description 发布成绩
267
+     * @Date 2025/1/8 16:52:37
268
+     * @Author WN
269
+     * @Param [msExam]
270
+     * @Return com.xhkjedu.vo.ResultVo
271
+     **/
272
+    @PostMapping("/fbcj")
273
+    public ResultVo updateExamStateForfbcj(@RequestBody MsExam msExam) {
274
+        Integer examid = msExam.getExamid();
275
+        N_Utils.validation(new Object[]{examid, "考试id", 1});
276
+        msExamService.updateExamStateForfbcj(examid);
277
+        return new ResultVo(0, "发布成功");
278
+    }
279
+
280
+    /*
281
+     * @Description 取消发布成绩
282
+     * @Date 2025/1/8 16:53:05
283
+     * @Author WN
284
+     * @Param [msExam]
285
+     * @Return com.xhkjedu.vo.ResultVo
286
+     **/
287
+    @PostMapping("/unfbcj")
288
+    public ResultVo updateExamStateForUnfbcj(@RequestBody MsExam msExam) {
289
+        Integer examid = msExam.getExamid();
290
+        N_Utils.validation(new Object[]{examid, "考试id", 1});
291
+        msExamService.updateExamStateForUnfbcj(examid);
292
+        return new ResultVo(0, "取消成功");
293
+    }
294
+
295
+
249 296
 }

+ 2
- 0
smarking/src/main/java/com/xhkjedu/smarking/mapper/exam/MsExamMapper.java View File

@@ -39,4 +39,6 @@ public interface MsExamMapper extends TkMapper<MsExam> {
39 39
 
40 40
     //获取考试学校id
41 41
     Integer getSchoolIdByExamId(@Param("examid") Integer examid);
42
+    //批量结束考试
43
+    Integer updateBatchExamState(@Param("examids") List<Integer> examids);
42 44
 }

+ 2
- 0
smarking/src/main/java/com/xhkjedu/smarking/mapper/exam/MsSubjectMapper.java View File

@@ -58,6 +58,8 @@ public interface MsSubjectMapper extends TkMapper<MsSubject> {
58 58
     int updateStartSubject(@Param("strtime") String strtime);
59 59
     //定时任务-科目结束考试-获取待结束考试
60 60
     List<Integer> listMsidsForEndSubject(@Param("strtime") String strtime);
61
+    //定时任务-科目对应考试下所有科目
62
+    List<MsSubject> listSubjectAndStateByMsids(@Param("list") List<Integer> list);
61 63
     //定时任务-科目结束考试
62 64
     int updateEndSubject(@Param("timestamp") Integer timestamp,@Param("msids")List<Integer> msids);
63 65
 

+ 3
- 2
smarking/src/main/java/com/xhkjedu/smarking/mapper/stupaper/MsPaperStudentMapper.java View File

@@ -66,14 +66,15 @@ public interface MsPaperStudentMapper extends TkMapper<MsPaperStudent> {
66 66
 
67 67
     //学生考试-获取开始考试学生数量
68 68
     Integer getStartNumByExamIdExamMode(@Param("examid") Integer examid,@Param("exammode") Integer exammode);
69
-    //清空试卷学生信息
69
+    //删除试卷学生信息
70 70
     int deletePaperStuByMpid(@Param("mpid") Integer mpid);
71 71
 
72 72
     //扫描异常-清空学生扫描试卷信息
73 73
     int updateStuPaperForScanErrorClear(@Param("mpsid")Integer mpsid);
74 74
     //扫描异常-缺考异常-更改状态
75 75
     int updateBatchStuPaperSstate(@Param("list") List<MsPaperStudent> paperStudents);
76
-
76
+    //扫描-清空学生试卷扫描信息
77
+    int updateStuPaperForScanClearByMpid(@Param("mpid") Integer mpid);
77 78
     //补录成绩-学生科目成绩
78 79
     List<MsPaperStudent> listPaperStudentForFillScore(@Param("examid") Integer examid,@Param("studentid") Integer studentid);
79 80
     //补录成绩-补录单科

+ 35
- 10
smarking/src/main/java/com/xhkjedu/smarking/service/exam/MsExamService.java View File

@@ -389,16 +389,6 @@ public class MsExamService {
389 389
         return map;
390 390
     }
391 391
 
392
-    /**
393
-     * @Description 更新考试状态
394
-     * @Date 2024/11/26 9:12
395
-     * @Author YWX
396
-     * @Param [examid, examstate]
397
-     * @Return void
398
-     **/
399
-    public void updateExamState(Integer examid, int examstate) {
400
-        msExamMapper.updateExamState(examid, examstate);
401
-    }
402 392
 
403 393
     /**
404 394
      * @Description 获取考试关联学校
@@ -435,4 +425,39 @@ public class MsExamService {
435 425
     public Integer getExamstate(Integer examid){
436 426
         return msExamMapper.getExamstate(examid);
437 427
     }
428
+
429
+    //改状态-一键统分
430
+    public void updateExamStateForYjtf(Integer examid){
431
+        Integer examstateOld = msExamMapper.getExamstate(examid);
432
+        if(examstateOld < 3){
433
+            msExamMapper.updateExamState(examid,3);
434
+        }else{
435
+            throw new ServiceException("已统分,禁止操作");
436
+        }
437
+    }
438
+
439
+    //改状态-取消一键统分
440
+    public void updateExamStateForUnYjtf(Integer examid){
441
+        Integer examstateOld = msExamMapper.getExamstate(examid);
442
+        if(examstateOld > 3){
443
+            msExamMapper.updateExamState(examid,2);
444
+        }
445
+    }
446
+
447
+    //该状态-发布成绩
448
+    public void updateExamStateForfbcj(Integer examid){
449
+        Integer examstateOld = msExamMapper.getExamstate(examid);
450
+        if(examstateOld == 4){
451
+            msExamMapper.updateExamState(examid,5);
452
+        }else if(examstateOld < 4){
453
+            throw new ServiceException("未生成报告,无法发布成绩");
454
+        }
455
+    }
456
+    //该状态-取消发布成绩
457
+    public void updateExamStateForUnfbcj(Integer examid){
458
+        Integer examstateOld = msExamMapper.getExamstate(examid);
459
+        if(examstateOld == 5){
460
+            msExamMapper.updateExamState(examid,4);
461
+        }
462
+    }
438 463
 }

+ 16
- 2
smarking/src/main/java/com/xhkjedu/smarking/service/scan/MsScanbatchPaperService.java View File

@@ -1,5 +1,7 @@
1 1
 package com.xhkjedu.smarking.service.scan;
2 2
 
3
+import com.xhkjedu.exception.ServiceException;
4
+import com.xhkjedu.smarking.mapper.exam.MsExamMapper;
3 5
 import com.xhkjedu.smarking.mapper.paper.MsPaperBlockQuestionMapper;
4 6
 import com.xhkjedu.smarking.mapper.paper.MsTemplateMapper;
5 7
 import com.xhkjedu.smarking.mapper.scan.MsScanbatchMapper;
@@ -64,6 +66,8 @@ public class MsScanbatchPaperService {
64 66
     private SchoolMapper schoolMapper;
65 67
     @Resource
66 68
     private MsTemplateMapper msTemplateMapper;
69
+    @Resource
70
+    private MsExamMapper msExamMapper;
67 71
 
68 72
     /*
69 73
      * @Description 扫描记录-查看原卷
@@ -100,11 +104,11 @@ public class MsScanbatchPaperService {
100 104
      * @Date 2024-10-15
101 105
      **/
102 106
     @Transactional(rollbackFor = Exception.class)
103
-    public void savePaperIdentify(MsScanbatchPaper msScanbatchPaper) throws Exception {
107
+    public void savePaperIdentify(MsScanbatchPaper msScanbatchPaper){
104 108
 
105 109
         Integer examstate  = msScanbatchMapper.getExamStateByBatchid(msScanbatchPaper.getBatchid());
106 110
         if(examstate == 3){
107
-            throw new Exception("考试已统分,禁止扫描");
111
+            throw new ServiceException("考试已统分,禁止扫描");
108 112
         }
109 113
 
110 114
         //更改扫描出错次数
@@ -632,7 +636,17 @@ public class MsScanbatchPaperService {
632 636
         msScanbatchPaperMapper.updateBatchScanPaperMspstate(list);
633 637
     }
634 638
 
639
+    @Transactional(rollbackFor = Exception.class)
635 640
     public void reIdentifyPaper(Integer examid,String subjectid,Integer mpid) {
641
+        Integer examstate  = msExamMapper.getExamstate(examid);
642
+        if(examstate >= 3){
643
+            throw new ServiceException("考试已统分,禁止重新识别");
644
+        }
645
+
646
+        //清空学生题块试题信息和学生试卷扫描信息
647
+        msPaperStudentBlockMapper.updateStudentBlockQuestionByMpid(mpid);
648
+        msPaperStudentMapper.updateStuPaperForScanClearByMpid(mpid);
649
+
636 650
         //获取考试对应的试卷模板及坐标信息
637 651
         List<MsTemplate> templates = msTemplateMapper.listTemplateBlockQueByMpid(mpid);
638 652
 

+ 22
- 0
smarking/src/main/java/com/xhkjedu/smarking/task/ExamSubjectTask.java View File

@@ -1,6 +1,8 @@
1 1
 package com.xhkjedu.smarking.task;
2 2
 
3
+import com.xhkjedu.smarking.mapper.exam.MsExamMapper;
3 4
 import com.xhkjedu.smarking.mapper.exam.MsSubjectMapper;
5
+import com.xhkjedu.smarking.model.exam.MsSubject;
4 6
 import com.xhkjedu.smarking.service.stupaper.MsPaperStudentQuestionService;
5 7
 import com.xhkjedu.utils.N_Utils;
6 8
 import lombok.extern.slf4j.Slf4j;
@@ -9,7 +11,10 @@ import org.springframework.scheduling.annotation.Scheduled;
9 11
 import org.springframework.stereotype.Component;
10 12
 
11 13
 import javax.annotation.Resource;
14
+import java.util.ArrayList;
12 15
 import java.util.List;
16
+import java.util.Map;
17
+import java.util.stream.Collectors;
13 18
 
14 19
 /**
15 20
  * @Description:考试科目状态定时任务
@@ -24,6 +29,8 @@ public class ExamSubjectTask {
24 29
     private MsSubjectMapper msSubjectMapper;
25 30
     @Resource
26 31
     private MsPaperStudentQuestionService msPaperStudentQuestionService;
32
+    @Resource
33
+    private MsExamMapper msExamMapper;
27 34
 
28 35
     /*
29 36
      * @Description 单科开始考试
@@ -54,6 +61,21 @@ public class ExamSubjectTask {
54 61
         List<Integer> msids = msSubjectMapper.listMsidsForEndSubject(strtime);
55 62
         if(N_Utils.isListNotEmpty(msids)){
56 63
             msSubjectMapper.updateEndSubject(timestamp, msids);
64
+            //获取科目对应的考试下所有科目
65
+            List<MsSubject> subjects = msSubjectMapper.listSubjectAndStateByMsids(msids);
66
+            Map<Integer,List<MsSubject>> subjectMap = subjects.stream().collect(Collectors.groupingBy(MsSubject::getExamid));
67
+            List<Integer> examids = new ArrayList<>();
68
+            for(Map.Entry<Integer,List<MsSubject>> entry : subjectMap.entrySet()){
69
+                List<MsSubject> subList = entry.getValue();
70
+                List<MsSubject> endSubList=subList.stream().filter(ms -> ms.getMsstate() == 2).collect(Collectors.toList());
71
+                if(!endSubList.isEmpty() && endSubList.size() == subList.size()){
72
+                    examids.add(entry.getKey());
73
+                }
74
+            }
75
+            //考试下所有科目已经结束考试,更改考试状态为已结束
76
+            if(!examids.isEmpty()){
77
+                msExamMapper.updateBatchExamState(examids);
78
+            }
57 79
         }
58 80
     }
59 81
 

+ 447
- 0
smarking/src/main/java/com/xhkjedu/smarking/utils/opencv/CvCommonUtils.java View File

@@ -0,0 +1,447 @@
1
+package com.xhkjedu.smarking.utils.opencv;
2
+
3
+import org.opencv.core.*;
4
+import org.opencv.imgcodecs.Imgcodecs;
5
+import org.opencv.imgproc.Imgproc;
6
+
7
+import java.util.Base64;
8
+import java.util.List;
9
+
10
+public class CvCommonUtils {
11
+    /**
12
+     * 逆时针旋转90度
13
+     *
14
+     * @param source 原Mat
15
+     * @return Mat
16
+     */
17
+    public static Mat rotate90Counter(Mat source) {
18
+        Mat dst = new Mat();
19
+        Core.rotate(
20
+                source,
21
+                dst,
22
+                Core.ROTATE_90_COUNTERCLOCKWISE
23
+        );
24
+        return dst;
25
+    }
26
+
27
+    /**
28
+     * 顺时针旋转90度
29
+     *
30
+     * @param source 原Mat
31
+     * @return Mat
32
+     */
33
+    public static Mat rotate90(Mat source) {
34
+        Mat dst = new Mat();
35
+        Core.rotate(
36
+                source,
37
+                dst,
38
+                Core.ROTATE_90_CLOCKWISE
39
+        );
40
+        return dst;
41
+    }
42
+
43
+    /**
44
+     * 旋转180度
45
+     *
46
+     * @param source 原Mat
47
+     * @return Mat
48
+     */
49
+    public static Mat rotate180(Mat source) {
50
+        Mat dst = new Mat();
51
+        Core.rotate(
52
+                source,
53
+                dst,
54
+                Core.ROTATE_180
55
+        );
56
+        return dst;
57
+    }
58
+
59
+    /**
60
+     * 旋转任意角度
61
+     *
62
+     * @param source 原Mat
63
+     * @param angle  角度
64
+     * @return Mat
65
+     */
66
+    public static Mat rotate(Mat source, int angle) {
67
+        angle = -angle;
68
+        // 获取图像的尺寸
69
+        Size size = source.size();
70
+        int width = (int) size.width;
71
+        int height = (int) size.height;
72
+        // 创建一个新的Mat对象来存储旋转后的图像
73
+        Mat dst;
74
+        int tempAngle = Math.abs(angle) % 180;
75
+        if (tempAngle > 45) {
76
+            dst = new Mat(
77
+                    width,
78
+                    height,
79
+                    source.type()
80
+            );
81
+        } else {
82
+            dst = new Mat(
83
+                    height,
84
+                    width,
85
+                    source.type()
86
+            );
87
+        }
88
+        // 获取图像的中心点
89
+        Point center = new Point((double) width / 2, (double) height / 2);
90
+        // 计算旋转矩阵
91
+        Mat rotMat = Imgproc.getRotationMatrix2D(
92
+                center,
93
+                angle,
94
+                1.0
95
+        );
96
+        // 调整旋转矩阵的平移部分以保持图像在目标矩阵中心
97
+        rotMat.put(
98
+                0,
99
+                2,
100
+                rotMat.get(
101
+                        0,
102
+                        2
103
+                )[0] + (dst.size().width / 2.0 - center.x)
104
+        );
105
+        rotMat.put(
106
+                1,
107
+                2,
108
+                rotMat.get(1, 2)[0] + (dst.size().height / 2.0 - center.y)
109
+        );
110
+        // 执行仿射变换(旋转)
111
+        Imgproc.warpAffine(
112
+                source,
113
+                dst,
114
+                rotMat,
115
+                dst.size()
116
+        );
117
+        return dst;
118
+    }
119
+
120
+    /**
121
+     * 缩放
122
+     *
123
+     * @param source 原Mat
124
+     * @param width  目标宽度
125
+     * @param height 目标高度
126
+     * @return Mat
127
+     */
128
+    public static Mat resize(Mat source, int width, int height) {
129
+        // 检查输入图像是否有效
130
+        if (source.empty()) {
131
+            return new Mat();
132
+        }
133
+        // 获取源图像的宽高
134
+        int sWidth = source.cols();
135
+        int sHeight = source.rows();
136
+        // 若目标宽度和高度同时为0,直接返回源图像
137
+        if (width == 0 && height == 0) {
138
+            return source.clone(); // 深拷贝源图像
139
+        }
140
+        // 计算目标宽高
141
+        double aspectRatio = (double) sWidth / sHeight; // 原始宽高比
142
+        int targetWidth = (width > 0) ? width : (int) (height * aspectRatio);
143
+        int targetHeight = (height > 0) ? height : (int) (width / aspectRatio);
144
+        // 创建结果Mat并进行缩放
145
+        Mat resultMat = new Mat();
146
+        Imgproc.resize(source, resultMat, new Size(targetWidth, targetHeight));
147
+        return resultMat;
148
+    }
149
+
150
+    /**
151
+     * 以灰度化读取图片
152
+     *
153
+     * @param imgPath 图片路径
154
+     * @return Mat
155
+     */
156
+    public static Mat gray(String imgPath) {
157
+        return Imgcodecs.imread(
158
+                imgPath,
159
+                Imgcodecs.IMREAD_GRAYSCALE
160
+        );
161
+    }
162
+
163
+    // 灰度化
164
+    public static Mat gray(Mat inputImage) {
165
+        Mat grayImage = new Mat();
166
+        Imgproc.cvtColor(
167
+                inputImage,
168
+                grayImage,
169
+                Imgproc.COLOR_BGR2GRAY
170
+        );
171
+        return grayImage;
172
+    }
173
+
174
+    /**
175
+     * 二值化 输入源必须是灰度化的图片
176
+     *
177
+     * @param grayImage 灰度化Mat
178
+     * @return Mat
179
+     */
180
+    public static Mat binary(Mat grayImage) {
181
+        Mat binaryImage = new Mat();
182
+        Imgproc.threshold(
183
+                grayImage,
184
+                binaryImage,
185
+                0,
186
+                255,
187
+                Imgproc.THRESH_BINARY | Imgproc.THRESH_OTSU
188
+        );
189
+        return binaryImage;
190
+    }
191
+
192
+    /**
193
+     * 腐蚀(黑色区域变大)
194
+     *
195
+     * @param source 原Mat
196
+     * @return Mat
197
+     */
198
+    public static Mat eroding(Mat source) {
199
+        return eroding(
200
+                source,
201
+                1
202
+        );
203
+    }
204
+
205
+    /**
206
+     * 腐蚀(黑色区域变大)
207
+     *
208
+     * @param source       原Mat
209
+     * @param erosion_size 腐蚀大小
210
+     * @return Mat
211
+     */
212
+    public static Mat eroding(Mat source, double erosion_size) {
213
+        Mat resultMat = new Mat(
214
+                source.rows(),
215
+                source.cols(),
216
+                source.type()
217
+        );
218
+        Mat element = Imgproc.getStructuringElement(
219
+                Imgproc.MORPH_RECT,
220
+                new Size(
221
+                        erosion_size + 1,
222
+                        erosion_size + 1
223
+                )
224
+        );
225
+        Imgproc.erode(
226
+                source,
227
+                resultMat,
228
+                element
229
+        );
230
+        return resultMat;
231
+    }
232
+
233
+    /**
234
+     * 膨胀(白色区域变大)
235
+     *
236
+     * @param source 原Mat
237
+     * @return Mat
238
+     */
239
+    public static Mat dilation(Mat source) {
240
+        return dilation(
241
+                source,
242
+                1
243
+        );
244
+    }
245
+
246
+    /**
247
+     * 膨胀(白色区域变大)
248
+     *
249
+     * @param source        原Mat
250
+     * @param dilation_size 膨胀因子2*x+1 里的x
251
+     * @return Mat
252
+     */
253
+    public static Mat dilation(Mat source, double dilation_size) {
254
+        Mat resultMat = new Mat(
255
+                source.rows(),
256
+                source.cols(),
257
+                source.type()
258
+        );
259
+        Mat element = Imgproc.getStructuringElement(
260
+                Imgproc.MORPH_RECT,
261
+                new Size(
262
+                        2 * dilation_size + 1,
263
+                        2 * dilation_size + 1
264
+                )
265
+        );
266
+        Imgproc.dilate(
267
+                source,
268
+                resultMat,
269
+                element
270
+        );
271
+        return resultMat;
272
+    }
273
+
274
+    /**
275
+     * 获取区域Mat
276
+     *
277
+     * @param source 原Mat
278
+     * @param rect   区域
279
+     * @return Mat
280
+     */
281
+    public static Mat getRectMat(Mat source, Rect rect) {
282
+        return new Mat(
283
+                source,
284
+                rect
285
+        );
286
+    }
287
+
288
+    /**
289
+     * 获取填涂率
290
+     *
291
+     * @param source 原Mat
292
+     * @param rect   区域
293
+     * @return Mat
294
+     */
295
+    public static double getSmearRate(Mat source, Rect rect) {
296
+        // 创建一个新的Mat对象,它表示源图像中的指定区域
297
+        Mat matTemp = new Mat(
298
+                source,
299
+                rect
300
+        );
301
+        // 计算非零像素的数量
302
+        int count = Core.countNonZero(matTemp);
303
+        // 计算总像素数
304
+        int total = matTemp.cols() * matTemp.rows();
305
+        // 计算零像素的比率
306
+        double rate = (double) (total - count) / total;
307
+        // 释放临时Mat对象的资源
308
+        matTemp.release();
309
+        // 返回比率是否大于最大值
310
+        return rate;
311
+    }
312
+
313
+    /**
314
+     * 是否涂卡
315
+     *
316
+     * @param source 图片
317
+     * @param rect   区域
318
+     * @param min    最小的填涂率
319
+     * @return 是否填涂
320
+     */
321
+    public static boolean isSmearCard(Mat source, Rect rect, double min) {
322
+        // 计算零像素的比率
323
+        double rate = getSmearRate(
324
+                source,
325
+                rect
326
+        );
327
+        // 返回比率是否大于最大值
328
+        return rate > min;
329
+    }
330
+
331
+    /**
332
+     * 颜色反转
333
+     *
334
+     * @param source 原Mat
335
+     * @return Mat
336
+     */
337
+    public static Mat bitwiseNot(Mat source) {
338
+        Mat resultMat = new Mat();
339
+        Core.bitwise_not(
340
+                source,
341
+                resultMat
342
+        );
343
+        return resultMat;
344
+    }
345
+
346
+    /**
347
+     * 垂直合并多个Mat
348
+     *
349
+     * @param matList 图片列表
350
+     * @return Mat
351
+     */
352
+    public static Mat jointMatV(List<Mat> matList) {
353
+        if (matList.isEmpty()) {
354
+            return new Mat();
355
+        }
356
+        int rows = 0;
357
+        int cols = 0;
358
+        for (Mat imgTemp : matList) {
359
+            rows += imgTemp.rows();
360
+            cols = Math.max(cols, imgTemp.cols());
361
+        }
362
+        // 创建一个白色背景的Mat对象
363
+        Mat result = new Mat(rows, cols, matList.get(0).type(), new Scalar(255, 255, 255));
364
+        int tempRows = 0;
365
+        for (Mat itemMat : matList) {
366
+            // 定义复制的区域
367
+            Mat roi = result.submat(new Rect(0, tempRows, itemMat.cols(), itemMat.rows()));
368
+            // 将itemMat的内容复制到ROI中
369
+            itemMat.copyTo(roi);
370
+            tempRows += itemMat.rows();
371
+        }
372
+        return result;
373
+    }
374
+
375
+    /**
376
+     * 水平合并多个Mat
377
+     *
378
+     * @param matList 图片列表
379
+     * @return Mat
380
+     */
381
+    public static Mat jointMatH(List<Mat> matList) {
382
+        if (matList.isEmpty()) {
383
+            return new Mat();
384
+        }
385
+        int rows = 0;
386
+        int cols = 0;
387
+        for (Mat imgTemp : matList) {
388
+            rows = Math.max(rows, imgTemp.rows());
389
+            cols += imgTemp.cols();
390
+        }
391
+        // 创建一个白色背景的Mat对象
392
+        Mat result = new Mat(rows, cols, matList.get(0).type(), new Scalar(255, 255, 255));
393
+        int tempCols = 0;
394
+        for (Mat itemMat : matList) {
395
+            // 定义复制的区域
396
+            Mat roi = result.submat(new Rect(tempCols, 0, itemMat.cols(), itemMat.rows()));
397
+            // 将itemMat的内容复制到ROI中
398
+            itemMat.copyTo(roi);
399
+            tempCols += itemMat.cols();
400
+        }
401
+        return result;
402
+    }
403
+
404
+    /**
405
+     * Mat转Base64字符串
406
+     *
407
+     * @param mat mat
408
+     * @return Base64字符串
409
+     */
410
+    public static String matToBase64(Mat mat) {
411
+        return matToBase64(mat, true);
412
+    }
413
+
414
+    /**
415
+     * Mat转Base64字符串
416
+     *
417
+     * @param mat       mat
418
+     * @param addPrefix 是否添加前缀
419
+     * @return Base64字符串
420
+     */
421
+    public static String matToBase64(Mat mat, boolean addPrefix) {
422
+        // 将Mat对象转换为PNG格式的字节数组
423
+        MatOfByte matOfByte = new MatOfByte();
424
+        Imgcodecs.imencode(".jpeg", mat, matOfByte);
425
+        // 将字节数组转换为Base64字符串
426
+        byte[] byteArray = matOfByte.toArray();
427
+        String imgBase64Str = Base64.getEncoder().encodeToString(byteArray);
428
+        if (addPrefix) {
429
+            String prefix = "data:image/jpeg;base64,";
430
+            return prefix + imgBase64Str;
431
+        }
432
+        return imgBase64Str;
433
+    }
434
+
435
+    /**
436
+     * 保存图片
437
+     *
438
+     * @param img  图片Mat
439
+     * @param path 保存路径
440
+     */
441
+    public static void saveImg(Mat img, String path) {
442
+        Imgcodecs.imwrite(
443
+                path,
444
+                img
445
+        );
446
+    }
447
+}

+ 123
- 0
smarking/src/main/java/com/xhkjedu/smarking/utils/opencv/CvContoursUtils.java View File

@@ -0,0 +1,123 @@
1
+package com.xhkjedu.smarking.utils.opencv;
2
+
3
+import org.opencv.core.Mat;
4
+import org.opencv.core.MatOfPoint;
5
+import org.opencv.core.Point;
6
+import org.opencv.imgproc.Imgproc;
7
+
8
+import java.util.Vector;
9
+
10
+/**
11
+ * 轮廓工具类
12
+ */
13
+public class CvContoursUtils {
14
+
15
+    /**
16
+     * 获取图片的四个顶点
17
+     *
18
+     * @param img 原图
19
+     * @return Point[]
20
+     */
21
+    public static Point[] getAllPoints(Mat img) {
22
+        Point[] potArr = new Point[4];
23
+        for (int i = 0; i < 4; i++) {
24
+            potArr[i] = new Point(-1, -1);
25
+        }
26
+
27
+        int[] spaceArr = new int[]{-1, -1, -1, -1};
28
+        int cols = img.cols();
29
+        int rows = img.rows();
30
+        int x1 = cols / 3;
31
+        int x2 = cols * 2 / 3;
32
+        int y1 = rows / 3;
33
+        int y2 = rows * 2 / 3;
34
+        for (int x = 0; x < cols; x++) {
35
+            for (int y = 0; y < rows; y++) {
36
+                if (x > x1 && x < x2 && y > y1 && y < y2) {
37
+                    continue;
38
+                }
39
+                double[] darr = img.get(y, x);
40
+                if (darr != null && darr.length >= 1 && darr[0] == 0) {
41
+                    if (spaceArr[0] == -1) {
42
+                        potArr[0].x = x;
43
+                        potArr[0].y = y;
44
+                        potArr[1].x = x;
45
+                        potArr[1].y = y;
46
+                        potArr[2].x = x;
47
+                        potArr[2].y = y;
48
+                        potArr[3].x = x;
49
+                        potArr[3].y = y;
50
+                        spaceArr[0] = getSpace(0, 0, x, y);
51
+                        spaceArr[1] = getSpace(cols, 0, x, y);
52
+                        spaceArr[2] = getSpace(cols, rows, x, y);
53
+                        spaceArr[3] = getSpace(0, rows, x, y);
54
+                    } else {
55
+                        int s0 = getSpace(0, 0, x, y);
56
+                        int s1 = getSpace(cols, 0, x, y);
57
+                        int s2 = getSpace(cols, rows, x, y);
58
+                        int s3 = getSpace(0, rows, x, y);
59
+                        if (s0 < spaceArr[0]) {
60
+                            spaceArr[0] = s0;
61
+                            potArr[0].x = x;
62
+                            potArr[0].y = y;
63
+                        }
64
+                        if (s1 < spaceArr[1]) {
65
+                            spaceArr[1] = s1;
66
+                            potArr[1].x = x;
67
+                            potArr[1].y = y;
68
+                        }
69
+                        if (s2 < spaceArr[2]) {
70
+                            spaceArr[2] = s2;
71
+                            potArr[2].x = x;
72
+                            potArr[2].y = y;
73
+                        }
74
+                        if (s3 < spaceArr[3]) {
75
+                            spaceArr[3] = s3;
76
+                            potArr[3].x = x;
77
+                            potArr[3].y = y;
78
+                        }
79
+                    }
80
+
81
+                }
82
+            }
83
+        }
84
+        return potArr;
85
+    }
86
+
87
+    /**
88
+     * 轮廓识别,使用最外轮廓发抽取轮廓RETR_EXTERNAL,轮廓识别方法为CHAIN_APPROX_SIMPLE
89
+     *
90
+     * @param source 传入进来的图片Mat对象
91
+     * @return 返回轮廓结果集
92
+     */
93
+    public static Vector<MatOfPoint> findContours(Mat source) {
94
+        Mat rs = new Mat();
95
+
96
+        /*
97
+          定义轮廓识别方法
98
+          边缘近似方法(除了RETR_RUNS使用内置的近似,其他模式均使用此设定的近似算法)。可取值如下:
99
+         CV_CHAIN_CODE:以Freeman链码的方式输出轮廓,所有其他方法输出多边形(顶点的序列)。
100
+         CHAIN_APPROX_NONE:将所有的连码点,转换成点。
101
+         CHAIN_APPROX_SIMPLE:压缩水平的、垂直的和斜的部分,也就是,函数只保留他们的终点部分。
102
+         CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS:使用the flavors of Teh-Chin chain近似算法的一种。
103
+         LINK_RUNS:通过连接水平段的1,使用完全不同的边缘提取算法。使用CV_RETR_LIST检索模式能使用此方法。
104
+         */
105
+        Vector<MatOfPoint> contours = new Vector<>();
106
+        Imgproc.findContours(
107
+                source,
108
+                contours,
109
+                rs,
110
+                Imgproc.RETR_LIST,
111
+                Imgproc.CHAIN_APPROX_SIMPLE
112
+        );
113
+        return contours;
114
+    }
115
+
116
+
117
+    private static int getSpace(int x1, int y1, int x2, int y2) {
118
+        int xspace = Math.abs(x1 - x2);
119
+        int yspace = Math.abs(y1 - y2);
120
+        return (int) Math.sqrt(Math.pow(xspace, 2) + Math.pow(yspace, 2));
121
+    }
122
+
123
+}

+ 63
- 0
smarking/src/main/java/com/xhkjedu/smarking/utils/opencv/CvWarpPerspectiveUtils.java View File

@@ -0,0 +1,63 @@
1
+package com.xhkjedu.smarking.utils.opencv;
2
+
3
+import org.opencv.core.CvType;
4
+import org.opencv.core.Mat;
5
+import org.opencv.core.Point;
6
+import org.opencv.core.Scalar;
7
+import org.opencv.imgproc.Imgproc;
8
+import org.opencv.utils.Converters;
9
+
10
+import java.util.Arrays;
11
+import java.util.List;
12
+
13
+/**
14
+ * 透视变换工具类
15
+ * 因为我透视变换做的也不是很好,就仅提供一个大概的函数...
16
+ */
17
+public class CvWarpPerspectiveUtils {
18
+
19
+    /**
20
+     * 执行透视变换
21
+     *
22
+     * @param src    输入图像
23
+     * @param points 变换所需的四个角点
24
+     * @return 透视变换后的图像
25
+     */
26
+    public static Mat warpPerspective(Mat src, Point[] points) {
27
+        // 点的顺序[左上 ,右上 ,右下 ,左下]
28
+        List<Point> listSrcs = Arrays.asList(
29
+                points[0],
30
+                points[1],
31
+                points[2],
32
+                points[3]
33
+        );
34
+        Mat srcPoints = Converters.vector_Point_to_Mat(listSrcs, CvType.CV_32F);
35
+
36
+        List<Point> listDsts = Arrays.asList(
37
+                new Point(0, 0),
38
+                new Point(src.width(), 0),
39
+                new Point(src.width(), src.height()),
40
+                new Point(0, src.height())
41
+        );
42
+
43
+
44
+        Mat dstPoints = Converters.vector_Point_to_Mat(listDsts, CvType.CV_32F);
45
+
46
+        Mat perspectiveMmat = Imgproc.getPerspectiveTransform(dstPoints, srcPoints);
47
+
48
+        Mat dst = new Mat();
49
+
50
+        Imgproc.warpPerspective(
51
+                src,
52
+                dst,
53
+                perspectiveMmat,
54
+                src.size(),
55
+                Imgproc.INTER_LINEAR + Imgproc.WARP_INVERSE_MAP,
56
+                1,
57
+                new Scalar(0)
58
+        );
59
+
60
+        return dst;
61
+    }
62
+
63
+}

+ 8
- 0
smarking/src/main/resources/mapper/exam/MsExamMapper.xml View File

@@ -75,4 +75,12 @@
75 75
     <select id="getSchoolIdByExamId" resultType="java.lang.Integer">
76 76
         select schoolid from ms_exam where examid=#{examid}
77 77
     </select>
78
+
79
+    <!--批量结束考试-->
80
+    <update id="updateBatchExamState">
81
+        update ms_exam set examstate=2 where examid in
82
+        <foreach collection="examids" item="item" open="(" separator="," close=")">
83
+            #{item}
84
+        </foreach>
85
+    </update>
78 86
 </mapper>

+ 7
- 1
smarking/src/main/resources/mapper/exam/MsSubjectMapper.xml View File

@@ -117,11 +117,17 @@
117 117
         and ms.msstate=0 and concat(ms.sdate,' ',ms.begintime)&lt;=#{strtime}
118 118
     </update>
119 119
     <!--定时任务-科目结束考试-获取待结束考试-->
120
-    <select id="listMsidsForEndSubject">
120
+    <select id="listMsidsForEndSubject" resultType="javj.lang.Integer">
121 121
         select ms.msid from ms_subject ms left join ms_exam e on ms.examid=e.examid
122 122
         where e.examstate>=1 and e.deleted=1 and e.exammode=3 and ms.msstate=1
123 123
         and adddate(concat(ms.sdate,' ',ms.endtime),interval 5 minute) &lt;#{strtime}
124 124
     </select>
125
+    <!--定时任务-科目对应考试下所有科目-->
126
+    <select id="listSubjectAndStateByMsids" resultType="com.xhkjedu.smarking.model.exam.MsSubject">
127
+        select distinct s.msid,s.msstate,s.examid from ms_subject s where s.examid in
128
+        (select t.examid from ms_subject t where t.msid in
129
+        <foreach collection="list" item="msid" open="(" close=")" separator=",">#{msid}</foreach>
130
+    </select>
125 131
     <!--获取考试科目ids-->
126 132
     <select id="listSubjectIdsByExamId" resultType="java.lang.String">
127 133
         select subjectid from ms_subject where examid=#{examid}

+ 5
- 0
smarking/src/main/resources/mapper/stupaper/MsPaperStudentMapper.xml View File

@@ -249,6 +249,11 @@
249 249
             update ms_paper_student set sstate=#{p.sstate} where mspid=#{p.mspid} and mpid=#{p.mpid}
250 250
         </foreach>
251 251
     </update>
252
+    <!--扫描-清空学生试卷扫描信息-->
253
+    <update id="updateStuPaperForScanClearByMpid">
254
+        update ms_paper_student set sstate=0,stuscore=0.0,stuscoretype=0,hasbad=0,batchid=null,mspid=null,
255
+                                    paperfront=null,paperback=null,pagenum=0 where mpid=#{mpid}
256
+    </update>
252 257
 
253 258
     <!--补录成绩-学生科目成绩-->
254 259
     <select id="listPaperStudentForFillScore" resultType="com.xhkjedu.smarking.model.stupaper.MsPaperStudent">

Loading…
Cancel
Save