简体   繁体   English

使用OpenCV python从手绘逻辑门图生成布尔表达式

[英]Generating the Boolean Expression from a hand drawn logic gates diagram using OpenCV python

Input hand drawn logic gate diagram 输入手绘逻辑门图 在此输入图像描述

I used YOLO to train and identify 7 different logic gates with the labels (letters). 我使用YOLO训练和识别7个不同的逻辑门和标签(字母)。 Detecting logic gates and labels in input image. 检测输入图像中的逻辑门和标签。 在此输入图像描述

Here I got an array list (boxes) which includes each rectangle. 在这里,我得到了一个包含每个矩形的数组列表(框)。 Each list consists following details of each rectangle in order, • label of the rectangle • x,y coordinate of the top-left corner of the rectangle • x,y coordinate of the right-bottom corner of the rectangle 每个列表按顺序包含每个矩形的详细信息,•矩形的标签•矩形左上角的x,y坐标•x,矩形右下角的y坐标

Array of rectangle boxes. 矩形框数组。

boxes = [['AND', (614, 98), (1146, 429)], ['NOT', (525, 1765), (1007, 1983)], ['NAND', (762, 1188), (1209, 1528)], ['NOR', (1323, 272), (1884, 682)], ['OR', (575, 599), (1225, 985)], ['XOR', (1393, 1368), (2177, 1842)], ['XNOR', (2136, 859), (2762, 1231)], ['A', (34, 50), (321, 224)], ['B', (12, 305), (344, 487)], ['C', (3, 581), (391, 779)], ['D', (0, 828), (400, 1060)], ['E', (0, 1143), (354, 1351)], ['F', (0, 1418), (313, 1615)], ['G', (0, 1753), (301, 1985)], ['OUTPUT', (2810, 940), (3069, 1184)]] boxes = [['AND',(614,98),(1146,429)],['NOT',(525,1765),(1007,1983)],['NAND',(762,1188), (1209,1528)],['NOR',(1323,272),(1884,682)],['OR',(575,599),(1225,985)],['XOR',(1393) ,1368),(2177,1842)],['XNOR',(2136,859),(2762,1231)],['A',(34,50),(321,224)],['B ',(12,305),(344,487)],['C',(3,581),(391,779)],['D',(0,828),(400,1060)] ,['E',(0,1143),(354,1351)],['F',(0,1418),(313,1615)],['G',(0,1753),(301 ,1985)],['OUTPUT',(2810,940),(3069,1184)]]

After that I used Probabilistic Hough Line Transform to detect the lines in-between labels and logic gates. 之后,我使用概率Hough线变换来检测标签和逻辑门之间的线。 For this I referred this link [ How to merge lines after HoughLinesP? 为此,我引用了这个链接[ 如何在HoughLinesP之后合并线条? . By using this link, I reduced to minimum number of lines and finally get only 35 lines. 通过使用此链接,我减少到最小行数,最后只有35行。 Detecting 35 lines in green color 检测35条绿色线条

在此输入图像描述

Then I classify those 35 lines and grouped the lines which are close to each other. 然后我将这35行分类并将彼此接近的行分组。 Finally, I got 14 lines. 最后,我得到了14行。 Final 14 lines image. 最终14行图像。

在此输入图像描述

The 14 lines array. 14行阵列。

final_line_points = [[[(87, 1864), (625, 1869)]], [[(623, 1815), (1354, 1855)], [(1343, 1660), (1770, 1655)], [(1348, 1656), (1348, 1869)]], [[(102, 971), (531, 945)], [(518, 835), (892, 825)], [(521, 830), (526, 949)]], [[(105, 1260), (494, 1254)], [(487, 1351), (891, 1340)], [(489, 1252), (491, 1356)]], [[(107, 1533), (526, 1510)], [(516, 1432), (892, 1410)], [(521, 1433), (520, 1514)]], [[(111, 432), (519, 396)],[(499, 313), (820, 299)], [(503, 310), (506, 402)]], [[(123, 157), (496, 150)], [(493, 144), (498, 247)], [(495, 242), (815, 234)]], [[(170, 692), (509, 687)], [(504, 771), (888, 764)], [(505, 685), (508, 775)]],[[(936, 264), (1229, 261)], [(1227, 257), (1240, 485)], [(1234, 481), (1535, 458)]], [[(985, 1361), (1343, 1347)], [(1341, 1344), (1348, 1578)], [(1345, 1575), (1773, 1571)]], [[(991, 796), (1264, 778)],[(1240, 535), (1544, 520)], [(1247, 532), (1254,783)]],[[(1546, 582), (2156, 489)], [(2154, 488), (2148, 1021)]], [[(2153, 1087), (2164, 1581)]], [[(2444, 1139), (301 final_line_points = [[[(87,1864),(625,1869)]],[[(623,1815),(1354,1855)],[(1343,1660),(1770,1655)],[( 1348,1656),(1348,1869)]],[[(102,971),(531,945)],[(518,835),(892,825)],[(521,830),( 526,949)],[[(105,1260),(494,1254)],[(487,1351),(891,1340)],[(489,1252),(491,1356)]] ,[[(107,1533),(526,1510)],[(516,1432),(892,1410)],[(521,1433),(520,1514)]],[[(111, 432),(519,396)],[(499,313),(820,299)],[(503,310),(506,402)]],[[(123,157),(496, 150)],[(493,144),(498,247)],[(495,242),(815,234)]],[[(170,692),(509,687)],[( 504,771),(888,764)],[(505,685),(508,775)]],[[(936,264),(1229,261)],[(1227,257),( 1240,485)],[(1234,481),(1535,458)]],[[(985,1361),(1343,1347)],[(1341,1344),(1348,1578)], [(1345,1575),(1773,1571)]],[[(991,796),(1264,778)],[(1240,535),(1544,520)],[(1247,532) ,([1254,783]],[[(1546,582),(2156,489)],[(2154,488),(2148,1021)]],[[(2153,1087),(2164, 1581)],[[(2444,1139),(301 7, 1055)]]] 7,1055)]]]

So how can I get the following output by using above 2 arrays (boxes, final_line_points) ? 那么如何通过使用上面的2个数组(box,final_line_points)获得以下输出?

在此输入图像描述

Your project seems cool so I spend some times finding a solution. 您的项目看起来很酷,所以我花了一些时间找到解决方案。 I came up the code below. 我提出了下面的代码。 The result of the code is: 代码的结果是:

OUTPUT[XNOR[NOR[AND[B, A], OR[D, C]], XOR[NOT[G], NAND[E, F]]]]

I made the assumptions that if an element is leftmost than another then it is a previous block. 我假设如果一个元素最左边,那么它就是前一个块。 Also I assumed that in your set of lines the first one is correct... This allow me to simplify your 14 set of several lines into a set of 14 lines. 另外我假设在你的行集中第一行是正确的...这允许我将14组几行简化为一组14行。

boxes = [['AND', (614, 98), (1146, 429)], ['NOT', (525, 1765), (1007, 1983)], ['NAND', (762, 1188), (1209, 1528)],
         ['NOR', (1323, 272), (1884, 682)], ['OR', (575, 599), (1225, 985)], ['XOR', (1393, 1368), (2177, 1842)],
         ['XNOR', (2136, 859), (2762, 1231)], ['A', (34, 50), (321, 224)], ['B', (12, 305), (344, 487)],
         ['C', (3, 581), (391, 779)], ['D', (0, 828), (400, 1060)], ['E', (0, 1143), (354, 1351)],
         ['F', (0, 1418), (313, 1615)], ['G', (0, 1753), (301, 1985)], ['OUTPUT', (2810, 940), (3069, 1184)]]
final_line_points = [[[(87, 1864), (625, 1869)]],
                     [[(623, 1815), (1354, 1855)], [(1343, 1660), (1770, 1655)], [(1348, 1656), (1348, 1869)]],
                     [[(102, 971), (531, 945)], [(518, 835), (892, 825)], [(521, 830), (526, 949)]],
                     [[(105, 1260), (494, 1254)], [(487, 1351), (891, 1340)], [(489, 1252), (491, 1356)]],
                     [[(107, 1533), (526, 1510)], [(516, 1432), (892, 1410)], [(521, 1433), (520, 1514)]],
                     [[(111, 432), (519, 396)], [(499, 313), (820, 299)], [(503, 310), (506, 402)]],
                     [[(123, 157), (496, 150)], [(493, 144), (498, 247)], [(495, 242), (815, 234)]],
                     [[(170, 692), (509, 687)], [(504, 771), (888, 764)], [(505, 685), (508, 775)]],
                     [[(936, 264), (1229, 261)], [(1227, 257), (1240, 485)], [(1234, 481), (1535, 458)]],
                     [[(985, 1361), (1343, 1347)], [(1341, 1344), (1348, 1578)], [(1345, 1575), (1773, 1571)]],
                     [[(991, 796), (1264, 778)], [(1240, 535), (1544, 520)], [(1247, 532), (1254, 783)]],
                     [[(1546, 582), (2156, 489)], [(2154, 488), (2148, 1021)]], [[(2153, 1087), (2164, 1581)]],
                     [[(2444, 1139), (3017, 1055)]]]
def dist(pt1, pt2):
    return (pt1[0] - pt2[0]) ** 2 + (pt1[1] - pt2[1]) ** 2


def seg_dist(seg1, seg2):
    distances = [dist(seg1[i], seg2[j]) for i in range(2) for j in range(2)]
    return min(enumerate(distances), key=lambda x: x[1])


sorted_lines = []
for lines in final_line_points:
    connected_part = lines[0]
    non_connected = lines[1:]
    while non_connected:
        mat_dist = [seg_dist(connected_part, non_connected[i])[1] for i in range(len(non_connected))]
        i, min_dist = min(enumerate(mat_dist), key=lambda x: x[1])
        seg_to_connect = non_connected.pop(i)
        idx, real_dist = seg_dist(connected_part, seg_to_connect)
        if idx == 0:
            print("error: this case is not handled")
            exit()
        elif idx == 1:
            print("error: this case is not handled")
            exit()
        elif idx == 2:
            connected_part[1] = seg_to_connect[1]
        elif idx == 3:
            connected_part[1] = seg_to_connect[0]
    sorted_lines.append(connected_part)


class node():
    def __init__(self, name, box) -> None:
        super().__init__()
        self.name = name
        self.box = [(min(box[0][0], box[1][0]), min(box[0][1], box[1][1])),
                    (max(box[0][0], box[1][0]), max(box[0][1], box[1][1]))]
        self.args = []
        self.outputs = []

    def __contains__(self, item):
        return self.box[0][0] <= item[0] <= self.box[1][0] and self.box[0][1] <= item[1] <= self.box[1][1]

    def __str__(self) -> str:
        if self.args:
            return f"{self.name}{self.args}"
        else:
            return f"{self.name}"

    def __repr__(self) -> str:
        return self.__str__()

    def center(self):
        return (self.box[0][0] + self.box[1][0]) / 2, (self.box[0][1] + self.box[1][1]) / 2


nodes = [node(box[0], box[1:]) for box in boxes]

for line in sorted_lines:
    start_point = line[0]
    end_point = line[1]
    try:
        gate1 = next(node for node in nodes if start_point in node)
        gate2 = next(node for node in nodes if end_point in node)
        if gate1.center() < gate2.center():
            source_gate = gate1
            dest_gate = gate2
        else:
            source_gate = gate2
            dest_gate = gate1
        source_gate.outputs.append(dest_gate)
        dest_gate.args.append(source_gate)
    except StopIteration:
        print(f"{start_point} or {end_point} not in any of the boxes")

print(next(node for node in nodes if node.name == "OUTPUT"))

I can explain more another day if needed or you can start from here. 如果需要,我可以解释另一天,或者你可以从这里开始。 Anyway have fun with your project. 无论如何,你的项目很有趣。

EDIT: 编辑:

My goal was to build a graph where the nodes are the boxes and the edges are the lines. 我的目标是构建一个图形,其中节点是框,边是线。 The issue was that those lines are only defined as a set of closed lines. 问题是这些线只被定义为一组封闭线。 Also they were in disorder but the first. 他们也是无序但第一个。 So the first step is to turn each set of lines into a straight line. 所以第一步是将每组线转成一条直线。 That is what I called sorted_lines . 这就是我所说的sorted_lines

To build this list I used the following logic: 为了构建这个列表,我使用了以下逻辑:

  1. For each set of lines split it into a connected part and a non connected part 对于每组线,将其分成连接部分和非连接部分
  2. The initialization of the connected part is the first line of the set. 连接部分的初始化是该组的第一行。 As I said, here I assumed that the first line is correct. 正如我所说,在这里我假设第一行是正确的。 Try to improve this because this assumption may be wrong in other cases. 尝试改进这一点,因为在其他情况下这种假设可能是错误的。
  3. While there is non connected line do the following: 虽然有非连接线,但执行以下操作:

    • Find the segment which has the closest end to the connected part 找到最接近连接部分的段
    • Remove it from the non connected part 将其从未连接的部件中取出
    • Check which end of the segment is the closest from the connected part 检查段的哪一端距连接部件最近
    • If it is the first point of the segment then the last point of the connected part becomes the second point of the segment else the first point becomes the last point. 如果它是段的第一个点,则连接部分的最后一个点成为段的第二个点,否则第一个点成为最后一个点。

In the checking the non handled cases are when the segment to connect is closed to the first point of the connected part instead of the last point. 在检查中,未处理的情况是当要连接的段关闭到连接部分的第一点而不是最后一点时。 This is not handled because I assumed that the first line is correct. 这是没有处理的,因为我认为第一行是正确的。 Once again, this can be improved. 再次,这可以改善。

Now that you have your lines sorted, for each one of them find the nodes containing each end. 现在您已经对线进行了排序,因为每个线都找到包含每个端点的节点。 Select the lestmost as the source gate and the rightmost as the destination gate. 选择最左边作为源门,最右边作为目标门。 Since edges are not oriented I had to assumed an orientation. 由于边缘没有定向,我不得不假定一个方向。 Update the inputs of the destination gate and the outputs of the source gate. 更新目标门的输入和源门的输出。

Finally print the last gate of your graph. 最后打印图表的最后一个门。

First you should define how to draw/output each gate, as a formula. 首先,您应该定义如何绘制/输出每个门,作为公式。 For instance, XNOR is a line as long as the text below it, with x arguments and a encircled + in between. 例如,XNOR是一条线,只要它下面的文本,x参数和中间的环绕+。

Now you can walk the schema in reverse. 现在,您可以反向遍历架构。 Start with the right most box, which is your output Q. Look for the line that ends (= rightmost point of line / highest x of tuple) in the output and select the box in which that line begins. 从最右边的框开始,这是您的输出Q.在输出中查找结束的行(=行的最右边的点/元组的最高x)并选择该行开始的框。 That is an XNOR. 这是一个XNOR。 As described above a XNOR is defined as having arguments/inputs. 如上所述,XNOR被定义为具有自变量/输入。 So look for the lines that have their rightmost point in the XNOR-box. 因此,在XNOR框中寻找最右边的线。 Next selected the boxes where those lines start. 接下来选择这些行开始的框。 That is first a NOR then an XOR. 这首先是NOR然后是异或。 Repeating the cycle, the arguments for the NOR are and AND gate and an OR gate. 重复该循环,NOR的参数是AND门和OR门。 Repeating again, the arguments for the AND gate are inputs A and B. These have no inputs / no lines ending in them, therefor these are the actual values to be printed. 再次重复,AND门的参数是输入A和B.它们没有输入/没有以它们结尾的行,因此这些是要打印的实际值。

To visualize: 想象:
(this is not code, but I can't submit the answer otherwise) (这不是代码,但我无法提交答案)

Q = XNOR(A,B)  
A = NOR(C,D)  
So, Q = XNOR(NOR(C,D),B)  
C = AND(E,F)  
So Q = XNOR(NOR(AND(E,F),D),B)  
E = input A  
F = input B  
So Q = XNOR(NOR(AND("A","B"),D),B)  
D = OR(G,H)  
So Q = XNOR(NOR(AND("A","B"),OR(G,H)),B)  
ect.

To account for the size of the formula your could make the size dependent on the 'layer'-number. 考虑到公式的大小,您可以使大小取决于“图层”编号。 Creating classes for the different types of gates will make the entire process far easier. 为不同类型的门创建类将使整个过程变得更加容易。

This pseudocode shows above concept: 这个伪代码显示了以上概念:

# to draw an XNOR gate:
def draw():
    arguments = get_start_box_of_lines_ending_in_this_box(this.boundingBox, lines)
    for gate in arguments:
        gate.draw()
        # TODO: draw encircled + (except after the last argument)

    # TODO: draw a line over the output generated by the code above

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM