图像、视频加载与显示
创建显示窗口
import cv2 if __name__ == "__main__": # 创建窗口 cv2.namedWindow('new', cv2.WINDOW_NORMAL) # 调整窗口大小 cv2.resizeWindow('new', 640, 480) # 显示窗口 cv2.imshow('new', 0) # 显示时长 key = cv2.waitKey(0) if key == 'q': exit() # 销毁窗口 cv2.destroyWindow()
运行结果
载入图片
import cv2 if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/WechatIMG12.jpeg") cv2.imshow('img', img) while True: key = cv2.waitKey(0) if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
保存文件
import cv2 if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/WechatIMG12.jpeg") cv2.imshow('img', img) while True: key = cv2.waitKey(0) if key & 0xFF == ord('q'): break elif key & 0xFF == ord('s'): cv2.imwrite("/Users/admin/Documents/帅照.png", img) cv2.destroyAllWindows()
当我们点击键盘”s”键的时候,运行结果
进入/Users/admin/Documents文件夹
(base) -bash-3.2$ ls | grep 帅照
帅照.png
摄像头视频采集
import cv2 if __name__ == "__main__": # 创建窗口 cv2.namedWindow('video', cv2.WINDOW_NORMAL) cv2.resizeWindow('video', 640, 480) # 获取视频设备 cap = cv2.VideoCapture(0) while True: # 从摄像头读视频桢 ret, frame = cap.read() # 将视频帧在窗口中显示 cv2.imshow('video', frame) key = cv2.waitKey(1) if key & 0xFF == ord('q'): break # 释放资源 cap.release() cv2.destroyAllWindows()
运行结果
这里可以看到摄像头已经打开,并开始采集视频。
读取视频文件
我们这里使用一段鹦鹉的视频,使用命令ffplay查看每秒播放帧数
./ffplay cockatoo.mp4
ffplay version N-104454-gd92fdc7144-tessus https://evermeet.cx/ffmpeg/ Copyright (c) 2003-2021 the FFmpeg developers
built with Apple clang version 11.0.0 (clang-1100.0.33.17)
configuration: --cc=/usr/bin/clang --prefix=/opt/ffmpeg --extra-version=tessus --enable-avisynth --enable-fontconfig --enable-gpl --enable-libaom --enable-libass --enable-libbluray --enable-libdav1d --enable-libfreetype --enable-libgsm --enable-libmodplug --enable-libmp3lame --enable-libmysofa --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenh264 --enable-libopenjpeg --enable-libopus --enable-librubberband --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libtheora --enable-libtwolame --enable-libvidstab --enable-libvmaf --enable-libvo-amrwbenc --enable-libvorbis --enable-libvpx --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxavs --enable-libxvid --enable-libzimg --enable-libzmq --enable-libzvbi --enable-version3 --pkg-config-flags=--static --enable-librtmp --enable-ffplay --enable-sdl2 --disable-ffmpeg --disable-ffprobe
libavutil 57. 7.100 / 57. 7.100
libavcodec 59. 12.100 / 59. 12.100
libavformat 59. 8.100 / 59. 8.100
libavdevice 59. 0.101 / 59. 0.101
libavfilter 8. 16.100 / 8. 16.100
libswscale 6. 1.100 / 6. 1.100
libswresample 4. 0.100 / 4. 0.100
libpostproc 56. 0.100 / 56. 0.100
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'cockatoo.mp4':
Metadata:
major_brand : isom
minor_version : 512
compatible_brands: isomiso2avc1mp41
encoder : Lavf56.4.101
Duration: 00:00:14.00, start: 0.000000, bitrate: 416 kb/s
Stream #0:0[0x1](und): Video: h264 (High 4:4:4 Predictive) (avc1 / 0x31637661), yuv444p(progressive), 1280x720, 387 kb/s, 20 fps, 20 tbr, 10240 tbn (default)
Metadata:
handler_name : VideoHandler
vendor_id : [0][0][0][0]
Stream #0:1[0x2](und): Audio: mp3 (mp4a / 0x6134706D), 16000 Hz, mono, fltp, 24 kb/s (default)
Metadata:
handler_name : SoundHandler
vendor_id : [0][0][0][0]
13.63 A-V: -0.031 fd= 0 aq= 0KB vq= 0KB sq= 0B f=0/0
我们可以看到有这么一段
Stream #0:0[0x1](und): Video: h264 (High 4:4:4 Predictive) (avc1 / 0x31637661), yuv444p(progressive), 1280x720, 387 kb/s, 20 fps, 20 tbr, 10240 tbn (default)
这里有一个20 fps,说明该视频是每秒播放20桢
import cv2 if __name__ == "__main__": # 创建窗口 cv2.namedWindow('video', cv2.WINDOW_NORMAL) cv2.resizeWindow('video', 640, 480) # 获取视频文件 cap = cv2.VideoCapture("/Users/admin/Downloads/cockatoo.mp4") while True: # 从文件读视频桢 ret, frame = cap.read() # 将视频帧在窗口中显示 cv2.imshow('video', frame) # 此处不能设为1,否则会过快,可以设的比播放视频每秒帧数长一点 key = cv2.waitKey(40) if key & 0xFF == ord('q'): break # 释放资源 cap.release() cv2.destroyAllWindows()
运行结果
摄像头采集数据输出为媒体文件
import cv2 if __name__ == "__main__": fourcc = cv2.VideoWriter_fourcc(*'MJPG') # 25为帧率,(1280, 720)为分辨率,该分辨率必须与设备摄像头分辨率保持一致 vw = cv2.VideoWriter("/Users/admin/Documents/out.mp4", fourcc, 25, (1280, 720)) # 创建窗口 cv2.namedWindow('video', cv2.WINDOW_NORMAL) cv2.resizeWindow('video', 640, 480) # 获取摄像头资源 cap = cv2.VideoCapture(0) # 判断摄像头是否打开 while cap.isOpened(): # 从摄像头读视频桢 ret, frame = cap.read() if ret: # 将视频帧在窗口中显示 cv2.imshow('video', frame) # 写数据到多媒体文件 vw.write(frame) key = cv2.waitKey(1) if key & 0xFF == ord('q'): break else: break # 释放资源 cap.release() vw.release() cv2.destroyAllWindows()
运行结果
(base) -bash-3.2$ ls | grep out
out.mp4
控制鼠标
import cv2 import numpy as np def mouse_callback(event, x, y, flags, userdata): print(event, x, y, flags, userdata) if __name__ == "__main__": cv2.namedWindow('mouse', cv2.WINDOW_NORMAL) cv2.resizeWindow('mouse', 640, 360) # 设置鼠标回调 cv2.setMouseCallback('mouse', mouse_callback, '123') # 设置背景为黑色 img = np.zeros((360, 640, 3), np.uint8) while True: cv2.imshow('mouse', img) key = cv2.waitKey(1) if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
当鼠标在黑色区域滑动的时候,控制台会将鼠标的坐标给打印出来
0 272 156 0 123
0 272 155 0 123
0 272 155 0 123
0 271 155 0 123
0 271 155 0 123
TrackBar的使用
TrackBar就是一种滑动条,滑动到不同的位置,获取相应的值做不同的处理。
import cv2 import numpy as np def callback(): pass if __name__ == "__main__": cv2.namedWindow('trackbar', cv2.WINDOW_NORMAL) # 创建trackbar,R是trackbar的名字,0是默认当前值,255是最大值 cv2.createTrackbar('R', 'trackbar', 0, 255, callback) cv2.createTrackbar('G', 'trackbar', 0, 255, callback) cv2.createTrackbar('B', 'trackbar', 0, 255, callback) # 纯黑色背景 img = np.zeros((480, 640, 3), np.uint8) while True: cv2.imshow('trackbar', img) r = cv2.getTrackbarPos('R', 'trackbar') g = cv2.getTrackbarPos('G', 'trackbar') b = cv2.getTrackbarPos('B', 'trackbar') img[:] = [b, g, r] key = cv2.waitKey(10) if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
trackbar取不同的值会有不同的背景色
OpenCV的色彩空间
RGB人眼的色彩空间
每一个像素有三种颜色——红色、绿色和蓝色。通过不同光源的组合,形成真彩色,有暗的,有明亮的。
上图中每一个方格都代表一个像素。
OpenCV默认使用的是BGR,BGR跟RGB的区别就是排列顺序的不同。电脑上一般的排列顺序都是RGB。
HSV/HSB/HSL
HSV代表的是色相、饱和度、明亮度。HSB和HSV是一个意思。
- Hue:色相,即色彩,如红色、蓝色
- Saturation:饱和度,颜色的纯度,值越大,纯度越高,最开始的时候是灰的,逐渐增大就纯度越高,如果是红色就是纯红,蓝色就是纯蓝
- Value:明度,代表更暗一些还是更亮一些,当更暗的时候,黑色的程度越高;更亮一些就黑色成分少一些。
该图中旋转一圈的过程中代表了不同的颜色。对于饱和度来说,以中心点为基础,底下是黑色,上面是白色,中间是黑与白之间的灰。越靠近于圆柱边缘的地方,颜色的纯度越高。而对于纵轴,底下是黑色的,越往上越来越亮,这个就是明亮度。
对于OpenCV来说更喜欢使用HSV,使用HSV在背景判断上要好过RGB,因为在一个背景中可能有各种绿色,使用HSV就可以统一将背景判断为绿色,而使用RGB就不太好判断,每一种成分都有。
判断背景是通过色度来进行判断的,上图中0度就是纯红,60度就是黄色,120度为绿色,180度为青色,240度为蓝色,300度为粉红。这里是不考虑从圆心到边缘的渐变的一些因素的。
HSL
- Hue:色相,即色彩,如红色、蓝色
- Saturation:饱和度
- Ligthness:亮度
HSL与HSV看起来差不多,但存在着不同。
这里左图是HSL的,右图是HSV的,对于HSL到最顶成的时候就是纯白,无论色相是什么,饱和度是什么。而HSV就没有这么夸张。我们基本上使用的都是HSV,HSL几乎是不使用的。
YUV
YUV主要用在视频领域。Y代表的是灰色图像,UV代表的是颜色。YUV来自于电视节目,以前的电视只有黑白电视,就只有这个Y,后来有了彩色电视,但是要兼容黑白电视剧,当彩色电视机播放黑白电视剧的时候就只播放这个Y。一般的YUV包含YUV4:2:0、YUV4:2:2、YUV4:4:4。
YUV4:2:0
上图中,4个Y对应2个U或者V。不同的间隔,U或者V都是不一定的。
色彩空间转换
import cv2 def callback(): pass if __name__ == "__main__": cv2.namedWindow('color', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") colorspaces = [cv2.COLOR_BGR2RGBA, cv2.COLOR_BGR2BGRA, cv2.COLOR_BGR2GRAY, cv2.COLOR_BGR2HSV_FULL, cv2.COLOR_BGR2YUV] cv2.createTrackbar('curcolor', 'color', 0, len(colorspaces) - 1, callback) while True: index = cv2.getTrackbarPos('curcolor', 'color') # 颜色空间转换 cvt_img = cv2.cvtColor(img, colorspaces[index]) cv2.imshow('color', cvt_img) key = cv2.waitKey(10) if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
trackbar取值为0的时候
trackbar取值为1的时候
trackbar取值为2的时候
trackbar取值为3的时候
trackbar取值为4的时候
ROI(Region of Image)
roi的意思是对图像的一个区域进行提取
import cv2 if __name__ == "__main__": cv2.namedWindow('roi', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") print(img.shape) roi = img[0:550, 750:1350] while True: # 颜色空间转换 cv2.imshow('roi', roi) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
(1080, 1920, 3)
我们这里需要注意的是img是一个numpy三维矩阵,它的第一个维度是图像的高,第二个维度是图像的宽,第三个维度是图像的通道数。
Mat
Mat就是矩阵,它的结构如下
对于Header头部来说,存放的是一些属性,包括维度、行数,列数。而Data是存放数据的地方,就是图像中的实际像素。总体如下
字段 | 说明 | 字段 | 说明 |
---|---|---|---|
dims | 维度 | channels | 通道数 RGB是3 |
rows | 行数 | size | 矩阵大小 |
cols | 列数 | type | dep+dt+chs CV_8UC3 |
depth | 像素的位深 | data | 存放数据 |
Mat拷贝
这里Mat A是第一个创建的Mat,Mat B是拷贝Mat A,这里我们可以看到Mat A和Mat B的Header是两部分,而Data是它们公用的,也就是说Mat A和Mat B的Header的指针指向的是同一块内存空间。所以当我们用Mat B来拷贝Mat A的时候,默认情况下属于浅拷贝。有关深浅拷贝的概念请参考浅析克隆
import cv2 if __name__ == "__main__": cv2.namedWindow('img1', cv2.WINDOW_NORMAL) cv2.namedWindow('img2', cv2.WINDOW_NORMAL) img1 = cv2.imread("/Users/admin/Documents/111.jpeg") # 浅拷贝 img2 = img1 # 将图像左上角变成红色小方块 img1[10:100, 10:100] = [0, 0, 255] while True: cv2.imshow('img1', img1) cv2.imshow('img2', img2) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
上面的两张图是两个不同的窗口,它们的左上角都有一小块红色的方块,说明,这种拷贝方式属于浅拷贝。
import cv2 if __name__ == "__main__": cv2.namedWindow('img1', cv2.WINDOW_NORMAL) cv2.namedWindow('img2', cv2.WINDOW_NORMAL) img1 = cv2.imread("/Users/admin/Documents/111.jpeg") # 深拷贝 img2 = img1.copy() # 将图像左上角变成红色小方块 img1[10:100, 10:100] = [0, 0, 255] while True: cv2.imshow('img1', img1) cv2.imshow('img2', img2) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
这里我们可以看到img2的左上角是没有红色小方块的,说明这是深拷贝。
Mat的属性
import cv2 if __name__ == "__main__": img = cv2.imread("/Users/admin/Documents/111.jpeg") print(img.shape) # 图像占用内存空间数,高度*宽度*通道数 print(img.size) # 图像中每个元素的位深 print(img.dtype)
运行结果
(1080, 1920, 3)
6220800
uint8
这里我们可以看到每个元素的类型是uint8,说明它是一个无符号8位整型,是从0~255的范围。
通道的分割与合并
这里我们需要明白一个概念,任何的单通道图像都是灰色的,而任何彩色图像都必须是三通道的。
import cv2 if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") # 分割图像的三个通道 b, g, r = cv2.split(img) while True: cv2.imshow('img', b) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
那么我们显示的是蓝色通道,为什么是黑白的呢?其实要显示蓝色通道的图像依然要合并另外两个通道,即红色通道和绿色通道,只不过这两个通道我们需要设置成纯黑。
import cv2 import numpy as np if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") # 分割图像的三个通道 b, g, r = cv2.split(img) filt = np.zeros((1080, 1920), np.uint8) # 合并三个通道,但只保留蓝色通道信息 imgnew = cv2.merge((b, filt, filt)) while True: cv2.imshow('img', imgnew) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
如果我们要一张纯蓝色的图片呢?
import cv2 import numpy as np if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) filt = np.zeros((1080, 1920), np.uint8) b = np.full((1080, 1920), 255, np.uint8) # 合并三个通道,但只保留蓝色通道信息 imgnew = cv2.merge((b, filt, filt)) while True: cv2.imshow('img', imgnew) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
对比两张图片,我们可以看到,在纯蓝图片中,蓝色通道中的所有像素值都是255,而从111.jpeg中蓝色通道的矩阵应该就是各不相同的像素大小最终显示出来的效果。
OpenCV图形绘制
画线
import cv2 if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") # 画一条白线(750, 550)为起始点坐标,(1350, 550)为终止点坐标 # (255, 255, 255)为颜色,用三通道表示,5为线宽 cv2.line(img, (750, 550), (1350, 550), (255, 255, 255), 5) while True: cv2.imshow('img', img) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
现在我们来画一条斜线,并增加锯齿感
import cv2 if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") # 画一条白线(750, 550)为起始点坐标,(1350, 550)为终止点坐标 # (255, 255, 255)为颜色,用三通道表示,8为线宽, # 1为锯齿度,越小锯齿感越强,越大越平滑 cv2.line(img, (759, 550), (1350, 900), (255, 255, 255), 8, 1) while True: cv2.imshow('img', img) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
在视频中画线
import cv2 if __name__ == "__main__": cv2.namedWindow('video', cv2.WINDOW_NORMAL) cv2.resizeWindow('video', 640, 480) # 获取视频文件 cap = cv2.VideoCapture("/Users/admin/Downloads/cockatoo.mp4") while True: # 从文件读视频桢 ret, frame = cap.read() if ret: cv2.line(frame, (0, 600), (1280, 600), (0, 0, 255), 5) # 将视频帧在窗口中显示 cv2.imshow('video', frame) # 此处不能设为1,否则会过快,可以设的比播放视频每秒帧数长一点 key = cv2.waitKey(40) if key & 0xFF == ord('q'): break else: key = cv2.waitKey(40) if key & 0xFF == ord('q'): break # 释放资源 cap.release() cv2.destroyAllWindows()
运行结果
画矩形
import cv2 if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") # 画一条白线(750, 550)为起始点坐标,(1350, 550)为终止点坐标 # (255, 255, 255)为颜色,用三通道表示,8为线宽, # 1为锯齿度,越小锯齿感越强,越大越平滑 # cv2.line(img, (759, 550), (1350, 900), (255, 255, 255), 8, 1) # 画一个红框(750, 0)为起始点坐标,(1350, 550)为终止点坐标 cv2.rectangle(img, (750, 0), (1350, 550), (0, 0, 255), 8) while True: cv2.imshow('img', img) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
画实心矩形
import cv2 if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") # 画一条白线(750, 550)为起始点坐标,(1350, 550)为终止点坐标 # (255, 255, 255)为颜色,用三通道表示,8为线宽, # 1为锯齿度,越小锯齿感越强,越大越平滑 # cv2.line(img, (759, 550), (1350, 900), (255, 255, 255), 8, 1) # 画一个红框(750, 0)为起始点坐标,(1350, 550)为终止点坐标 # -1表示线宽无限大,即为实心 cv2.rectangle(img, (750, 0), (1350, 550), (0, 0, 255), -1) while True: cv2.imshow('img', img) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
画圆
import cv2 if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") # 画一条白线(750, 550)为起始点坐标,(1350, 550)为终止点坐标 # (255, 255, 255)为颜色,用三通道表示,8为线宽, # 1为锯齿度,越小锯齿感越强,越大越平滑 # cv2.line(img, (759, 550), (1350, 900), (255, 255, 255), 8, 1) # 画一个红框(750, 0)为起始点坐标,(1350, 550)为终止点坐标 # -1表示线宽无限大,即为实心 # cv2.rectangle(img, (750, 0), (1350, 550), (0, 0, 255), -1) # 画一个红色的圆,(1050, 275)为圆心坐标,275为半径,5为线宽,16的锯齿度数 cv2.circle(img, (1050, 275), 275, (0, 0, 255), 5, 16) while True: cv2.imshow('img', img) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
画实心圆
import cv2 if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") # 画一条白线(750, 550)为起始点坐标,(1350, 550)为终止点坐标 # (255, 255, 255)为颜色,用三通道表示,8为线宽, # 1为锯齿度,越小锯齿感越强,越大越平滑 # cv2.line(img, (759, 550), (1350, 900), (255, 255, 255), 8, 1) # 画一个红框(750, 0)为起始点坐标,(1350, 550)为终止点坐标 # -1表示线宽无限大,即为实心 # cv2.rectangle(img, (750, 0), (1350, 550), (0, 0, 255), -1) # 画一个红色的圆,(1050, 275)为圆心坐标,275为半径,-1为线宽无限大,即为实心,16的锯齿度数 cv2.circle(img, (1050, 275), 275, (0, 0, 255), -1, 16) while True: cv2.imshow('img', img) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
画椭圆
import cv2 if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") # 画一条白线(750, 550)为起始点坐标,(1350, 550)为终止点坐标 # (255, 255, 255)为颜色,用三通道表示,8为线宽, # 1为锯齿度,越小锯齿感越强,越大越平滑 # cv2.line(img, (759, 550), (1350, 900), (255, 255, 255), 8, 1) # 画一个红框(750, 0)为起始点坐标,(1350, 550)为终止点坐标 # -1表示线宽无限大,即为实心 # cv2.rectangle(img, (750, 0), (1350, 550), (0, 0, 255), -1) # 画一个红色的圆,(1050, 275)为圆心坐标,275为半径,-1为线宽无限大,即为实心,16的锯齿度数 # cv2.circle(img, (1050, 275), 275, (0, 0, 255), -1, 16) # 画一个红色的椭圆,(1050, 275)为中心点坐标,(500, 275)为长宽的一半 # 第一个0为长方体角度起始值,第二个0为长方体角度终止值 # 360是椭圆的画线部分的度数 cv2.ellipse(img, (1050, 275), (500, 275), 0, 0, 360, (0, 0, 255), 5, 16) while True: cv2.imshow('img', img) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
如果我们要绘制椭圆的下半部分
import cv2 if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") # 画一条白线(750, 550)为起始点坐标,(1350, 550)为终止点坐标 # (255, 255, 255)为颜色,用三通道表示,8为线宽, # 1为锯齿度,越小锯齿感越强,越大越平滑 # cv2.line(img, (759, 550), (1350, 900), (255, 255, 255), 8, 1) # 画一个红框(750, 0)为起始点坐标,(1350, 550)为终止点坐标 # -1表示线宽无限大,即为实心 # cv2.rectangle(img, (750, 0), (1350, 550), (0, 0, 255), -1) # 画一个红色的圆,(1050, 275)为圆心坐标,275为半径,-1为线宽无限大,即为实心,16的锯齿度数 # cv2.circle(img, (1050, 275), 275, (0, 0, 255), -1, 16) # 画一个红色的椭圆,(1050, 275)为中心点坐标,(500, 275)为长宽的一半 # 第一个0为长方体角度起始值,第二个0为长方体角度终止值 # 180是椭圆的画线部分的度数 cv2.ellipse(img, (1050, 275), (500, 275), 0, 0, 180, (0, 0, 255), 5, 16) while True: cv2.imshow('img', img) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
如果我们要绘制椭圆的上半部分
import cv2 if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") # 画一条白线(750, 550)为起始点坐标,(1350, 550)为终止点坐标 # (255, 255, 255)为颜色,用三通道表示,8为线宽, # 1为锯齿度,越小锯齿感越强,越大越平滑 # cv2.line(img, (759, 550), (1350, 900), (255, 255, 255), 8, 1) # 画一个红框(750, 0)为起始点坐标,(1350, 550)为终止点坐标 # -1表示线宽无限大,即为实心 # cv2.rectangle(img, (750, 0), (1350, 550), (0, 0, 255), -1) # 画一个红色的圆,(1050, 275)为圆心坐标,275为半径,-1为线宽无限大,即为实心,16的锯齿度数 # cv2.circle(img, (1050, 275), 275, (0, 0, 255), -1, 16) # 画一个红色的椭圆,(1050, 275)为中心点坐标,(500, 275)为长宽的一半 # 第一个0为长方体角度起始值,第二个0为长方体角度终止值 # 180是椭圆的画线部分的度数 cv2.ellipse(img, (1050, 275), (500, 275), 180, 0, 180, (0, 0, 255), 5, 16) while True: cv2.imshow('img', img) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
如果我们要绘制一些斜的椭圆
import cv2 if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") # 画一条白线(750, 550)为起始点坐标,(1350, 550)为终止点坐标 # (255, 255, 255)为颜色,用三通道表示,8为线宽, # 1为锯齿度,越小锯齿感越强,越大越平滑 # cv2.line(img, (759, 550), (1350, 900), (255, 255, 255), 8, 1) # 画一个红框(750, 0)为起始点坐标,(1350, 550)为终止点坐标 # -1表示线宽无限大,即为实心 # cv2.rectangle(img, (750, 0), (1350, 550), (0, 0, 255), -1) # 画一个红色的圆,(1050, 275)为圆心坐标,275为半径,-1为线宽无限大,即为实心,16的锯齿度数 # cv2.circle(img, (1050, 275), 275, (0, 0, 255), -1, 16) # 画一个红色的椭圆,(1050, 275)为中心点坐标,(500, 275)为长宽的一半 # 第一个0为长方体角度起始值,第二个0为长方体角度终止值 # 180是椭圆的画线部分的度数 cv2.ellipse(img, (1050, 275), (500, 275), 60, 0, 360, (0, 0, 255), 5, 16) while True: cv2.imshow('img', img) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
如果我们要绘制一个与第一个椭圆垂直的椭圆
import cv2 if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") # 画一条白线(750, 550)为起始点坐标,(1350, 550)为终止点坐标 # (255, 255, 255)为颜色,用三通道表示,8为线宽, # 1为锯齿度,越小锯齿感越强,越大越平滑 # cv2.line(img, (759, 550), (1350, 900), (255, 255, 255), 8, 1) # 画一个红框(750, 0)为起始点坐标,(1350, 550)为终止点坐标 # -1表示线宽无限大,即为实心 # cv2.rectangle(img, (750, 0), (1350, 550), (0, 0, 255), -1) # 画一个红色的圆,(1050, 275)为圆心坐标,275为半径,-1为线宽无限大,即为实心,16的锯齿度数 # cv2.circle(img, (1050, 275), 275, (0, 0, 255), -1, 16) # 画一个红色的椭圆,(1050, 275)为中心点坐标,(500, 275)为长宽的一半 # 第一个0为长方体角度起始值,第二个0为长方体角度终止值 # 180是椭圆的画线部分的度数 cv2.ellipse(img, (1050, 275), (500, 275), 90, 0, 360, (0, 0, 255), 5, 16) while True: cv2.imshow('img', img) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
如果我们要画一个实体扇形
import cv2 if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") # 画一条白线(750, 550)为起始点坐标,(1350, 550)为终止点坐标 # (255, 255, 255)为颜色,用三通道表示,8为线宽, # 1为锯齿度,越小锯齿感越强,越大越平滑 # cv2.line(img, (759, 550), (1350, 900), (255, 255, 255), 8, 1) # 画一个红框(750, 0)为起始点坐标,(1350, 550)为终止点坐标 # -1表示线宽无限大,即为实心 # cv2.rectangle(img, (750, 0), (1350, 550), (0, 0, 255), -1) # 画一个红色的圆,(1050, 275)为圆心坐标,275为半径,-1为线宽无限大,即为实心,16的锯齿度数 # cv2.circle(img, (1050, 275), 275, (0, 0, 255), -1, 16) # 画一个红色的椭圆,(1050, 275)为中心点坐标,(500, 275)为长宽的一半 # 第一个0为长方体角度起始值,第二个0为长方体角度终止值 # 180是椭圆的画线部分的度数 cv2.ellipse(img, (1050, 275), (500, 275), 120, 330, 360, (0, 0, 255), -1, 16) while True: cv2.imshow('img', img) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
画多边形
import cv2 import numpy as np if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") # 画一条白线(750, 550)为起始点坐标,(1350, 550)为终止点坐标 # (255, 255, 255)为颜色,用三通道表示,8为线宽, # 1为锯齿度,越小锯齿感越强,越大越平滑 # cv2.line(img, (759, 550), (1350, 900), (255, 255, 255), 8, 1) # 画一个红框(750, 0)为起始点坐标,(1350, 550)为终止点坐标 # -1表示线宽无限大,即为实心 # cv2.rectangle(img, (750, 0), (1350, 550), (0, 0, 255), -1) # 画一个红色的圆,(1050, 275)为圆心坐标,275为半径,-1为线宽无限大,即为实心,16的锯齿度数 # cv2.circle(img, (1050, 275), 275, (0, 0, 255), -1, 16) # 画一个红色的椭圆,(1050, 275)为中心点坐标,(500, 275)为长宽的一半 # 第一个0为长方体角度起始值,第二个0为长方体角度终止值 # 180是椭圆的画线部分的度数 # cv2.ellipse(img, (1050, 275), (500, 275), 120, 330, 360, (0, 0, 255), -1, 16) pts = np.array([(750, 550), (1350, 550), (1050, 0)], np.int32) # 画一个多边形(三角形),pts是三个点的坐标 cv2.polylines(img, [pts], True, (0, 0, 255), 8, 16) while True: cv2.imshow('img', img) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
画实心多边形
import cv2 import numpy as np if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") # 画一条白线(750, 550)为起始点坐标,(1350, 550)为终止点坐标 # (255, 255, 255)为颜色,用三通道表示,8为线宽, # 1为锯齿度,越小锯齿感越强,越大越平滑 # cv2.line(img, (759, 550), (1350, 900), (255, 255, 255), 8, 1) # 画一个红框(750, 0)为起始点坐标,(1350, 550)为终止点坐标 # -1表示线宽无限大,即为实心 # cv2.rectangle(img, (750, 0), (1350, 550), (0, 0, 255), -1) # 画一个红色的圆,(1050, 275)为圆心坐标,275为半径,-1为线宽无限大,即为实心,16的锯齿度数 # cv2.circle(img, (1050, 275), 275, (0, 0, 255), -1, 16) # 画一个红色的椭圆,(1050, 275)为中心点坐标,(500, 275)为长宽的一半 # 第一个0为长方体角度起始值,第二个0为长方体角度终止值 # 180是椭圆的画线部分的度数 # cv2.ellipse(img, (1050, 275), (500, 275), 120, 330, 360, (0, 0, 255), -1, 16) pts = np.array([(750, 550), (1350, 550), (1050, 0)], np.int32) # 画一个多边形(三角形),pts是三个点的坐标 # cv2.polylines(img, [pts], True, (0, 0, 255), 8, 16) # 画一个实心多边形(三角形),pts是三个点的坐标 cv2.fillPoly(img, [pts], (0, 0, 255)) while True: cv2.imshow('img', img) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
这里需要注意的是,画实心多边形和普通多边形是两个不同的API。
绘制文本
import cv2 import numpy as np if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") # 画一条白线(750, 550)为起始点坐标,(1350, 550)为终止点坐标 # (255, 255, 255)为颜色,用三通道表示,8为线宽, # 1为锯齿度,越小锯齿感越强,越大越平滑 # cv2.line(img, (759, 550), (1350, 900), (255, 255, 255), 8, 1) # 画一个红框(750, 0)为起始点坐标,(1350, 550)为终止点坐标 # -1表示线宽无限大,即为实心 # cv2.rectangle(img, (750, 0), (1350, 550), (0, 0, 255), -1) # 画一个红色的圆,(1050, 275)为圆心坐标,275为半径,-1为线宽无限大,即为实心,16的锯齿度数 # cv2.circle(img, (1050, 275), 275, (0, 0, 255), -1, 16) # 画一个红色的椭圆,(1050, 275)为中心点坐标,(500, 275)为长宽的一半 # 第一个0为长方体角度起始值,第二个0为长方体角度终止值 # 180是椭圆的画线部分的度数 # cv2.ellipse(img, (1050, 275), (500, 275), 120, 330, 360, (0, 0, 255), -1, 16) # pts = np.array([(750, 550), (1350, 550), (1050, 0)], np.int32) # 画一个多边形(三角形),pts是三个点的坐标 # cv2.polylines(img, [pts], True, (0, 0, 255), 8, 16) # 画一个实心多边形(三角形),pts是三个点的坐标 # cv2.fillPoly(img, [pts], (0, 0, 255)) # 绘制一段文本,(750, 275)为起始坐标,cv2.FONT_ITALIC为字体,8为字体大小,(0, 0, 0)为黑色,10为字体粗细 cv2.putText(img, "Beauty", (750, 275), cv2.FONT_ITALIC, 8, (0, 0, 0), 10) while True: cv2.imshow('img', img) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
鼠标绘制图形
import cv2 import math if __name__ == "__main__": curshape = 0 startpos = (0, 0) def mouse_callback(event, x, y, flags, userdata): global startpos if event & cv2.EVENT_LBUTTONDOWN == cv2.EVENT_LBUTTONDOWN: startpos = (x, y) elif event & cv2.EVENT_LBUTTONUP == cv2.EVENT_LBUTTONUP: if curshape == 0: cv2.line(img, startpos, (x, y), (0, 0, 255), 8, 16) elif curshape == 1: cv2.rectangle(img, startpos, (x, y), (0, 0, 255), 8, 16) elif curshape == 2: a = x - startpos[0] b = y - startpos[1] r = int(math.sqrt(a**2 + b**2)) cv2.circle(img, startpos, r, (0, 0, 255), 8, 16) else: print("无效类型") cv2.namedWindow('drawshape', cv2.WINDOW_NORMAL) cv2.setMouseCallback('drawshape', mouse_callback, "123") img = cv2.imread("/Users/admin/Documents/111.jpeg") while True: cv2.imshow('drawshape', img) key = cv2.waitKey(1) & 0xFF if key == ord('q'): break elif key == ord('l'): # 画线 curshape = 0 elif key == ord('r'): # 画矩形 curshape = 1 elif key == ord('c'): # 画圆 curshape = 2 cv2.destroyAllWindows()
运行结果
这上面的形状都是用鼠标绘制出来的。
OpenCV的算术与位运算
图像加法运算
import cv2 import numpy as np if __name__ == "__main__": cv2.namedWindow('imgadd', cv2.WINDOW_NORMAL) img1 = cv2.imread("/Users/admin/Documents/111.jpeg") img2 = np.full((1080, 1920, 3), 50, np.uint8) result = cv2.add(img1, img2) while True: cv2.imshow('imgadd', result) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
这里我们可以看到图像好像是增加了曝光。
图像减法运算
import cv2 import numpy as np if __name__ == "__main__": cv2.namedWindow('imgsub', cv2.WINDOW_NORMAL) img1 = cv2.imread("/Users/admin/Documents/111.jpeg") img2 = np.full((1080, 1920, 3), 100, np.uint8) result = cv2.subtract(img1, img2) while True: cv2.imshow('imgsub', result) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
这里需要注意的是,图像的减法运算,两个参数的位置不可调换,但是加法就没有这个要求。上图中就像是原图得到了锐化。
图像乘法运算
import cv2 import numpy as np if __name__ == "__main__": cv2.namedWindow('imgmul', cv2.WINDOW_NORMAL) img1 = cv2.imread("/Users/admin/Documents/111.jpeg") img2 = np.full((1080, 1920, 3), 2, np.uint8) result = cv2.multiply(img1, img2) while True: cv2.imshow('imgmul', result) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
图像的乘法运算如果乘的像素点太大,基本上整个图像就看不清楚了。
图像除法运算
import cv2 import numpy as np if __name__ == "__main__": cv2.namedWindow('imgdiv', cv2.WINDOW_NORMAL) img1 = cv2.imread("/Users/admin/Documents/111.jpeg") img2 = np.full((1080, 1920, 3), 3, np.uint8) result = cv2.divide(img1, img2) while True: cv2.imshow('imgdiv', result) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
图像融合
我们这里有一张新的图片
现在我们要将这张图片与之前的美女的图片进行融合。
import cv2 if __name__ == "__main__": cv2.namedWindow('imgadd', cv2.WINDOW_NORMAL) img1 = cv2.imread("/Users/admin/Documents/111.jpeg") img2 = cv2.imread("/Users/admin/Documents/222.jpeg") print(img1.shape) print(img2.shape) # 融合两张图片,0.3和0.7分别表示两张图片融合的权重,0表示融合 # 后的图片的所有元素都加0,表示静态权重 result = cv2.addWeighted(img1, 0.3, img2, 0.7, 0) while True: cv2.imshow('imgadd', result) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
(1080, 1920, 3)
(1080, 1920, 3)
这里需要注意的是,要融合的两张图片的高和宽以及通道数必须相等,才可以进行融合。这里两张图片都是1080*1920*3的图像。
图像位运算
- 非运算
非运算就是将图像中的像素进行255-x操作(x为原像素值)
import cv2 if __name__ == "__main__": cv2.namedWindow('imgbit', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") new_img = cv2.bitwise_not(img) while True: cv2.imshow('imgbit', new_img) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
这个有点像彩色照片的底片
import cv2 import numpy as np if __name__ == "__main__": cv2.namedWindow('imgbit', cv2.WINDOW_NORMAL) img1 = cv2.imread("/Users/admin/Documents/111.jpeg") img2 = np.full((1080, 1920, 3), 255, np.uint8) new_img = cv2.subtract(img2, img1) while True: cv2.imshow('imgbit', new_img) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
使用255-原图像像素值与非运算效果一样
- 与运算
与运算就是将图像各个通道像素点值转成二进制数按位与进行运算。
import cv2 if __name__ == "__main__": cv2.namedWindow('imgbit', cv2.WINDOW_NORMAL) img1 = cv2.imread("/Users/admin/Documents/111.jpeg") img2 = cv2.imread("/Users/admin/Documents/222.jpeg") new_img = cv2.bitwise_and(img1, img2) while True: cv2.imshow('imgbit', new_img) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
上面的代码等同与
import cv2 import numpy as np if __name__ == "__main__": cv2.namedWindow('imgbit', cv2.WINDOW_NORMAL) img1 = cv2.imread("/Users/admin/Documents/111.jpeg") img2 = cv2.imread("/Users/admin/Documents/222.jpeg") result = np.bitwise_and(img1, img2) while True: cv2.imshow('imgbit', result) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
- 或运算
或运算就是将图像各个通道像素点值转成二进制数按位或进行运算。
import cv2 if __name__ == "__main__": cv2.namedWindow('imgbit', cv2.WINDOW_NORMAL) img1 = cv2.imread("/Users/admin/Documents/111.jpeg") img2 = cv2.imread("/Users/admin/Documents/222.jpeg") new_img = cv2.bitwise_or(img1, img2) while True: cv2.imshow('imgbit', new_img) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
上面的代码等同于
import cv2 import numpy as np if __name__ == "__main__": cv2.namedWindow('imgbit', cv2.WINDOW_NORMAL) img1 = cv2.imread("/Users/admin/Documents/111.jpeg") img2 = cv2.imread("/Users/admin/Documents/222.jpeg") result = np.bitwise_or(img1, img2) while True: cv2.imshow('imgbit', result) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
- 异或运算
异或运算就是将图像各个通道像素点值转成二进制数按位异或进行运算。
import cv2 if __name__ == "__main__": cv2.namedWindow('imgbit', cv2.WINDOW_NORMAL) img1 = cv2.imread("/Users/admin/Documents/111.jpeg") img2 = cv2.imread("/Users/admin/Documents/222.jpeg") new_img = cv2.bitwise_xor(img1, img2) while True: cv2.imshow('imgbit', new_img) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
上面的代码等同于
import cv2 import numpy as np if __name__ == "__main__": cv2.namedWindow('imgbit', cv2.WINDOW_NORMAL) img1 = cv2.imread("/Users/admin/Documents/111.jpeg") img2 = cv2.imread("/Users/admin/Documents/222.jpeg") result = np.bitwise_xor(img1, img2) while True: cv2.imshow('imgbit', result) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
添加水印
我们先把logo给画出来,看看是什么样子的
import cv2 import numpy as np if __name__ == "__main__": cv2.namedWindow('imlog', cv2.WINDOW_NORMAL) img1 = cv2.imread("/Users/admin/Documents/111.jpeg") logo = np.zeros((200, 200, 3), np.uint8) mask = np.zeros((200, 200), np.uint8) logo[20:120, 20:120] = [0, 0, 255] logo[80:180, 80:180] = [0, 255, 0] while True: cv2.imshow('imlog', logo) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
现在我们要将该logo放入到图片里面去
import cv2 import numpy as np if __name__ == "__main__": cv2.namedWindow('imlog', cv2.WINDOW_NORMAL) # 导入图片 img = cv2.imread("/Users/admin/Documents/111.jpeg") # 创建logo和mask logo = np.zeros((200, 200, 3), np.uint8) mask = np.full((200, 200), 255, np.uint8) # 绘制logo logo[20:120, 20:120] = [0, 0, 255] # 该区域为红色 logo[80:180, 80:180] = [0, 255, 0] # 该区域为绿色 mask[20:120, 20:120] = 0 # 该区域为黑色 mask[80:180, 80:180] = 0 # 该区域为黑色 # 选择图像添加logo的位置,并提取该部分的图像像素(3通道) roi = img[0:200, 0:200] # 对mask进行位与操作(0与任何值位与都是0,即为黑色,而任何值与255(二进制11111111)) # 进行位与操作都是该值本身 # 这里roi是三通道,而mask为单通道, # 则这里为roi的每个通道都与mask进行位与操作 # 最终roi的位置与mask为0的部分变为0,其他部分保留roi其像素本身 tmp = cv2.bitwise_and(roi, roi, mask=mask) # 0加任何数等于任何数,所以这里roi为0的位置变成logo的像素 # 而logo为0的部分保留roi的像素值 dst = cv2.add(tmp, logo) # 将得到的图像放入原始图像中 img[0:200, 0:200] = dst while True: cv2.imshow('imlog', img) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
图像基本变换
图像放大与缩小
import cv2 if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") new_img = cv2.resize(img, (200, 200)) while True: cv2.imshow('img', new_img) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
我们可以看到在进行缩小的过程中,图像是有失真的。我们也可以直接使用比例缩放
import cv2 if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") new_img = cv2.resize(img, None, fx=0.3, fy=0.3) while True: cv2.imshow('img', new_img) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
我们来看一下一张小图的放大会是什么效果,原图如下
import cv2 if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_AUTOSIZE) img = cv2.imread("/Users/admin/Documents/333.jpeg") # 图像缩放,cv2.INTER_AREA表示效果最好 # cv2.INTER_NEAREST表示临近插值 new_img = cv2.resize(img, None, fx=3, fy=3, interpolation=cv2.INTER_AREA) while True: cv2.imshow('img', new_img) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
图像翻转
上下翻转
import cv2 if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") # 上下翻转 new_img = cv2.flip(img, 0) while True: cv2.imshow('img', new_img) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
左右翻转
import cv2 if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") # 0上下翻转,大于0左右翻转 new_img = cv2.flip(img, 1) while True: cv2.imshow('img', new_img) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
上下+左右翻转
import cv2 if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") # 0上下翻转,大于0左右翻转,小于0上下+左右翻转 new_img = cv2.flip(img, -1) while True: cv2.imshow('img', new_img) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
图像旋转
import cv2 if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") # 0上下翻转,大于0左右翻转,小于0上下+左右翻转 # new_img = cv2.flip(img, -1) # 顺时针旋转90度 new_img = cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE) while True: cv2.imshow('img', new_img) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
import cv2 if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") # 0上下翻转,大于0左右翻转,小于0上下+左右翻转 # new_img = cv2.flip(img, -1) # 顺时针旋转180度 new_img = cv2.rotate(img, cv2.ROTATE_180) while True: cv2.imshow('img', new_img) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
import cv2 if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") # 0上下翻转,大于0左右翻转,小于0上下+左右翻转 # new_img = cv2.flip(img, -1) # 逆时针旋转90度 new_img = cv2.rotate(img, cv2.ROTATE_90_COUNTERCLOCKWISE) while True: cv2.imshow('img', new_img) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
这里需要注意的是,cv2.rotate无法旋转任意一个角度,只有这么三个角度可以旋转。
图像的仿射变换
仿射变换是图像旋转、缩放、平移的总称。
import cv2 import numpy as np if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") h, w, ch = img.shape # 变换矩阵之右平移100像素,下平移100像素 M = np.float32([[1, 0, 100], [0, 1, 100]]) # 图像的仿射变换,M是变换矩阵,(w, h)是图像大小 new_img = cv2.warpAffine(img, M, (w, h)) while True: cv2.imshow('img', new_img) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
这里需要说明的是,变换矩阵是线性代数中的基础,可以参考线性代数整理 中的图形变换矩阵
之前我们在看图形的旋转中,只能旋转3个角度,无法任意旋转,现在我们就自己定义旋转的变换矩阵来让图片旋转任意角度
这个是使图形旋转的变换矩阵公式,现在我们来让图形逆时针旋转15度(围绕(0,0)旋转)
import cv2 import numpy as np import math if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") h, w, ch = img.shape # 变换矩阵之右平移100像素,下平移100像素 # M = np.float32([[1, 0, 100], [0, 1, 100]]) theta = math.pi / 12 # 将图像逆时针旋转15度 M = np.float32([[math.cos(theta), math.sin(theta), 0], [-math.sin(theta), math.cos(theta), 0]]) # 图像的仿射变换,M是变换矩阵,(w, h)是图像大小 new_img = cv2.warpAffine(img, M, (w, h)) while True: cv2.imshow('img', new_img) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
又比如是将图像进行拉伸的变换矩阵
import cv2 import numpy as np if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") h, w, ch = img.shape # 变换矩阵之右平移100像素,下平移100像素 # M = np.float32([[1, 0, 100], [0, 1, 100]]) # theta = math.pi / 12 # 将图像逆时针旋转15度 # M = np.float32([[math.cos(theta), math.sin(theta), 0], [-math.sin(theta), math.cos(theta), 0]]) # 将图像进行拉伸 M = np.float32([[1, 0.3, 0], [0, 1, 0]]) # 图像的仿射变换,M是变换矩阵,(w, h)是图像大小 new_img = cv2.warpAffine(img, M, (w, h)) while True: cv2.imshow('img', new_img) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
获取变换矩阵
由于上述变换矩阵需要特定的变换矩阵公式,OpenCV提供了一种获取该变换矩阵的方法
import cv2 if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") h, w, ch = img.shape # 变换矩阵之右平移100像素,下平移100像素 # M = np.float32([[1, 0, 100], [0, 1, 100]]) # theta = math.pi / 12 # 将图像逆时针旋转15度 # M = np.float32([[math.cos(theta), math.sin(theta), 0], [-math.sin(theta), math.cos(theta), 0]]) # 将图像进行拉伸 # M = np.float32([[1, 0.3, 0], [0, 1, 0]]) # 获取变换矩阵,(w / 2, h / 2)为图像中心点,逆时针旋转15度,1.0表示不缩放 M = cv2.getRotationMatrix2D((w / 2, h / 2), 15, 1.0) # 图像的仿射变换,M是变换矩阵,(w, h)是图像大小 new_img = cv2.warpAffine(img, M, (w, h)) while True: cv2.imshow('img', new_img) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
现在我们再将图像旋转后进行缩放
import cv2 if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") h, w, ch = img.shape # 变换矩阵之右平移100像素,下平移100像素 # M = np.float32([[1, 0, 100], [0, 1, 100]]) # theta = math.pi / 12 # 将图像逆时针旋转15度 # M = np.float32([[math.cos(theta), math.sin(theta), 0], [-math.sin(theta), math.cos(theta), 0]]) # 将图像进行拉伸 # M = np.float32([[1, 0.3, 0], [0, 1, 0]]) # 获取变换矩阵,(w / 2, h / 2)为图像中心点,逆时针旋转15度,0。6表示缩小为60% M = cv2.getRotationMatrix2D((w / 2, h / 2), 15, 0.6) # 图像的仿射变换,M是变换矩阵,(w, h)是图像大小 new_img = cv2.warpAffine(img, M, (w, h)) while True: cv2.imshow('img', new_img) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
变换矩阵二
在上图中由三个绿点来获取变换矩阵。注意,这里左右两个图的三个绿点都要给出,左图的三个绿点叫做src,右图的三个绿点叫做dst。
import cv2 import numpy as np if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") h, w, ch = img.shape # 变换矩阵之右平移100像素,下平移100像素 # M = np.float32([[1, 0, 100], [0, 1, 100]]) # theta = math.pi / 12 # 将图像逆时针旋转15度 # M = np.float32([[math.cos(theta), math.sin(theta), 0], [-math.sin(theta), math.cos(theta), 0]]) # 将图像进行拉伸 # M = np.float32([[1, 0.3, 0], [0, 1, 0]]) # 获取变换矩阵,(w / 2, h / 2)为图像中心点,逆时针旋转15度,0。6表示缩小为60% # M = cv2.getRotationMatrix2D((w / 2, h / 2), 15, 0.6) src = np.float32([[400, 300], [800, 300], [400, 1000]]) dst = np.float32([[200, 400], [600, 500], [150, 1100]]) M = cv2.getAffineTransform(src, dst) # 图像的仿射变换,M是变换矩阵,(w, h)是图像大小 new_img = cv2.warpAffine(img, M, (w, h)) while True: cv2.imshow('img', new_img) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
透视变换
透视变换是将拍摄的比较倾斜的图转换成比较方正的图,这里我们使用一张非常倾斜的图
import cv2 import numpy as np if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/444.jpeg") print(img.shape) # 源数据图取大概一个梯形形状 src = np.float32([[474, 100], [1659, 100], [0, 1200], [1896, 1200]]) dst = np.float32([[0, 0], [2300, 0], [0, 2400], [2300, 2400]]) # 获取转换矩阵 M = cv2.getPerspectiveTransform(src, dst) # 进行透视变换 new_img = cv2.warpPerspective(img, M, (2300, 2400)) while True: cv2.imshow('img', new_img) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
OpenCV中的滤波器
图像滤波
一副图像通过滤波器得到另一幅图像。其中滤波器又称为卷积核,滤波的过程称为卷积。有关卷积核的内容请参考Tensorflow深度学习算法整理 ,这里不再赘述。
低通滤波与高通滤波
- 低通滤波可以去除噪音或平滑图像。比方说这样一个卷积核(一般可以用于美颜)
import cv2 import numpy as np if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") # 5*5的卷积核 kernal = np.ones((5, 5), np.float32) / 25 # 低通滤波处理(卷积操作),-1表示不改变卷积后的图像大小 dst = cv2.filter2D(img, -1, kernal) while True: cv2.imshow('img', dst) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
这张图可能看起来不是特别明显,不过没关系。
- 高通滤波可以帮助查找图像的边缘。(一般可以用于抠图)
方盒滤波与均值滤波
对于卷积核,如果我们自己去手工创建卷积核可能比较困难,OpenCV为我们提供了一些常用的卷积核作为滤波器。
- 方盒滤波
参数a的作用:
- normalize=True,a = 1/(W*H),这里W、H是滤波器的宽和高,这是一个均值滤波
- normalize=False,a = 1
当normalize==True的时候,方盒滤波==均值滤波,一般情况下,我们使用的都是均值滤波。
import cv2 if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") # 5*5的卷积核 # kernal = np.ones((5, 5), np.float32) / 25 # 低通滤波处理(卷积操作),-1表示不改变卷积后的图像大小 # dst = cv2.filter2D(img, -1, kernal) # 均值滤波,效果跟上面相同 dst = cv2.blur(img, (5, 5)) while True: cv2.imshow('img', dst) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
我们再来看一下方盒滤波
import cv2 if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") # 5*5的卷积核 # kernal = np.ones((5, 5), np.float32) / 25 # 低通滤波处理(卷积操作),-1表示不改变卷积后的图像大小 # dst = cv2.filter2D(img, -1, kernal) # 方盒滤波 dst = cv2.boxFilter(img, -1, (5, 5), normalize=False) while True: cv2.imshow('img', dst) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
高斯滤波
高斯滤波是一种呈现正态分布的滤波器,当图像中的噪音呈正态分布的时候,可以使用高斯滤波来矫正。
它的原理就是在我们的卷积核中,中心点不是最大的,但是比重是最大的,而周围的点可能比较大,但比重比较低。总之就是越靠边上,比重越低;越靠近中心,比重越高。
- 高斯权重
在上图中,我们可以看到,中心点25的权重为14.7%,最高,靠近中心点的上下左右的权重为11.83%,而四个角的权重只有9.47%,最低。
我们先来给美女图像添加高斯噪声。
import cv2 import numpy as np if __name__ == "__main__": def gasuss_noise(image, mean=0, var=0.001): ''' 添加高斯噪声 mean : 均值 var : 方差 ''' image = np.array(image / 255, dtype=np.float) noise = np.random.normal(mean, var ** 0.5, image.shape) out = image + noise if out.min() < 0: low_clip = -1. else: low_clip = 0. out = np.clip(out, low_clip, 1.0) out = np.uint8(out * 255) return out cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") img_out = gasuss_noise(img, mean=0, var=0.001) # 5*5的卷积核 # kernal = np.ones((5, 5), np.float32) / 25 # 低通滤波处理(卷积操作),-1表示不改变卷积后的图像大小 # dst = cv2.filter2D(img, -1, kernal) # 方盒滤波 # dst = cv2.boxFilter(img, -1, (5, 5), normalize=False) while True: cv2.imshow('img', img_out) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
现在我们再使用高斯滤波器来进行处理
import cv2 import numpy as np if __name__ == "__main__": def gasuss_noise(image, mean=0, var=0.001): ''' 添加高斯噪声 mean : 均值 var : 方差 ''' image = np.array(image / 255, dtype=np.float) noise = np.random.normal(mean, var ** 0.5, image.shape) out = image + noise if out.min() < 0: low_clip = -1. else: low_clip = 0. out = np.clip(out, low_clip, 1.0) out = np.uint8(out * 255) return out cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") img_out = gasuss_noise(img, mean=0, var=0.001) # 5*5的卷积核 # kernal = np.ones((5, 5), np.float32) / 25 # 低通滤波处理(卷积操作),-1表示不改变卷积后的图像大小 # dst = cv2.filter2D(img, -1, kernal) # 方盒滤波 # dst = cv2.boxFilter(img, -1, (5, 5), normalize=False) # 高斯滤波,sigmaX和sigmaY是辐射半径 dst = cv2.GaussianBlur(img_out, (33, 33), sigmaX=50, sigmaY=50) while True: cv2.imshow('img', dst) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
中值滤波
假设我们使用3*3的卷积核去遍历图像,每取得一个区域的像素的时候,就将该区域的图像像素进行排序,比如[1,5,5,5,6,7,8,9,11],取中间值作为卷积后的结果值。这里就是6.
中值滤波的优点是对胡椒噪音(在图像中分布随机的噪音点)效果明显。
我们先来对美女图像生成胡椒噪音
import cv2 import numpy as np import random if __name__ == "__main__": def sp_noise(image, prob): ''' 添加椒盐噪声 prob:噪声比例 ''' output = np.zeros(image.shape, np.uint8) thres = 1 - prob for i in range(image.shape[0]): for j in range(image.shape[1]): rdn = random.random() if rdn < prob: output[i][j] = 0 elif rdn > thres: output[i][j] = 255 else: output[i][j] = image[i][j] return output cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") out_img = sp_noise(img, 0.02) while True: cv2.imshow('img', out_img) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
现在我们再使用中值滤波器来进行处理
import cv2 import numpy as np import random if __name__ == "__main__": def sp_noise(image, prob): ''' 添加椒盐噪声 prob:噪声比例 ''' output = np.zeros(image.shape, np.uint8) thres = 1 - prob for i in range(image.shape[0]): for j in range(image.shape[1]): rdn = random.random() if rdn < prob: output[i][j] = 0 elif rdn > thres: output[i][j] = 255 else: output[i][j] = image[i][j] return output cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") out_img = sp_noise(img, 0.02) # 中值滤波 dst = cv2.medianBlur(out_img, 5) while True: cv2.imshow('img', dst) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
我们再来看另外一张图
import cv2 import numpy as np import random if __name__ == "__main__": def sp_noise(image, prob): ''' 添加椒盐噪声 prob:噪声比例 ''' output = np.zeros(image.shape, np.uint8) thres = 1 - prob for i in range(image.shape[0]): for j in range(image.shape[1]): rdn = random.random() if rdn < prob: output[i][j] = 0 elif rdn > thres: output[i][j] = 255 else: output[i][j] = image[i][j] return output cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/555.jpeg") # out_img = sp_noise(img, 0.02) # 中值滤波 dst = cv2.medianBlur(img, 15) while True: cv2.imshow('img', dst) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
双边滤波
双边滤波的优点:可以保留边缘,同时可以对边缘内的区域进行平滑处理。他的主要作用是进行美颜。
- 双边滤波的原理
在上图中的a输入中,有一个高度,这个高度代表颜色的边缘,颜色的落差特别大。上下两块代表边缘的两部分。对于双边滤波来说,对于边缘不会做处理,但是对于边缘的两部分的突起的部分给抹平。最终输出的就是d图。对于b空域核和c值域核,它们影响的是不同的地点的。b空域核影响的是颜色的落差边缘,c值域核影响的是边缘之外的平滑效果的。
现在依然来对美女图片进行双边滤波,先看一下原图,头发和脸的颜色都略显暗淡。
import cv2 if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") # 双边滤波,7代表边缘直径,20表示空域核的值,意思为颜色的变化范围 # 在这个范围认为都是相同的颜色 # 50为值域核的值,意思为在平面上进行平滑处理的范围 dst = cv2.bilateralFilter(img, 7, 20, 50) while True: cv2.imshow('img', dst) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
经过双边滤波后,我们可以看到,头发和脸都变得更加红润有光泽,眼睛也更加明亮。
高通滤波——索贝尔算子
前面的都是低通滤波,主要作用是消除噪音,平滑图像。OpenCV也提供了很多高通滤波。
常见的高通滤波:
- Sobel(索贝尔),对噪音适应性很强,在内部实现中使用了高斯滤波,对噪音首先进行了过滤,之后通过一阶导数求得图像的边缘。有很多算法都是使用索贝尔为基础的。
- Scharr(沙尔),卷积核不可改变的,尺寸是固定的,3*3大小的一个卷积核,如果索贝尔的size设成-1,则自动使用的是沙尔滤波算法,所以一般情况下使用的是索贝尔,很少使用沙尔。对于3*3的卷积核来说,索贝尔的效果是没有沙尔好的,因为沙尔可以检测出更细的边缘线。而索贝尔就比较粗糙一些,但它可以改变卷积核大小。对于索贝尔和沙尔来说,在计算边缘的时候,只能求一个方向的,要么是横轴要么是纵轴。最终的结果我们需要将横轴检测的边缘与纵轴检测的边缘加在一起,做一次加法运算才能得出最终的结果。
- Laplacian(拉普拉斯),不用单独求横轴或者纵轴边缘,它一下就能将横轴和纵轴的边缘全部检测出来。但是对于噪音比较敏感,在内部没有降噪的功能,所以在使用拉普拉斯之前还需要手工降噪。这样才能更好的体验出拉普拉斯的效果。
Sobel算子:先向x方向求导,然后在y方向求导,最终结果:|G|=|Gx|+|Gy|
我们先来看一下只求一个方向导数的结果,这里先向y方向求导。
import cv2 if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") # 索贝尔滤波,cv2.CV_64F是位深,即数据类型,这里是64位float类型,1表示向y方向求导 # 0表示不向x方向求导,ksize为卷积核大小 d1 = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=5) while True: cv2.imshow('img', d1) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
我们再来看一下只向x方向求导
import cv2 if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") # 索贝尔滤波,cv2.CV_64F是位深,即数据类型,这里是64位float类型,1表示向y方向求导 # 0表示不向x方向求导,ksize为卷积核大小 d1 = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=5) # 向x方向求导 d2 = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=5) while True: cv2.imshow('img', d2) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
最终我们将两个方向求导的结果加起来
import cv2 if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") # 索贝尔滤波,cv2.CV_64F是位深,即数据类型,这里是64位float类型,1表示向y方向求导 # 0表示不向x方向求导,ksize为卷积核大小 d1 = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3) # 向x方向求导 d2 = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=3) dst = cv2.add(d1, d2) while True: cv2.imshow('img', dst) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
我们再来看一张这个图,效果会更加明显
import cv2 if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/888.jpg") # 索贝尔滤波,cv2.CV_64F是位深,即数据类型,这里是64位float类型,1表示向y方向求导 # 0表示不向x方向求导,ksize为卷积核大小 d1 = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3) # 向x方向求导 d2 = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=3) dst = cv2.add(d1, d2) while True: cv2.imshow('img', dst) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
沙尔算子
import cv2 if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") # 索贝尔滤波,cv2.CV_64F是位深,即数据类型,这里是64位float类型,1表示向y方向求导 # 0表示不向x方向求导,ksize为卷积核大小 # d1 = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3) # 向x方向求导 # d2 = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=3) # 沙尔滤波,它的卷积核大小固定为3*3,求y方向导数 d1 = cv2.Scharr(img, cv2.CV_64F, 1, 0) # dst = cv2.add(d1, d2) while True: cv2.imshow('img', d1) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
import cv2 if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") # 索贝尔滤波,cv2.CV_64F是位深,即数据类型,这里是64位float类型,1表示向y方向求导 # 0表示不向x方向求导,ksize为卷积核大小 # d1 = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3) # 向x方向求导 # d2 = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=3) # 沙尔滤波,它的卷积核大小固定为3*3,求y方向导数 d1 = cv2.Scharr(img, cv2.CV_64F, 1, 0) # 求x方向导数 d2 = cv2.Scharr(img, cv2.CV_64F, 0, 1) # dst = cv2.add(d1, d2) while True: cv2.imshow('img', d2) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
import cv2 if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") # 索贝尔滤波,cv2.CV_64F是位深,即数据类型,这里是64位float类型,1表示向y方向求导 # 0表示不向x方向求导,ksize为卷积核大小 # d1 = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3) # 向x方向求导 # d2 = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=3) # 沙尔滤波,它的卷积核大小固定为3*3,求y方向导数 d1 = cv2.Scharr(img, cv2.CV_64F, 1, 0) # 求x方向导数 d2 = cv2.Scharr(img, cv2.CV_64F, 0, 1) dst = cv2.add(d1, d2) while True: cv2.imshow('img', dst) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
拉普拉斯算子
import cv2 if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") # 索贝尔滤波,cv2.CV_64F是位深,即数据类型,这里是64位float类型,1表示向y方向求导 # 0表示不向x方向求导,ksize为卷积核大小 # d1 = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3) # 向x方向求导 # d2 = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize=3) # 沙尔滤波,它的卷积核大小固定为3*3,求y方向导数 # d1 = cv2.Scharr(img, cv2.CV_64F, 1, 0) # 求x方向导数 # d2 = cv2.Scharr(img, cv2.CV_64F, 0, 1) # dst = cv2.add(d1, d2) # 拉普拉斯滤波 dst = cv2.Laplacian(img, cv2.CV_64F, ksize=5) while True: cv2.imshow('img', dst) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
边缘检测Canny
Canny是图像边缘检测的终极大法,由于之前的三个算子有着这样那样的问题,我们来看看Canny的优势
- 使用5*5高斯滤波消除噪音
- 计算图像梯度的方向(0°/45°/90°/135°)
- 取局部极大值
- 阈值计算
如果超过来maxVal最大值,肯定是边缘;如果低于minVal最小值,肯定不是边缘。如果在最大值和最小值之间,则要看所确定的值与之前是否是连贯的,比如说A是一个边缘,C与A在连线上,所以C也是一个边缘。对于B来说,也在最大值和最小值之间,由于它不在边缘的一条线上,所以B不是边缘。
import cv2 if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") # Canny滤波,50为最小值,160为最大值 dst = cv2.Canny(img, 50, 160) while True: cv2.imshow('img', dst) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
这里随着最小值和最大值的调整,它检测出来的边缘是不一样的,比如我调小最大值
import cv2 if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") # Canny滤波,50为最小值,100为最大值 dst = cv2.Canny(img, 50, 100) while True: cv2.imshow('img', dst) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
我们可以看到它会把更多地方视为边缘。在后面进行目标检测的时候就会使用Canny进行轮廓的查找。
OpenCV中的形态学
形态学概述
- 什么是形态学处理:
- 基于图像形态进行处理的一些基本方法。比如说图片中有一个杯子,形态学可以帮我们找到这个杯子在哪,它并不关心我们要找的是什么。
- 这些处理方法基本是对二进制图像进行处理(即黑白图像)。如果我们拿到的是一个彩色图像,则需要先进行转换成灰色图像。
- 卷积核决定着图像处理后的效果
- 形态学图像处理方式:
- 腐蚀与膨胀。腐蚀的意思就是将一个区域变小。膨胀的意思就是将一个区域变大。
- 开运算,就是先做腐蚀再做膨胀。
- 闭运算,就是先做膨胀再做腐蚀。
- 顶帽
- 黑帽
顶帽和黑帽都是一些插值处理
图像全局二值化
什么是二值化:
- 将图像的每个像素变成两种值,如0,255。对于灰色图像,它是有灰色程度的,它是一层一层由黑到白(即非0和255的中间值),有梯度的。我们在进行形态处理的时候,如果有梯度处理起来就比较麻烦。
- 全局二值化,我们选出一个阈值,所有的像素都和这个阈值做对比,如果低于这个阈值,就变成0;如果高于这个阈值,就变成255。
- 局部二值化,将图形分成很多的域,很多的小块,在每一个小块里再做二值化。这样就可以对一些光线的图像,特别暗的部分处理起来就会有特别的效果。
import cv2 if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") # 将彩色图像转成灰色图像 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 二值化,180为阈值,255是超过阈值的转化值 # cv2.THRESH_BINARY是类型,还有一个反向的cv2.THRESH_BINARY_INV # 低于阈值的变成255,高于阈值的变成0 ret, dst = cv2.threshold(gray, 180, 255, cv2.THRESH_BINARY) while True: cv2.imshow('img', dst) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
我们来看一下相反值的情况
import cv2 if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") # 将彩色图像转成灰色图像 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 二值化,180为阈值,255是超过阈值的转化值 # cv2.THRESH_BINARY是类型,还有一个反向的cv2.THRESH_BINARY_INV # 低于阈值的变成255,高于阈值的变成0 ret, dst = cv2.threshold(gray, 180, 255, cv2.THRESH_BINARY_INV) while True: cv2.imshow('img', dst) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
我们也可以通过改变阈值来看一下变化
import cv2 if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/111.jpeg") # 将彩色图像转成灰色图像 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 二值化,180为阈值,255是超过阈值的转化值 # cv2.THRESH_BINARY是类型,还有一个反向的cv2.THRESH_BINARY_INV # 低于阈值的变成255,高于阈值的变成0 ret, dst = cv2.threshold(gray, 60, 255, cv2.THRESH_BINARY) while True: cv2.imshow('img', dst) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
阈值类型
前面我们只介绍了两种类型的阈值类型,就是THRESH_BINARY以及THRESH_BINARY_INV,也就是上图中的第二项和第三项。而第一项是一个原始图,表示像素点的不同值,它可能有峰值和谷值。这里是以中间的线为阈值的,所以对于THRESH_BINARY来说就是其原始图像素转化图。而对于THRESH_BINARY_INV来说与THRESH_BINARY来说刚好相反。
对于第四项TRUNCATE来说,实际就是进行了削峰操作,大于阈值的值都变成了阈值本身,小于阈值的值保留原值。对于第五项THRESH_TO_ZERO_INV和第六项THRESH_TO_ZERO来说也是一个相反的过程,THRESH_TO_ZERO是将小于阈值的值变成0,保留大于阈值的原值。THRESH_TO_ZERO_INV是将大于阈值的值变成0,保留小于阈值的原值。TRUNCATE、THRESH_TO_ZERO、THRESH_TO_ZERO_INV是用的比较少的类型,我们一般使用的是THRESH_BINARY和THRESH_BINARY_INV。
自适应阈值二值化
由于光照不均匀以及阴影的存在,只有一个阈值会使得在阴影处的白色被二值化成黑色。
我们现在用一张儿童思维导图来看一下
import cv2 if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/999.jpeg") # 将彩色图像转成灰色图像 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 二值化,180为阈值,255是超过阈值的转化值 # cv2.THRESH_BINARY是类型,还有一个反向的cv2.THRESH_BINARY_INV # 低于阈值的变成255,高于阈值的变成0 ret, dst = cv2.threshold(gray, 120, 255, cv2.THRESH_BINARY_INV) while True: cv2.imshow('img', dst) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
对比两张图,我们可以看见虽然二值图保留了原图的绝大部分信息,但还是有很多的信息丢失了,现在我们使用自适应的二值化来处理
import cv2 if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/999.jpeg") # 将彩色图像转成灰色图像 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 二值化,180为阈值,255是超过阈值的转化值 # cv2.THRESH_BINARY是类型,还有一个反向的cv2.THRESH_BINARY_INV # 低于阈值的变成255,高于阈值的变成0 # ret, dst = cv2.threshold(gray, 120, 255, cv2.THRESH_BINARY_INV) # 自适应二值化,255为最大值,cv2.ADAPTIVE_THRESH_GAUSSIAN_C为高斯窗口加权平均值 # 即越在中心点权值越重,越靠近边缘权值越低, # 还有一种为cv2.ADAPTIVE_THRESH_MEAN_C,表示计算临近区域的平均值 # 11为邻近区域的大小,0为从计算的平均值或加权平均值重减去的值 dst = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, 11, 0) while True: cv2.imshow('img', dst) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
这样我们可以看到所有该保留的信息的纹路。我们也可以将颜色给反过来
import cv2 if __name__ == "__main__": cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread("/Users/admin/Documents/999.jpeg") # 将彩色图像转成灰色图像 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 二值化,180为阈值,255是超过阈值的转化值 # cv2.THRESH_BINARY是类型,还有一个反向的cv2.THRESH_BINARY_INV # 低于阈值的变成255,高于阈值的变成0 # ret, dst = cv2.threshold(gray, 120, 255, cv2.THRESH_BINARY_INV) # 自适应二值化,255为最大值,cv2.ADAPTIVE_THRESH_GAUSSIAN_C为高斯窗口加权平均值 # 即越在中心点权值越重,越靠近边缘权值越低, # 还有一种为cv2.ADAPTIVE_THRESH_MEAN_C,表示计算临近区域的平均值 # 11为邻近区域的大小,0为从计算的平均值或加权平均值重减去的值 dst = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_MEAN_C, cv2.THRESH_BINARY, 11, 0) while True: cv2.imshow('img', dst) key = cv2.waitKey() if key & 0xFF == ord('q'): break cv2.destroyAllWindows()
运行结果
无论是哪种方式,我们都希望除了纹路之外,其他部分都是纯白色或者纯黑色的,而不是有这么多的噪点,这个可以后面来处理。
{{m.name}}
原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/199810.html