diff --git a/src/main/java/com/hy/java/uct/pre/datacolle/ProjectAnalyzer.java b/src/main/java/com/hy/java/uct/pre/datacolle/ProjectAnalyzer.java index adce3e11f5e25fbae63d6af053e6dac06c38f81e..7c55d409416f2e1b5cd41333017c0cc6f88398b6 100644 --- a/src/main/java/com/hy/java/uct/pre/datacolle/ProjectAnalyzer.java +++ b/src/main/java/com/hy/java/uct/pre/datacolle/ProjectAnalyzer.java @@ -24,7 +24,7 @@ import org.apache.poi.xssf.usermodel.XSSFWorkbook; import com.hy.java.uct.pre.datacolle.util.InfoGetter; public class ProjectAnalyzer { - private static final String excel_path = "src/main/resources/temp3-UML模型数1~9的项目不一致性&辅助文档链接.xlsx"; + private static final String excel_path = "src/main/resources/UML模型数1~9的项目不一致性&辅助文档链接.xlsx"; public static void main(String[] args) { // TODO Auto-generated method stub diff --git a/src/main/java/com/hy/java/uct/umlrecog/cddetector/ClassDetector.java b/src/main/java/com/hy/java/uct/umlrecog/cddetector/ClassDetector.java index 498a20b21ad6a40c6666e17d0ae4ec89fbbdbed1..8615e3542e91f1e810ce5c620be59cb6093caa8e 100644 --- a/src/main/java/com/hy/java/uct/umlrecog/cddetector/ClassDetector.java +++ b/src/main/java/com/hy/java/uct/umlrecog/cddetector/ClassDetector.java @@ -188,7 +188,7 @@ public class ClassDetector { // 针对当前矩形,与所有其他矩形对比完毕后,则完成了该类的识别 result.add(uml_class); } - System.out.println("已将" + cd_path + "中的矩形整合为类,共" + result.size() + "个"); + System.out.println("完成将" + cd_path + "中所有矩形整合为类的业务,共" + result.size() + "个类"); return result; } diff --git a/src/main/java/com/hy/java/uct/umlrecog/cddetector/ClassRelationDetector.java b/src/main/java/com/hy/java/uct/umlrecog/cddetector/ClassRelationDetector.java index 3afcfc46a7784fd7464b7cb8f277aabee2e86ee1..ca07a20cfdd58b31b9eb5c2056b542e6d8728e68 100644 --- a/src/main/java/com/hy/java/uct/umlrecog/cddetector/ClassRelationDetector.java +++ b/src/main/java/com/hy/java/uct/umlrecog/cddetector/ClassRelationDetector.java @@ -1,17 +1,24 @@ package com.hy.java.uct.umlrecog.cddetector; import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashSet; import java.util.List; +import java.util.Set; import org.opencv.core.Mat; import org.opencv.core.MatOfPoint; import org.opencv.core.MatOfPoint2f; import org.opencv.core.Point; import org.opencv.core.Scalar; +import org.opencv.core.Size; import org.opencv.imgcodecs.Imgcodecs; import org.opencv.imgproc.Imgproc; import com.hy.java.uct.umlrecog.util.Line; +import com.hy.java.uct.umlrecog.util.PolygonalLine; +import com.hy.java.uct.umlrecog.util.Rectangle; +import com.hy.java.uct.umlrecog.util.RelationType; import com.hy.java.uct.umlrecog.util.UMLClass; import com.hy.java.utility.common.Pair; @@ -28,73 +35,262 @@ public class ClassRelationDetector { } public void recog() { - // 先检测实线 - List solid_lines = detectSolidLines(classes_in_cd.getLeft(), 0.0505); + // 先检测实线。检测结果需借助类的位置关系、去除“单纯的线” + Set solid_lines = detectLines(classes_in_cd.getLeft(), 0.0504, true); + // 再检测虚线。虚线检测除了间隔设置与实线不同外,其他完全一样 + Set dash_lines = detectLines(classes_in_cd.getLeft(), 0.0504, false); // 再检测关系类型检测 - // Mat temp = detectRelationType(classes_in_cd, 0.00002); - // 最后再检测虚线 - // detectLines(classes_in_cd.getLeft()); + // List relation_types = detectRelationType(classes_in_cd, 0.0001); + } - private List detectSolidLines(Mat cls_diagram, double ratio) { - System.out.println("识别" + cd_path + "中的实线"); - List result = new ArrayList<>(); + private Set detectLines(Mat cls_diagram, double ratio, boolean detect_solid) { + if (detect_solid) { + System.out.println("开始识别" + cd_path + "中的实线"); + } else { + System.out.println("开始识别" + cd_path + "中的虚线"); + } + Set result = null; + List line_segments = new ArrayList<>(); /* * 先检测边缘,然后从边缘集中初步检测“原始直线” */ // 先检测边缘。然后从边缘集中检测直线 Mat edges = new Mat(); Imgproc.Canny(cls_diagram, edges, 50, 50 * 3, 3, true); - // 从边缘集中检测直线。实线检测时,“最小直线长度”与图像像素有关;“最大像素间隔”越小越接近实线 + // 从边缘集中检测直线。膨胀(将线外面的空白区域膨胀)、腐蚀(让线侵蚀周围的空白区域),可以提高识别准确率 + Imgproc.dilate(cls_diagram, cls_diagram, Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(1.7, 1.7)), new Point(-1, -1), 2); + Imgproc.erode(cls_diagram, cls_diagram, Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(1, 1)), new Point(-1, -1), 1); Mat lines = new Mat(); - Imgproc.HoughLinesP(edges, lines, 1, Math.PI / 180, 12, Math.min(cls_diagram.width() * ratio, cls_diagram.height() * ratio), 3); + if (detect_solid) { + // 实线检测时,“最小直线长度”与图像像素有关;“最大像素间隔”越小越接近实线 + Imgproc.HoughLinesP(edges, lines, 1, Math.PI / 180, 8, 29, 2); + } else { + Imgproc.HoughLinesP(edges, lines, 1, Math.PI / 180, 12, Math.min(cls_diagram.width() * ratio, cls_diagram.height() * ratio), 10); + } // 保存所有刚检测出来的“原始直线” for (int i = 0; i < lines.rows(); i++) { - result.add(new Line(new Point(lines.get(i, 0)[0], lines.get(i, 0)[1]), new Point(lines.get(i, 0)[2], lines.get(i, 0)[3]))); + line_segments.add(new Line(new Point(lines.get(i, 0)[0], lines.get(i, 0)[1]), new Point(lines.get(i, 0)[2], lines.get(i, 0)[3]))); } /* * 检查“原始直线”集中是否有重合直线,去除冗余或合并 */ - int current_size = result.size(); + int current_size = line_segments.size(); for (int i = 0; i < current_size; i++) { - Line line = result.get(i); - // 将line与other_line做比较 + Line line = line_segments.get(i); + // 将line与other_line做比较,标记出所有与当前直线重合、或需合并的直线 for (int j = i + 1; j < current_size; j++) { - Line other_line = result.get(j); - // “原始直线”中存在“两端都接近”的情况,将其算为一条线;或其中一端接近、且两直线斜率接近,则合并两直线 - if (line.near(other_line)) { + Line other_line = line_segments.get(j); + // 下列情况算为一条线:“两端都接近”;或其中一端接近、且两直线斜率接近;或有一部分重合 + if (line.isCoincideWith(other_line)) { other_line.should_be_del = true; } } // 去除冗余;合并同斜率 for (int j = i + 1; j < current_size;) { - Line other_line = result.get(j); + Line other_line = line_segments.get(j); if (other_line.should_be_del) { - if (other_line.merge_with != null) { - // 合并同斜率 - mergeKNearLines(line, other_line); + // 将两条直线合并为一条长直线,存在line中 + line_segments.set(i, mergeCoincideLines(line, other_line)); + // 去除冗余 + line_segments.remove(other_line); + current_size = line_segments.size(); + } else { + j++; + } + } + } + // 此时可以还有“斜率接近,但端点不接近”的线段,如果这种线段间距很小,则也合并 + current_size = line_segments.size(); + for (int i = 0; i < current_size; i++) { + Line line = line_segments.get(i); + // 将line与other_line做比较 + for (int j = i + 1; j < current_size; j++) { + Line other_line = line_segments.get(j); + if (Line.Knear(line, other_line)) { + if (Line.ptNearPt(line.pt1, other_line.pt1, false, true, other_line) || Line.ptNearPt(line.pt1, other_line.pt2, false, true, other_line) + || Line.ptNearPt(line.pt2, other_line.pt1, false, true, other_line) || Line.ptNearPt(line.pt2, other_line.pt2, false, true, other_line)) { + other_line.should_be_del = true; } + } + } + // 去除冗余;合并同斜率 + for (int j = i + 1; j < current_size;) { + Line other_line = line_segments.get(j); + if (other_line.should_be_del) { + // 将两条直线合并为一条长直线,存在line中 + line_segments.set(i, mergeCoincideLines(line, other_line)); // 去除冗余 - result.remove(other_line); - current_size = result.size(); + line_segments.remove(other_line); + current_size = line_segments.size(); } else { j++; } } } - for (Line l : result) { - System.out.println("(" + l.pt1.x + ", " + l.pt1.y + ")" + "(" + l.pt2.x + ", " + l.pt2.y + ")"); - Imgproc.line(cls_diagram, l.pt1, l.pt2, new Scalar(55, 55, 55), 1); + /* + * 对所有实线,将可能的“折线”识别并合并。合并为折线的直线,其belonged_polygonal_line会标识其所属折线 + */ + result = mergePolygonalLines(line_segments); + if (detect_solid) { + // 实线识别结果 + for (PolygonalLine pl : result) { + System.out.println("(" + pl.pt1.x + ", " + pl.pt1.y + ")" + "(" + pl.pt2.x + ", " + pl.pt2.y + ")"); + // 在图中抹掉已识别的线,防止干扰后续识别 + for (Line segment : pl.line_list) { + Imgproc.line(cls_diagram, segment.pt1, segment.pt2, new Scalar(255, 255, 255), 5); + } + // 看一眼。看结果相当于把抹掉的线再画出来,会影响后续识别,所以看完就注释掉 + // Imgproc.line(cls_diagram, pl.pt1, pl.pt2, new Scalar(55, 55, 55), 5); + } + Imgcodecs.imwrite(temp_res_path, cls_diagram); + System.out.println("完成对" + cd_path + "中的实线识别,共" + result.size() + "条"); + } else { + // 虚线识别结果 + for (PolygonalLine pl : result) { + System.out.println("(" + pl.pt1.x + ", " + pl.pt1.y + ")" + "(" + pl.pt2.x + ", " + pl.pt2.y + ")"); + // 在图中抹掉已识别的线,防止干扰后续识别 + for (Line segment : pl.line_list) { + Imgproc.line(cls_diagram, segment.pt1, segment.pt2, new Scalar(255, 255, 255), 5); + } + // 看一眼。看结果相当于把抹掉的线再画出来,会影响后续识别,所以看完就注释掉 + Imgproc.line(cls_diagram, pl.pt1, pl.pt2, new Scalar(55, 55, 55), 5); + } + Imgcodecs.imwrite(temp_res_path, cls_diagram); + System.out.println("完成对" + cd_path + "中的虚线识别,共" + result.size() + "条"); } - // Writing the image - Imgcodecs.imwrite(temp_res_path, cls_diagram); - System.out.println("识别实线完毕,共" + result.size() + "条"); return result; } - // 将斜率接近的两条直线合并成“最长”的一条线,并存在line中 - private void mergeKNearLines(Line line, Line other_line) { - // 这TM怎么合并啊?!! + /** + * 将斜率接近的两条直线合并成“最长”的一条线,并存在line中 + */ + private Line mergeCoincideLines(Line line, Line other_line) { + // 两条线的4个端点共可能有4C2条直线,把最长的一条存在line中即可 + Line l1 = new Line(line.pt1, line.pt2); + Line l2 = new Line(line.pt1, other_line.pt1); + Line l3 = new Line(line.pt1, other_line.pt2); + Line l4 = new Line(line.pt2, other_line.pt1); + Line l5 = new Line(line.pt2, other_line.pt2); + Line l6 = new Line(other_line.pt1, other_line.pt2); + List lines = new ArrayList<>(); + lines.add(l1); + lines.add(l2); + lines.add(l3); + lines.add(l4); + lines.add(l5); + lines.add(l6); + return ClassRelationDetector.longestLine(lines); + } + + /** + * 返回一列直线中最长的直线 + */ + private static Line longestLine(List lines) { + lines.sort(new Comparator() { + @Override + public int compare(Line l1, Line l2) { + return Integer.compare((int) l1.length, (int) l2.length); + } + }); + return lines.get(lines.size() - 1); + } + + /** + * 将可能的“折线”识别并合并 + */ + private Set mergePolygonalLines(List lines) { + Set result = new HashSet<>(); + // 从第一条线开始,检查当前线与每条后面的线是否能合并 + for (int i = 0; i < lines.size(); i++) { + Line line = lines.get(i); + if (line.pt1_in_polyline && line.pt2_in_polyline) { + continue; + } + if (line.belonged_poly == null) { + // 将当前直线作为一条新折线的一段 + line.belonged_poly = new PolygonalLine(line); + result.add(line.belonged_poly); + } + // 检查当前线与每条后面的线是否能合并 + for (int j = i + 1; j < lines.size(); j++) { + Line other_line = lines.get(j); + Pair pt1_other_line = Line.ptNearLine(line.pt1, other_line); + Pair pt2_other_line = Line.ptNearLine(line.pt2, other_line); + if (pt1_other_line.getLeft() == true) { + if (pt1_other_line.getRight() == other_line.pt1) { + if (Line.ptNearPt(line.pt1, other_line.pt1, false, false, other_line)) { + // 在折线的线段list中添加other_line + line.belonged_poly.line_list.add(other_line); + // 更新line和other_line的端点状态 + line.pt1_in_polyline = true; + other_line.pt1_in_polyline = true; + // 顺便给other_line也存上折线 + other_line.belonged_poly = line.belonged_poly; + } + + } else { + if (Line.ptNearPt(line.pt1, other_line.pt2, false, false, other_line)) { + // 在折线的线段list中添加other_line + line.belonged_poly.line_list.add(other_line); + // 更新line和other_line的端点状态 + line.pt1_in_polyline = true; + other_line.pt2_in_polyline = true; + // 顺便给other_line也存上折线 + other_line.belonged_poly = line.belonged_poly; + } + } + + } + // 同上,只是换一个端点 + else if (pt2_other_line.getLeft() == true) { + if (pt2_other_line.getRight() == other_line.pt1) { + if (Line.ptNearPt(line.pt2, other_line.pt1, false, false, other_line)) { + // 在折线的线段list中添加other_line + line.belonged_poly.line_list.add(other_line); + // 更新line和other_line的端点状态 + line.pt2_in_polyline = true; + other_line.pt1_in_polyline = true; + // 顺便给other_line也存上折线 + other_line.belonged_poly = line.belonged_poly; + } + + } else { + if (Line.ptNearPt(line.pt2, other_line.pt2, false, false, other_line)) { + // 在折线的线段list中添加other_line + line.belonged_poly.line_list.add(other_line); + // 更新line和other_line的端点状态 + line.pt2_in_polyline = true; + other_line.pt2_in_polyline = true; + // 顺便给other_line也存上折线 + other_line.belonged_poly = line.belonged_poly; + } + } + } + } + } + // 设置每条折线的两端 + for (PolygonalLine pl : result) { + for (Line l : pl.line_list) { + Point pt = l.notInPolyPt(); + if (pt != null) { + // 如果该直线两端都“暂时不在折线中”,说明这是根单独的线。将其两端设置为折线两端即可 + if (pt.x == 23579 && pt.y == 59873572) { + pl.pt1 = l.pt1; + pl.pt2 = l.pt2; + // 无需再检查(也没得可检查,因为pl.line_list中只有这一根线) + break; + } else { + if (pl.pt1 == null) { + pl.pt1 = pt; + } else if (pl.pt2 == null) { + pl.pt2 = pt; + } + } + } + } + } + return result; } /** @@ -103,46 +299,55 @@ public class ClassRelationDetector { * @param classes_in_cd * @return */ - private Mat detectRelationType(Pair> classes_in_cd, double ratio) { + private List detectRelationType(Pair> classes_in_cd, double ratio) { System.out.println("开始识别" + cd_path + "中所有关系符号"); + List result = new ArrayList<>(); Mat cls_diagram = classes_in_cd.getLeft(); - List rela_shapes = new ArrayList<>(); /* * 识别图中所有“轮廓”并存在contours中 */ + // 先腐蚀(让符号像素侵蚀周围空间,得以连接起来)、后膨胀(让点周围的空白区域膨胀,从而去掉孤立的噪点),提高识别准确率 + Imgproc.erode(cls_diagram, cls_diagram, Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(3.5, 3.5)), new Point(-1, -1), 4); + Imgproc.dilate(cls_diagram, cls_diagram, Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(1.7, 1.7)), new Point(-1, -1), 5); List contours = new ArrayList<>(); Imgproc.findContours(classes_in_cd.getLeft(), contours, new Mat(), Imgproc.RETR_LIST, Imgproc.CHAIN_APPROX_SIMPLE); /* * 对每个轮廓contour,检测是否为某种关系符号,并将contour和检测结果存在Rectangle中。所有轮廓的检测结果最终存在rela_shapes中 */ // 根据图片像素计算轮廓面积阈值。如果轮廓面积太小或太大,则直接忽略 - int cd_width = cls_diagram.width(); - int cd_height = cls_diagram.height(); - long cd_area = cd_width * cd_height; - double min_cls_area = cd_area * ratio; - double max_cls_area = cd_area * 0.5; + long cd_area = cls_diagram.width() * cls_diagram.height(); + double min_area = cd_area * ratio; + double max_area = cd_area * 0.5; + List rela_shapes = new ArrayList<>(); for (MatOfPoint contour : contours) { // 如果轮廓面积太小或太大,则直接忽略 double contour_area = Imgproc.contourArea(contour); - if (contour_area < min_cls_area || contour_area > max_cls_area) { + if (contour_area < min_area || contour_area > max_area) { + // 将噪点直接涂白 + Imgproc.fillConvexPoly(classes_in_cd.getLeft(), contour, new Scalar(255, 255, 255)); continue; } // 如果轮廓面积合适,则检测是否为关系符号形状。采用多边形逼近法 MatOfPoint2f curve = new MatOfPoint2f(contour.toArray()); MatOfPoint2f approx_curve = new MatOfPoint2f(); Imgproc.approxPolyDP(curve, approx_curve, 0.01 * Imgproc.arcLength(curve, true), true); - System.out.print(approx_curve.dump()); // 将每个关系符号存在别处。存完后将其从图中抹掉,防止其干扰直线识别 + RelationType relation_type = new RelationType(); + /* + * 目前不可能从approx_curve中找到关系符号形状。只能先确定关系符号位置、大小。或许可以根据关系符号大小,画一个圆。 + * + */ rela_shapes.add(contour); + System.out.println(contour.dump()); // 将关系符号从图中抹掉(涂白)。后面还需对所有边框进行涂白 - Imgproc.fillConvexPoly(classes_in_cd.getLeft(), contour, new Scalar(15, 225, 25)); + Imgproc.fillConvexPoly(classes_in_cd.getLeft(), contour, new Scalar(55, 55, 55)); } // 对所有边框进行涂白 - Imgproc.drawContours(classes_in_cd.getLeft(), rela_shapes, -1, new Scalar(15, 225, 25), 1); + Imgproc.drawContours(classes_in_cd.getLeft(), rela_shapes, -1, new Scalar(55, 55, 55), 5); // 将图片写入result文件 Imgcodecs.imwrite(temp_res_path, classes_in_cd.getLeft()); - System.out.println("识别关系符号完毕,共" + rela_shapes.size() + "个"); - return classes_in_cd.getLeft(); + System.out.println("完成对" + cd_path + "中识别关系符号的识别,共" + result.size() + "个"); + return result; } public Object getResult() { diff --git a/src/main/java/com/hy/java/uct/umlrecog/util/Line.java b/src/main/java/com/hy/java/uct/umlrecog/util/Line.java index 4224daa128b6f6d2a4de2def2156b3efcde95297..cc3e431bd563cb9a498ddd675e596285c3802da4 100644 --- a/src/main/java/com/hy/java/uct/umlrecog/util/Line.java +++ b/src/main/java/com/hy/java/uct/umlrecog/util/Line.java @@ -2,71 +2,196 @@ package com.hy.java.uct.umlrecog.util; import org.opencv.core.Point; +import com.hy.java.utility.common.Pair; + public class Line { public Point pt1; public Point pt2; + Equation equation; + public double length; + + /** + * 只在处理“原始直线”集时使用,即去除冗余或合并时使用。之后不再使用 + */ public boolean should_be_del = false; - public Line merge_with = null; + public Line init_merge_with = null; + // 其实暂时没用 + public Pair near_points = new Pair<>(); + + /** + * 在“合并折线”时使用 + */ + public boolean pt1_in_polyline = false; + public boolean pt2_in_polyline = false; + public PolygonalLine belonged_poly = null; public Line(Point pt1, Point pt2) { this.pt1 = pt1; this.pt2 = pt2; + this.equation = new Equation(this); + this.length = Math.sqrt(Math.pow(pt1.x - pt2.x, 2) + Math.pow(pt1.y - pt2.y, 2)); } /** - * 判断一条直线是否与另一条直线重合 + * 判断一条直线是否与另一条直线重合。如果重合,则记录另一条直线为near_line;如果某一端点与另一条直线重合,则同时记录该端点和另一条直线的端点为near_points + * + * 只在处理“原始直线”集时使用,即去除冗余或合并时使用。之后不再使用 */ - public boolean near(Line line) { + public boolean isCoincideWith(Line line) { boolean result = false; + /* + * 判断该直线端点是否与另一条直线重合。即“是否与另一条直线的某一端点接近” + */ + Pair if_pt1_near_line = Line.ptNearLine(pt1, line); + Pair if_pt2_near_line = Line.ptNearLine(pt2, line); // 如果该直线的每个端点都与另一条直线重合,则该直线与另一条直线重合 - if (ptNearLine(pt1, line) && ptNearLine(pt2, line)) { + if (if_pt1_near_line.getLeft() && if_pt2_near_line.getLeft()) { result = true; + line.init_merge_with = this; } - // 或者该直线某一个端点与另一条直线重合,且两条直线斜率接近,则也认为两条直线重合 - else { - // 先判断该直线是否存在某一个端点与另一条直线重合 - if (ptNearLine(pt1, line) || ptNearLine(pt2, line)) { - // 再判断是否斜率接近 - if (Line.getK(this) == "无穷大" || Line.getK(line) == "无穷大") { - if (Line.getK(this) == Line.getK(line)) { - line.merge_with = this; - result = true; - } - } else if (Math.abs(Double.parseDouble(getK(this)) - Double.parseDouble(getK(line))) <= 0.5) { - line.merge_with = this; - result = true; + // 如果该直线某一个端点与另一条直线重合,且两条直线斜率接近,则也认为两条直线重合 + else if (if_pt1_near_line.getLeft() || if_pt2_near_line.getLeft()) { + // 既然有点重合,则可先检查斜率 + result = Line.Knear(this, line); + // 若result==true,则说明该直线与另一条直线斜率接近,即重合。此时记录待合并直线、以及pt1(或pt2)与另一条直线的相邻端点为一对near_points + if (result == true) { + line.init_merge_with = this; + // 如果pt1另一条直线重合,则记录pt1。如果不重合,则记录pt2 + if (if_pt1_near_line.getLeft()) { + near_points.setLeft(pt1); + near_points.setRight(if_pt1_near_line.getRight()); + } else { + near_points.setLeft(pt2); + near_points.setRight(if_pt2_near_line.getRight()); } } } return result; } - private static String getK(Line line) { - if (line.pt1.x == line.pt2.x) { - return "无穷大"; - } else { - return String.valueOf((line.pt2.y - line.pt1.y) / (line.pt2.x - line.pt1.x)); + /** + * 判断某一点是否与另一条直线重合。即“是否与另一条直线的某一端点接近”、“是否在另一条直线内”。记录接近的直线端点 + */ + public static Pair ptNearLine(Point pt, Line line) { + Pair result = new Pair<>(false, null); + // “是否与另一条直线的某一端点接近” + if (Line.ptNearPt(pt, line.pt1, false, false, line)) { + result.setLeft(true); + result.setRight(line.pt1); + return result; } - } - - private boolean ptNearLine(Point pt, Line line) { - boolean result = false; - // 如果该端点与另一条直线的某一个端点接近,则认为该端点与另一条直线重合 - if (ptNearPt(pt, line.pt1) || ptNearPt(pt, line.pt2)) { - result = true; + if (Line.ptNearPt(pt, line.pt2, false, false, line)) { + result.setLeft(true); + result.setRight(line.pt2); + return result; + } + // “是否在另一条直线内” + if (Line.ptInLine(pt, line)) { + result.setLeft(true); + if (Line.ptNearPt(pt, line.pt1, true, false, line)) { + result.setRight(line.pt1); + } else { + result.setRight(line.pt2); + } + return result; } return result; } - private boolean ptNearPt(Point p1, Point p2) { + /** + * 判断两点是否接近。如果要判断的是两个端点,则check_in_line==false;如果要判断的p1在line中、且p2是line的端点,则check_in_line==true,且threshold=line.length()/2 + * + * @param p1 + * @param p2 + * @param check_in_line + * @param line + */ + public static boolean ptNearPt(Point p1, Point p2, boolean check_in_line, boolean check_segments, Line line) { boolean x_near = false; boolean y_near = false; - if (Math.abs(p1.x - p2.x) <= 3) { + int threshold; + // 如果要判断的p1在line中、且p2是line的端点,则check_in_line==true,且threshold=line.length()/2 + if (check_in_line) { + threshold = (int) (line.length / 2); + } else if (check_segments) { + threshold = 35; + } else { + threshold = 10; + } + // 判断两点是否接近 + if (Math.abs(p1.x - p2.x) < threshold) { x_near = true; } - if (Math.abs(p1.y - p2.y) <= 3) { + if (Math.abs(p1.y - p2.y) < threshold) { y_near = true; } return x_near && y_near; } + + /** + * 判断点是否在直线内。计算点到直线距离,与阈值比较 + */ + private static boolean ptInLine(Point pt, Line line) { + double threshold = 2.0; + double a = Math.abs(line.equation.a * pt.x + line.equation.b * pt.y + line.equation.c); + double b = Math.sqrt(Math.pow(line.equation.a, 2) + Math.pow(line.equation.b, 2)); + return a / b <= threshold; + } + + /** + * 判断两条直线斜率是否接近 + */ + public static boolean Knear(Line l1, Line l2) { + boolean result = false; + // 斜率均为“无穷大”时算作斜率接近 + if (Line.getK(l1) == "无穷大" || Line.getK(l2) == "无穷大") { + if (Line.getK(l1) == Line.getK(l2)) { + result = true; + } + } + // 斜率差不超过阈值时算作斜率接近 + else if (Math.abs(Double.parseDouble(getK(l1)) - Double.parseDouble(getK(l2))) <= 0.5) { + result = true; + } + return result; + } + + /** + * 获得直线斜率 + */ + private static String getK(Line line) { + if (line.pt1.x == line.pt2.x) { + return "无穷大"; + } else { + return String.valueOf((line.pt2.y - line.pt1.y) / (line.pt2.x - line.pt1.x)); + } + } + + /** + * 返回“暂时不在折线中的端点”,用于更新折线端点 + */ + public Point notInPolyPt() { + if (!pt1_in_polyline && pt2_in_polyline) { + return pt1; + } else if (pt1_in_polyline && !pt2_in_polyline) { + return pt2; + } else if (pt1_in_polyline && pt2_in_polyline) { + return null; + } else { + // 如果两端都不在折线中,说明是一根单独的线。返回一个特殊的点用于标识。(59873572:我就不信这么巧) + return new Point(23579, 59873572); + } + } + + private class Equation { + double a; + double b; + double c; + + Equation(Line line) { + a = line.pt2.y - line.pt1.y; + b = line.pt1.x - line.pt2.x; + c = line.pt2.x * line.pt1.y - line.pt1.x * line.pt2.y; + } + } } diff --git a/src/main/java/com/hy/java/uct/umlrecog/util/PolygonalLine.java b/src/main/java/com/hy/java/uct/umlrecog/util/PolygonalLine.java new file mode 100644 index 0000000000000000000000000000000000000000..1b2d5c549b1e5e1000ba97218f99cb06b5351f99 --- /dev/null +++ b/src/main/java/com/hy/java/uct/umlrecog/util/PolygonalLine.java @@ -0,0 +1,20 @@ +package com.hy.java.uct.umlrecog.util; + +import java.util.ArrayList; +import java.util.List; + +import org.opencv.core.Point; + +/** + * “将两条线合并为一根折线”时的结果。包括该折线的“两端”、“线段” + */ +public class PolygonalLine { + + public Point pt1; + public Point pt2; + public List line_list = new ArrayList<>(); + + public PolygonalLine(Line line) { + line_list.add(line); + } +} diff --git a/src/main/java/com/hy/java/uct/umlrecog/util/RelationType.java b/src/main/java/com/hy/java/uct/umlrecog/util/RelationType.java new file mode 100644 index 0000000000000000000000000000000000000000..f853c132317b238aa3cca9b3d8e59670cab6245c --- /dev/null +++ b/src/main/java/com/hy/java/uct/umlrecog/util/RelationType.java @@ -0,0 +1,5 @@ +package com.hy.java.uct.umlrecog.util; + +public class RelationType { + +} diff --git "a/src/main/resources/temp3-UML\346\250\241\345\236\213\346\225\2601~9\347\232\204\351\241\271\347\233\256\344\270\215\344\270\200\350\207\264\346\200\247&\350\276\205\345\212\251\346\226\207\346\241\243\351\223\276\346\216\245.xlsx" "b/src/main/resources/UML\346\250\241\345\236\213\346\225\2601~9\347\232\204\351\241\271\347\233\256\344\270\215\344\270\200\350\207\264\346\200\247&\350\276\205\345\212\251\346\226\207\346\241\243\351\223\276\346\216\245.xlsx" similarity index 100% rename from "src/main/resources/temp3-UML\346\250\241\345\236\213\346\225\2601~9\347\232\204\351\241\271\347\233\256\344\270\215\344\270\200\350\207\264\346\200\247&\350\276\205\345\212\251\346\226\207\346\241\243\351\223\276\346\216\245.xlsx" rename to "src/main/resources/UML\346\250\241\345\236\213\346\225\2601~9\347\232\204\351\241\271\347\233\256\344\270\215\344\270\200\350\207\264\346\200\247&\350\276\205\345\212\251\346\226\207\346\241\243\351\223\276\346\216\245.xlsx" diff --git a/src/main/resources/cd/temp result.png b/src/main/resources/cd/temp result.png index 16a319315b960adda0f266dd6af8642488dab69d..257dd37729af022763690fb2b4bbc111e1ca4820 100644 Binary files a/src/main/resources/cd/temp result.png and b/src/main/resources/cd/temp result.png differ diff --git "a/src/main/resources/temp3-UML\346\250\241\345\236\213\346\225\2601~9\347\232\204\351\241\271\347\233\256\344\270\215\344\270\200\350\207\264\346\200\247&\350\276\205\345\212\251\346\226\207\346\241\243\351\223\276\346\216\245-backup.xlsx" "b/src/main/resources/temp3-UML\346\250\241\345\236\213\346\225\2601~9\347\232\204\351\241\271\347\233\256\344\270\215\344\270\200\350\207\264\346\200\247&\350\276\205\345\212\251\346\226\207\346\241\243\351\223\276\346\216\245-backup.xlsx" deleted file mode 100644 index bab6ac196c2384aaa8dfbbbf5c1f041483917d44..0000000000000000000000000000000000000000 Binary files "a/src/main/resources/temp3-UML\346\250\241\345\236\213\346\225\2601~9\347\232\204\351\241\271\347\233\256\344\270\215\344\270\200\350\207\264\346\200\247&\350\276\205\345\212\251\346\226\207\346\241\243\351\223\276\346\216\245-backup.xlsx" and /dev/null differ