【问题标题】:How to find corners on a Image using OpenCv如何使用 OpenCv 在图像上找到角点
【发布时间】:2011-11-07 23:34:35
【问题描述】:

我正在尝试找到图像上的角,我不需要轮廓,只需要 4 个角。我将使用 4 个角来改变视角。

我正在使用 Opencv,但我需要知道找到角点的步骤以及我将使用什么函数。

我的图像会是这样的:(没有红点,我会在之后画点)

编辑:

按照建议的步骤,我编写了代码:(注意:我不是使用纯 OpenCv,我使用的是 javaCV,但逻辑是一样的)。

// Load two images and allocate other structures (I´m using other image)
    IplImage colored = cvLoadImage(
            "res/scanteste.jpg",
            CV_LOAD_IMAGE_UNCHANGED);

    IplImage gray = cvCreateImage(cvGetSize(colored), IPL_DEPTH_8U, 1);
    IplImage smooth = cvCreateImage(cvGetSize(colored), IPL_DEPTH_8U, 1);

    //Step 1 - Convert from RGB to grayscale (cvCvtColor)
    cvCvtColor(colored, gray, CV_RGB2GRAY);

    //2 Smooth (cvSmooth)
    cvSmooth( gray, smooth, CV_BLUR, 9, 9, 2, 2); 

    //3 - cvThreshold  - What values?
    cvThreshold(gray,gray, 155, 255, CV_THRESH_BINARY);

    //4 - Detect edges (cvCanny) -What values?
    int N = 7;
    int aperature_size = N;
    double lowThresh = 20;
    double highThresh = 40;     
    cvCanny( gray, gray, lowThresh*N*N, highThresh*N*N, aperature_size );   

    //5 - Find contours (cvFindContours)
    int total = 0;
    CvSeq contour2 = new CvSeq(null);
    CvMemStorage storage2 = cvCreateMemStorage(0);
    CvMemStorage storageHull = cvCreateMemStorage(0);
    total = cvFindContours(gray, storage2, contour2, Loader.sizeof(CvContour.class), CV_RETR_CCOMP, CV_CHAIN_APPROX_NONE);
    if(total > 1){
          while (contour2 != null && !contour2.isNull()) {
              if (contour2.elem_size() > 0) {
                //6 - Approximate contours with linear features (cvApproxPoly)
                  CvSeq points = cvApproxPoly(contour2,Loader.sizeof(CvContour.class), storage2, CV_POLY_APPROX_DP,cvContourPerimeter(contour2)*0.005, 0);
                  cvDrawContours(gray, points,CvScalar.BLUE, CvScalar.BLUE, -1, 1, CV_AA);

              }
              contour2 = contour2.h_next();
          }

    } 

所以,我想找到角点,但我不知道如何使用角点函数,如 cvCornerHarris 等。

【问题讨论】:

  • OpenCV 的“角”函数不会以您想的方式找到角 - 粗略地说,它们会找到具有显着水平和垂直变化的区域。 OpenCV 中角函数的目标是找到图像中对视觉跟踪有用的独特部分,这不一定是我们通常认为的角。
  • stackoverflow.com/a/14368605/1832154 的确切代码(调整大小部分除外,因为您的图像已经足够小)给出i.imgur.com/hMdAlHX.png
  • 找到一个全局阈值并为这种应用程序使用“轮廓”(blob)检测并不是一种可靠的方法。如果您的应用程序要查找纸的(扭曲的)矩形,则从图像边缘向内水平和垂直扫描边缘将是一个合理的开始。
  • @Ricardo 能否提供最终的工作代码?

标签: image-processing opencv


【解决方案1】:

首先,查看您的 OpenCV 发行版中的 /samples/c/squares.c。这个例子提供了一个方形检测器,它应该是如何检测角状特征的一个很好的开始。然后,看看 OpenCV 的面向特征的函数,如 cvCornerHarris() 和 cvGoodFeaturesToTrack()。

上述方法可以返回 许多 类似角的特征 - 大多数不会是您正在寻找的“真正的角”。在我的应用程序中,我必须检测已旋转或倾斜的正方形(由于透视)。我的检测管道包括:

  1. 从 RGB 转换为灰度 (cvCvtColor)
  2. 平滑 (cvSmooth)
  3. 阈值 (cvThreshold)
  4. 检测边缘 (cvCanny)
  5. 查找轮廓 (cvFindContours)
  6. 具有线性特征的近似轮廓 (cvApproxPoly)
  7. 查找具有以下结构的“矩形”:具有 4 个点的多边形轮廓、足够的面积、相邻的边缘约为 90 度、“相对”顶点之间的距离足够大等。

第 7 步是必要的,因为稍微嘈杂的图像会产生许多多边形化后呈现矩形的结构。在我的应用程序中,我还必须处理出现在所需正方形内或与所需正方形重叠的正方形结构。我发现轮廓的面积属性和重心有助于辨别正确的矩形。

【讨论】:

  • 第 7 步需要一点帮助,如何使用 cvCornerHarris,以我的示例为例,请参阅编辑后的帖子,您能帮帮我吗?
  • cvSmooth 是不是类似于高斯模糊?你扩大了 cvCanny 的结果吗?你如何近似轮廓,比如说 5 个角(由于阴影等而变形的正方形)或带有一点脊的 suqares。您的方法几乎是我想做的,但我正在努力奋斗。你能提供一些代码示例吗?会很有帮助的。
【解决方案2】:

将 houghlines 应用于 canny 图像 - 您将获得点列表 对这组点应用凸包

【讨论】:

    【解决方案3】:

    乍一看,人眼有 4 个角。但在计算机视觉中,角点被认为是在其邻域内强度梯度变化较大的点。邻域可以是 4 像素邻域或 8 像素邻域。

    在提供的计算强度梯度的方程中,已经考虑了 4 像素邻域SEE DOCUMENTATION

    这是我对相关图像的处理方法。我也有python中的代码:

    path = r'C:\Users\selwyn77\Desktop\Stack\corner'
    filename = 'env.jpg'
    
    img = cv2.imread(os.path.join(path, filename))
    gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)    #--- convert to grayscale 
    

    始终模糊图像以消除不太可能的梯度变化并保留更强烈的变化是一个不错的选择。我选择了bilateral filter,它与高斯过滤器不同,它不会模糊附近的所有像素。它会模糊像素强度与中心像素相似的像素。简而言之,它保留了高梯度变化的边缘/角落,但模糊了梯度变化最小的区域。

    bi = cv2.bilateralFilter(gray, 5, 75, 75)
    cv2.imshow('bi',bi)
    

    对于人类来说,与原始图像相比并没有太大差异。但这确实很重要。现在寻找可能的角落:

    dst = cv2.cornerHarris(bi, 2, 3, 0.04)
    

    dst 返回一个数组(图像的相同 2D 形状),其特征值从提到的最终方程获得 HERE

    现在必须应用阈值来选择超过某个值的角点。我将使用文档中的那个:

    #--- create a black image to see where those corners occur ---
    mask = np.zeros_like(gray)
    
    #--- applying a threshold and turning those pixels above the threshold to white ---           
    mask[dst>0.01*dst.max()] = 255
    cv2.imshow('mask', mask)
    

    白色像素是可能存在角的区域。您可以找到许多彼此相邻的角落。

    在图像上绘制选定的角:

    img[dst > 0.01 * dst.max()] = [0, 0, 255]   #--- [0, 0, 255] --> Red ---
    cv2.imshow('dst', img)
    

    (红色像素是角落,不那么明显)

    为了得到一个包含所有带角像素的数组:

    coordinates = np.argwhere(mask)
    

    更新

    变量coor 是一个数组数组。将其转换为列表列表

    coor_list = [l.tolist() for l in list(coor)]

    将上面的转换为元组列表

    coor_tuples = [tuple(l) for l in coor_list]

    我有一个简单且相当天真的方法来找到 4 个角。我只是计算了每个角落到其他每个角落的距离。我保留了那些距离超过某个阈值的角落。

    代码如下:

    thresh = 50
    
    def distance(pt1, pt2):
        (x1, y1), (x2, y2) = pt1, pt2
        dist = math.sqrt( (x2 - x1)**2 + (y2 - y1)**2 )
        return dist
    
    coor_tuples_copy = coor_tuples
    
    i = 1    
    for pt1 in coor_tuples:
    
        print(' I :', i)
        for pt2 in coor_tuples[i::1]:
            print(pt1, pt2)
            print('Distance :', distance(pt1, pt2))
            if(distance(pt1, pt2) < thresh):
                coor_tuples_copy.remove(pt2)      
        i+=1
    

    coor_tuples 上面运行 sn-p 之前有所有角点: [(4, 42), (4, 43), (5, 43), (5, 44), (6, 44), (7, 219), (133, 36), (133, 37), (133, 38), (134, 37), (135, 224), (135, 225), (136, 225), (136, 226), (137, 225), (137, 226), (137, 227), (138, 226)]

    运行 sn-p 后,我只剩下 4 个角:

    [(4, 42), (7, 219), (133, 36), (135, 224)]

    更新 2

    现在您只需在原始图像的副本上标记这 4 个点即可。

    img2 = img.copy()
    for pt in coor_tuples:
        cv2.circle(img2, tuple(reversed(pt)), 3, (0, 0, 255), -1)
    cv2.imshow('Image with 4 corners', img2) 
    

    【讨论】:

    • 谢谢!最后一步:如何仅提取 4 个点作为角点(用于进一步的去偏斜/透视校正)?您是否需要使用算法在许多点中找到聚类? (“白色像素是可能存在角的区域。您可以找到许多彼此相邻的角。”)您能举个例子吗?
    • 非常感谢@JeruLuke!
    • 你能帮我把这些示例代码翻译成c++语言吗?
    • @noobie 我没有 C++ 代码
    【解决方案4】:
    1. 使用 RETR_EXTERNAL 选项查找轮廓。(灰色 -> 高斯滤波器 -> 精明边缘 -> 查找轮廓)
    2. 找到最大尺寸的轮廓 -> 这将是矩形的边缘
    3. 用很少的计算找到角落

      Mat m;//image file
      findContours(m, contours_, hierachy_, RETR_EXTERNAL);
      auto it = max_element(contours_.begin(), contours_.end(),
          [](const vector<Point> &a, const vector<Point> &b) {
              return a.size() < b.size(); });
      Point2f xy[4] = {{9000,9000}, {0, 1000}, {1000, 0}, {0,0}};
      for(auto &[x, y] : *it) {
          if(x + y < xy[0].x + xy[0].y) xy[0] = {x, y};
          if(x - y > xy[1].x - xy[1].y) xy[1] = {x, y};
          if(y - x > xy[2].y - xy[2].x) xy[2] = {x, y};
          if(x + y > xy[3].x + xy[3].y) xy[3] = {x, y};
       }
      

    xy[4] 将是四个角。 我可以通过这种方式提取四个角。

    【讨论】:

      【解决方案5】:

      这是一个使用cv2.goodFeaturesToTrack() 检测角点的实现。方法是

      • 将图像转换为灰度
      • 执行canny edge detection
      • 检测拐角
      • 可选择执行 4 点透视变换以获得图像的自上而下视图

      使用此起始图像,

      转为灰度后,我们进行canny边缘检测

      现在我们有了一个不错的二进制图像,我们可以使用cv2.goodFeaturesToTrack()

      corners = cv2.goodFeaturesToTrack(canny, 4, 0.5, 50)
      

      对于参数,我们给它 canny 图像,将最大角数设置为 4 (maxCorners),使用最小可接受质量 0.5 (qualityLevel),并设置最小可能的欧几里得距离返回角到 50 (minDistance)。这是结果

      现在我们已经确定了角点,我们可以执行 4 点透视变换来获得对象的俯视图。我们首先对点进行顺时针排序,然后将结果绘制到蒙版上。

      注意:我们可以在 Canny 图像上找到轮廓,而不是执行此步骤来创建蒙版,但假设我们只有 4 个角点可以使用

      接下来,我们使用cv2.arcLength()cv2.approxPolyDP() 在此蒙版上找到轮廓和过滤器。这个想法是,如果轮廓有 4 个点,那么它一定是我们的对象。一旦我们有了这个轮廓,我们就执行透视变换

      最后,我们根据所需的方向旋转图像。这是结果

      仅检测角点的代码

      import cv2
      
      image = cv2.imread('1.png')
      gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
      canny = cv2.Canny(gray, 120, 255, 1)
      
      corners = cv2.goodFeaturesToTrack(canny,4,0.5,50)
      
      for corner in corners:
          x,y = corner.ravel()
          cv2.circle(image,(x,y),5,(36,255,12),-1)
      
      cv2.imshow('canny', canny)
      cv2.imshow('image', image)
      cv2.waitKey()
      

      检测角点和执行透视变换的代码

      import cv2
      import numpy as np
      
      def rotate_image(image, angle):
          # Grab the dimensions of the image and then determine the center
          (h, w) = image.shape[:2]
          (cX, cY) = (w / 2, h / 2)
      
          # grab the rotation matrix (applying the negative of the
          # angle to rotate clockwise), then grab the sine and cosine
          # (i.e., the rotation components of the matrix)
          M = cv2.getRotationMatrix2D((cX, cY), -angle, 1.0)
          cos = np.abs(M[0, 0])
          sin = np.abs(M[0, 1])
      
          # Compute the new bounding dimensions of the image
          nW = int((h * sin) + (w * cos))
          nH = int((h * cos) + (w * sin))
      
          # Adjust the rotation matrix to take into account translation
          M[0, 2] += (nW / 2) - cX
          M[1, 2] += (nH / 2) - cY
      
          # Perform the actual rotation and return the image
          return cv2.warpAffine(image, M, (nW, nH))
      
      def order_points_clockwise(pts):
          # sort the points based on their x-coordinates
          xSorted = pts[np.argsort(pts[:, 0]), :]
      
          # grab the left-most and right-most points from the sorted
          # x-roodinate points
          leftMost = xSorted[:2, :]
          rightMost = xSorted[2:, :]
      
          # now, sort the left-most coordinates according to their
          # y-coordinates so we can grab the top-left and bottom-left
          # points, respectively
          leftMost = leftMost[np.argsort(leftMost[:, 1]), :]
          (tl, bl) = leftMost
      
          # now, sort the right-most coordinates according to their
          # y-coordinates so we can grab the top-right and bottom-right
          # points, respectively
          rightMost = rightMost[np.argsort(rightMost[:, 1]), :]
          (tr, br) = rightMost
      
          # return the coordinates in top-left, top-right,
          # bottom-right, and bottom-left order
          return np.array([tl, tr, br, bl], dtype="int32")
      
      def perspective_transform(image, corners):
          def order_corner_points(corners):
              # Separate corners into individual points
              # Index 0 - top-right
              #       1 - top-left
              #       2 - bottom-left
              #       3 - bottom-right
              corners = [(corner[0][0], corner[0][1]) for corner in corners]
              top_r, top_l, bottom_l, bottom_r = corners[0], corners[1], corners[2], corners[3]
              return (top_l, top_r, bottom_r, bottom_l)
      
          # Order points in clockwise order
          ordered_corners = order_corner_points(corners)
          top_l, top_r, bottom_r, bottom_l = ordered_corners
      
          # Determine width of new image which is the max distance between 
          # (bottom right and bottom left) or (top right and top left) x-coordinates
          width_A = np.sqrt(((bottom_r[0] - bottom_l[0]) ** 2) + ((bottom_r[1] - bottom_l[1]) ** 2))
          width_B = np.sqrt(((top_r[0] - top_l[0]) ** 2) + ((top_r[1] - top_l[1]) ** 2))
          width = max(int(width_A), int(width_B))
      
          # Determine height of new image which is the max distance between 
          # (top right and bottom right) or (top left and bottom left) y-coordinates
          height_A = np.sqrt(((top_r[0] - bottom_r[0]) ** 2) + ((top_r[1] - bottom_r[1]) ** 2))
          height_B = np.sqrt(((top_l[0] - bottom_l[0]) ** 2) + ((top_l[1] - bottom_l[1]) ** 2))
          height = max(int(height_A), int(height_B))
      
          # Construct new points to obtain top-down view of image in 
          # top_r, top_l, bottom_l, bottom_r order
          dimensions = np.array([[0, 0], [width - 1, 0], [width - 1, height - 1], 
                          [0, height - 1]], dtype = "float32")
      
          # Convert to Numpy format
          ordered_corners = np.array(ordered_corners, dtype="float32")
      
          # Find perspective transform matrix
          matrix = cv2.getPerspectiveTransform(ordered_corners, dimensions)
      
          # Return the transformed image
          return cv2.warpPerspective(image, matrix, (width, height))
      
      image = cv2.imread('1.png')
      original = image.copy()
      gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
      canny = cv2.Canny(gray, 120, 255, 1)
      
      corners = cv2.goodFeaturesToTrack(canny,4,0.5,50)
      
      c_list = []
      for corner in corners:
          x,y = corner.ravel()
          c_list.append([int(x), int(y)])
          cv2.circle(image,(x,y),5,(36,255,12),-1)
      
      corner_points = np.array([c_list[0], c_list[1], c_list[2], c_list[3]])
      ordered_corner_points = order_points_clockwise(corner_points)
      mask = np.zeros(image.shape, dtype=np.uint8)
      cv2.fillPoly(mask, [ordered_corner_points], (255,255,255))
      
      mask = cv2.cvtColor(mask, cv2.COLOR_BGR2GRAY)
      cnts = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
      cnts = cnts[0] if len(cnts) == 2 else cnts[1]
      
      for c in cnts:
          peri = cv2.arcLength(c, True)
          approx = cv2.approxPolyDP(c, 0.015 * peri, True)
          if len(approx) == 4:
              transformed = perspective_transform(original, approx)
      
      result = rotate_image(transformed, -90)
      
      cv2.imshow('canny', canny)
      cv2.imshow('image', image)
      cv2.imshow('mask', mask)
      cv2.imshow('transformed', transformed)
      cv2.imshow('result', result)
      cv2.waitKey()
      

      【讨论】:

        猜你喜欢
        • 2018-12-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-09
        • 2014-10-18
        • 2020-02-12
        • 2012-06-04
        • 2017-02-22
        相关资源
        最近更新 更多