在这篇文章中,我将介绍以下内容:

  • 什么是4点OpenCV图像变换?

  • 如何使用Gocv在Golang中实现4点OpenCV图像变换?

  • 演示在 Golang 中使用 OpenCV getPerspectiveTransform 函数。

  • 演示如何用Golang做计算机视觉和图像处理

  • 演示如何在 Golang 中使用gonum 进行数值处理

先决条件

brew install opencvbrew install pkgconfig

什么是 OpenCV?

OpenCV 也称为开源计算机视觉库,是一个流行的开源软件库,包含 2500 多种用于计算机视觉和机器学习问题的算法。这些算法被广泛用于检测人脸或移动物体,生成高分辨率 3D 图像等。你可以在他们的官方网站上阅读更多关于 OpenCV 的信息

GoCV

什么是4点OpenCV图像变换?

4 点图像变换是通过选择图像中的四个点(角)来拉直图像的过程。在封面图像中,绿色突出显示的四点用于变换图像。 OpenCV 的 getPerspectiveTransform() 是帮助实现图像变换的函数。


理论够了,让我们来编码

下面提供的大多数代码都有不错的注释,以了解正在发生的事情。完整的代码也可以在Github找到。

随意按原样使用以下代码或自定义用于个人/商业用途,风险自负。如有任何反馈或问题,请通过Linkedin或Twitter与我联系。

package main

import (
    "gocv.io/x/gocv"
    "gonum.org/v1/gonum/floats"
    "gonum.org/v1/gonum/mat"
    "image"
    "math"
)

func main() {
    // Create a new gocv window to show the original image.
    original_window := gocv.NewWindow("Original")

    // Use IMRead function to read the image in colored form
    // Change the image location here to your custom image.
    original_image := gocv.IMRead("samples/original.png", gocv.IMReadColor)

    // Show the original image in the gocv window.
    // Press Return/ Enter to proceed next.
    for {
        original_window.IMShow(original_image)
        if original_window.WaitKey(1) >= 0 {
            break
        }
    }

    // Create a 4 x 2 matrix which has 4 rows and two columns
    // Each row represents the coordinates of a point.
    // Column (0, 1) represents the X, Y coordinate of a point.
    // We need to pass 4 points i.e 4 corners of image) or
    // as I call it, 4 points of interest to the below Matrix.
    // Important, the order of rows doesn't matter. We will
    // calculate which point is Top-left, top-right, bottom-left,
    // bottom-right automatically.
    pts := mat.NewDense(4, 2, []float64{
        69, 145,
        97, 735,
        938, 723,
        971, 170,
    })
    // Obviously, the above 4-points are hard-coded here.
    // Check my other article where I explain how to find the
    // four points automatically. It's a bit complex process
    // but fun to learn.

    // Create a new gocv window to show the transformed image.
    tranformed_window := gocv.NewWindow("Transformed")

    // Call our custom function FourPointTransform() to
    // transform the image. This function expects the
    // original gocv image and points of interest matrix.
    transformed_image := FourPointTransform(original_image, pts)

    // Show the original image in the gocv window.
    // Press Return/ Enter to proceed next.
    for {
        tranformed_window.IMShow(transformed_image)
        if tranformed_window.WaitKey(1) >= 0 {
            break
        }
    }
}

进入全屏模式 退出全屏模式


// Our custom function to perform 4-point transformation.
func FourPointTransform(img gocv.Mat, pts *mat.Dense) gocv.Mat{

    // We need to order the points so that we can find top-right,
    // top-left, bottom-right, bottom-left points.
    // The function orderPoints() is custom written and code is
    // provided in the same article.
    rect := orderPoints(pts)
    tl := rect.RawRowView(0)
    tr := rect.RawRowView(1)
    br := rect.RawRowView(2)
    bl := rect.RawRowView(3)

    // compute the width of the new image, which will be the
    // maximum distance between bottom-right and bottom-left
    // x-coordiates or the top-right and top-left x-coordinates
    widthA := math.Sqrt(math.Pow((br[0] - bl[0]), 2) + math.Pow((br[1] - bl[1]), 2))
    widthB := math.Sqrt(math.Pow((tr[0] - tl[0]), 2) + math.Pow((tr[1] - tl[1]), 2))
    maxWidth := int(math.Max(widthA, widthB))

    // compute the height of the new image, which will be the
    // maximum distance between the top-right and bottom-right
    // y-coordinates or the top-left and bottom-left y-coordinates
    heightA := math.Sqrt(math.Pow((tr[0] - br[0]),2) + math.Pow((tr[1] - br[1]), 2))
    heightB := math.Sqrt(math.Pow((tl[0] - bl[0]), 2) + math.Pow((tl[1] - bl[1]), 2))
    maxHeight := int(math.Max(heightA, heightB))

    // now that we have the dimensions of the new image, construct
    // the set of destination points to obtain a "birds eye view",
    // (i.e. top-down view) of the image, again specifying points
    // in the top-left, top-right, bottom-right, and bottom-left order
    dst := mat.NewDense(4, 2, []float64{
        0, 0,
        (float64(maxWidth) - 1), 0,
        (float64(maxWidth) - 1), (float64(maxHeight) - 1),
        0, (float64(maxHeight) - 1),
    })

    // Call the gocv's GetPerspectiveTransform() function and
    // WarpPerspective() function which does the magic of transforming
    // the image and writing it to destination.
    M := gocv.GetPerspectiveTransform(convertDenseToImagePoint(rect), convertDenseToImagePoint(dst))
    gocv.WarpPerspective(img, &img, M, image.Point{X: maxWidth, Y: maxHeight})

    // convertDenseToImagePoint() function is custom written, it converts
    // gonum matrix (*mat.Dense) -> []image.Point
    // This is very important as at this moment, gocv doesn't support
    // *mat.Dense directly and I did a lot of search and couldn't find
    // any easy solution except writing a convertor.

    return img
}

func convertDenseToImagePoint(pts *mat.Dense) []image.Point {
    var sd []image.Point

    r, c := pts.Dims()
    if (c !=2 ) {
        return sd
    }
    for i := 0; i < r; i++ {
        row := pts.RowView(i)
        sd = append(sd, image.Point{
            X: int(row.AtVec(0)),
            Y: int(row.AtVec(1)),
        })
    }
    return sd
}

进入全屏模式 退出全屏模式

func orderPoints(pts *mat.Dense) *mat.Dense{
    // initialzie a list of coordinates that will be ordered
    // such that the first entry in the list is the top-left,
    // the second entry is the top-right, the third is the
    // bottom-right, and the fourth is the bottom-left

    rect := mat.NewDense(4, 2, nil)

    // the top-left point will have the smallest sum, whereas
    // the bottom-right point will have the largest sum
    sumMinIndex, sumMaxIndex := findMinMaxSumIndex(*pts)
    rect.SetRow(0, pts.RawRowView(sumMinIndex))
    rect.SetRow(2, pts.RawRowView(sumMaxIndex))

    // now, compute the difference between the points, the
    // top-right point will have the smallest difference,
    // whereas the bottom-left will have the largest difference
    diffMinIndex, diffMaxIndex := findMinMaxDiffIndex(*pts)
    rect.SetRow(1, pts.RawRowView(diffMinIndex))
    rect.SetRow(3, pts.RawRowView(diffMaxIndex))

    // return the ordered coordinates
    return rect
}

进入全屏模式 退出全屏模式

func findMinMaxSumIndex(pts mat.Dense) (int, int){
    r, c := pts.Dims()

    maxIndex := 0
    maxValue := 0.0
    minIndex := 0
    minValue := 0.0

    for i := 0; i < r; i++ {
        row := pts.RowView(i)
        sum := 0.0
        for j := 0; j < c; j++ {
            sum += row.AtVec(j)
        }

        if (i == 0 ) {
            maxValue = sum
            minValue = sum
        }

        //Find max value and index
        if (sum > maxValue) {
            maxValue = sum
            maxIndex = i
        }
        //Find min value and index
        if (sum < minValue) {
            minValue = sum
            minIndex = i
        }
    }
    return minIndex, maxIndex
}

func findMinMaxDiffIndex(pts mat.Dense) (int, int){
    r, c := pts.Dims()

    maxIndex := 0
    maxValue := 0.0
    minIndex := 0
    minValue := 0.0

    for i := 0; i < r; i++ {
        row := pts.RowView(i)
        diff := row.AtVec(c - 1) //Do check c is not Zero or 1
        for j := c - 2; j >= 0; j-- {
            diff -= row.AtVec(j)
        }

        if (i == 0 ) {
            maxValue = diff
            minValue = diff
        }

        //Find max value and index
        if (diff > maxValue) {
            maxValue = diff
            maxIndex = i
        }
        //Find min value and index
        if (diff < minValue) {
            minValue = diff
            minIndex = i
        }
    }
    return minIndex, maxIndex
}

进入全屏模式 退出全屏模式