百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程字典 > 正文

机器视觉——python识别数字手写体

toyiye 2024-07-06 00:34 13 浏览 0 评论

HOG:Histogram of Oriented Gradients。类似于边缘方向直方图和局部不变描述符(例如SIFT),HOG对图像的梯度幅度进行操作。

然而,与SIFT不同,SIFT计算图像的小的局部区域中的边缘方向上的直方图,HOG在均匀间隔的单元的密集网格上计算这些直方图。此外,这些单元也可以重叠并进行对比度归一化,以提高描述符的准确性。

在这种情况下,我们将应用HOG图像描述符和线性支持向量机(SVM)来学习图像数字的表示。

幸运的是,scikit-image库已经实现了HOG描述符,因此在计算其特征表示时我们可以直接使用它。

from skimage import feature 
class HOG:
 def __init__(self,orientations = 9,pixelsPerCell=(8,8),
 cellsPerBlock=(3,3),transform=False):
 # store the number of orientations, pixels per cell,
 # cells per block, and whether or not power law
 # compression should be applied
 self.orienations = orientations
 self.pixelsPerCell = pixelsPerCell
 self.cellsPerBlock = cellsPerBlock
 self.transform = transform
 def describe(self,image):
 # compute HOG for the image
 hist = feature.hog(image,orientations=self.orienations,
 pixels_per_cell=self.pixelsPerCell,
 cells_per_block=self.cellsPerBlock,
 transform_sqrt=self.transform)
 # return the HOG features 
 return hist 

我们首先导入scikit-image的feature子包。该包包含许多从图像中提取特征的方法。

接着,我们设置__init__构造函数,需要四个参数。第一个orientations定义每个直方图中将有多少个梯度方向(即,bins的数量)。pixelsPerCell参数定义将落入每个单元格的像素数。当在图像上计算HOG描述符时,图像将被划分为多个单元,每个单元的大小为pixelsPerCell × pixelsPerCell。然后将为每个单元计算梯度幅度的直方图。

然后,HOG将根据cellsPerBlock参数将落入每个块的单元格数来标准化每个直方图。

可选地,HOG可以应用幂律压缩(获取输入图像的对数/平方根),这可以导致描述符的更好准确性。

在存储了构造函数的参数之后,我们定义了describe方法,只需要一个参数——要计算HOG描述符的图像。

计算HOG描述符由scikit-image的feature子包的hog方法处理。我们传递orientations的数量,每个单元的像素数,每个块的单元格,以及在计算HOG描述符之前是否应该将平方根变换应用于图像。

最后我们将计算的HOG特征向量返回给调用者。

接下来,我们需要一个数字数据集,他可以用来从中提取特征并训练我们的机器学习模型。我们决定使用MNIST数字识别数据集的样本,这是计算机视觉和机器学习文献中的经典数据集。

完整的数据集

数据集的样本由5000个数据点组成,每个数据点具有长度为784的特征向量,对应于图像的28×28灰度像素强度。

但首先,我们需要定义一些方法来帮助我们操作和准备数据集以进行特征提取和训练我们的模型。我们将这些数据集操作函数存储在dataset.py中:

from . import imutils
import numpy as np 
import mahotas
import cv2 
def load_digits(datasetPath):
 # build the dataset and then split it into data
 # and labels
 data = np.genfromtxt(datasetPath,delimiter=",",dtype="uint8")
 target = data[:,0]
 data = data[:,1:].reshape(data.shape[0],28,28)
 # return a tuple of the data and targets 
 return (data,target)

我们首先导入我们需要的包。我们将使用numpy进行数字处理,mahotas是另一个计算机视觉库来辅助cv2,最后是imutils,其中包含执行常见图像处理任务(如调整大小和旋转图像)的便利功能。

为了将我们的数据集加载到磁盘上,我们定义了load_digits方法。该方法只需要一个参数,即datasetPath,它是MNIST样本数据集驻留在磁盘上的路径。

从那里,NumPy的genfromtext函数将数据集加载到磁盘上并将其存储为无符号的8位NumPy数组。请记住,此数据集由图像的像素强度组成。这些像素强度永远不会小于0且绝不会大于255,因此我们能够使用8位无符号整数数据类型。

数据矩阵的第一列包含我们的target,它是图像包含的数字。target将落在[0,9]范围内。

同样,第一个之后的所有列都包含图像的像素强度。同样,这些是尺寸为M×N的数字图像的灰度像素,并且将始终落在[0,255]的范围内。

最后,我们将data和target以元组的形式返回给调用者。

接下来,我们需要对数字图像执行一些预处理:

def deskew(image,width):
 # grab the width and height of the image and compute
 # moments for the image
 (h,w) = image.shape[:2]
 moments = cv2.moments(image)
 # deskew the image by applying an affine transformation
 skew = moments["mu11"] / moments["mu02"]
 M = np.float32([
 [1,skew,-0.5 * w * skew],
 [0,1,0]
 ])
 image = cv2.warpAffine(image,M,(w,h),
 flags=cv2.WARP_INVERSE_MAP | cv2.INTER_LINEAR)
 # resize the image to have a constant width
 image = imutils.resize(image,width = width)
 # return the deskewed image
 return image

每个人都有不同的写作风格。虽然我们大多数人写的数字“向左倾斜”,但有些数字向右倾斜。我们中的一些人以不同的角度写数字。这些变化的角度可能导致试图学习各种数字表示的机器学习模型的混淆。

为了帮助修复一些“lean”数字,我们定义了deskew(倾斜)方法。这个函数有两个参数。第一个是要被歪斜的数字图像。第二个是图像要调整大小的宽度。

我们首先获取图像的高度和宽度,然后计算图像的moment。这些moment包含有关图像中白色像素位置分布的统计信息

根据前面的moments,我们计算出了skew。接着我们构造了warping matrix M。该矩阵M将用于对图像进行去歪斜。

图像的实际偏斜校是调用cv2.warpAffine函数。第一个参数是将要倾斜的图像,第二个参数是定义图像将被歪斜的“方向”的矩阵M,第三个参数是偏斜图像的最终宽度和高度。最后,flags参数控制图像的校正方式。 在这种情况下,我们使用线性插值。

最后我们调整偏斜图像的大小并返回给调用者。

为了获得一致的数字表示,其中所有图像具有相同的宽度和高度,数字位于图像的中心,然后我们需要定义图像的范围:

def center_extent(image,size):
 # grab the extent width and height
 (eW,eH) = size 
 # handle when the width is greater than the height 
 if image.shape[1] > image.shape[0]:
 image = imutils.resize(image,width = eW)
 # otherwise , the height is greater than the width
 else:
 image = imutils.resize(image,height = eH)
 # allocate memory for the extent of the image and 
 # grab it 
 extent = np.zeros((eH,eW),dtype="uint8")
 offsetX = (eW - image.shape[1]) // 2
 offsetY = (eH - image.shape[0]) // 2
 extent[offsetY:offsetY + image.shape[0],offsetX:offsetX + image.shape[1]] = image 
 # compute the center of mass of the image and then
 # move the center of mass to the center of the image
 (cY,cX) = np.round(mahotas.center_of_mass(extent)).astype("int32")
 (dX,dY) = ((size[0] // 2) - cX,(size[1] // 2) - cY)
 M = np.float32([[1,0,dX],[0,1,dY]])
 extent = cv2.warpAffine(extent,M,size)
 # return the extent of the image
 return extent 

我们首先定义了center_extent函数,该函数有两个参数。第一个是偏斜校正的图像,第二个是图像的输出尺寸(即输出宽度和高)。

然后检查宽度是否大于图像的高度。如果是这种情况,则会根据图像的宽度调整图像大小。否则,高度大于宽度,因此必须根据图像的高度调整图像大小。

这些都是重要的检查。如果没有进行这些检查并且总是根据图像的宽度调整大小,那么高度可能会大于宽度,因此不适合图像的“extent”。

然后,我们使用相同的维度,给这个extnet的图像分配空间。

接着,我们计算offsetX和offsetY。这些偏移表示图像放置在extent(扩展后)的图像的起始(x,y)坐标(以y,x顺序放置)。

我们使用NumPy数组切片设置实际的extent。

下一步是translate the digit,使其位于图像的中心。

我们使用mahotas包的center_of_mass函数计算图像中白色像素的加权平均值。此函数返回图像中心的加权(x,y)坐标。然后,将这些(x,y)坐标转换为整数而不是浮点数。

然后,我们translates the digit,使其位于图像的中心。

M是我们的平移矩阵,该矩阵告诉我们的图像要进行平移多少像素(从左到右,从上到下)。该矩阵被定义为float32类型的数组,因为OpenCV希望该矩阵是一个float类型。[1,0,tx],其中tx是the number of pixels we will shift the image left or right,而负值则表示图像将向左平移,正值表示图像将向右平移。然后[0,1,ty],其中,ty是the number of pixels we will shift the image up or down。其中,负值表示图像向上平移,正值表示图像向下平移。我们定义好了平移矩阵之后,图像的实际平移是使用了cv2.warpAffine函数来执行,该函数的第一个参数是我们要进行平移的图像,第二个参数是我们的平移矩阵M,最后我们需要手动地提供图像的尺寸(width and height)作为第三个参数。

最后,我们将居中图像返回给调用者。

接下来训练我们的机器模型,编写train.py文件

# import the necessary packages
from sklearn.externals import joblib
from sklearn.svm import LinearSVC
from preprocess.hog import HOG
from preprocess import dataset
import argparse
# construct the argument parse and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-d", "--dataset", required = True,
 help = "path to the dataset file")
ap.add_argument("-m", "--model", required = True,
 help = "path to where the model will be stored")
args = vars(ap.parse_args())

首先导入需要的包。我们将使用scikit-learn中的LinearSVC模型来训练线性支持向量机(SVM)。同时还将导入HOG图像描述符和dataset utility functions。最后,argparse将用于解析命令行参数,而joblib将用于将训练过的模型转储到文件中。

我们的脚本需要两个命令行参数,第一个是 --dataset,它是磁盘上的MNIST样本数据集的路径。第二个参数是 --model,是我们训练过的LinearSVC的输出路径。

# load the dataset and initialize the data matrix
(digits,target) = dataset.load_digits(args["dataset"])
data = []
# initialize the HOG descriptor
hog = HOG(orientations=18,pixelsPerCell=(10,10),
 cellsPerBlock=(1,1),transform=True)
#loop over the images 
for image in digits:
 # deskew the image, center it 
 image = dataset.deskew(image,20)
 image = dataset.center_extent(image,(20,20))
 # describe the image and update the data matrix 
 hist = hog.describe(image)
 data.append(hist)

首先我们从磁盘加载由images和targets组成的数据集。然后初始化用于保存每个图像的HOG描述符的数据列表.

接下来,实例化HOG描述符,使用18个orientations作为梯度幅度直方图,每个单元10个像素,每个块1个单元。最后,通过设置transform=True,表示在创建直方图之前将计算像素强度的平方根。

然后开始循环我们的digit images。图像接着被校正并被转换到图像中心。

通过调用describe方法,为预处理图像计算HOG特征向量。最后,使用HOG特征向量更新数据矩阵。

# train the model 
model = LinearSVC(random_state=42)
model.fit(data,target)
# dump the model to file 
joblib.dump(model,args["model"])

最后训练我们的模型。使用伪随机状态42实例化我们的LinearSVC,以确保我们的结果是可重复的。然后使用数据矩阵和target训练模型。最后将我们的模型dump到磁盘里面。

接下来我们可以使用训练好的模型来进行分类,新建classify.py文件

from __future__ import print_function
from sklearn.externals import joblib
from preprocess.hog import HOG
from preprocess import dataset
import argparse
import mahotas
import cv2
# construct the argument parse and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-m", "--model", required = True,
 help = "path to where the model will be stored")
ap.add_argument("-i", "--image", required = True,
 help = "path to the image file")
args = vars(ap.parse_args())
model = joblib.load(args["model"])
# initialize the HOG descriptor
hog = HOG(orientations = 18, pixelsPerCell = (10, 10),
 cellsPerBlock = (1, 1), transform = True)

首先导入必要的包,然后我们将两个命令行参数传递给classify.py。第一个是--model,即存储cPickle'd模型的路径。第二个--image,是包含我们想要分类和识别的数字的图像的路径。

接着将经过训练的LinearSVC从磁盘加载。

然后,使用与训练阶段期间完全相同的参数来实例化HOG描述符。

现在我们已准备好找到图像中的数字,以便对它们进行分类:

# load the image and convert it to grayscale
image = cv2.imread(args["image"])
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
# blur the image, find edges, and then find contours along
# the edged regions
blurred = cv2.GaussianBlur(gray,(5,5),0)
edged = cv2.Canny(blurred,30,150)
(_,cnts,_) = cv2.findContours(edged.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
# sort the contours by their x-axis position, ensuring
# that we read the numbers from left to right
cnts = sorted([(c,cv2.boundingRect(c)[0]) for c in cnts],key=lambda x:x[1])

第一步是将查询图像加载到磁盘上,并将其转换为灰度。

接着,使用高斯模糊来模糊图像,并使用Canny边缘检测器在图像中找到边缘。

最后,我们在边缘图像中找到轮廓并从左到右对它们进行排序。这些轮廓中的每一个都代表图像中需要分类的数字。

接下来,我们现在需要处理这些数字中的每一个:

# loop over the contours
for (c,_) in cnts:
 # compute the bouding box for the rectangle
 (x,y,w,h) = cv2.boundingRect(c)
 # if the width is at least 7 pixels and the height
 # is at least 20 pixels, the contour is likely a digit
 if w>=7 and h>=20:
 # crop the ROI and then threshold the grayscale
 # ROI to reveal the digit
 roi = gray[y:y + h,x:x + w]
 thresh = roi.copy()
 T = mahotas.thresholding.otsu(roi)
 thresh[thresh > T] = 255
 thresh = cv2.bitwise_not(thresh)
 # deskew the image center its extent
 thresh = dataset.deskew(thresh, 20)
 thresh = dataset.center_extent(thresh, (20, 20))
 cv2.imshow("thresh", thresh)

接下来我们开始循环我们的轮廓图。使用cv2.boundingRect函数每个轮廓的边界框,该函数返回边界框的起始(x,y)坐标,后跟框的宽度和高度。

然后我们边界框的宽度和高度,以确保它至少有七个像素宽,二十个像素高(视情况而定,比如1的话可能宽度没有那么宽)。如果边界框区域不满足这些尺寸,则认为它太小而不是数字。如果尺寸检查成立,则使用NumPy阵列切片从灰度图像中提取感兴趣区域(ROI)。

此ROI现在保留将被分类的数字。但首先,我们需要应用一些预处理步骤。

首先是应用Otsu的阈值处理方法来分割背景中的前景(数字)(数字写在纸上)。正如在训练阶段一样,数字然后被去偏斜并转换到图像中心。

现在,我们可以对数字进行分类:

 # extract features from the image and classify it 
 hist = hog.describe(thresh)
 digit = model.predict([hist])[0]
 print("I think thath number is : {}".format(digit))
 # draw a rectangle around the digit, the show what 
 # digit was classified as 
 cv2.rectangle(image,(x,y),(x + w,y + h),(0,255,0),1)
 cv2.putText(image,str(digit),(x-10,y-10),
 cv2.FONT_HERSHEY_SIMPLEX,1.2,(0,255,0),2)
 cv2.imshow("Image",image)
 cv2.waitKey(0)

首先,我们通过调用HOG描述符的describe方法来计算阈值ROI的HOG特征向量。

HOG特征向量被馈入LinearSVC的预测方法,该方法根据HOG特征向量对ROI进行分类。

然后将分类的数字打印出来。最后在原始图片上显示预测的数字。

我们使用cv2.putText方法在原始图像上绘制数字。cv2.putText函数的第一个参数是我们想要绘制的图像,第二个参数是包含我们想要绘制的字符串。在这种情况下就是我们的数字了。接下来,我们提供将绘制文本的位置的(x,y)坐标。我们希望这个文本在ROI边界框的左边十个像素和上方十个像素。第四个参数是一个内置的OpenCV常量,用于定义将用于绘制文本的字体。第五个参数是文本的相对大小,第六个参数是文本的颜色(绿色),最后一个参数是文本的粗细(两个像素)。

最后执行我们的脚本程序。

python classify.py --model model\svm.cpickle --image images\test1.png

预测结果:

I think thath number is : 8
I think thath number is : 6
I think thath number is : 7
I think thath number is : 4
I think thath number is : 1

内容有点多,有点难,需要花时间好好消化!!!

博客地址(有完整代码):https://0leo0.github.io/2018/case_study_05.html

关注不迷路哦!!

相关推荐

为何越来越多的编程语言使用JSON(为什么编程)

JSON是JavascriptObjectNotation的缩写,意思是Javascript对象表示法,是一种易于人类阅读和对编程友好的文本数据传递方法,是JavaScript语言规范定义的一个子...

何时在数据库中使用 JSON(数据库用json格式存储)

在本文中,您将了解何时应考虑将JSON数据类型添加到表中以及何时应避免使用它们。每天?分享?最新?软件?开发?,Devops,敏捷?,测试?以及?项目?管理?最新?,最热门?的?文章?,每天?花?...

MySQL 从零开始:05 数据类型(mysql数据类型有哪些,并举例)

前面的讲解中已经接触到了表的创建,表的创建是对字段的声明,比如:上述语句声明了字段的名称、类型、所占空间、默认值和是否可以为空等信息。其中的int、varchar、char和decimal都...

JSON对象花样进阶(json格式对象)

一、引言在现代Web开发中,JSON(JavaScriptObjectNotation)已经成为数据交换的标准格式。无论是从前端向后端发送数据,还是从后端接收数据,JSON都是不可或缺的一部分。...

深入理解 JSON 和 Form-data(json和formdata提交区别)

在讨论现代网络开发与API设计的语境下,理解客户端和服务器间如何有效且可靠地交换数据变得尤为关键。这里,特别值得关注的是两种主流数据格式:...

JSON 语法(json 语法 priority)

JSON语法是JavaScript语法的子集。JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔花括号保存对象方括号保存数组JS...

JSON语法详解(json的语法规则)

JSON语法规则JSON语法是JavaScript对象表示法语法的子集。数据在名称/值对中数据由逗号分隔大括号保存对象中括号保存数组注意:json的key是字符串,且必须是双引号,不能是单引号...

MySQL JSON数据类型操作(mysql的json)

概述mysql自5.7.8版本开始,就支持了json结构的数据存储和查询,这表明了mysql也在不断的学习和增加nosql数据库的有点。但mysql毕竟是关系型数据库,在处理json这种非结构化的数据...

JSON的数据模式(json数据格式示例)

像XML模式一样,JSON数据格式也有Schema,这是一个基于JSON格式的规范。JSON模式也以JSON格式编写。它用于验证JSON数据。JSON模式示例以下代码显示了基本的JSON模式。{"...

前端学习——JSON格式详解(后端json格式)

JSON(JavaScriptObjectNotation)是一种轻量级的数据交换格式。易于人阅读和编写。同时也易于机器解析和生成。它基于JavaScriptProgrammingLa...

什么是 JSON:详解 JSON 及其优势(什么叫json)

现在程序员还有谁不知道JSON吗?无论对于前端还是后端,JSON都是一种常见的数据格式。那么JSON到底是什么呢?JSON的定义...

PostgreSQL JSON 类型:处理结构化数据

PostgreSQL提供JSON类型,以存储结构化数据。JSON是一种开放的数据格式,可用于存储各种类型的值。什么是JSON类型?JSON类型表示JSON(JavaScriptO...

JavaScript:JSON、三种包装类(javascript 包)

JOSN:我们希望可以将一个对象在不同的语言中进行传递,以达到通信的目的,最佳方式就是将一个对象转换为字符串的形式JSON(JavaScriptObjectNotation)-JS的对象表示法...

Python数据分析 只要1分钟 教你玩转JSON 全程干货

Json简介:Json,全名JavaScriptObjectNotation,JSON(JavaScriptObjectNotation(记号、标记))是一种轻量级的数据交换格式。它基于J...

比较一下JSON与XML两种数据格式?(json和xml哪个好)

JSON(JavaScriptObjectNotation)和XML(eXtensibleMarkupLanguage)是在日常开发中比较常用的两种数据格式,它们主要的作用就是用来进行数据的传...

取消回复欢迎 发表评论:

请填写验证码