Python计算机视觉编程
(一)OpenCV 的 Python 接口
OpenCV 是一个C++ 库,它包含了计算机视觉领域的很多模块。除了 C++ 和 C, Python 作为一种简洁的脚本语言,在 C++ 代码基础上的 Python 接口得到了越来越广泛的支持
(二)OpenCV 基础知识
2.1 读取和写入图像
载入一张jpg图像,打印出图像大小,对图像进行转换并保存为 png 格式:
# -*- coding: utf-8 -*-
import cv2
# 读取图像
im = cv2.imread('D:\\Python\\chapter10\\empire.jpg')
h,w = im.shape[:2]
print h,w
# 保存图像
cv2.imwrite('D:\\Python\\chapter10\\result.png',im)
控制台输出:
函数 imread() 返回图像为一个标准的 NumPy 数组,并且该函数能够处理很多不同格式的图像。
载入一张ppm图像,打印出图像大小,对图像进行转换并保存为 png 格式:
# -*- coding: utf-8 -*-
import cv2
# 读取图像
im = cv2.imread('D:\\Python\\chapter10\\images\\viff.000.ppm')
h,w = im.shape[:2]
print h,w
# 保存图像
cv2.imwrite('D:\\Python\\chapter10\\result3.png',im)
控制台输出:
2.2 颜色空间
在 OpenCV 中,图像不是按传统的 RGB 颜色通道,而是按 BGR 顺序(即 RGB 的倒序)存储的。读取图像时默认的是 BGR。
在读取原图像之后,紧接其后的是 OpenCV 颜色转换代码,其中最有用的一些转换代码如下:
- cv2.COLOR_BGR2GRAY (原图像转化为灰度图像)
- cv2.COLOR_BGR2RGB (原图像转化为RGB图像)
- cv2.COLOR_GRAY2BGR (灰度图像图像转化为BGR图像)
编写代码:
# -*- coding: utf-8 -*-
import cv2
import numpy as np
from pylab import *
from PIL import Image
# 添加中文字体支持
from matplotlib.font_manager import FontProperties
font = FontProperties(fname=r"c:\windows\fonts\SimSun.ttc", size=14)
# 读取图像
im = imread('D:\\Python\\chapter10\\empire.jpg')
im1 = cv2.imread('D:\\Python\\chapter10\\empire.jpg')
gray = cv2.cvtColor(im1,cv2.COLOR_BGR2GRAY)
BGR = cv2.cvtColor(gray,cv2.COLOR_GRAY2BGR)
figure()
subplot(131)
title(u'(a)原始图像', fontproperties=font)
axis('off')
imshow(im)
subplot(132)
title(u'(b)COLOR_BGR2GRAY转换后图像', fontproperties=font)
axis('off')
imshow(gray)
subplot(133)
title(u'(c)(b)经过COLOR_GRAY2RGB转换后图像', fontproperties=font)
axis('off')
imshow(BGR)
show()
代码运行效果如下:



扩展:
图像处理中有多种色彩空间,例如 RGB、HLS、HSV、HSB、YCrCb、CIE XYZ、CIE Lab等,经常要遇到色彩空间的转化,以便生成mask图等操作.
# -*- coding: utf-8 -*-
import cv2
import numpy as np
from pylab import *
from PIL import Image
import matplotlib.pyplot as plt
import cv2
img_BGR = cv2.imread('D:\\Python\\chapter10\\empire.jpg') # BGR
plt.subplot(3,3,1); plt.imshow(img_BGR);plt.axis('off');plt.title('BGR')
img_RGB = cv2.cvtColor(img_BGR, cv2.COLOR_BGR2RGB)
plt.subplot(3,3,2); plt.imshow(img_RGB);plt.axis('off');plt.title('RGB')
img_GRAY = cv2.cvtColor(img_BGR, cv2.COLOR_BGR2GRAY)
plt.subplot(3,3,3); plt.imshow(img_GRAY);plt.axis('off');plt.title('GRAY')
img_HSV = cv2.cvtColor(img_BGR, cv2.COLOR_BGR2HSV)
plt.subplot(3,3,4); plt.imshow(img_HSV);plt.axis('off');plt.title('HSV')
img_YcrCb = cv2.cvtColor(img_BGR, cv2.COLOR_BGR2YCrCb)
plt.subplot(3,3,5); plt.imshow(img_YcrCb);plt.axis('off');plt.title('YcrCb')
img_HLS = cv2.cvtColor(img_BGR, cv2.COLOR_BGR2HLS)
plt.subplot(3,3,6); plt.imshow(img_HLS);plt.axis('off');plt.title('HLS')
img_XYZ = cv2.cvtColor(img_BGR, cv2.COLOR_BGR2XYZ)
plt.subplot(3,3,7); plt.imshow(img_XYZ);plt.axis('off');plt.title('XYZ')
img_LAB = cv2.cvtColor(img_BGR, cv2.COLOR_BGR2LAB)
plt.subplot(3,3,8); plt.imshow(img_LAB);plt.axis('off');plt.title('LAB')
img_YUV = cv2.cvtColor(img_BGR, cv2.COLOR_BGR2YUV)
plt.subplot(3,3,9); plt.imshow(img_YUV);plt.axis('off');plt.title('YUV')
plt.show()


疑问:为什么GRAY格式不是灰色?
代码验证:
print('img_GRAY:',img_GRAY.shape)
print('img_RGB:',img_RGB.shape)

可以看到 GRAY 是单通道的灰度图了,也可以采用 PIL 库可视化一下,看到是灰度图。
img_pil = Image.fromarray(img_GRAY);
img_pil.show()

原因是matplotlib默认以彩色图显示图像,不是RGB三通道的会自行渲染成RGB格式,可以在参数里设置:
plt.imshow(img_GRAY,cmap=plt.cm.gray);

2.3 显示图像及结果
从文件中读取一幅图像,并创建一个整数图像表示:
# -*- coding: utf-8 -*-
import cv2
import numpy as np
from pylab import *
from PIL import Image
# 添加中文字体支持
from matplotlib.font_manager import FontProperties
font = FontProperties(fname=r"c:\windows\fonts\SimSun.ttc", size=14)
# 读取图像
im = cv2.imread('D:\\Python\\chapter10\\fisherman.jpg')
gray = cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
# 计算积分图像
intim = cv2.integral(gray)
# 归一化并保存
intim = (255.0*intim) / intim.max()
figure()
subplot(121)
title(u'(a)原始图像', fontproperties=font)
axis('off')
imshow(im)
subplot(122)
title(u'(b)归一化后图像', fontproperties=font)
axis('off')
imshow(intim)
show()

读取图像后,将其转化为灰度图像,函数 integral() 创建一幅图像,该图像的每个像素值是原图上方和左边强度值相加后的结果;这对于快速评估特征是一个非常有用的技巧
从一个种子像素进行泛洪填充:
# -*- coding: utf-8 -*-
import cv2
import numpy as np
from pylab import *
from PIL import Image
# 添加中文字体支持
from matplotlib.font_manager import FontProperties
font = FontProperties(fname=r"c:\windows\fonts\SimSun.ttc", size=14)
# 读取图像
im = cv2.imread('D:\\Python\\chapter10\\fisherman.jpg')
h,w = im.shape[:2]
# 泛洪填充
diff = (6,6,6)
mask = zeros((h+2,w+2),uint8)
im1 = cv2.floodFill(im,mask,(10,10), (255,255,0),diff,diff)
# 在OpenCV 窗口中显示结果
cv2.imshow('flood fill',im)
cv2.waitKey()
# 保存结果
cv2.imwrite('D:\\Python\\chapter10\\result.jpg',im)

对图像应用泛洪填充并在 OpenCV 窗口中显示结果。waitKey() 函数一直处于暂停状态,直到有按键按下,此时窗口才会自动关闭。这里的 floodfill() 函数获取(灰度或彩色)图像、一个掩膜(非零像素区域表明该区域不会被填充)、一 个种子像素以及新的颜色值来代替下限和上限阈值差的泛洪像素。泛洪填充以种子像素为起始,只要能在阈值的差异范围内添加新的像素,泛洪填充就会持续扩展。 不同的阈值差异由元组 (R,G,B) 给出。
用 OpenCV 提取 SURF 特征并画出提取出来的 SURF 特征:
# -*- coding: utf-8 -*-
import cv2
import numpy as np
from pylab import *
from PIL import Image
# 添加中文字体支持
from matplotlib.font_manager import FontProperties
font = FontProperties(fname=r"c:\windows\fonts\SimSun.ttc", size=14)
# 读取图像
im = cv2.imread('D:\\Python\\chapter10\\fisherman.jpg')
# 下采样
im_lowres = cv2.pyrDown(im)
# 变换成灰度图像
gray = cv2.cvtColor(im_lowres,cv2.COLOR_RGB2GRAY)
# 检测特征点
s = cv2.xfeatures2d.SIFT_create()
mask = uint8(ones(gray.shape))
keypoints = s.detect(gray,mask)
# 显示结果及特征点
vis = cv2.cvtColor(gray,cv2.COLOR_GRAY2BGR)
for k in keypoints[::10]:
cv2.circle(vis,(int(k.pt[0]),int(k.pt[1])),2,(0,255,0),-1)
cv2.circle(vis,(int(k.pt[0]),int(k.pt[1])),int(k.size),(0,255,0),2)
cv2.imshow('local descriptors', vis)
cv2.waitKey()

读取图像后,如果没有给定新尺寸,则用 pyrDown() 进行下采样,创建一个尺寸为原 图像大小一半的新图像,然后将该图像转换为灰度图像,并传递到一个 SURF 关键点检测对象;掩膜决定了在哪些区域应用关键点检测器。在画出检测结果时,将灰度图像转换成彩色图像,并用绿色通道画出检测到的关键点。在每到第十个关键点时循环一次,并在中心画一个圆环,每一个圆环显示出关键点的尺度(大小)。绘图函数 circle() 获取一幅图像、图像坐标(仅整数坐标)元组、半径、绘图的颜色元组以及线条粗细(-1 是实线圆环)。上图显示了提取出来的 SURF 特征。
遇到问题:
AttributeError: 'module' object has no attribute 'SURF'
问题解决:
修改
s = cv2.SURF()
为
s = cv2.xfeatures2d.SURF_create()
如果继续报错
AttributeError: module ‘cv2.cv2’ has no attribute ‘xfeatures2d’
需降低opencv版本
pip install opencv-contrib-python==3.3.0.10
OpenCV基础知识扩展:
(1)图像平移:
# -*- coding: utf-8 -*-
import cv2
import numpy as np
import matplotlib.pyplot as plt
# 读取图像
img = cv2.imread('D:\\Python\\chapter10\\road.jpg')
pr基础int(img.shape)
# 定义平移矩阵
M = np.float32([[1, 0, 500],
[0, 1, 100]])
# 变换后的大小
rows, cols = img.shape[:2]
# 平移
translation = cv2.warpAffine(img, M, (cols, rows))
plt.subplot(121)
plt.imshow(img)
plt.subplot(122)
plt.imshow(translation)
plt.show()

(2)图像缩放
# -*- coding: utf-8 -*-
import cv2
import matplotlib.pyplot as plt
from pylab import *
from PIL import Image
import sys
img = imread('D:\\Python\\chapter10\\jimei.jpg')
# 插值:interpolation
# None本应该是放图像大小的位置的,后面设置了缩放比例,
# 所有就不要了
res1 = cv2.resize(img, None, fx=2, fy=2, interpolation=cv2.INTER_CUBIC)
# 直接规定缩放大小,这个时候就不需要缩放因子
height, width = img.shape[:2]
res2 = cv2.resize(img, (2 * width, 2 * height), interpolation=cv2.INTER_CUBIC)
plt.subplot(131)
plt.imshow(img)
plt.subplot(132)
plt.imshow(res1)
plt.show()

(3)图像旋转
# -*- coding: utf-8 -*-
import cv2
import matplotlib.pyplot as plt
from pylab import *
from PIL import Image
import sys
img = imread('D:\\Python\\chapter10\\dog2.jpg')
rows, cols = img.shape[:2]
# 第一个参数旋转中心,第二个参数旋转角度,第三个参数:缩放比例
M = cv2.getRotationMatrix2D((cols / 2, rows / 2), 45, 1)
# 第三个参数:变换后的图像大小
res = cv2.warpAffine(img, M, (cols, rows))
plt.subplot(121)
plt.imshow(img)
plt.subplot(122)
plt.imshow(res)
plt.show()

(4)图像翻转
# -*- coding: utf-8 -*-
import cv2
import matplotlib.pyplot as plt
from pylab import *
from PIL import Image
import sys
# 添加中文字体支持
from matplotlib.font_manager import FontProperties
font = FontProperties(fname=r"c:\windows\fonts\SimSun.ttc", size=10)
img = imread('D:\\Python\\chapter10\\face.jpg')
#横向翻转
res1 = cv2.flip(img,1)
#纵向翻转
res2 = cv2.flip(img,0)
#同时翻转
res3 = cv2.flip(img,-1)
plt.subplot(141)
plt.imshow(img)
title(u'(a)原始图像', fontproperties=font)
plt.subplot(142)
plt.imshow(res1)
title(u'(b)横向翻转', fontproperties=font)
plt.subplot(143)
plt.imshow(res2)
title(u'(c)#纵向翻转', fontproperties=font)
plt.subplot(144)
plt.imshow(res3)
title(u'(d)#同时翻转', fontproperties=font)
plt.show()

(5)图像仿射
# -*- coding: utf-8 -*-
import cv2
import matplotlib.pyplot as plt
from pylab import *
from PIL import Image
import sys
# 添加中文字体支持
from matplotlib.font_manager import FontProperties
font = FontProperties(fname=r"c:\windows\fonts\SimSun.ttc", size=10)
img = imread('D:\\Python\\chapter10\\sun.jpg')
rows, cols = img.shape[:2]
pts1 = np.float32([[50, 50], [200, 50], [50, 200]])
pts2 = np.float32([[10, 100], [200, 50], [100, 250]])
M = cv2.getAffineTransform(pts1, pts2)
# 第三个参数:变换后的图像大小
res = cv2.warpAffine(img, M, (cols, rows))
plt.subplot(121)
plt.imshow(img)
plt.subplot(122)
plt.imshow(res)
plt.show()

(6)图像透射
# -*- coding: utf-8 -*-
import cv2
import matplotlib.pyplot as plt
from pylab import *
from PIL import Image
import sys
# 添加中文字体支持
from matplotlib.font_manager import FontProperties
font = FontProperties(fname=r"c:\windows\fonts\SimSun.ttc", size=10)
img = imread('D:\\Python\\chapter10\\flower4.jpg')
rows,cols = img.shape[:2]
pts1 = np.float32([[56,65],[238,52],[28,237],[239,240]])
pts2 = np.float32([[0,0],[200,0],[0,200],[200,200]])
M = cv2.getPerspectiveTransform(pts1,pts2)
res = cv2.warpPerspective(img,M,(200,150))
plt.subplot(121)
plt.imshow(img)
plt.subplot(122)
plt.imshow(res)
plt.show()

(7)画图
# -*- coding: utf-8 -*-
import cv2
import numpy as np
#生成一个空的黑底图像
img=np.zeros((512,512,3), np.uint8)#np.zeros()有两个参数,一个是创建的图片矩阵大小,另一个是数据类型,512,512是像素(第一个512像素高,第二个是512像素宽),3指BGR三种颜色uint8是用0-255表示所有颜色。
#画线
cv2.line(img,(0,0),(511,511),(255,228,225),5) #img图像名称,起点坐标,终点坐标,(255,228,225)是颜色,5是线的宽度
#画矩形
cv2.rectangle(img,(384,0),(510,128),(0,255,127),3) #img图像名称,左上顶点坐标,右下顶点坐标,(0,255,127)是颜色,线宽为3
cv2.rectangle(img,(0,0),(126,128),(0,255,127),3)
#画圆
cv2.circle(img,(447,63), 63, (255,181,197), -1) #图像名称,圆心坐标,半径63,(0,0,255)颜色,线宽为-1,当线宽-1时表示封闭图形的颜色填充。
#画圆
cv2.circle(img,(63,63), 63, (255,181,197), -1)
#画椭圆
cv2.ellipse(img,(256,256),(100,50),0,0,180,(144,238,144),-1) #图像名称,中心点坐标,长轴长度,短轴长度,旋转角度,图像出现的部分(长轴顺时针方向起始的角度和结束角度)0,180是下半个椭圆,颜色数组,线宽
#画多边形
pts = np.array([[240,240],[200,245],[235,255],[300,250]], np.int32) #图像名称,顶点列表(这个多边形在array中有四个顶点),True表示闭合,(0,255,255)是黄色,3是线宽
pts = pts.reshape((-1,1,2))
img = cv2.polylines(img,[pts],True,(0,255,255),3)
#添加文字
font=cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(img,'OpenCV',(10,500), font, 4,(0,255,255),2)#图像名称,字符串,坐标,字体,字号,(0,255,255)是黄色、线宽2
winname = 'img'
cv2.namedWindow(winname)
cv2.imshow(winname, img)
cv2.waitKey(0)
cv2.destroyWindow(winname)

(8)单通道与多通道转换
# -*- coding: utf-8 -*-
import cv2
import argparse
import numpy as np
from pylab import *
from PIL import Image
# 添加中文字体支持
from matplotlib.font_manager import FontProperties
font = FontProperties(fname=r"c:\windows\fonts\SimSun.ttc", size=14)
img1 = cv2.imread("D:\\Python\\chapter10\\flower3.jpg")
(B, G, R) = cv2.split(img1)#将一个多通道数组分离成几个单通道数组
figure()
subplot(131)
title(u'(a)R通道图像', fontproperties=font)
axis('off')
imshow(R)
subplot(132)
title(u'(b)G通道图像', fontproperties=font)
axis('off')
imshow(G)
subplot(133)
title(u'(c)B通道图像', fontproperties=font)
axis('off')
imshow(B)
show()

分离出来的各个通道并不是RGB本身的颜色的,彩色图像都是三个通道的。要想得到有颜色需要扩展通道。
(9)扩展通道
# -*- coding: utf-8 -*-
import cv2
import argparse
import numpy as np
from pylab import *
from PIL import Image
# 添加中文字体支持
from matplotlib.font_manager import FontProperties
font = FontProperties(fname=r"c:\windows\fonts\SimSun.ttc", size=14)
import cv2
import argparse
import numpy as np
img1 = cv2.imread("D:\\Python\\chapter10\\flower3.jpg")
(B, G, R) = cv2.split(img1)
# 通道扩展
zeros = np.zeros(img1.shape[:2], np.uint8)
img_R = cv2.merge([R, zeros, zeros])
img_G = cv2.merge([zeros, G, zeros])
img_B = cv2.merge([zeros, zeros, B])
figure()
subplot(131)
title(u'(a)R通道图像', fontproperties=font)
axis('off')
imshow(img_R)
subplot(132)
title(u'(b)G通道图像', fontproperties=font)
axis('off')
imshow(img_G)
subplot(133)
title(u'(c)B通道图像', fontproperties=font)
axis('off')
imshow(img_B)
show()

(10)像素的逻辑运算
# -*- coding: utf-8 -*-
import cv2
import argparse
import numpy as np
from pylab import *
from PIL import Image
import matplotlib.pyplot as plt
# 添加中文字体支持
from matplotlib.font_manager import FontProperties
font = FontProperties(fname=r"c:\windows\fonts\SimSun.ttc", size=14)
src1 = cv2.imread('D:\\Python\\chapter10\\sun.jpg')
src2 = cv2.imread('D:\\Python\\chapter10\\result3.jpg')
#像素的逻辑运算(与、或、非) 两张图片必须shape一致
#与运算 每个像素点每个通道的值按位与
dst1 = cv2.bitwise_and(src1, src2)
#或运算 每个像素点每个通道的值按位或
dst2 = cv2.bitwise_or(src1, src2)
#非运算 每个像素点每个通道的值按位取反
dst3 = cv2.bitwise_not(src1)
figure()
subplot(231)
title(u'(a)原图像src1', fontproperties=font)
axis('off')
plt.imshow(src1)
subplot(232)
title(u'(b)原图像src2', fontproperties=font)
axis('off')
plt.imshow(src2)
subplot(233)
title(u'(c)与运算', fontproperties=font)
axis('off')
plt.imshow(dst1)
subplot(234)
title(u'(d)或运算', fontproperties=font)
axis('off')
plt.imshow(dst2)
subplot(235)
title(u'(e)非运算', fontproperties=font)
axis('off')
plt.imshow(dst3)
show()

(11)像素的算术运算
# -*- coding: utf-8 -*-
import cv2
import argparse
import numpy as np
from pylab import *
from PIL import Image
import matplotlib.pyplot as plt
# 添加中文字体支持
from matplotlib.font_manager import FontProperties
font = FontProperties(fname=r"c:\windows\fonts\SimSun.ttc", size=14)
src1 = cv2.imread('D:\\Python\\chapter10\\sun.jpg')
src2 = cv2.imread('D:\\Python\\chapter10\\opencv.jpg')
#像素的加运算
dst1 = cv2.add(src1, src2)
#像素的减运算
dst2 = cv2.subtract(src1, src2)
#像素的除运算
dst3 = cv2.divide(src1, src2)
#像素的乘运算
dst4 = cv2.multiply(src1, src2)
figure()
subplot(231)
title(u'(a)原图像src1', fontproperties=font)
axis('off')
plt.imshow(src1)
subplot(232)
title(u'(b)原图像src2', fontproperties=font)
axis('off')
plt.imshow(src2)
subplot(233)
title(u'(c)像素的加运算', fontproperties=font)
axis('off')
plt.imshow(dst1)
subplot(234)
title(u'(d)像素的减运算', fontproperties=font)
axis('off')
plt.imshow(dst2)
subplot(235)
title(u'(e)像素的除法运算', fontproperties=font)
axis('off')
plt.imshow(dst3)
subplot(236)
title(u'(f)像素的乘法运算', fontproperties=font)
axis('off')
plt.imshow(dst4)
show()

(12)调节图片对比度和亮度
# -*- coding: utf-8 -*-
import cv2
import argparse
import numpy as np
from pylab import *
from PIL import Image
src = cv2.imread('D:\\Python\\chapter10\\face.jpg')
def contrast_brightness_image(img1, ratio, b): #第2个参数rario为对比度 第3个参数b为亮度
h, w, ch = img1.shape
img2 = np.zeros([h, w, ch], img1.dtype) # 新建的一张全黑图片和img1图片shape类型一样,元素类型也一样
dst = cv2.addWeighted(img1, ratio, img2, 1 - ratio, b)
cv2.imshow("after", dst)
cv2.imshow("begin", src)
contrast_brightness_image(src, 1, 100)
cv2.waitKey(0)
cv2.destroyAllWindows()

(三)处理视频
单纯使用 Python 来处理视频有些困难,因为需要考虑速度、编解码器、摄像机、操作系统和文件格式。目前还没有针对 Python 的视频库,使用 OpenCV 的 Python 接口是唯一不错的选择。
3.1 视频输入
OpenCV 能够很好地支持从摄像头读取视频。下面给出了一个捕获视频帧并在 OpenCV 窗口中显示这些视频帧的完整例子:
# -*- coding: utf-8 -*-
import cv2
# 设置视频捕获
cap = cv2.VideoCapture(0)
while True:
ret,im = cap.read()
cv2.imshow('video test',im)
key = cv2.waitKey(10)
if key == 27:
break
if key == ord('