1. 介绍
在计算机视觉领域中,图片识别可以说是一个非常重要的课题。但是,在实际应用中,我们通常不仅要对整张图片进行识别,可能还需要对图片进行分割后再进行识别。今天我们就来学习用Golang来实现图片的分割和内容识别。
2. 图片分割
2.1 概述
在实现图片的内容识别之前,我们需要先对整张图片进行分割,将图片分割成多个小块,这样有助于我们更好地进行内容识别。
图片分割通常有两种方式:基于颜色的分割和基于边缘的分割。基于颜色的分割是通过计算相邻像素之间的颜色差异,将相邻像素颜色差异较大的部分分离出来。基于边缘的分割则是找到图片边缘处的像素,将边缘处像素组成块,这种方式更为普遍。
2.2 基于边缘的分割
基于边缘的分割基于图片的边缘信息,可以有效地将不同的物体分割开来。Golang语言有一个非常棒的库——GoCV,它提供了各种图像处理的功能,包括了基于边缘的图片分割。GoCV提供了多种算法来实现图片分割,本文使用的是Watershed算法。
下面的代码演示了如何使用GoCV进行图片分割:
import (
"gocv.io/x/gocv"
)
func main() {
img := gocv.IMRead("test.jpg", gocv.IMReadColor)
if img.Empty() {
panic("can not load image")
}
// Convert image to grayscale
gray := gocv.NewMat()
gocv.CvtColor(img, gray, gocv.ColorBGRToGray)
// Apply GaussianBlur to reduce noise
blur := gocv.NewMat()
gocv.GaussianBlur(gray, blur, image.Pt(5, 5), 0, 0, gocv.BorderDefault)
// Apply Threshold to separate background and foreground
thresh := gocv.NewMat()
gocv.Threshold(blur, thresh, 0, 255, gocv.ThresholdBinary|gocv.ThresholdOtsu)
// Perform Morphological transformations
kernel := gocv.GetStructuringElement(gocv.MorphRect, image.Pt(3, 3))
dilate := gocv.NewMat()
erode := gocv.NewMat()
gocv.Dilate(thresh, dilate, kernel)
gocv.Erode(dilate, erode, kernel)
// Perform Distance Transform
dist := gocv.NewMat()
gocv.DistanceTransform(erode, dist, gocv.DistC, gocv.DistMaskPrecise)
// Normalize Distance Transform
distNorm := gocv.NewMat()
gocv.Normalize(dist, distNorm, 0.0, 1.0, gocv.NormMinMax)
// Apply Threshold to distance transform
threshDist := gocv.NewMat()
_, maxVal, _, _ := gocv.MinMaxLoc(distNorm)
threshVal := maxVal * 0.7
gocv.Threshold(distNorm, threshDist, threshVal, 255, gocv.ThresholdBinaryInv)
// Convert threshold back to uint8
threshDist.ConvertTo(threshDist, gocv.MatTypeCV8UC1)
// Find contours in the processed image
contours := gocv.FindContours(threshDist, gocv.RetrievalExternal, gocv.ChainApproxSimple)
// Use Watershed to segment the image
markers := gocv.NewMat()
gocv.Watershed(img, markers)
// Convert markers to 3 channel matrix to display in an RGB image
markers = markers.ConvertTo(gocv.MatTypeCV8UC1)
markers = gocv.NewMatWithSize(img.Rows(), img.Cols(), gocv.MatTypeCV8UC3)
for i := 0; i < markers.Rows(); i++ {
for j := 0; j < markers.Cols(); j++ {
value := markers.GetIntAt(i, j)
blue := (value * 10) % 255
green := (value * 20) % 255
red := (value * 30) % 255
markers.SetUCharAt(i, j, byte(blue), 0)
markers.SetUCharAt(i, j, byte(green), 1)
markers.SetUCharAt(i, j, byte(red), 2)
}
}
// Display Image with Watershed Segmentation in a new window
gocv.IMShow("Watershed Segmentation Result", markers)
gocv.WaitKey(0)
// Release all Mats
img.Close()
gray.Close()
blur.Close()
thresh.Close()
kernel.Close()
dilate.Close()
erode.Close()
dist.Close()
distNorm.Close()
threshDist.Close()
markers.Close()
}
代码中,我们首先读取一张图片,将其转为灰度图,然后使用高斯模糊算法(GaussianBlur)来降低噪声。接着,我们对图片进行二值化处理(Threshold),这样可以将图片中的前景物体和背景分离出来。然后,我们使用形态学运算(Dilate和Erode)来对图片进行形态学图像处理,这样可以使前景物体更加清晰。接下来,我们使用距离变换(Distance Transform)将图片中距离值小的区域设为前景,这将有助于我们对图片进行分割。然后,我们将值大于某一阈值的像素设为前景,该阈值为最大距离的70%。最后,我们使用Watershed算法对图片进行分割。
3. 内容识别
3.1 概述
通过图片分割,我们已经将图片分割成了多个小块,这样有助于我们更好地进行内容识别。对于每一个小块,我们可以使用深度学习算法对小块进行识别。这样,就可以得到每一个小块中的物体的种类。
3.2 图像分类算法
对于识别图片中的物体种类,我们可以使用卷积神经网络(Convolutional Neural Network,CNN)来进行分类。在CNN中,每一个卷积层都有一些卷积核,这些卷积核将对待分类的图片进行卷积操作,从而得到特征图。然后,特征图会经过各种操作和处理,并传递给下一个卷积层。在最后一个卷积层之后,我们会使用全连接层将特征图转化成一个向量,然后将该向量作为输入传入输出层,从而实现对图片中物体种类的分类。
3.3 图像分类框架
在实践中,我们可以使用已经训练好的深度学习模型来对图片进行分类。常用的深度学习框架有TensorFlow、Keras、PyTorch等。这些框架都可以非常方便地实现卷积神经网络的训练和分类。这里我们使用TensorFlow和Keras来实现深度学习分析。
3.4 图像分类代码
下面的代码演示了如何使用Keras和TensorFlow进行图片分类:
import (
"gocv.io/x/gocv"
"github.com/jinzhu/gorm"
"github.com/jinzhu/gorm/dialects/mysql"
"github.com/konpa/devpix-server/models"
"github.com/konpa/devpix-server/utils"
"github.com/machinebox/sdk-go/facebox"
"golang.org/x/sync/errgroup"
"gorm.io/gorm/logger"
"os"
)
// LoadModel function is loading the pre-trained deep learning model
func LoadModel() (*tensorflow.Model, error) {
model, err := tensorflow.LoadSavedModel("/path/to/saved_model", []string{"serve"}, nil)
if err != nil {
return nil, err
}
return model, nil
}
// Predict function is using the pre-trained deep learning model to predict the image's label
func Predict(image []byte, model *tensorflow.Model) (int, error) {
tensor, err := makeTensorFromImage(image)
if err != nil {
return -1, err
}
result, err := model.Session.Run(
map[tensorflow.Output]*tensorflow.Tensor{
model.Graph.Operation("input_1").Output(0): tensor,
},
[]tensorflow.Output{
model.Graph.Operation("output_1").Output(0),
},
nil)
if err != nil {
return -1, err
}
probabilities := result[0].Value().([][]float32)[0]
maxProb := float32(0)
maxLabel := -1
for i := 0; i < len(probabilities); i++ {
if probabilities[i] > maxProb {
maxProb = probabilities[i]
maxLabel = i + 1
}
}
return maxLabel, nil
}
func main() {
// Connect to the database
db, err := gorm.Open(mysql.New(mysql.Config{
DSN: "user:password@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local",
}), &gorm.Config{
Logger: logger.Default.LogMode(logger.Silent),
})
if err != nil {
panic(err)
}
defer db.Close()
// Initialize Facebox client
faceboxClient := facebox.New("http://localhost:8080")
// Load pre-trained deep learning model
model, err := LoadModel()
if err != nil {
panic(err)
}
// Load image and convert to JPEG format
img := gocv.IMRead("test.jpg", gocv.IMReadColor)
if img.Empty() {
panic("can not load image")
}
defer img.Close()
// Convert Image to JPEG format
imgBytes, err := gocv.IMEncode(".jpg", img)
if err != nil {
panic(err)
}
// Split Image into small blocks
blocks := SplitImageIntoBlocks(img)
// For each block, use deep learning model to predict its label
var eg errgroup.Group
for i, block := range blocks {
i, block := i, block
eg.Go(func() error {
label, err := Predict(block, model)
if err != nil {
return err
}
// Save image block to file system
filename, err := SaveImageBlock(block)
if err != nil {
return err
}
// Use Facebox to detect faces in image block
faces, err := faceboxClient.Check(bytes.NewReader(block))
if err != nil {
return err
}
// Save image block data to database
_, err = models.CreateImageBlock(db, filename, i, label, faces.Count)
if err != nil {
return err
}
return nil
})
}
if err = eg.Wait(); err != nil {
panic(err)
}
// Remove the image block files from file system
for i := 0; i < len(blocks); i++ {
filename := fmt.Sprintf("block_%d.jpg", i)
err := os.Remove(filename)
utils.PanicIfErr(err)
}
}
代码中,我们首先连接数据库及Facebox,并加载预训练的深度学习模型。然后,我们读取一张图片,并对其进行分割,对于每一个小块,我们使用深度学习模型进行识别,从而得到每一个小块中的物体类别。同时,我们使用Facebox对每一个小块进行人脸识别,并将识别结果存入数据库。最后,我们将小块图片文件从文件系统中删除。