简介 笔者因为近期的一些需求,需要一些图像处理算法来构建一些需要的内容,故整理了一下python-opencv的资料,这里面有一部分内容是笔者自己构建出来的,也有相当一部分内容是借鉴前人所留下的经验,因此,本文集前人之精髓,加之本人的整理,希望对大家有帮助。
开发环境 需要导入cv2,numpy,matplotlib三个库
pip install cv2//如果无法安装成功可以尝试一下pip install python-opencv
pip install numpy
pip install matplotlib
快速上手 读取图像
import cv2image = cv2.imread('temp.png')
显示图像
import cv2if __name__ == '__main__':
image = cv2.imread('temp.png')
# imshow显示图像
# 其中"image"为标题,image为需要显示的图像
cv2.imshow("image", image)
# 等待销毁
cv2.waitKey(0)
cv2.destroyAllWindows()
画圆
cv.circle(img, point, point_size, point_color, thickness)
#eg:cv2.circle(img, center, radius, (255, 255, 255), 1)
- img 为图片
- center为圆心
- radius为半径
- point_color为颜色
- thickness为画笔
可以把画点看上画一个小圆,参考画圆
cv2.circle(img, center, 1, (255, 255, 255), 1)
画线
cv2.line(img, (x1, y1), (x2, y2), (0, 255, 0), 2)
常用的图像处理方法 二值化
图像二值化即是设置一个阈值T,如果图像像素点的灰度值小于T此点值设为0(黑),反之设为255(白),最后图像只有黑和白两种颜色,如下图所示。
文章图片
opencv中的二值化-函数有两种,阈值化的图像二值化,自适应阈值化的二值化。
优质博客解析二值化固定阈值 代码实现
import cv2 as cv# 读入灰度图像
img = cv.imread('baby_g.jpg', 0)# 以127为阈值进行二值化分割图像,这个值可以自己调整
ret, th = cv.threshold(img, 127, 255, cv.THRESH_BINARY)
cv.imshow('thresh', th)
cv.waitKey(0)
cv.destroyAllWindows()
函数讲解
**cv.threshold()**用来实现阈值分割,ret是return value缩写,代表当前的阈值,暂时不用理会。函数有4个参数:cv.threshold() 参数4阈值方式详解
参数1:要处理的原图,一般是灰度图
参数2:设定的阈值
参数3:最大阈值,一般为255
参数4:阈值的方式,主要有5种。
import cv2 as cv
import matplotlib.pyplot as pltimg = cv.imread('gradient.jpg',0)# 应用5种不同的阈值方法
ret, th1 = cv.threshold(img, 127, 255, cv.THRESH_BINARY)
ret, th2 = cv.threshold(img, 127, 255, cv.THRESH_BINARY_INV)
ret, th3 = cv.threshold(img, 127, 255, cv.THRESH_TRUNC)
ret, th4 = cv.threshold(img, 127, 255, cv.THRESH_TOZERO)
ret, th5 = cv.threshold(img, 127, 255, cv.THRESH_TOZERO_INV)titles = ['Original', 'BINARY', 'BINARY_INV', 'TRUNC', 'TOZERO', 'TOZERO_INV']
images = [img, th1, th2, th3, th4, th5]# 使用Matplotlib显示
for i in range(6):
plt.subplot(2, 3, i + 1)
plt.imshow(images[i], 'gray')
plt.title(titles[i], fontsize=8)
plt.xticks([]), plt.yticks([])# 隐藏坐标轴plt.show()
文章图片
图像平滑处理
均值滤波
文章图片
高通滤波器
高通滤波器(HPF)是检测图像的某个区域,根据该像素与周围像素的亮度差值来提升该像素的亮度的滤波器。
代码实现
import cv2
import numpy as np
from scipy import ndimageif __name__ == '__main__':
kernel_3x3 = np.array([
[-1, -1, -1],
[-1, 8, -1],
[-1, -1, -1],
])kernel_5x5 = np.array([
[-1, -1, -1, -1, -1],
[-1, -1, 2, -1, -1],
[-1, 2, 4, 2, -1],
[-1, -1, 2, -1, -1],
[-1, -1, -1, -1, -1],
])img = cv2.imread('temp3.png',flags=cv2.IMREAD_GRAYSCALE)
k3 = ndimage.convolve(img, kernel_3x3)
k5 = ndimage.convolve(img, kernel_5x5)
# 高通滤波处理
GBlur = cv2.GaussianBlur(img, (11, 11), 0)
g_hpf = img - GBlurcv2.imshow('img', img)
cv2.imshow('3x3', k3)
cv2.imshow('5x5', k5)
cv2.imshow('g_hpf', g_hpf)
cv2.waitKey()
cv2.destroyAllWindows()
代码优化
import cv2if __name__ == '__main__':img = cv2.imread('temp3.png',flags=cv2.IMREAD_GRAYSCALE)GBlur = cv2.GaussianBlur(img, (11, 11), 0)
g_hpf = img - GBlurcv2.imshow('g_hpf', g_hpf)
cv2.waitKey()
cv2.destroyAllWindows()
低通滤波器
低通滤波器则在像素与周围像素的亮度差值小于一个特定值时,平滑该像素的亮度,主要用于去噪和模糊化。
边缘轮廓检测
Canny边缘检测 使用canny算子进行边缘检测
# 高通滤波降噪
gaussian = cv2.GaussianBlur(gray, (7, 7),0)
# 利用 Canny 进行边缘检测
edges = cv2.Canny(gaussian,160,180, apertureSize=3)
cv2.imshow("edges",edges)
circles = cv2.HoughCircles(edges, cv2.HOUGH_GRADIENT, 1,
60, param1=1500, param2=30, minRadius=50, maxRadius=0)
简单轮廓检测 轮廓检测也是图像处理中经常用到的。OpenCV-Python接口中使用cv2.findContours()函数来查找检测物体的轮廓。
注意事项:笔者使用的是Python3.7,OpenCV2。Python3.x与2.x语法不一样,OpenCV2.x与3.x也不一样。b站视频讲解
实现
import cv2
img = cv2.imread('D:\\test\\contour.jpg')
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
ret, binary = cv2.threshold(gray,127,255,cv2.THRESH_BINARY)
contours, hierarchy = cv2.findContours(binary,cv2.RETR_TREE,cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(img,contours,-1,(0,0,255),3)
cv2.imshow("img", img)
cv2.waitKey(0)
需要注意的是cv2.findContours()函数接受的参数为二值图,即黑白的(不是灰度图),所以读取的图像要先转成灰度的,再转成二值图,参见4、5两行。第六行是检测轮廓,第七行是绘制轮廓。运行结果 原图如下:
文章图片
检测结果如下:
文章图片
函数解析
cv2.findContours(image, mode, method[, contours[, hierarchy[, offset ]]])
形参解析
- img表示寻找轮廓的图像
- mode表示轮廓的检索模式,有四种
- RETR_LIST 从解释的角度来看,这中应是最简单的。它只是提取所有的轮廓,而不去创建任何父子关系。
- RETR_EXTERNAL 如果你选择这种模式的话,只会返回最外边的的轮廓,所有的子轮廓都会被忽略掉。
- RETR_CCOMP 在这种模式下会返回所有的轮廓并将轮廓分为两级组织结构。
- RETR_TREE 这种模式下会返回所有轮廓,并且创建一个完整的组织结构列表。它甚至会告诉你谁是爷爷,爸爸,儿子,孙子等。
- RETR_LIST 从解释的角度来看,这中应是最简单的。它只是提取所有的轮廓,而不去创建任何父子关系。
- method为轮廓的近似办法
- cv2.CHAIN_APPROX_NONE存储所有的轮廓点,相邻的两个点的像素位置差不超过1,即max(abs(x1-x2),abs(y2-y1))==1
- cv2.CHAIN_APPROX_SIMPLE压缩水平方向,垂直方向,对角线方向的元素,只保留该方向的终点坐标,例如一个矩形轮廓只需4个点来保存轮廓信息
- cv2.CHAIN_APPROX_TC89_L1,CV_CHAIN_APPROX_TC89_KCOS使用teh-Chinl chain 近似算法
contours
,还有一个是每条轮廓对应的属性hierarchy
。contours
cv2.findContours()函数首先返回一个list,list中每个元素都是图像中的一个轮廓,用numpy中的ndarray表示。这个概念非常重要。在下面drawContours中会看见。通过
print (type(contours))
print (type(contours[0]))
print (len(contours))
可以验证上述信息。会看到本例中有两条轮廓,一个是五角星的,一个是矩形的。每个轮廓是一个ndarray,每个ndarray是轮廓上的点的集合。
由于我们知道返回的轮廓有两个,因此可通过
cv2.drawContours(img,contours,0,(0,0,255),3)
和
cv2.drawContours(img,contours,1,(0,255,0),3)
分别绘制两个轮廓,关于该参数可参见下面一节的内容。同时通过
print (len(contours[0]))
print (len(contours[1]))
输出两个轮廓中存储的点的个数,可以看到,第一个轮廓中只有4个元素,这是因为轮廓中并不是存储轮廓上所有的点,而是只存储可以用直线描述轮廓的点的个数,比如一个“正立”的矩形,只需4个顶点就能描述轮廓了。
hierarchy
此外,该函数还可返回一个可选的hiararchy结果,这是一个ndarray,其中的元素个数和轮廓个数相同,每个轮廓contours[i]对应4个hierarchy元素hierarchy[i][0] ~hierarchy[i][3],分别表示后一个轮廓、前一个轮廓、父轮廓、内嵌轮廓的索引编号,如果没有对应项,则该值为负数。
通过
print (type(hierarchy))
print (hierarchy.ndim)
print (hierarchy[0].ndim)
print (hierarchy.shape)
运行结果
3
2
(1, 2, 4)
可以看出,hierarchy本身包含两个ndarray,每个ndarray对应一个轮廓,每个轮廓有四个属性。
轮廓的绘制 OpenCV中通过cv2.drawContours在图像上绘制轮廓。
cv2.drawContours()函数
cv2.drawContours(image, contours, contourIdx, color[, thickness[, lineType[, hierarchy[, maxLevel[, offset ]]]]])
- 第一个参数是指明在哪幅图像上绘制轮廓;
- 第二个参数是轮廓本身,在Python中是一个list。
- 第三个参数指定绘制轮廓list中的哪条轮廓,如果是-1,则绘制其中的所有轮廓。后面的参数很简单。其中thickness表明轮廓线的宽度,如果是-1(cv2.FILLED),则为填充模式。绘制参数将在以后独立详细介绍。
pentagram = contours[1] #第二条轮廓是五角星leftmost = tuple(pentagram[:,0][pentagram[:,:,0].argmin()])
rightmost = tuple(pentagram[:,0][pentagram[:,:,0].argmin()])cv2.circle(img, leftmost, 2, (0,255,0),3)
cv2.circle(img, rightmost, 2, (0,0,255),3)
得到的结果为如下:
文章图片
形态学操作
膨胀和腐蚀被称为形态学操作,它们通常在二进制图像上执行,类似于轮廓检测。通过将像素添加到该图像中的对象的感知边界,扩张放大图像中的明亮白色区域。侵蚀恰恰相反:它沿着物体边界移除像素并缩小物体的大小。
膨胀 要在OpenCV中扩展图像,您可以使用该dilate函数和三个输入:原始二进制图像,确定扩张大小的内核(无将导致默认大小),以及执行扩张的多次迭代(通常= 1)
在下面的例子中,我们有一个5x5的内核,它们在图像上移动,就像一个滤波器一样,如果任何周围的像素在5x5窗口中都是白色,则将像素变成白色!我们将使用草书字母“j”的简单图像作为示例。
腐蚀 为了侵蚀图像,我们采用erode函数
# Reads in a binary image
image = cv2.imread(‘j.png’, 0) # Create a 5x5 kernel of ones
kernel = np.ones((5,5),np.uint8)# Dilate the image
dilation = cv2.dilate(image, kernel, iterations = 1)
# Erode the image
erosion = cv2.erode(image, kernel, iterations = 1)
OPENING 如上所述,这些操作通常组合在一起以获得理想的结果!一种这样的组合称为Opening,先是侵蚀,然后是膨胀,这在降噪中是有用的,其中侵蚀首先消除噪声(并收缩物体)然后扩张再次扩大物体,但噪声将从先前的侵蚀中消失。为了在OpenCV中实现这一点,我们将函数morphologyEx与原始图像,我们想要执行的操作以及传入的内核一起使用。
相关实现
opening = cv2.morphologyEx(image, cv2.MORPH_OPEN, kernel)
文章图片
Closing Closing 是Opening的反向组合,它先是膨胀,然后是侵蚀。这对于关闭物体内的小孔或暗区很有用
它可用于关闭前景对象内的小孔或对象上的小黑点。
相关实现
closing = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel)
文章图片
Blob分析
Blob分析:(Blob Analysis)是对图像中相同像素的连通域进行分析,该连通域称为Blob。经二值化(Binary Thresholding)处理后的图像中色斑可认为是blob。Blob分析工具可以从背景中分离出目标,并可以计算出目标的数量、位置、形状、方向和大小,还可以提供相关斑点间的拓扑结构。在处理过程中不是对单个像素逐一分析,而是对图像的行进行操作。图像的每一行都用游程长度编码(RLE)来表示相邻的目标范围。这种算法与基于像素的算法相比,大大提高了处理的速度。
也可以称Blob(斑点)是指二维图像中和周围像素点存在颜色差异和灰度差异的特征区域,针对这些特征区域所提取出某些具有区域代表性的信息,就被称为Blob特征。由于Blob特征代表的是一个区域,所以相比单纯的角点,它的稳定性要更好,抗噪声能力也更强,所以它在图像配准上扮演了很重要的角色。例如在opencv::ccalib模块中,进行双目相机的三维重建时需要先利用两张分别由不同相机拍摄出的图像来进行立体匹配从而得到深度图,而深度图表现出来就是具有多个Blob的灰度图像,而进行立体匹配过程也会在两张图像之间寻找相似的区域特征。
同时有时图像中的Blob也是我们关心的区域,比如在医学与生物领域,我们需要从一些X光照片或细胞显微照片中提取一些具有特殊意义的斑点的位置或数量。
Blob特征分析算法使用相对简单的方式来检测斑点类的特征,OpenCV提供了一种方便的API
SimpleBlobDetector
来检测斑点并根据不同的特征对其进行过滤。简单来说就是可以判断图像里具有某一个特征的目标,如下图所示首先,需要先告诉
SimpleBlobDetector
需要检测什么类型的目标,这个时候就需要我们输入一些特定的参数来确定它。文章图片
代码实现
# Standard imports
import cv2
import numpy as np;
# Read image
im = cv2.imread("blob.jpg", cv2.IMREAD_GRAYSCALE)
# Set up the detector with default parameters.
detector = cv2.SimpleBlobDetector()
# Detect blobs.
keypoints = detector.detect(im)
# Draw detected blobs as red circles.
# cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS ensures the size of the circle corresponds to the size of blob
im_with_keypoints = cv2.drawKeypoints(im, keypoints, np.array([]), (0,0,255), cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
# Show keypoints
cv2.imshow("Keypoints", im_with_keypoints)
cv2.waitKey(0)
cv2.destroyAllWindows()
霍夫检测
霍夫直线检测
cv2.HoughLinesP(image, rho, theta, threshold, lines, minLineLength, maxLineGap)
- src:输入图像,必须8-bit的灰度图像
- rho:生成极坐标时候的像素扫描步长
- theta:生成极坐标时候的角度步长
- threshold:阈值,只有获得足够交点的极坐标点才被看成是直线
- lines:输出的极坐标来表示直线
- minLineLength:最小直线长度,比这个短的线都会被忽略。
- maxLineGap:最大间隔,如果小于此值,这两条直线 就被看成是一条直线。
import cv2
import numpy as np
img = cv2.imread('test19.jpg')
img1 = img.copy()
img2 = img.copy()
img = cv2.GaussianBlur(img, (3, 3), 0)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray, 50, 150, apertureSize=3)
lines = cv2.HoughLines(edges, 1, np.pi/180, 110)
for line in lines:
rho = line[0][0]
theta = line[0][1]
a = np.cos(theta)
b = np.sin(theta)
x0 = a*rho
y0 = b*rho
x1 = int(x0 + 1000*(-b))
y1 = int(y0 + 1000*(a))
x2 = int(x0 - 1000*(-b))
y2 = int(y0 - 1000*(a))
cv2.line(img1, (x1, y1), (x2, y2), (0, 0, 255), 2)
lines = cv2.HoughLinesP(edges, 1, np.pi/180, 30, 300, 5)
for line in lines:
x1 = line[0][0]
y1 = line[0][1]
x2 = line[0][2]
y2 = line[0][3]
cv2.line(img2, (x1, y1), (x2, y2), (0, 255, 0), 2)
cv2.imshow('houghlines3', img1)
cv2.imshow('edges', img2)
cv2.waitKey(0)
print(lines)
霍夫圆检测 霍夫圆变换的基本思路是认为图像上每一个非零像素点都有可能是一个潜在的圆上的一点,跟霍夫线变换一样,也是通过投票,生成累积坐标平面,设置一个累积权重来定位圆。
参考资料 CV2模块使用(详细教程)
Opencv3入门教程(一)基础知识
opencv 二值化图像详解 一文看懂各种二值化方法
形态学操作—膨胀与腐蚀
OpenCV中blob的概念以及OpenCV中BLOB特征提取与几何形状分类
OpenCV4学习笔记(49)——Blob特征分析算法
二值图像分析—霍夫圆检测
霍夫直线检测
Python OpenCV findContours()函数与drawContours()函数用法
【python|python-opencv快速上手教程】OpenCV-Python教程(11、轮廓检测)
推荐阅读
- c++|opencv安装及配置,对源码进行编译并配置环境
- opencv学习|OpenCV中的图像金字塔与图片尺寸缩放(18)
- Python中5个你可以能不知道的知识
- python四个性能检测工具,包括函数的运行内存、时间等等...
- Python中的JSON数据处理
- Pandas教程(Python中的DataFrame)
- Python|使用python对全球最新疫情情况进行可视化地图绘制!
- 人工智能|人工智能基础作业2
- 网络安全|2021第一届网刃杯网络安全大赛-baby-usb