当前位置: 首页 > 知识库问答 >
问题:

如何消除数独方块中的凸性缺陷?

勾炜
2023-03-14

我在做一个有趣的项目:使用OpenCV(如谷歌护目镜等)从输入图像中求解数独。我已经完成了任务,但最后我发现了一个小问题,我来到这里。

我使用OpenCV 2.3.1的Python API进行编程。

以下是我所做的:

>

  • 读取图像
  • 找到轮廓
  • 选择面积最大的一个,(也有点等同于正方形)。
  • 找到角点。

    e、 g.如下所示:

    (请注意,这里的绿线与数独的真实边界正确重合,因此可以正确扭曲数独。请查看下一张图片)

    将图像扭曲为完美的正方形

    eg图像:

    执行OCR(为此,我使用OpenCV Python中简单数字识别OCR中给出的方法)

    该方法效果良好。

    问题:

    看看这张图片。

    在此图像上执行步骤4会得到以下结果:

    绘制的红线是原始轮廓,它是数独边界的真实轮廓。

    绘制的绿线是近似轮廓,这将是扭曲图像的轮廓。

    当然,绿色线和红色线在数独的顶部边缘是有区别的。所以在扭曲的时候,我没有得到数独的原始边界。

    我的问题:

    如何将图像扭曲到数独的正确边界上,即红线,或者如何消除红线和绿线之间的差异?在OpenCV中有什么方法可以做到这一点吗?

  • 共有3个答案

    司马英才
    2023-03-14

    您可以尝试使用某种基于栅格的任意扭曲建模。既然数独已经是一个网格,这应该不会太难。

    因此,您可以尝试检测每个3x3次区域的边界,然后单独扭曲每个区域。如果检测成功,它将为您提供更好的近似值。

    骆嘉石
    2023-03-14

    Nikie的回答解决了我的问题,但他的答案是在数学中。所以我想我应该在这里给出它的OpenCV适配。但是实现后我可以看到OpenCV代码比nikie的数学代码大得多。而且,我在OpenCV中找不到nikie完成的插值方法(虽然可以使用spse y完成,但我会在时间到来时告诉它。)

    1、图像预处理(关闭操作)

    import cv2
    import numpy as np
    
    img = cv2.imread('dave.jpg')
    img = cv2.GaussianBlur(img,(5,5),0)
    gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
    mask = np.zeros((gray.shape),np.uint8)
    kernel1 = cv2.getStructuringElement(cv2.MORPH_ELLIPSE,(11,11))
    
    close = cv2.morphologyEx(gray,cv2.MORPH_CLOSE,kernel1)
    div = np.float32(gray)/(close)
    res = np.uint8(cv2.normalize(div,div,0,255,cv2.NORM_MINMAX))
    res2 = cv2.cvtColor(res,cv2.COLOR_GRAY2BGR)
    

    结果:

    2、查找数独方块并创建掩码图像

    thresh = cv2.adaptiveThreshold(res,255,0,1,19,2)
    contour,hier = cv2.findContours(thresh,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
    
    max_area = 0
    best_cnt = None
    for cnt in contour:
        area = cv2.contourArea(cnt)
        if area > 1000:
            if area > max_area:
                max_area = area
                best_cnt = cnt
    
    cv2.drawContours(mask,[best_cnt],0,255,-1)
    cv2.drawContours(mask,[best_cnt],0,0,2)
    
    res = cv2.bitwise_and(res,mask)
    

    结果:

    3.寻找垂直线

    kernelx = cv2.getStructuringElement(cv2.MORPH_RECT,(2,10))
    
    dx = cv2.Sobel(res,cv2.CV_16S,1,0)
    dx = cv2.convertScaleAbs(dx)
    cv2.normalize(dx,dx,0,255,cv2.NORM_MINMAX)
    ret,close = cv2.threshold(dx,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
    close = cv2.morphologyEx(close,cv2.MORPH_DILATE,kernelx,iterations = 1)
    
    contour, hier = cv2.findContours(close,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
    for cnt in contour:
        x,y,w,h = cv2.boundingRect(cnt)
        if h/w > 5:
            cv2.drawContours(close,[cnt],0,255,-1)
        else:
            cv2.drawContours(close,[cnt],0,0,-1)
    close = cv2.morphologyEx(close,cv2.MORPH_CLOSE,None,iterations = 2)
    closex = close.copy()
    

    结果:

    4.寻找水平线

    kernely = cv2.getStructuringElement(cv2.MORPH_RECT,(10,2))
    dy = cv2.Sobel(res,cv2.CV_16S,0,2)
    dy = cv2.convertScaleAbs(dy)
    cv2.normalize(dy,dy,0,255,cv2.NORM_MINMAX)
    ret,close = cv2.threshold(dy,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
    close = cv2.morphologyEx(close,cv2.MORPH_DILATE,kernely)
    
    contour, hier = cv2.findContours(close,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
    for cnt in contour:
        x,y,w,h = cv2.boundingRect(cnt)
        if w/h > 5:
            cv2.drawContours(close,[cnt],0,255,-1)
        else:
            cv2.drawContours(close,[cnt],0,0,-1)
    
    close = cv2.morphologyEx(close,cv2.MORPH_DILATE,None,iterations = 2)
    closey = close.copy()
    

    结果:

    当然,这个就不怎么样了。

    5.寻找网格点

    res = cv2.bitwise_and(closex,closey)
    

    结果:

    6.纠正缺陷

    在这里,nikie做了一些插值,关于这一点我不太了解。我找不到这个OpenCV的任何对应函数。(可能在那里,我不知道)。

    看看这个软件,它解释了如何使用我不想使用的SciPy:OpenCV中的图像转换

    所以,这里我取了每个子正方形的4个角,并对每个子正方形应用了扭曲透视。

    为此,首先我们找到质心。

    contour, hier = cv2.findContours(res,cv2.RETR_LIST,cv2.CHAIN_APPROX_SIMPLE)
    centroids = []
    for cnt in contour:
        mom = cv2.moments(cnt)
        (x,y) = int(mom['m10']/mom['m00']), int(mom['m01']/mom['m00'])
        cv2.circle(img,(x,y),4,(0,255,0),-1)
        centroids.append((x,y))
    

    但是生成的质心不会被排序。查看下图以查看它们的顺序:

    所以我们从左到右,从上到下对它们进行排序。

    centroids = np.array(centroids,dtype = np.float32)
    c = centroids.reshape((100,2))
    c2 = c[np.argsort(c[:,1])]
    
    b = np.vstack([c2[i*10:(i+1)*10][np.argsort(c2[i*10:(i+1)*10,0])] for i in xrange(10)])
    bm = b.reshape((10,10,2))
    

    现在看到下面他们的顺序:

    最后,我们应用转换并创建一个大小为450x450的新图像。

    output = np.zeros((450,450,3),np.uint8)
    for i,j in enumerate(b):
        ri = i/10
        ci = i%10
        if ci != 9 and ri!=9:
            src = bm[ri:ri+2, ci:ci+2 , :].reshape((4,2))
            dst = np.array( [ [ci*50,ri*50],[(ci+1)*50-1,ri*50],[ci*50,(ri+1)*50-1],[(ci+1)*50-1,(ri+1)*50-1] ], np.float32)
            retval = cv2.getPerspectiveTransform(src,dst)
            warp = cv2.warpPerspective(res2,retval,(450,450))
            output[ri*50:(ri+1)*50-1 , ci*50:(ci+1)*50-1] = warp[ri*50:(ri+1)*50-1 , ci*50:(ci+1)*50-1].copy()
    

    结果:

    结果与nikie的几乎相同,但代码长度很大。可能,那里有更好的方法,但在那之前,这可以正常工作。

    问候方舟。

    房泉
    2023-03-14

    我有一个可行的解决方案,但您必须自己将其转换为OpenCV。它是用Mathematica写的。

    第一步是通过将每个像素与关闭操作的结果分割来调整图像中的亮度:

    src = ColorConvert[Import["http://davemark.com/images/sudoku.jpg"], "Grayscale"];
    white = Closing[src, DiskMatrix[5]];
    srcAdjusted = Image[ImageData[src]/ImageData[white]]
    

    下一步是找到数独区域,这样我就可以忽略(屏蔽)背景。为此,我使用连接组件分析,并选择凸面面积最大的组件:

    components = 
      ComponentMeasurements[
        ColorNegate@Binarize[srcAdjusted], {"ConvexArea", "Mask"}][[All, 
        2]];
    largestComponent = Image[SortBy[components, First][[-1, 2]]]
    

    通过填充此图像,我得到了数独网格的掩码:

    mask = FillingTransform[largestComponent]
    

    现在,我可以使用二阶导数过滤器在两个单独的图像中查找垂直线和水平线:

    lY = ImageMultiply[MorphologicalBinarize[GaussianFilter[srcAdjusted, 3, {2, 0}], {0.02, 0.05}], mask];
    lX = ImageMultiply[MorphologicalBinarize[GaussianFilter[srcAdjusted, 3, {0, 2}], {0.02, 0.05}], mask];
    

    我再次使用连接组件分析从这些图像中提取网格线。网格线比数字长得多,因此我可以使用卡尺长度仅选择网格线连接的组件。按位置排序,我得到图像中每个垂直/水平网格线的2x10遮罩图像:

    verticalGridLineMasks = 
      SortBy[ComponentMeasurements[
          lX, {"CaliperLength", "Centroid", "Mask"}, # > 100 &][[All, 
          2]], #[[2, 1]] &][[All, 3]];
    horizontalGridLineMasks = 
      SortBy[ComponentMeasurements[
          lY, {"CaliperLength", "Centroid", "Mask"}, # > 100 &][[All, 
          2]], #[[2, 2]] &][[All, 3]];
    

    接下来我取每对垂直/水平网格线,将它们展开,逐像素计算交点,并计算结果的中心,这些点就是网格线交点:

    centerOfGravity[l_] := 
     ComponentMeasurements[Image[l], "Centroid"][[1, 2]]
    gridCenters = 
      Table[centerOfGravity[
        ImageData[Dilation[Image[h], DiskMatrix[2]]]*
         ImageData[Dilation[Image[v], DiskMatrix[2]]]], {h, 
        horizontalGridLineMasks}, {v, verticalGridLineMasks}];
    

    最后一步是定义两个插值函数,用于通过这些点进行X/Y映射,并使用这些函数对图像进行变换:

    fnX = ListInterpolation[gridCenters[[All, All, 1]]];
    fnY = ListInterpolation[gridCenters[[All, All, 2]]];
    transformed = 
     ImageTransformation[
      srcAdjusted, {fnX @@ Reverse[#], fnY @@ Reverse[#]} &, {9*50, 9*50},
       PlotRange -> {{1, 10}, {1, 10}}, DataRange -> Full]
    

    所有的操作都是基本的图像处理功能,所以这在OpenCV中也应该是可能的。基于样条的图像转换可能更难,但我认为您并不真正需要它。可能使用您现在在每个单元格上使用的透视转换会产生足够好的结果。

     类似资料:
    • 问题内容: 我当时在做一个有趣的项目:使用OpenCV(如Google护目镜等)从输入图像中解决数独。我已经完成了任务,但是最后我发现了一个我来到这里的小问题。 我使用OpenCV 2.3.1的Python API进行了编程。 以下是我所做的: 读取图像 找到轮廓 选择一个具有最大面积的((也有些等同于正方形))。 找到拐角点。 例如下面给出: (请注意,绿线正确地与数独的真实边界重合,因此数独可

    • 从“反应”中导入{useEffects, useState}; 常量使用斯皮斯 = () = } 导出默认使用小工具;

    • 两天前,我得到了一个我试图用Python 3解决的数独问题。我被告知确实存在一个解决方案,但我不确定是否存在多个解决方案。 问题如下:一个9x9的数独网格完全是空的。然而,它确实包含彩色框,在这些框中,数字的总和必须是一个平方数。除此之外,通常的数独规则也适用。 这里的问题不是解决一个数独谜题,而是生成一个可行的谜题,满足彩色框的规则。 我的策略 使用numpy数组,我将网格划分为81个索引,这些

    • 本文向大家介绍如何在测试中消除不确定性?相关面试题,主要包含被问及如何在测试中消除不确定性?时的应答技巧和注意事项,需要的朋友参考一下 不确定性测试(NDT)基本上是不可靠的测试。因此,它们有时可能会通过,显然有时也可能会失败。当它们失败时,会重新运行以通过。 从测试中排除不确定性的一些方法如下: 隔离 异步 远程服务 分离 时间 资源泄漏

    • 本文向大家介绍DBMS中的数据独立性,包括了DBMS中的数据独立性的使用技巧和注意事项,需要的朋友参考一下 数据库包含大量数据。并非所有数据都是用户数据,某些数据可能是元数据等。因此,数据库具有数据独立性非常重要。 数据独立性基本上意味着,如果在某个级别上更改数据,则不会影响更高级别上的数据视图,因此,更高级别上的数据应与更低级别上的数据修改无关。  有两种类型的数据独立性。这些是- 逻辑数据独立

    • 问题内容: 我正在尝试使用A 查询Google地方。为此,在发给我的文字更改电话中,我正在向Google地方提出要求。问题是我宁愿将此呼叫退信以每250 ms仅请求一次,以避免不必要的网络流量。我不想自己编写此功能,但是如果需要的话,我会写的。 我发现:https : //gist.github.com/ShamylZakariya/54ee03228d955f458389,但我不太确定如何使用它