在这篇文章中,我将介绍以下内容:
-
什么是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
}
进入全屏模式 退出全屏模式