Opencv 库安装教程

OpenCV 是一个开源的计算机视觉库,OpenCV 库用C语言和 C++ 语言编写,可以在 Windows、Linux、Mac OS X 等系统运行。同时也在积极开发 Python、Java、Matlab 以及其他一些语言的接口,将库导入安卓和 iOS 中为移动设备开发应用。

OpenCV 库包含从计算机视觉各个领域衍生出来的 500 多个函数,包括工业产品质量检验、医学图像处理、安保领域、交互操作、相机校正、双目视觉以及机器人学。

首先我们来安装我们需要的模块:pip install opencv-python

pip的仓库一般都是在国外的服务器上,加了镜像源可以提供下载的速度。

pip install opencv-python -i https://pypi.tuna.tsinghua.edu.cn/simple

常见 pip 镜像源(国内源)

镜像源 链接
清华镜像源 https://pypi.tuna.tsinghua.edu.cn/simple
阿里云镜像源 http://mirrors.aliyun.com/pypi/simple/
中国科技大学镜像源 https://pypi.mirrors.ustc.edu.cn/simple/
华中理工大学镜像源 http://pypi.hustunique.com/
山东理工大学镜像源 http://pypi.sdutlinux.org/
豆瓣镜像源 http://pypi.douban.com/simple/

临时使用pip镜像源可以在使用pip的时候加参数:-i https://pypi.tuna.tsinghua.edu.cn/simple

imread 图片读取展示

imread 函数

imread函数的作用非常简单,从函数的名称也可以看出来,imread为image read的缩写,即图像读取的意思。那么imread函数的作用就很明显了,负责读取图像。

其实学过 matlab 的同学就会知道,matlab 中也有一个读取图像的函数也命名为 imread,这是 opencv 借鉴了 matlab 而命名的,因为在 opencv1.x时代,加载图像的函数并不叫 imread,二是由 cvLoadImage 函数负责。

retval = cv.imread(filename[, flags])

cv.imread:传入 1 是读取彩色图,传入 0 是读取灰度图

详细代码展示:

import cv2img_color = cv2.imread('wife_darling_02.jpg', 1)
# 读取彩色图
img_gray = cv2.imread('wife_darling_02.jpg', 0)
# 读取灰度图
cv2.imshow('color image', img_color)
cv2.imshow('gray image', img_gray)cv2.waitKey(0)
cv2.destroyAllWindows()

运行结果展示:

当然 cv2.IMREAD_COLORcv2.IMREAD_GRAYSCALE 同样奏效:

img_color = cv2.imread('wife_darling_02.jpg', cv2.IMREAD_COLOR)
# 读取彩色图
img_gray = cv2.imread('wife_darling_02.jpg', cv2.IMREAD_GRAYSCALE)
# 读取灰度图

视频 读取与处理

我们经常需要使用摄像头捕获实时图像。OpenCV 为这中应用提供了一个非常简单的接口。让我们使用摄像头来捕获一段视频,并把它转换成灰度视频显示出来。从这个简单的任务开始吧。

为了获取视频,你应该创建一个 VideoCapture 对象。他的参数可以是设备的索引号,或者是一个视频文件。设备索引号就是在指定要使用的摄像头。一般的笔记本电脑都有内置摄像头。所以参数就是 0。你可以通过设置成 1 或者其他的来选择别的摄像头。之后,你就可以一帧一帧的捕获视频了。但是最后,别忘了停止捕获视频。

import cv2capture = cv2.VideoCapture(0)
# 0为电脑内置摄像头
while True:ret, frame = capture.read()# 摄像头读取, ret为是否成功打开摄像头, true, false:frame为视频的每一帧图像frame = cv2.flip(frame, 1)# 摄像头是和人对立的,将图像左右调换回来正常显示。cv2.imshow("video", frame)c = cv2.waitKey(50)if c == 27: # 27 对应是 esc 键break

关于函数 waitKey(delay=None) 的介绍:@param delay 参数,等待的时长,会发生同步阻塞,单位是 milliseconds 毫秒,如果传值为 0,那么就是永久阻塞直到键盘事件发生。

关于函数 flip(src, flipCode, dst=None) 的介绍:@param flipCode 参数,翻转码,分别有三种取值:大于0,小于0,等于0,下面的源码注释也详细地介绍了。

def waitKey(delay=None): # real signature unknown; restored from __doc__
# @param delay Delay in milliseconds. 0 is the special value that means "forever".def flip(src, flipCode, dst=None): # real signature unknown; restored from __doc__""".   The function cv::flip flips the array in one of three different ways (row.   and column indices are 0-based):.   The example scenarios of using the function are the following:.   *   Vertical flipping of the image (flipCode == 0) to switch between.       top-left and bottom-left image origin. This is a typical operation.       in video processing on Microsoft Windows\* OS..   *   Horizontal flipping of the image with the subsequent horizontal.       shift and absolute difference calculation to check for a.       vertical-axis symmetry (flipCode \> 0)..   *   Simultaneous horizontal and vertical flipping of the image with.       the subsequent shift and absolute difference calculation to check.       for a central symmetry (flipCode \< 0)..   *   Reversing the order of point arrays (flipCode \> 0 or.       flipCode == 0).
"""

关于调用手机摄像头的方法,我的另一篇博客:基于opencv第三方视觉库,通过内网IP调用手机摄像头,实现人脸识别与图形监测

读取本地视频资源进行播放:

import cv2video = cv2.VideoCapture('video.mp4')if video.isOpened():open, frame = video.read()
else:open = Falsewhile open:ret, frame = video.read()if frame is None:breakif ret:gray = cv2.cvtColor(frame, cv2.COLOR_BGRA2GRAY)cv2.imshow('result', gray)if cv2.waitKey(10) & 0xFF == 27:break
video.release()
cv2.destroyAllWindows()

运行结果展示:

ROI 感兴趣区域 操作

ROI(region of interest),感兴趣区域。机器视觉、图像处理中,从被处理的图像以方框、圆、椭圆、不规则多边形等方式勾勒出需要处理的区域,称为感兴趣区域,ROI。

在Halcon、OpenCV、Matlab等机器视觉软件上常用到各种算子(Operator)和函数来求得感兴趣区域ROI,并进行图像的下一步处理。

使用像素坐标来提取ROI,前提是知道感兴趣区域的具体坐标范围 案例代码展示:

import cv2image = cv2.imread("wife_darling_02.jpg")print(image.shape)  # 图片的尺寸
width = image.shape[0]
height = image.shape[1]
ROI = image[0:int(width * 0.5), 0:int(height * 0.5)]
cv2.imshow("original", image)
cv2.imshow("ROI", ROI)cv2.waitKey()
cv2.destroyAllWindows()

运行结果展示:

使用鼠标选择想要的感兴趣区域(引用)

import cv2
global img
global first_point, sec_point, border_width
border_width = 2
def on_mouse(event, x, y, flags, param):global img, first_point, sec_point, border_widthROI_IMG = img.copy()if event == cv2.EVENT_LBUTTONDOWN:  # 左键点击first_point = (x, y)cv2.circle(ROI_IMG, first_point, 10, (0, 255, 0), border_width)cv2.imshow('src', ROI_IMG)elif event == cv2.EVENT_MOUSEMOVE and (flags & cv2.EVENT_FLAG_LBUTTON):  # 按住左键拖曳cv2.rectangle(ROI_IMG, first_point, (x, y), (255, 0, 0), border_width)cv2.imshow('src', ROI_IMG)elif event == cv2.EVENT_LBUTTONUP:  # 左键释放sec_point = (x, y)cv2.rectangle(ROI_IMG, first_point, sec_point, (0, 0, 255), border_width)cv2.imshow('src', ROI_IMG)min_x = min(first_point[0], sec_point[0])min_y = min(first_point[1], sec_point[1])width = abs(first_point[0] - sec_point[0])height = abs(first_point[1] - sec_point[1])cut_img = img[min_y:min_y + height, min_x:min_x + width]cv2.imshow('ROI_rectangle', cut_img)cv2.waitKey(0)cv2.destroyWindow('ROI_rectangle')
def main():global imgimg = cv2.imread('wife_darling_02.jpg')cv2.namedWindow('src')cv2.setMouseCallback('src', on_mouse)cv2.imshow('src', img)cv2.waitKey(0)cv2.destroyAllWindows()
if __name__ == '__main__':main()

运行结果展示:

图像 边界填充算法

Boundary fill is the algorithm used frequently in computer graphics to fill a desired color inside a closed polygon having the same boundary color for all of its sides.

边界填充是在计算机图形学中经常使用的算法,用于在其所有边都具有相同边界颜色的封闭多边形内填充所需颜色。

The most approached implementation of the algorithm is a stack-based recursive function.

该算法最接近的实现是基于堆栈的递归函数。

设置边界框方法:

cv2.copyMakeBorder(src, top, bottom, left, right, borderType,  value)
  • src: 输入的图片

  • top, bottom, left, right: 相应方向上的边框宽度

  • borderType: 定义添加边框的方法

  • value:如果 borderType 为 cv2.BORDER_CONSTANT 时需要填充的常数值

常见的 borderType 方法如下:

borderType 解释
cv2.BORDER_REPLICATE 复制法,复制最边缘像素
cv2.BORDER_REFLECT 反射法,在图像中指定像素的两边进行反射复制,如: gfedcba
cv2.BORDER_REFLECT_101 反射法,以最边缘像素为轴,如:dcba
cv2.BORDER_WRAP 外包装法,如:cdefgh
cv2.BORDER_CONSTANT 常量数值填充

效果展示如下:

我们从图中可以看出不同方法的区别。可能有同学可能会问,这颜色怎么这么奇怪。注意,在opencv中图像的颜色通道是BGR组成的,而matplot中颜色通道是RGB构成,在展示图像时,会有颜色的差别,所以尽量用opencv的图像展示方法。

案例代码展示:

import cv2
# 先指定在上下左右分别填充的大小
img = cv2.imread('wife_darling_02.jpg')
top_size, bottom_size, left_size, right_size = (10, 10, 10, 10)
# 填充函数cv2.copyMakeBorder(输入图像,填充多少,borderType=填充方法)# 复制填充
img = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, right_size, borderType=cv2.BORDER_REPLICATE)
cv2.imshow('BORDER_REPLICATE', img)
# 反射填充
img = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, right_size, borderType=cv2.BORDER_REFLECT)
cv2.imshow('BORDER_REFLECT', img)
# 反射101填充
img = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, right_size, borderType=cv2.BORDER_REFLECT_101)
cv2.imshow('BORDER_REFLECT_101', img)
# 外包装填充
img = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, right_size, borderType=cv2.BORDER_WRAP)
cv2.imshow('BORDER_WRAP', img)
# 常量填充,value=0
img = cv2.copyMakeBorder(img, top_size, bottom_size, left_size, right_size, borderType=cv2.BORDER_CONSTANT, value=0)
cv2.imshow('BORDER_CONSTANT', img)cv2.waitKey(0)
cv2.destroyAllWindows()

数值计算、图像融合

数值计算 直接相加

两张图片直接做加法,需要两张图像的 size 一样大,对应的像素点相加,由于每个像素点值的大小在 0 - 255 之间,因此相加后大于255的像素值自动减去255,如150+150=300,没有300的像素值,自动变成45。如果一张图像整体加上一个常量,即每个元素的增加一个常量值,亮度提升。

import cv2img_1 = cv2.imread('img/img_1.png')
img_2 = cv2.imread('img/img_2.png')img_add = img_1 + img_2
cv2.imshow('img_add', img_add)
cv2.waitKey(0)
print(img_1.shape)

图像融合

将两张图像按一定比例融合在一起,需要两张图像的 size 和通道数相同。

首先改变图像尺寸:

  • 指定具体的长度和高度: cv2.resize(图像, (长, 高))

  • 指定缩放比例: cv2.resize(图像, (0,0), fx=沿x轴缩放比例, fy=沿y轴缩放比例)

案例代码展示:

# 改变大小cv2.resize(图像,指定大小),接收返回值
# 注意先指定宽,再指定高
# 将img2变得和img1的size一样
import cv2img = cv2.imread('wife_02.jpg')
img_new = cv2.resize(img, (200, 200))
print(img_new.shape)  # (200, 200, 3)
# 不指定具体数值,只给出倍数关系,即图像沿x和y轴的缩放比例
img_new = cv2.resize(img_new, (0, 0), fx=1.5, fy=1.5)
print(img_new.shape)  # (300, 300, 3)
cv2.imshow('name', img_new)
cv2.waitKey(0)

图像融合

两张图像按一定比例融合:cv2.addWeighted(图像1, 权重1, 图像2, 权重2, 亮度偏置)

相当于 y = a x 1 + b x 2 + c y = a x_1 + b x_2 + c y=ax1​+bx2​+c,其中 a 、 b a、b a、b 代表权重, c c c 代表亮度上提亮值。

案例代码展示:

import cv2img_1 = cv2.imread('img/img_1.png')
img_1 = cv2.resize(img_1, (200, 200))
cv2.imshow('img_1', img_1)
img_2 = cv2.imread('img/img_2.png')
img_2 = cv2.resize(img_2, (200, 200))
cv2.imshow('img_2', img_2)
img_remix = cv2.addWeighted(img_1, 0.5, img_2, 0.5, 0)
cv2.imshow('img remix', img_remix)
cv2.waitKey(0)
cv2.destroyAllWindows()

代码运行结果展示:

图像 阈值化处理

什么是阈值?最简单的图像分割的方法。

应用举例:从一副图像中利用阈值分割出我们需要的物体部分(当然这里的物体可以是一部分或者整体)。这样的图像分割方法是基于图像中物体与背景之间的灰度差异,而且此分割属于像素级的分割。

为了从一副图像中提取出我们需要的部分,应该用图像中的每一个像素点的灰度值与选取的阈值进行比较,并作出相应的判断。(注意:阈值的选取依赖于具体的问题)

即:物体在不同的图像中有可能会有不同的灰度值。

图像阈值处理函数: ret, dst = cv2.threshold(src, thresh, maxval, type)

参数 解释
src 输入图,只能输入单通道图像,通常是灰度图
dst 输出图
thresh 阈值,是一个值,通常为 127
maxval 当图像超过了阈值或低于阈值 ( 由 type 决定) ,所赋予的值
type 二值化操作的类型
ret 阈值
dst 输出图

二值化操作的类型:

  • cv2.THRESH_BINARY:二值法,超过阈值thresh部分取maxval(设定的最大值),否则取0

  • cv2.THRESH_BINARY_INV:超过阈值的部分取0,小于阈值取maxval

  • cv2.THRESH_TRUNC:截断,大于阈值的部分设为阈值,小于阈值的不变

  • cv2.THRESH_TOZERO:大于阈值的部分不变,小于阈值的部分变成0。亮的部分不变,暗的部分变成黑点

  • cv2.THRESH_TOZERO_INV:大于阈值的变成0,小于阈值的不变。暗的部分不变,亮的部分变成黑点

案例代码展示:

# 方法1:输入灰度图,阈值127,大于阈值变成255,小于阈值变成0
import cv2
from matplotlib import pyplot as pltimg = cv2.imread('wife_darling_02.jpg')
ret, thresh1 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
# 方法2:大于阈值127变成0,小于阈值变成255。原本比较亮的变成黑点,原来暗的变成白点
ret, thresh2 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV)
# 方法3:大于阈值127的设为阈值,小于阈值的不变
ret, thresh3 = cv2.threshold(img, 127, 255, cv2.THRESH_TRUNC)
# 方法4:大于阈值127的值变成127,小于127的值不变
ret, thresh4 = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO)
# 方法5:小于127的值不变,大于127的值变成0
ret, thresh5 = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO_INV)# 绘图
titles = ['original', 'BINARY', 'BINARY_INV', 'TRUNC', 'TOZERO', 'TOZERO_INV']
images = [img, thresh1, thresh2, thresh3, thresh4, thresh5]
plt.figure(figsize=(8, 8))
for i in range(6):plt.subplot(2, 3, i + 1)plt.imshow(images[i], 'gray')plt.title(titles[i])
plt.show()

代码运行结果:

图像 平滑处理(滤波)

图像在获取、传输的过程中,可能会受到干扰的影响,会产生噪声,噪声是一种出错了的信号,噪声会造成图像粗糙,需要我们对图像进行平滑处理。图像去噪是一种信号滤波的方法,目的就是为了保留有用的信号。

噪声的基本特点就是灰度值不相关、空间位置都是随机的。

平滑的目的

  1. 模糊:在提取大目标之前,去除太小的细节,将目标内的小间断点连接起来。
  2. 消除噪声:改善图像质量,降低干扰。

平滑滤波对图像的低频分量增强,同时会消弱高频分量。用于消除图像中的随机噪声,起到平滑作用。

图像平滑处理的实质:是将图像中与周围像素点的像素值差异较大的进行处理,将其值调整为周围像素点像素值的近似值。


位于第 3 行第 3 列的像素点,与周围像素点值的大小存在明显差异。反映在图像上,该点周围的像素点都是灰度点,而该点的颜色较深,是一个黑色点可能是噪声,需要将该点的值调整为周围像素值的近似值。

基本方法

本节的方法主要是运用在空间域内,所谓空间域就是指直接在像素坐标处对其值进行操作。相应的,还有频域法,所谓频域法就是通过傅里叶变换、拉普拉斯变化,将图像数据映射到频域里,然后滤除噪声的频率,再把数据映射回空间域。

空间滤波增强技术,都是基于模板进行的,模板也叫做滤波器、掩膜,窗口。用某一模板对每个像元与其周围邻域的所有像元进行某种数学运算,得到该像元新的灰度值。新的灰度值不仅与该像元的灰度值有关,还与其邻域内的像元的灰度值有关。

实际上,模板的大小是可以人为确定的,可以 3 * 3,也可以 5 * 5。但一定是要是奇数,各种系数也可以通过我们的需要来确定。

图像平滑,有以下三种基本方法:

  1. 线性平滑:每一个像素的灰度值用它的邻域值代替,邻域为 NXN,N 取奇数。

  2. 非线性平滑:改进,取一个阈值,当像素值与其邻域平均值之间的差大于阈值,以均值代替;反之,取其本身值。

  3. 自适应平滑:物体边缘在不同的方向上有不同的统计特性,即不同的均值和方差,为保留一定的边缘信息,采用自适。

图像平滑处理的方式,下面主要介绍:

均值滤波、方框滤波、高斯滤波、中值滤波、双边滤波、2D卷积(自定义滤波)

什么是卷积运算?

卷积运算卷积,就是作加权求和的过程。卷积核就是模板模板,大小与邻域相同。邻域中的每个像素分别与卷积核中的每–个元素相乘,求和结果即为中心像素的新值。

卷积核中的元素称作加权系数(卷积系数),系数的大小及 排列顺序,决定了处理的类型。改变加权系数与符号,影响新值。我们这里所说的卷积与复变函数中的卷积公式有所差比,但表达的意思都是一样的,都是想进行加权求和。

当模板在图像移动时候,遇到没有领域的像素该如何处理?

  • 最简单的处理方法就是忽略这些像素,不去计算他
  • 在图像周围再复制一圈原图像的边界像素值
  • 如果我们计算出来的值超过255,我们将超过范围的值重新再人为指定一个值

均值滤波 Mean filtering

均值滤波是将一个 m ∗ n m*n m∗n( m , n m, n m,n 为奇数) 大小的 kernel 放在图像上,中间像素的值用 kernel 覆盖区域的像素平均值替代。平均滤波对高斯噪声的表现比较好,对椒盐噪声的表现比较差。

在进行均值滤波的时候要考虑需要对周围多少个像素取平均值,即确定核 的大小,通常情况下都是以当前像素点为中心,读行数和列数相等的一块区域内的所有像素点求平均。将计算得到的结果作为该点的像素。

例如,我们可以以当前像素点的像素周围 3x3 区域内所有像素点的像素取平均值,也可以对周围 5x5 区域内所有像素点的像素值取平均值。


因此,针对边缘的像素点,可以只取图像内存在的周围领域点的像素值均值。如图上右图所示,计算左上角的均值滤波结果时,仅取图中灰色背景的3*3领域内的像素值的平均值。

均值滤波 边框保留不变 如下所示:

缺陷:均值滤波本身存在着固有的缺陷,即它不能很好地保护图像细节,在图像去噪的同时也破坏了图像的细节部分,从而使图像变得模糊,不能很好地去除噪声点。特别是椒盐噪声。

根据上述运算,针对每一个像素点,都是与一个内部值均为1/25的55举证相乘,得到均值滤波的计算结果。示意图如图:

案例代码展示:

import cv2
Gn=cv2.imread("Gaussian_noise.jpg")
Mf_a=cv2.blur(Gn,(2,2))
Mf_b=cv2.blur(Gn,(5,5))
Mf_c=cv2.blur(Gn,(10,10))
cv2.imshow("噪声图像",Gn)
cv2.imshow("使用2×2的卷积核进行均值滤波",Mf_a)
cv2.imshow("使用5×5的卷积核进行均值滤波",Mf_b)
cv2.imshow("使用10×10的卷积核进行均值滤波",Mf_c)
cv2.waitKey()
cv2.destroyAllWindows()

中值滤波 Median filtering

中值滤波法是一种非线性平滑技术,它将每一像素点的灰度值设置为该点某邻域窗口内的所有像素点灰度值的中值。

中值滤波的方法是用某种结构的二维滑动模板,将板内像素按照像素值的大小进行排序,生成单调上升(或下降)的为二维数据序列。

基本原理

中值滤波会选取数字图像或数字序列中像素点及其周围临近像素点(一共有奇数个像素点)的像素值,将这些像素值排序,然后将位于中间位置的像素值作为当前像素点的像素值,让周围的像素值接近真实值,从而消除孤立的噪声点。

将其邻域设置为3×3大小,对其3×3邻域内像素点的像素值进行排序(升序降序均可),按升序排序后得到序列值为:[66,78,90,91,93,94,95,97,101]。在该序列中,处于中心位置(也叫中心点或中值点)的值是“93”,因此用该值替换原来的像素值 78,作为当前点的新像素值,处理结果如图:


在 OpenCV 中,实现中值滤波的函数是 cv2.medianBlur(),其语法格式如下:

dst = cv2.medianBlur(src, ksize)

函数参数解析:

  • dst:返回值,表示进行中值滤波后得到的处理结果。
  • src:是需要处理的图像,即源图像。它能够有任意数量的通道,并能对各个通道独立处理。图像深度应该是 CV_8U、CV_16U、CV_16S、CV_32F 或者 CV_64F 中的一种。
  • ksize:滤波核的大小。滤波核大小是指在滤波处理过程中其邻域图像的高度和宽度。需要注意,核大小必须是比 1 大的奇数,比如3、5、7等。

程序案例展示:

import cv2
Gn=cv2.imread("Gaussian_noise.jpg")
Gf=cv2.medianBlur(Gn,3)
cv2.imshow("噪声图像",Gn)
cv2.imshow("中值滤波处理结果图像",Gf)
cv2.waitKey()
cv2.destroyAllWindows()

腐蚀算法、膨胀算法

图解图像腐蚀、膨胀

经验之谈:形态学操作一般作用于二值图像,来连接相邻的元素(膨胀)或分离成独立的元素(侵蚀)。腐蚀和膨胀是针对图片中的白色(即前景)部分。

腐蚀操作

腐蚀运算的含义:每当在目标图像中找到一个与结构元素相同的子图像时,就把该子图像中与结构元素的原点位置对应的那个像素位置标注出来,目标图像上被标注出来的所有像素组成的集合,即为腐蚀运算的结果。其实质就是在目标图像中标出那些与结构元素相同的子图像的原点位置的像素。

算法描述:

  1. 获得源图像每行像素的宽度

  2. 创建一幅大小与源图像相同,所有像素置黑的目标图像

  3. 为防止越界,不处理最左边、最右边、最上边和最下边的像素,从第2行、第2列开始检查源图像中的像素点,先将当前点在目标图像中的对应像素点置白,如果当前点对应结构元素中为白色的那些点中有一个不是白色,则将目标图像中的对应像素点置为黑。

  4. 循环3步骤,直至处理完源图像

  5. 所得的目标图像即为腐蚀结果

腐蚀算法

输入有三个变量,分别为待处理的二值图像bin_im,结构元素kernel,结构元素的原点位置(起始点为[0,0]):

def img_erode(bin_im, kernel, center_coo):kernel_w = kernel.shape[0]kernel_h = kernel.shape[1]if kernel[center_coo[0], center_coo[1]] == 0:raise ValueError("指定原点不在结构元素内!")erode_img = np.zeros(shape=bin_im.shape)for i in range(center_coo[0], bin_im.shape[0]-kernel_w+center_coo[0]+1):for j in range(center_coo[1], bin_im.shape[1]-kernel_h+center_coo[1]+1):a = bin_im[i-center_coo[0]:i-center_coo[0]+kernel_w,j-center_coo[1]:j-center_coo[1]+kernel_h]  # 找到每次迭代中对应的目标图像小矩阵if np.sum(a * kernel) == np.sum(kernel):  # 判定是否“完全重合”erode_img[i, j] = 1return erode_img

opencv 提供的 API 方法:

mport cv2 as cvdef erode_demo(image):gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)ret, binary = cv.threshold(gray, 0, 255, cv.THRESH_BINARY_INV | cv.THRESH_OTSU)cv.imshow("binary", binary)kernel = cv.getStructuringElement(cv.MORPH_RECT, (3, 3))dst = cv.erode(binary, kernel)cv.imshow("erode", dst)src = cv.imread("shufa.jpg")
cv.imshow("shufa", src)
dilate_demo(src)
cv.waitKey(0)
cv.destroyAllWindows()

膨胀操作

膨胀运算的含义:先对结构元素 B 做关于其原点的反射得到反射集合 B,然后在目标图像 X 上将 B 平移x,则那些 B 平移后与目标图像X至少有一个非零公共元素相交时,对应的原点位置所组成的集合就是膨胀运算的结果。

可以理解为,将结构B在结构A上进行卷积操作,如果移动结构B的过程中,于结构A存在重叠区域,则记录该位置,所有移动结构B与结构A存在交集的位置的集合为结构A在结构B作用下的膨胀结果。

算法描述:

  1. 获得源图像每行像素的宽度

  2. 创建一幅大小与源图像相同,所有像素置黑的目标图像

  3. 为防止越界,不处理最左边、最右边、最上边和最下边的像素,从第2行、第2列开始检查源图像中的像素点,如果当前点对应结构元素中为白色的那些点中只要有一个点是白色,则将目标图像中的当前像素点置为白。

  4. 循环3步骤,直至处理完源图像

  5. 所得的目标图像即为膨胀结果

膨胀算法

输入有三个变量,分别为待处理的二值图像bin_im,结构元素kernel,结构元素的原点位置(起始点为[0,0]):

def img_dilate(bin_im, kernel, center_coo):kernel_w = kernel.shape[0]kernel_h = kernel.shape[1]if kernel[center_coo[0], center_coo[1]] == 0:raise ValueError("指定原点不在结构元素内!")dilate_img = np.zeros(shape=bin_im.shape)for i in range(center_coo[0], bin_im.shape[0] - kernel_w + center_coo[0] + 1):for j in range(center_coo[1], bin_im.shape[1] - kernel_h + center_coo[1] + 1):a = bin_im[i - center_coo[0]:i - center_coo[0] + kernel_w,j - center_coo[1]:j - center_coo[1] + kernel_h]dilate_img[i, j] = np.max(a * kernel)  # 若“有重合”,则点乘后最大值为0return dilate_img

opencv 提供的 API 方法:

import cv2 as cvdef dilate_demo(image):gray = cv.cvtColor(image, cv.COLOR_BGR2GRAY)ret, binary = cv.threshold(gray, 0, 255, cv.THRESH_BINARY_INV | cv.THRESH_OTSU)cv.imshow("binary", binary)kernel = cv.getStructuringElement(cv.MORPH_RECT, (5, 5))dst = cv.dilate(binary, kernel)cv.imshow("dilate", dst)src = cv.imread("shufa.jpg")
cv.imshow("shufa", src)
dilate_demo(src)
cv.waitKey(0)
cv.destroyAllWindows()

开操作 与 闭操作

开操作:先腐蚀后膨胀的操作称为开操作。她具有消除细小物体,在纤细处分离物体和平滑较大物体边界的作用。


闭操作:先膨胀后腐蚀的操作称为闭操作。它具有填充物体内细小空洞,连接临近物体和平滑边界的作用。

图像 梯度计算原理

图像梯度

图像梯度可以把图像看成二维离散函数,图像梯度简单来说就是求导,在图像上表现出来的就是提取图像的边缘(横向、纵向等等)。

图像是二维函数f(x,y),这时候的微分就是偏微分了:

现在考虑一个问题, ϵ \epsilon ϵ 这个值如何选取呢?上高数的时候,我们都是连续函数,因此这个值可以取得很小, ϵ \epsilon ϵ 可以理解为 x x x 的最小前进步伐,但是图像是一个离散的二维函数, ϵ \epsilon ϵ 不能取得很小,图像中像素来离散的,而像素之间最小的距离是 1, ϵ \epsilon ϵ 取为 1,所以,上面的公式变为:

由此,我们得到了图像在 x 方向和 y 方向的梯度公式了,值得注意的是,如果我们仔细观察公式就可发现,所谓 x 方向和 y 方向的梯度公式不就是相邻连个像素值之间的差值吗?是的,你没看错,当然,我们很多时候都会将两个方向的梯度进行合成:

由于上面的合成方式在数学计算上有点麻烦,因为直接采用绝对值计算:

我们总结一句话:图像梯度的本质:当前方向上相邻像素的差值。

梯度简单来说就是求导。OpenCV 提供了三种不同的梯度滤波器,或者说高通滤波器:Sobel,Scharr 和Laplacian。什么叫高通呢?其实就是和图像模糊相反。图像模糊是让低频通过,阻挡高频,这样就可以去除噪点,让锐利的边缘变平滑。高通滤波器就是让高频通过,阻挡低频,可以让边缘更加明显,增强图像。

卷积的作用除了实现图像模糊或者去噪,还可以寻找一张图像上所有梯度信息,这些梯度信息是图像的最原始特征数据,进一步处理之后就可以生成一些比较高级的特征用来表示一张图像实现基于图像特征的匹配,图像分类等应用。

图像梯度 - Sobel算子

Sobel算子是一种很经典的图像梯度提取算子,其本质是基于图像空间域卷积,背后的思想是图像一阶导数算子的理论支持。

sobel算子主要用于获得数字图像的一阶梯度,常见的应用和物理意义是边缘检测。(Laplacian边缘检测并不局限于水平方向或垂直方向,这是Laplacian与sobel的区别)


Sobel 算子是高斯平滑和微分操作的结合体,所以他的抗噪声能力很好。你可以设定求导的方向(xorder 或 yorder)。还可以设定使用的卷积核大小(ksize)。当 ksize = -1 时,会使用 3 x 3 的 Scharr 滤波器,他的效果要比 3 x 3的 Sobel 滤波器好,而且速度相同,所以在使用 3 x 3 滤波器时应该尽量使用 Scharr 滤波器(一般就用 Sobel 算子即可)

前一个 Sobel 矩阵与原始图像 A 进行卷积操作后得到的是右边的像素值减去左边的像素值;后一个 Sobel 矩阵与原始图像 A 进行卷积操作后得到的是下边的像素值减去上边的像素值。

dst = cv2.Sobel(src, ddepth, dx, dy, ksize)

ddepth 是图像的深度;dx 和 dy 分别表示水平和竖直方向;ksize 是 Sobel 算子的大小:

sobelx = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=3)
sobely = cv2.Sobel(img,cv2.CV_64F,0,1,ksize=3)res = np.hstack((sobelx,sobely))
cv_show(res,'res')

案例代码展示:

img = cv2.imread('lena.jpg',cv2.IMREAD_GRAYSCALE)
sobelx = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=3)
sobelx = cv2.convertScaleAbs(sobelx)
sobely = cv2.Sobel(img,cv2.CV_64F,0,1,ksize=3)
sobely = cv2.convertScaleAbs(sobely)
sobelxy = cv2.addWeighted(sobelx,0.5,sobely,0.5,0)
cv_show(sobelxy,'sobelxy')

运行结果展示:


图像梯度 - Scharr算子

Scharr - 算子:其实就是将Sobel算子的数增大了,这样对边缘的检测更敏感。

案例代码:

scharrx = cv2.Scharr(img,cv2.CV_64F,1,0)
scharry = cv2.Scharr(img,cv2.CV_64F,0,1)
scharrx = cv2.convertScaleAbs(scharrx)
scharry = cv2.convertScaleAbs(scharry)
scharrxy =  cv2.addWeighted(scharrx,0.5,scharry,0.5,0)

图像梯度 - laplacian算子

由 G 矩阵的形式可知,拉普拉斯算子只关心离中心点最近的几个边缘点。

三种算法比较:

img = cv2.imread('lena.jpg',cv2.IMREAD_GRAYSCALE)
sobelx = cv2.Sobel(img,cv2.CV_64F,1,0,ksize=3)
sobely = cv2.Sobel(img,cv2.CV_64F,0,1,ksize=3)
sobelx = cv2.convertScaleAbs(sobelx)
sobely = cv2.convertScaleAbs(sobely)
sobelxy=  cv2.addWeighted(sobelx,0.5,sobely,0.5,0)  scharrx = cv2.Scharr(img,cv2.CV_64F,1,0)
scharry = cv2.Scharr(img,cv2.CV_64F,0,1)
scharrx = cv2.convertScaleAbs(scharrx)
scharry = cv2.convertScaleAbs(scharry)
scharrxy =  cv2.addWeighted(scharrx,0.5,scharry,0.5,0) laplacian = cv2.Laplacian(img,cv2.CV_64F)
laplacian = cv2.convertScaleAbs(laplacian)   res = np.hstack((sobelxy,scharrxy,laplacian))
cv_show(res,'res')

运行结果展示:

第 1 张原图:第 2 张为 sobel 算子,第 3 张为 scharr 算子、最后一张为 laplacian 算子

Canny算子

Canny算子的基本思想是寻找梯度的局部最大值。首先使用高斯平滑滤波器卷积降噪,再用一对卷积阵列计算边缘梯度和方向,然后使用非极大值抑制移除非边缘线条,最后使用滞后阈值(高阈值和低阈值)检测并连接边缘。

图像 边缘检测算法

边缘检测是图形图像处理、计算机视觉和机器视觉中的一个基本工具,通常用于特征提取和特征检测,旨在检测一张数字图像中有明显变化的边缘或者不连续的区域,在一维空间中,类似的操作被称作步长检测(step detection)。边缘是一幅图像中不同屈原之间的边界线,通常一个边缘图像是一个二值图像。边缘检测的目的是捕捉亮度急剧变化的区域,而这些区域通常是我们关注的。

边缘检测算子分类

(1)一阶导数的边缘算子

通过模板作为核与图像的每个像素点做卷积和运算,然后选取合适的阈值来提取图像的边缘。常见的有Roberts 算子、Sobel 算子和 Prewitt 算子。

(2)二阶导数的边缘算子

依据于二阶导数过零点,常见的有 Laplacian 算子,此类算子对噪声敏感。

(3)其他边缘算子

前面两类均是通过微分算子来检测图像边缘,还有一种就是Canny算子,其是在满足一定约束条件下推导出来的边缘检测最优化算子。

边缘检测处理流程

边缘检测的一般处理流程如下:

  1. 获取图像。

  2. 用 ROI 裁剪图像。

  3. 图像滤波。对输入图像使用边缘滤波器是采集后的一个关键步骤,为了获取图像的边缘部分,在读取了输入图像之后,可以使用边缘滤波器获取边缘的梯度和方向。对于像素级边缘,Halcon 中提供了常用算子,如 sobel_amp、sobel_dir、edges_image、derivate_gauss、edges_color 等。

  4. 提取边缘。将符合条件的边缘提取出来,应用滤波器之后,可以使用阈值处理将图像中的高亮边缘提取出来。这里可以使用前文介绍的 threshold 算子,也可以使用 hysteresis_threshold 算子减少非关键的边缘,将符合条件的边缘提取出来。还可以进一步对结果进行非极大值抑制,然后使用 skelcton 算子将边缘绘制出来。

  5. 边缘处理。根据检测的需要对提取出的边缘进行处理,有时得到的边缘可能会比较粗略,往往大于 1 个像素,需要进行一些细化;有时得到的边缘并不连续,因此还需要对边缘做一些处理,如生成轮廓、合并非连续的边缘、分离背景等。

  6. 显示结果。将结果绘制在窗口中,以表现直观的边缘提取效果。

边缘检测处理 - Opencv

在Python中,Roberts算子主要通过numpy定义模板,再调用OpenCV的 filter2D() 函数实现边缘提取。该函数主要是利用内核实现对图像的卷积运算。filter2D() 函数用法如下所示:

dst = filter2D(src, ddepth, kernel[, dst[, anchor[, delta[, borderType]]]])
参数 作用
src 表示输入图像;
dst 表示输出的边缘图,其大小和通道数与输入图像相同;
ddepth 表示目标图像所需的深度;
kernel 表示卷积核,一个单通道浮点型矩阵;
anchor 表示内核的基准点,其默认值为 (-1,-1),位于中心位置;
delta 表示在储存目标图像前可选的添加到像素的值,默认值为0;
borderType 表示边框模式;

代码如下所示:

import cv2
import numpy as np
import matplotlib.pyplot as plt# 读取图像
img = cv2.imread('test.jpg')
img_RGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
# 转成RGB 方便后面显示grayImage = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 灰度化处理图像# Roberts算子
kernelx = np.array([[-1, 0], [0, 1]], dtype=int)
kernely = np.array([[0, -1], [1, 0]], dtype=int)
x = cv2.filter2D(grayImage, cv2.CV_16S, kernelx)
y = cv2.filter2D(grayImage, cv2.CV_16S, kernely)
# 转uint8
absX = cv2.convertScaleAbs(x)
absY = cv2.convertScaleAbs(y)
Roberts = cv2.addWeighted(absX, 0.5, absY, 0.5, 0)# 用来正常显示中文标签
plt.rcParams['font.sans-serif'] = ['SimHei']""" 显示图形titles = [u'原始图像', u'Roberts算子']
images = [img_RGB, Roberts]
for i in range(2):plt.subplot(1, 2, i + 1), plt.imshow(images[i], 'gray')plt.title(titles[i])plt.xticks([]), plt.yticks([])plt.show()
"""# 显示图形
plt.subplot(121),plt.imshow(img_RGB),plt.title('原始图像'), plt.axis('off') #坐标轴关闭
plt.subplot(122),plt.imshow(Roberts, cmap=plt.cm.gray ),plt.title('Roberts算子'), plt.axis('off')
plt.show()

运行结果展示:

各类算子实验比较

边缘检测算法主要是基于图像强度的一阶导数和二阶导数,但导数通常对噪声很敏感,因此需要采用滤波器来过滤噪声,并调用图像增强或阈值化算法进行处理,最后再进行边缘检测。下面是采用高斯滤波去噪和阈值化处理之后,再进行边缘检测的过程,并对比了四种常见的边缘提取算法。

import cv2
import numpy as np
import matplotlib.pyplot as plt# 读取图像
img = cv2.imread('wife_darling_02.jpg')
img_RGB = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)  # 转成 RGB 方便后面显示# 灰度化处理图像
grayImage = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)# 高斯滤波
gaussianBlur = cv2.GaussianBlur(grayImage, (3, 3), 0)# 阈值处理
ret, binary = cv2.threshold(gaussianBlur, 127, 255, cv2.THRESH_BINARY)# Roberts算子
kernelx = np.array([[-1, 0], [0, 1]], dtype=int)
kernely = np.array([[0, -1], [1, 0]], dtype=int)
x = cv2.filter2D(binary, cv2.CV_16S, kernelx)
y = cv2.filter2D(binary, cv2.CV_16S, kernely)
absX = cv2.convertScaleAbs(x)
absY = cv2.convertScaleAbs(y)
Roberts = cv2.addWeighted(absX, 0.5, absY, 0.5, 0)# Prewitt算子
kernelx = np.array([[1, 1, 1], [0, 0, 0], [-1, -1, -1]], dtype=int)
kernely = np.array([[-1, 0, 1], [-1, 0, 1], [-1, 0, 1]], dtype=int)
x = cv2.filter2D(binary, cv2.CV_16S, kernelx)
y = cv2.filter2D(binary, cv2.CV_16S, kernely)
absX = cv2.convertScaleAbs(x)
absY = cv2.convertScaleAbs(y)
Prewitt = cv2.addWeighted(absX, 0.5, absY, 0.5, 0)# Sobel算子
x = cv2.Sobel(binary, cv2.CV_16S, 1, 0)
y = cv2.Sobel(binary, cv2.CV_16S, 0, 1)
absX = cv2.convertScaleAbs(x)
absY = cv2.convertScaleAbs(y)
Sobel = cv2.addWeighted(absX, 0.5, absY, 0.5, 0)# Laplacian算子
dst = cv2.Laplacian(binary, cv2.CV_16S, ksize=3)
Laplacian = cv2.convertScaleAbs(dst)"""效果图
titles = ['Source Image', 'Binary Image', 'Roberts Image','Prewitt Image','Sobel Image', 'Laplacian Image']
images = [lenna_img, binary, Roberts, Prewitt, Sobel, Laplacian]
for i in np.arange(6):plt.subplot(2,3,i+1),plt.imshow(images[i],'gray')plt.title(titles[i])plt.xticks([]),plt.yticks([])
plt.show()
"""# 用来正常显示中文标签
plt.rcParams['font.sans-serif'] = ['SimHei']# # 显示图形
plt.subplot(231), plt.imshow(img_RGB), plt.title('原始图像')  # 坐标轴关闭
plt.subplot(232), plt.imshow(binary, cmap=plt.cm.gray), plt.title('二值图')
plt.subplot(233), plt.imshow(Roberts, cmap=plt.cm.gray), plt.title('Roberts算子')
plt.subplot(234), plt.imshow(Prewitt, cmap=plt.cm.gray), plt.title('Prewitt算子')
plt.subplot(235), plt.imshow(Sobel, cmap=plt.cm.gray), plt.title('Sobel算子')
plt.subplot(236), plt.imshow(Laplacian, cmap=plt.cm.gray), plt.title('Laplacian算子')plt.show()

运行结果展示:

各类算子的优缺点 比较

(1)Roberts 算子

Roberts 算子利用局部差分算子寻找边缘,边缘定位精度较高,但容易丢失一部分边缘,不具备抑制噪声的能力。该算子对具有陡峭边缘且含噪声少的图像效果较好,尤其是边缘正负 45 度较多的图像,但定位准确率较差。

(2)Sobel 算子

Sobel 算子考虑了综合因素,对噪声较多的图像处理效果更好,Sobel 算子边缘定位效果不错,但检测出的边缘容易出现多像素宽度。

(3) Prewitt 算子

Prewitt算子对灰度渐变的图像边缘提取效果较好,而没有考虑相邻点的距离远近对当前像素点的影响,与Sobel 算子类似,不同的是在平滑部分的权重大小有些差异。

(4)Laplacian 算子

Laplacian 算子不依赖于边缘方向的二阶微分算子,对图像中的阶跃型边缘点定位准确,该算子对噪声非常敏感,它使噪声成分得到加强,这两个特性使得该算子容易丢失一部分边缘的方向信息,造成一些不连续的检测边缘,同时抗噪声能力比较差,由于其算法可能会出现双像素边界,常用来判断边缘像素位于图像的明区或暗区,很少用于边缘检测。

唤醒手腕 - 爆肝 3 天整理出来关于 Opencv 计算机图像处理详细教程(更新中)相关推荐

  1. 爆肝三天整理!2021年阿里巴巴社招面试题总结,三轮技术面+HR面,总结的明明白白!

    前言: 今年是我第一次正式面试,期间看了很多网上的帖子,给了我很大帮助.面试结束后一直想着将面经整理出来,但实验室一直有事,老师天天找,所以一直没有找到机会.端午终于有些空闲时间,赶紧将面经整理出来, ...

  2. ❤️❤️爆肝3万字整理小白入门与提升分布式版本管理软件:Git,图文并茂(建议收藏)❤️❤️

    小白快速快入门Git 什么是Git SVN VS Git 什么是版本控制 安装Git 谁在操作? Git本地仓库 本地仓库构造 重点 Git常用基本操作 git add git commit git ...

  3. 【Windows核心编程+第一个内核程序】爆肝120小时整理-80%程序员最欠缺的能力,一半以上研究生毕业了还不懂?理解各种深度技术的基本功

  4. 万字长文爆肝Python基础入门【第二弹、超详细数据类型总结】

    目录 一.建立一个数据火车--列表 1.创建列表 2.列表元素的获取 3.列表元素的添加 4.列表元素的删除 5.列表元素的修改 二.列表的亲兄弟--元组 1.创建元组 2.元组元素的获取 3.元组和 ...

  5. 整理了一些面试题,还在更新中,有时间的可以看看

    github.com/petitspois/- 1. Front-End-interview-questions 1.1. HTML 1.1.1. DOCTYPE相关 1.1.2. web语义化 1. ...

  6. html css js知识整理,Html+Css+Js实用知识汇总(持续更新中...)

    Html篇 基本概念: html:超文本标记语言(Hyper Text Markup Language) html5:下一代的html xhtml:更严谨更纯净的html 表头 网站标题 //页面编码 ...

  7. 整理Python lxml读写xml文件详细教程

    Python lxml读写xml文件详细教程 xml文档解析 创建xml文档节点 更新xml文档节点 xml文档解析 下面展示详细解析过程. <?xml version='1.0' encodi ...

  8. python黑科技脚本_利用Python实现FGO自动战斗脚本,再也不用爆肝啦~

    欢迎点击右上角关注小编,除了分享技术文章之外还有很多福利,私信学习资料可以领取包括不限于Python实战演练.PDF电子文档.面试集锦.学习资料等. 利用Python实现FGO自动战斗脚本,再也不用爆 ...

  9. 熬夜爆肝!C++基础入门大合集【万字干货预警 建议收藏】

    前言 前几天有粉丝问我,大一结束c++刚学完,不知道自己目前学得怎么样?要掌握的知识点有没有都弄懂了?是否基础入门了? 这就安排上,熬夜爆肝整理出来的C++基础入门知识! 一篇文带你入门C++!一篇文 ...

最新文章

  1. c++采集声卡输出_耳上明珠 | 魅族双 C 耳机 — EP2C
  2. C语言博客作业03--函数
  3. web api 二
  4. Eclipse中查看没有源码的Class文件的方法
  5. CSAPP第4章家庭作业参考答案
  6. Linux下Verilog仿真过程(二)
  7. 更新Android Studio 3.0,你遇到坑了吗?
  8. python运维案例开发_python运维开发之第六天
  9. (005)CSS选择器的具体性与层叠
  10. 在VMware16虚拟机安装Ubuntu详细教程
  11. 嵌入式硬件常见英文总结
  12. 注册表写入二进制数据
  13. c语言c 哪个好学,C语言好学吗?
  14. VGA数模转换电阻匹配网络分析
  15. 程序员是什么又代表这多少角色?你想过吗?
  16. 只有一端开口的瓶子(C++)
  17. 使用wireshark没有抓到websocket包
  18. Git泄露 之Stash(做题过程)
  19. Android Studio 提示:更新 TKK 失败,请检查网络连接
  20. Pytorch+PyG实现GraphSAGE

热门文章

  1. 雨听 | 英语学习笔记(五)~作文范文:学生退学
  2. 百度地图实现自定义搜索
  3. 自编码器,变分自编码器和生成对抗网络异同
  4. Liquibase 使用(全)
  5. 云服务器的IP是显示哪里,云服务器的ip在哪里看
  6. Hibernate第三讲:Hibernate主键策略和Hibernate的查询方案
  7. explicit,violate,volatile,mutable
  8. 僵尸物联网大战区块链
  9. SQL 中的注释语句
  10. WildFly:如何从位于另一个应用程序中的 EJB 调用 EJB