python+opencv实现机器视觉基础技术(边缘提取,图像滤波,边缘检测算子,投影,车牌字符分割)
机器视觉是人工智能正在快速发展的一个分支。简单说来,机器视觉就是用机器代替人眼来做测量和判断。它是一项综合技术,包括图像处理、机械工程技术、控制、电光源照明、光学成像、传感器、模拟与数字视频技术、计算机软硬件技术(图像增强和分析算法、图像卡、 i/o卡等)。
下面介绍一些机器视觉的基础方法,用到的技术是python+opencv。python是一种很方便的高级编程语言,代码量少,而opencv是一个基于bsd许可发行的跨平台计算机视觉库,可以运行在linux、windows、android和mac os操作系统上。它轻量级而且高效——由一系列 c 函数和少量 c++ 类构成,同时提供了python、ruby、matlab等语言的接口,实现了图像处理和计算机视觉方面的很多通用算法。
一:边缘提取
在机器视觉中,一个非常基础的操作就是图像处理,而在图像处理中有一个十分重要的知识就是边缘提取。边缘提取,指数字图像处理中,对于图片轮廓的一个处理。对于边界处,灰度值变化比较剧烈的地方,就定义为边缘。也就是拐点,拐点是指函数发生凹凸性变化的点。和高数的导数有联系,将某个指定的物体的边缘进行提取出来。而用python+opencv可以很方便地进行边缘提取操作。
步骤如下:
1.对图像进行阈值分割并反色
首先需要新建一个python文件,导入cv2的库(opencv2的python库),并显示一张图片,代码为:
import cv2 # 读取本相对路径下的initial.bmp文件 image = cv2.imread ("initial.bmp") # 将image对应图像在图像窗口显示出来 cv2.imshow('initial',image) # waitkey使窗口保持静态直到用户按下一个键 cv2.waitkey(0)
对图像进行阈值分割,阈值设定为80,得到二值化灰度图,代码为:
# 对图像进行阈值分割,阈值设定为80,得到二值化灰度图 ret,image1 = cv2.threshold(image,80,255,cv2.thresh_binary) cv2.imshow('grayscale',image1)
将图像进行反色,代码如下:
image2 = image1.copy() # 复制图片 for i in range(0,image1.shape[0]): #image.shape表示图像的尺寸和通道信息(高,宽,通道) for j in range(0,image1.shape[1]): image2[i,j]= 255 - image1[i,j] cv2.imshow('colorreverse',image2)
2.边缘提取
下面就是边缘提取了,用findcontours差影法或者canny方法检测边缘,用原图像减去腐蚀后的收缩图像,提取边缘。代码如下:
# 边缘提取 img = cv2.cvtcolor(image2,cv2.color_bgr2gray) canny_img_one = cv2.canny(img,300,150) canny_img_two = canny_img_one.copy() # 复制图片 for i in range(0,canny_img_one.shape[0]): #image.shape表示图像的尺寸和通道信息(高,宽,通道) for j in range(0,canny_img_one.shape[1]): canny_img_two[i,j]= 255 - canny_img_one[i,j] cv2.imshow('edge',canny_img_two)
最终我们就可以得到一个边缘提取后的图形,如下:
二:图像滤波
图像滤波,即在尽量保留图像细节特征的条件下对目标图像的噪声进行抑制,是图像预处理中不可缺少的操作,其处理效果的好坏将直接影响到后续图像处理和分析的有效性和可靠性。
噪声就是由于成像系统、传输介质和记录设备等的不完善,数字图像在其形成、传输记录过程中或者在图像处理的某些环节当输入的像对象并不如预想时受到的污染。
而图像滤波有很多种滤波方式,本次实验采用的滤波方式是均值滤波,中值滤波和高斯滤波,以及高斯边缘检测。
均值滤波是典型的线性滤波算法,它是指在图像上对目标像素给一个模板,该模板包括了其周围的临近像素(以目标像素为中心的周围8个像素,构成一个滤波模板,即去掉目标像素本身),再用模板中的全体像素的平均值来代替原来像素值。
中值滤波是基于排序统计理论的一种能有效抑制噪声的非线性信号处理技术,中值滤波的基本原理是把数字图像或数字序列中一点的值用该点的一个邻域中各点值的中值代替,让周围的像素值接近的真实值,从而消除孤立的噪声点。
高斯滤波是一种线性平滑滤波,适用于消除高斯噪声,广泛应用于图像处理的减噪过程。高斯滤波就是对整幅图像进行加权平均的过程,每一个像素点的值,都由其本身和邻域内的其他像素值经过加权平均后得到。
边缘检测的目的是标识数字图像中亮度变化明显的点。高斯边缘检测是用高斯滤波的方式进行边缘检测。
步骤如下:
1.读取原图
首先展示原图,代码如下:
import cv2 import cv2 as cv # 读取本相对路径下的initial.bmp文件 image = cv2.imread ("initial.png") # 加入文本信息 cv2.puttext(image,'initial',(50,50),cv2.font_hershey_simplex,1.5,(255,0, 0),4) # 将image对应图像在图像窗口显示出来 cv2.imshow('initial',image) cv2.waitkey(0)
2.均值滤波
然后进行均值滤波,代码如下:
# 均值滤波 image2 = cv2.blur(image,(10,5)) cv2.puttext(image2,'averagefiltering',(50,50),cv2.font_hershey_simplex,1.5,(255,0, 0),4) cv2.imshow('averagefiltering',image2)
3.中值滤波
然后进行中值滤波,代码如下:
image3 = cv2.medianblur(image, 5) cv2.puttext(image3,'medianfiltering',(50,50),cv2.font_hershey_simplex,1.5,(255,0, 0),4) cv2.imshow('medianfiltering',image3)
4.高斯滤波
之后进行高斯滤波,代码如下:
# 高斯滤波 image4 = cv2.gaussianblur(image,(5,5),0) cv2.puttext(image4,'gaussianfilter',(50,50),cv2.font_hershey_simplex,1.5,(255,0, 0),4) cv2.imshow('gaussianfilter',image4)
5.高斯边缘检测
最终进行高斯边缘检测,代码如下:
# 高斯边缘检测 gau_matrix = np.asarray([[-2/28,-5/28,-2/28],[-5/28,28/28,-5/28],[-2/28,-5/28,-2/28]]) img = np.zeros(image.shape) hight,width = image.shape for i in range(1,hight-1): for j in range(1,width-1): img[i-1,j-1] = np.sum(image[i-1:i+2,j-1:j+2]*gau_matrix) image5 = img.astype(np.uint8) cv2.puttext(image5,'gaussianedgedetection',(50,50),cv2.font_hershey_simplex,1.5,(255,0, 0),4) cv2.imshow('gaussianedgedetection',image5)
三:边缘检测算子
边缘检测是图像处理和计算机视觉中的基本问题,边缘检测的目的是标识数字图像中亮度变化明显的点。包括深度上的不连续、表面方向不连续、物质属性变化和场景照明变化等。是计算机视觉的特征提取的一个领域。
在实际的图像分割中,往往只用到一阶和二阶导数,虽然原理上,可以用更高阶的导数,但是因为噪声的影响,在纯粹二阶的导数操作中就会出现对噪声的敏感现象,三阶以上的导数信息往往失去了应用价值。二阶导数还可以说明灰度突变的类型。在某些情况下,如灰度变化均匀的图像,只利用一阶导数可能找不到边界,此时二阶导数就能提供很有用的信息。二阶导数对噪声也比较敏感,解决的方法是先对图像进行平滑滤波,消除部分噪声,再进行边缘检测。不过,利用二阶导数信息的算法是基于过零检测的,因此得到的边缘点数比较少,有利于后继的处理和识别工作。
计算机视觉正是模仿人类视觉的这个过程。因此在检测物体边缘时,先对其轮廓点进行粗略检测,然后通过链接规则把原来检测到的轮廓点连接起来,同时也检测和连接遗漏的边界点及去除虚假的边界点。图像的边缘是图像的重要特征,是计算机视觉、模式识别等的基础,因此边缘检测是图象处理中一个重要的环节。
而在opencv中也有几个边缘检测方法,一阶的有roberts cross算子,prewitt算子,sobel算子,canny算子,krisch算子,罗盘算子;而二阶的还有marr-hildreth,在梯度方向的二阶导数过零点。
步骤如下:
1.显示原图
首先用下面的代码表现出原图像:
# 读取本相对路径下的dip_switch_02.bmp文件 src_s = cv2.imread ("dip_switch_02.bmp",0) cv2.imshow('src_s',src_s)
2.对图像进行反色
对图像进行反色,反色是与原色叠加可以变为白色的颜色,可以用白色(rgb:255,255,255)减去原来图片的颜色,因此对于黑白图片,我们先加载一个8位灰度图像,每一个像素对应的灰度值从0-255,则只需要读取每个像素的灰度值a,再将255-a写入,这样操作一遍后,图像就会反色了。代码如下:
src = cv.imread("dip_switch_02.bmp") height, width, channels = src.shape for row in range(height): for list in range(width): for c in range(channels): pv = src[row, list, c] src[row, list, c] = 255 - pv cv.imshow("afterdeal", src)
3.对图像用sobel方法进行边缘检测
sobel算子是一种用于边缘检测的离散微分算子,它结合了高斯平滑和微分求导。该算子用于计算图像明暗程度近似值。根据图像边缘旁边明暗程度把该区域内超过某个数的特定点记为边缘。sobel 算子在prewitt算子的基础上增加了权重的概念,认为相邻点的距离远近对当前像素点的影响是不同的,距离越近的像素点对应当前像素的影响越大,从而实现图像锐化并突出边缘轮廓。
sobel算子的边缘定位更准确,常用于噪声较多,灰度渐变的图像
sobel算子包含两组3x3的矩阵,分别为横向及纵向模板,将之与图像作平面卷积,即可分别得出横向及纵向的亮度差分近似值。
代码如下:
# 用sobel方法进行边缘检测 x = cv2.sobel(src,cv2.cv_16s,1,0) y = cv2.sobel(src,cv2.cv_16s,0,1) absx = cv2.convertscaleabs(x) # 转回uint8 # cv2.imshow("absx", absx) # 截图后反转 def inverse_color(image): height, width, channels = image.shape for row in range(height): for list in range(width): for c in range(channels): pv = image[row, list, c] image[row, list, c] = 255 - pv cv.imshow("result", image) one = cv.imread("2.jpg") inverse_color(one)
4.对图像用robert方法进行边缘检测
roberts 算子又称为交叉微分算子,它是基于交叉差分的梯度算法,通过局部差分计算检测边缘线条。常用来处理具有陡峭的低噪声图像,当图像边缘接近于正 45 度或负 45 度时,该算法处理效果更理想。其缺点是对边缘的定位不太准确,提取的边缘线条较粗。
# 用robert方法进行边缘检测 dst = cv2.addweighted(absx,0.5,absy,0.5,0) # cv2.imshow("dst", dst) # 截图后反转 def inverse_color(image): height, width, channels = image.shape for row in range(height): for list in range(width): for c in range(channels): pv = image[row, list, c] image[row, list, c] = 255 - pv cv.imshow("result", image) three = cv.imread ("3.jpg") inverse_color(three)
四:投影
投影就是就是把一个场景投影到摄像机的像平面上。类型有透视投影,仿射投影,弱透视投影与类透视投影等。
在opencv中,投影主要分为水平投影和垂直投影。水平投影是二维图像在y轴上的投影;垂直投影是二维图像在x轴上的投影。
在水平和垂直的方向上进行投影,先将投影的图像转化为灰度图,然后进行二值化阈值分割,之后在水平和垂直的方向上进行投影。所用库为cv2以及matplotlib数据分析库进行操作。
步骤如下:
1.显示原图
import cv2 import numpy as np from matplotlib import pyplot as plt img=cv2.imread('123.jpg')
2.垂直方向投影
img=cv2.imread('123.jpg') #读取图片,装换为可运算的数组 grayimage=cv2.cvtcolor(img,cv2.color_bgr2gray) #将bgr图转为灰度图 ret,thresh1=cv2.threshold(grayimage,130,255,cv2.thresh_binary)#将图片进行二值化(130,255)之间的点均变为255(背景) (h,w)=thresh1.shape #返回高和宽 a = [0 for z in range(0, w)] #记录每一列的波峰 for j in range(0,w): #遍历一列 for i in range(0,h): #遍历一行 if thresh1[i,j]==0: #如果改点为黑点 a[j]+=1 #该列的计数器加一计数 thresh1[i,j]=255 #记录完后将其变为白色 for j in range(0,w): #遍历每一列 for i in range((h-a[j]),h): #从该列应该变黑的最顶部的点开始向最底部涂黑 thresh1[i,j]=0 #涂黑 plt.imshow(thresh1,cmap=plt.gray()) plt.show() cv2.imshow('one',thresh1) cv2.waitkey(0)
3.水平方向投影
for j in range(0,h): for i in range(0,w): if thresh1[j,i]==0: a[j]+=1 thresh1[j,i]=255 for j in range(0,h): for i in range(0,a[j]): thresh1[j,i]=0 plt.imshow(thresh1,cmap=plt.gray()) plt.show()
五:车牌字符分割
车牌识别系统(vehicle license plate recognition,vlpr) 是计算机视频图像识别技术在车辆牌照识别中的一种应用。
车牌识别技术要求能够将运动中的汽车牌照从复杂背景中提取并识别出来,通过车牌提取、图像预处理、特征提取、车牌字符识别等技术,识别车辆牌号、颜色等信息,目前最新的技术水平为字母和数字的识别率可达到99.7%,汉字的识别率可达到99%。
要将车牌进行字符分割,先进行字符识别操作。这里进行的是直接用方框识别并括起来。需要的步骤是首先要让彩色的车牌图像转化为灰度图,再反色,阈值分割,再利用水平和垂直方向的投影依次截取需要的小方框的长和宽,进行识别。用到的技术是python+opencv其中的cv2库以及数据分析numpy库。
步骤如下:
1.读取原图
import cv2 import cv2 as cv import numpy as np image1 = cv.imread('123456.jpg',1) cv.imshow('image1', image1)
2.灰度转换
然后把图片转化为灰度图,这里设置读取图片用灰度图片读取即可。
image2 = cv.imread('123456.jpg',0) cv.imshow('image2', image2)
3.反色
接着将图像进行反色,记录宽高和深度,并用255减去灰度图颜色的宽高。
height, width, deep = image1.shape dst = np.zeros((height,width,1), np.uint8) for i in range(0, height): for j in range(0, width): graypixel = image2[i, j] dst[i, j] = 255-graypixel cv2.imshow('image3', dst)
4.阈值分割
然后将反色后的图像进行阈值分割,阈值设为100。
ret, thresh = cv2.threshold(dst, 100, 255, cv2.thresh_tozero) cv2.imshow('image4', thresh)
5.投影
之后在水平和垂直方向上进行投影,画出投影图,并记录投影小黑点的单位起始长度数组,设定两个函数,返回之后要用到的每个小分割矩形的特征点坐标值。
# 水平方向投影 def hproject(binary): h, w = binary.shape # 水平投影 hprojection = np.zeros(binary.shape, dtype=np.uint8) # 创建h长度都为0的数组 h_h = [0]*h for j in range(h): for i in range(w): if binary[j,i] == 0: h_h[j] += 1 # 画出投影图 for j in range(h): for i in range(h_h[j]): hprojection[j,i] = 255 return h_h # 垂直反向投影 def vproject(binary): h, w = binary.shape # 垂直投影 vprojection = np.zeros(binary.shape, dtype=np.uint8) # 创建 w 长度都为0的数组 w_w = [0]*w for i in range(w): for j in range(h): if binary[j, i ] == 0: w_w[i] += 1 for i in range(w): for j in range(w_w[i]): vprojection[j,i] = 255 return w_w
6.字符识别匹配分割
根据返回的数组,确定分割位置,进行字符识别匹配分割。
th = thresh h,w = th.shape h_h = hproject(th) start = 0 h_start, h_end = [], [] position = [] # 根据水平投影获取垂直分割 for i in range(len(h_h)): if h_h[i] > 0 and start == 0: h_start.append(i) start = 1 if h_h[i] ==0 and start == 1: h_end.append(i) start = 0 for i in range(len(h_start)): cropimg = th[h_start[i]:h_end[i], 0:w] if i ==0: pass w_w = vproject(cropimg) wstart , wend, w_start, w_end = 0, 0, 0, 0 for j in range(len(w_w)): if w_w[j] > 0 and wstart == 0: w_start = j wstart = 1 wend = 0 if w_w[j] ==0 and wstart == 1: w_end = j wstart = 0 wend = 1 # 当确认了起点和终点之后保存坐标 if wend == 1: position.append([w_start, h_start[i], w_end, h_end[i]]) wend = 0 # 确定分割位置 for p in position: cv2.rectangle(thresh, (p[0], p[1]), (p[2], p[3]), (0, 0, 255), 2) cv2.imshow('image7', thresh) cv.waitkey(0)
上一篇: DSA_06:队列