半吊子 opencv学习笔记

OpenCV4.1+python学习笔记

你好,我是一名大三的非计算机专业的学生,最近因为参加比赛需要实现用摄像头寻找一个圆柱体坐标并测算距离的功能。方案打算用淘宝买的双目镜头进行特征值检测,识别到物体后进行测距,具体的实现过程需要进一步的学习。我打算先在我的台式的Ubuntu上成功搞定这个功能后移植到nano上。有一定python基础,Opencv从零开始自学,希望能用大概一周的时间搞定掉这个功能。
写这篇博客记录我的学习与实践过程,同时也希望帮助有需要的人,欢迎大家在评论里指出我的错误。
部分教程参考自EX2TRON
在这里做几个跳转方便以后看这篇文章

  • 环境搭建
  • 读取双目镜头视频信号
  • 对图像的基本操作
  • 阈值分割
  • 显示帧数
  • 图像几何变换
  • 模板匹配
  • 函数库
  • 遇到的问题

环境搭建

环境搭建教程
装环境时参考的这篇文章,中间遇到了一个在执行sudo apt install 时进程被占用的小问题,最开始用查找进程然后kill pid的方法,但始终有一个调用了apt的进程无法关闭,最后还是用了重启大法,重启后无卡顿每一步完美执行,最后进行测试

   import cv2
   print(cv2.__version__)
   4.1.1

返回opencv版本为4.1.1,结果无误,成功配置环境

读取双目镜头视频信号与加载图片

最开始读取视频只有左摄像头有输出,以为是输入源只设置了一个,于是使用下面的指令查看了一下我的双目镜头的video编号。

$ ls /dev/video*
/dev/video0

只有一个输入,经查询得知,我用的是双目的镜头,虽然有两个镜头的输出但是只有一根usb线,只在usb总线上只有一个视频输入源。
思考了一下,想起淘宝里的商品描述写着如果要输出双目的信号必须设置特定的分辨率,于是设置了一下分辨率,分辨率改为1280*480果然输出了两路视频信号。
Attention !!一定要设置一个退出的按键,否则点×是不好使的,因为只能×掉这一帧的图片,是关不掉程序的。在程序的末尾随便设置一个退出键就ok了

if cv2.waitKey(1) ==  ord('q'):
		break

读取双目的的完整程序如下

import cv2
capture = cv2.VideoCapture(0)
# 设置摄像头分辨率
capture.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
capture.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
while(True):
	# 获取一帧
	ret,frame = capture.read(1)
	#转换成灰度图
	gray = cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)
	cv2.imshow('frame',gray)
	if cv2.waitKey(1) ==  ord('q'):
		break

加载图片
使用cv2.imgread()函数,该函数有两个参数:
参数1:图片的文件名
如果图片放在当前文件夹下,直接写文件名就行了,如’lena.jpg’
否则需要给出绝对路径,如’D:\OpenCVSamples\lena.jpg’
参数2:读入方式,省略即采用默认值
cv2.IMREAD_COLOR:彩色图,默认值(1)
cv2.IMREAD_GRAYSCALE:灰度图(0)
cv2.IMREAD_UNCHANGED:包含透明通道的彩色图(-1)
注意路径名中不能有中文。

对图像的基本操作

我们先读入一张图片:

import cv2
img = cv2.imread('lena.jpg')

img[y,x]对应的是这张图片在y行x列像素点的BGR值(对于彩色图),对于灰度或者是单通道的图片只有一个值。

px = img[100, 90]
print(px)  # [103 98 197]
# 只获取蓝色blue通道的值
px_blue = img[100, 90, 0]
print(px_blue)  # 103

修改像素的值也是同样的方式:

img[100, 90] = [255, 255, 255]
print(img[100, 90])  # [255 255 255]

注意:这步操作只是内存中的img像素点值变了,因为没有保存,所以原图并没有更改。
图片的属性
img.shape获取图像的形状,图片是彩色的话,返回一个包含行数(高度)、列数(宽度)和通道数的元组,灰度图只返回行数和列数:

print(img.shape)  # (263, 247, 3)
# 形状中包括行数、列数和通道数
height, width, channels = img.shape
# img是灰度图的话:height, width = img.shape

img.dtype获取图像数据类型:

print(img.dtype)  # uint8

img.size获取图像总像素数:

print(img.size)  # 263*247*3=194883

ROI

Region of interest 感兴趣区域,用来节省算力,一副图片我要提取眼睛,眼睛肯定在脸上所以不是脸的区域我都不care。
提取ROI

face=img[100:200,150:160]		#提取第100行到200行中150列到160列为ROI

阈值分割

固定阈值分割

非常直接,就是像素点的阈值大于定义的阈值就是一类值,小于就是一类值。大二的时候做的智能车比赛用的鹰眼ov7725就是固定阈值分割的,将灰度图处理成黑白二值化的图像,阈值可以自己修改寄存器来制定大小。

自适应阈值分割

将图像分割为多个小块,取每个小块的均值之和进行加权计算得出的阈值为整幅图像的阈值,适用于明暗分布不均的图片。使用函数为cv2.adaptiveThreshold(),具体说明见函数库。
这里使用了python的matplotlib库进行图像显示,我第一次接触这个库,打算用多少学多少,看着函数名加上百度把这些函数的功能猜的七七八八,大概弄明白是干什么的就好。
看了半天matplotlib库结果编译提示GTK版本不兼容,想要弄好好像挺费劲就没用。

固定均值
半吊子 opencv学习笔记_第1张图片
小区域内取均值
半吊子 opencv学习笔记_第2张图片小区域内取高斯和

半吊子 opencv学习笔记_第3张图片自动阈值完整代码

import cv2
capture = cv2.VideoCapture(0)
# 设置摄像头分辨率
capture.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
capture.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
while(True):
	# 获取一帧
	ret,frame = capture.read()
	#转换成灰度图
	gray = cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)
	# 固定阈值
	ret,th1 =  cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
	# 自适应阈值
	th2 = cv2.adaptiveThreshold(
    gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 11, 4)
	th3 = cv2.adaptiveThreshold(
    gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 17, 6)
	cv2.imshow('frame',th2)
	if cv2.waitKey(1) ==  ord('q'):
		break


大津法(OTSU)

Otsu的教程,写的非常好
按照教程给列的式子自己推导了一下,数学太烂,非常简单的东西推了快半个小时,不过推完感觉对这个算法理解了一些。

当时做智能车的时候队友就一直吵吵要用这个算法不过当时时间比较紧张没有学,以为是特别高端的算法其实也不算太难。

大津法将图片视为背景和前景,区分背景和前景的是阈值,通过遍历所有阈值找出使这幅图像背景与前景区分最明显的一个值,反应到数学上就是方差,遍历求方差的最大值。

非常适合用在那种对比度明显的场合,比如说飞卡的赛道,用了这个算法就不用每次自己根据光照环境去手动调阈值了,大津前辈真是强!
半吊子 opencv学习笔记_第4张图片usb线太短了拍不到飞卡赛道,拿我的ipad做下示范。

大津算法完整代码:

import cv2
import matplotlib.pyplot as plt
capture = cv2.VideoCapture(0)
# 设置摄像头分辨率
capture.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
capture.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
while(True):
	# 获取一帧
	ret,frame = capture.read()
	#转换成灰度图
	gray = cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)
	# Otsu
	ret,th1 =  cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
	plt.subplot(3, 1, 2)
	plt.imshow(th1, 'gray')
	plt.xticks([]), plt.yticks([])
	plt.title('Otsu', fontsize=8)
	plt.subplot(3, 3, 3)
	plt.hist(frame.ravel(), 256)
	plt.xticks([]), plt.yticks([])
	plt.title('Histogram', fontsize=8)
	plt.show()
	if cv2.waitKey(1) ==  ord('q'):
		break


显示帧数

由于我做的是机器人比赛,在处理视频信号时帧数越高,控制周期越短,响应的更及时一些。所以显示帧数也是一个比较大的需求。

上网查了一下opencv中居然木有封装好的函数,找了别人写的c++版本,然后翻译成了python的。
具体思路就是可以获取操作系统启动以来的计时的周期和cpu的频率,每次开始获取图像时进行计时,获取到图像后将时间-开始时的时间可得获取图像所用周期,除以主频就是时间,就是fps的倒数。
完整程序如下:

import cv2
capture = cv2.VideoCapture(0)
# 设置摄像头分辨率
capture.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
capture.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
t = float (0)
while(True):
	# 开始计时,操作系统从启动到现在所经历的周期数
	t = cv2.getTickCount();
	# 获取一帧
	ret,frame = capture.read()
	if ret == True :
		#获取一帧消耗的时间=获取一帧所用的周期数/cpu的频率
		t = (cv2.getTickCount()-t) / cv2.getTickFrequency()
		#估算fps,就是获取一帧时间所用的倒数
		fps = round(1.0/t,2)
		fps = str(fps)
	#转换成灰度图
	gray = cv2.cvtColor(frame,cv2.COLOR_BGR2GRAY)
	cv2.putText(gray,fps,(5,20),cv2.FONT_HERSHEY_SIMPLEX,0.5,(0,0,0))
	cv2.imshow('frame',gray)
	if cv2.waitKey(1) ==  ord('q'):
		break

运行效果图:
半吊子 opencv学习笔记_第5张图片左上角是帧数,弄得有点太小了
以后有时间了把这个封装成函数,这么用太费劲了。
_(: 」∠)_

图像几何变换

缩放何旋转都比较简单就不做了,参照下边的两个函数就可以实现。

平移图像

要平移图片,我们需要定义下面这样一个矩阵,tx,ty是向x和y方向平移的距离:

M=
[
1 0 tx
0 1 ty
]

平移是用仿射变换函数cv2.warpAffine()实现的,这里边进行矩阵运算时调用了python的numpy的库需要import一下,numpy是python的一个负责进行矩阵运算的拓展库。
平移完整程序:

import cv2
import numpy as np
capture = cv2.VideoCapture(1)
# 设置摄像头分辨率
capture.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
capture.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
while(True):
	# 获取一帧
	ret,img = capture.read(1)
	rows,cols = img.shape[:2]
	#定义平移矩阵
	M = np.float32([[1,0,100],[0,1,50]])
	#用放射变换实现平移
	dst  = cv2.warpAffine(img,M,(cols,rows))
	#转换成灰度图
	gray = cv2.cvtColor(dst,cv2.COLOR_BGR2GRAY)
	cv2.imshow('frame',gray)
	if cv2.waitKey(1) ==  ord('q'):
		break

半吊子 opencv学习笔记_第6张图片

旋转图像

也是调用函数就可以实现,cv2.getRotationMatrix2D(),具体函数说明见函数库。

模板匹配

模板匹配就是大图中找下图,适应性不好,在图像大小和角度改变的情况下匹配度比较低,不适合用做物体识别。
模板匹配完整代码:

import numpy as np
import cv2 
capture = cv2.VideoCapture(0)
# 设置摄像头分辨率
capture.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
capture.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
template1 = cv2.imread('3.png',0)
h,w = template1.shape[:2]
threshold = 0.95
while(True):
	# 获取一帧
	ret,img = capture.read(1)
	#转换成灰度图
	gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
	res = cv2.matchTemplate(gray, template1, cv2.TM_CCOEFF_NORMED)
	min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
	print(max_val)
	left_top = max_loc  # 左上角
	right_bottom = (left_top[0] + w, left_top[1] + h)  # 右下角
	cv2.rectangle(gray, left_top, right_bottom, 255, 2)  # 画出矩形位置
	cv2.imshow('frame',gray)
	if cv2.waitKey(1) ==  ord('q'):
		break

半吊子 opencv学习笔记_第7张图片

函数库

cv2.VideoCapture()

要使用摄像头,需要使用cv2.VideoCapture(0)创建VideoCapture对象,参数0指的是摄像头的编号,如果你电脑上有两个摄像头的话,访问第2个摄像头就可以传入1,依此类推.

cv2.capture.read()

capture.read()函数返回的第1个参数ret(return value缩写)是一个布尔值,表示当前这一帧是否获取正确,第二个就是当前这一帧的图像了。

cv2.cvtColor()

cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

cv2.cvtColor()用来转换颜色,这里将彩色图转成灰度图。

cv2.waitKey()

if cv2.waitKey(1) ==  ord('q')://检测按键q是否被按下

检测按键是否被按下,十分友好,炒鸡好用!!

cv2.imgread()

参数1:图片的文件名
如果图片放在当前文件夹下,直接写文件名就行了,如’lena.jpg’
否则需要给出绝对路径,如’D:\OpenCVSamples\lena.jpg’
参数2:读入方式,省略即采用默认值
cv2.IMREAD_COLOR:彩色图,默认值(1)
cv2.IMREAD_GRAYSCALE:灰度图(0)
cv2.IMREAD_UNCHANGED:包含透明通道的彩色图(-1)

import cv2
# 加载灰度图
img = cv2.imread('lena.jpg', 0)

cv2.threshold()

threshold是阈值的意思
用来实现阈值分割,ret是return value缩写,代表当前的阈值,暂时不用理会。函数有4个参数:
参数1:要处理的原图,一般是灰度图
参数2:设定的阈值
参数3:最大阈值,一般为255
参数4:阈值的方式,主要有5种
五种阈值的方式如下:
半吊子 opencv学习笔记_第8张图片第一种THRESH_BIANARY:意思就是大于你设定的阈值就是maxval(一般都是255),小于你设定的阈值就是0。
半吊子 opencv学习笔记_第9张图片
第二种THRESH_BIANARY_INV:后边那个inv是invertrd 翻转的意思就是大于设定阈值的变成0小于的变成maxval。
半吊子 opencv学习笔记_第10张图片
第三种THRESH_TRUNC: trunc的意思是取整,就是大于阈值的都变成阈值,其他的都变成maxval。
半吊子 opencv学习笔记_第11张图片
第四种THTRSH_TOZERO:大于阈值的不变,其他的都取0.

半吊子 opencv学习笔记_第12张图片
第五种:大于阈值的变成0,其他的不变。
半吊子 opencv学习笔记_第13张图片
应用5种不同的阈值方法

ret, th1 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY)
ret, th2 = cv2.threshold(img, 127, 255, cv2.THRESH_BINARY_INV)
ret, th3 = cv2.threshold(img, 127, 255, cv2.THRESH_TRUNC)
ret, th4 = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO)
ret, th5 = cv2.threshold(img, 127, 255, cv2.THRESH_TOZERO_INV)

cv2.adaptiveThreshold()

看得出来固定阈值是在整幅图片上应用一个阈值进行分割,它并不适用于明暗分布不均的图片。 cv2.adaptiveThreshold()自适应阈值会每次取图片的一小部分计算阈值,这样图片不同区域的阈值就不尽相同。它有6个参数
参数1:要处理的原图
参数2:最大阈值,一般为255
参数3:小区域阈值的计算方式
ADAPTIVE_THRESH_MEAN_C:小区域内取均值
ADAPTIVE_THRESH_GAUSSIAN_C:小区域内加权求和,权重是个高斯核
参数4:阈值方式(跟前面讲的那5种相同)
参数5:小区域的面积,如11就是11*11的小块
参数6:最终阈值等于小区域计算出的阈值再减去此值
我理解这个函数的意思是通过对整幅图像进行分割,然后为每个小方块进行加权求和最后得出的阈值相对于手动设置的阈值效果更好一点。

cv2.resize()

缩放图片

# 按照指定的宽度、高度缩放图片
res = cv2.resize(img, (132, 150))
# 按照比例缩放,如x,y轴均放大一倍
res2 = cv2.resize(img, None, fx=2, fy=2, interpolation=cv2.INTER_LINEAR

cv2.flip()

镜像翻转图片。

dst = cv2.flip(img, 1)

其中,参数2 = 0:垂直翻转(沿x轴),参数2 > 0: 水平翻转(沿y轴),参数2 < 0: 水平垂直翻转。

cv2.getRotationMatrix2D()

旋转同平移一样,也是用仿射变换实现的,因此也需要定义一个变换矩阵。OpenCV直接提供了 cv2.getRotationMatrix2D()函数来生成这个矩阵,该函数有三个参数:
参数1:图片的旋转中心
参数2:旋转角度(正:逆时针,负:顺时针)
参数3:缩放比例,0.5表示缩小一半

45°旋转图片并缩小一半
M = cv2.getRotationMatrix2D((cols / 2, rows / 2), 45, 0.5)
dst = cv2.warpAffine(img, M, (cols, rows))
cv2.imshow('rotation', dst)
cv2.waitKey(0)

遇到的问题

1 安装环境

在我用笔记本装opencv的环境时,使用同样的步骤,每一步都没有问题,到最后验证版本时提示无法找到cv2模块,重试了几次都有这个问题。目前没找到解决办法,如果笔记本的ubuntu装不上的话,可能会考虑在Windows下装一下环境。
问题解决了 使用

sudo apt install python3-opencv

2 GTK版本不兼容

在使用Matplotlib库时提示过这个错误,一开始以为要重新安装OpenCV后来发现调用的时候使用这个就没问题了。

import matplotlib.pyplot as plt

Attention好像不是这么一回事,调用matplotlib库的时候不应使用显示视频的imshow,否则就会报版本不兼容的错误

你可能感兴趣的:(机器视觉)