# OpenCV **Repository Path**: mtoooo/open-cv ## Basic Information - **Project Name**: OpenCV - **Description**: 自学OpenCV,实现车辆检测与计数,学习更新ing - **Primary Language**: Python - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 2 - **Created**: 2022-10-14 - **Last Updated**: 2023-03-15 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # OpenCV ## 一、基础操作 ### 窗口的展示 #### 窗口 **创建显示窗口** 1. *namedWindow('name',cv2.window_nomal)* 创建窗口 2. imshow('name',img) 显示窗口 3. destoryAllWindows() 释放窗口 4. resizeWindow('name', 800, 600) 设置大小 5. waitKey(0) 显示时间ms ```python # 02.win_api.py import cv2 cv2.namedWindow('new', cv2.WINDOW_AUTOSIZE) cv2.resizeWindow('new', 640, 480) cv2.imshow('new', 0) key = cv2.waitKey(0) if(key == 'q'): exit() cv2.destroyAllWindows() ``` ### 图像/视频的加载 #### 读取/保存图片 1. **加载图片** *imread(path,flag)* 2. **保存图片** *imwrite(name,img)* img是mat类型 ``` python #03.reading_writing.py import cv2 from sqlalchemy import true cv2.namedWindow('img', cv2.WINDOW_NORMAL) img = cv2.imread( 'G:\\BaiduNetdiskWorkspace\\Code From VSCode\OpenCV\\timg_.jpg') while true: cv2.imshow('img', img) key = cv2.waitKey(0) # if key == 'q': if key & 0xff == ord('q'): # 退出 break elif key & 0xff == ord('s'): cv2.imwrite( 'G:\\BaiduNetdiskWorkspace\\Code From VSCode\OpenCV\\save.png', img) else: print(key, 'otherkeys') cv2.destroyAllWindows() ``` ### 从摄像头/视频文件读取视频帧 1. **捕捉摄像头** *VideoCapure(0, api)* 2. **读取视频** *VideoCapure('path')* - cap.read() 将数据一帧一帧采集处理,返回两个值: - 第一个为状态值,读到为True; - 第二个为视频帧 3. **释放资源** *cap.release()* 4. **判断摄像头是否打开** *isOpened()* ```python #04.VideoCap.py import cv2 from sqlalchemy import true # 创建窗口 cv2.namedWindow('video', cv2.WINDOW_GUI_NORMAL) # 设置大小 cv2.resizeWindow('video', 800, 600) # 获取摄像头设备 cap = cv2.VideoCapture(0) # 读取视频 # cap = cv2.VideoCapture( # 'G:\\BaiduNetdiskWorkspace\\Code From VSCode\\OpenCV\\VLAN划分.mp4') # 判断摄像头是否为打开状态 while cap.isOpened(): # 从摄像头读取视频帧 ret, frame = cap.read() if ret == True: # 将视频帧在窗口显示 cv2.imshow('video', frame) # 重新设置窗口 cv2.resizeWindow('video', 640, 360) # 等待键盘事件 key = cv2.waitKey(30) if key & 0xff == ord('q'): break else: break # 释放VideoCapture cap.release() cv2.destroyAllWindows() ``` #### 视频录制(保存) 1. *VideoWriter_fourcc(*'MJPG')* 2. *VideoWriter('文件',格式,帧率,分辨率)* 3. *vw.write()* 4. *vw.release()* ```python #05.VideoWrite.py import cv2 # 创建VideoWriter为多媒体文件 fourcc = cv2.VideoWriter_fourcc(*'MJPG') vw = cv2.VideoWriter('./out.mp4v', fourcc, 25, (1280, 720)) # 创建窗口 cv2.namedWindow('video', cv2.WINDOW_AUTOSIZE) # 设置大小 # cv2.resizeWindow('video', 800, 600) # 获取摄像头设备 cap = cv2.VideoCapture(0) while True: # 从摄像头读取视频帧 ret, frame = cap.read() # 将视频帧在窗口显示 cv2.imshow('video', frame) # 写数据到多媒体文件 vw.write(frame) # 等待键盘事件 key = cv2.waitKey(30) if key & 0xff == ord('q'): break # 释放VideoCapture cap.release() # 释放VideoWriter vw.release() cv2.destroyAllWindows() ``` ### TrackBar 1. **创建trackbar** *creatTrackbar(tackbarname,winname,value,count)* - trackbarname : 命名 - winname: 所在窗口名 - value: trackbar当前值 - count:最小0,最大count - callback(回调函数),userdata 2. **获取trackbar值** *getTrackbarPos(trackbarname,winname)* ```python #06.trackbar.py import cv2 import numpy as np def callback(): pass # 创建窗口 cv2.namedWindow('trackbar') # 创建trackbar 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: # 获取当前trackbar的值 r = cv2.getTrackbarPos('R', 'trackbar') g = cv2.getTrackbarPos('G', 'trackbar') b = cv2.getTrackbarPos('B', 'trackbar') # 改变背景图片颜色 img[:] = [b, g, r] cv2.imshow('trackbar', img) key = cv2.waitKey(10) if key & 0xff == ord('q'): break cv2.destroyAllWindows() ``` ### 鼠标事件 ```python #09.mouse.py import cv2 import numpy as np # 鼠标回调函数 def mouse_callback(event, x, y, flags, userdata): #获取鼠标点击事件及位置坐标 print(event, x, y, flags, userdata) # mouse_callback(1, 100, 100, 16, "6666") # 创建窗口 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() ``` ## 二、基本图形的绘制 ### 色彩空间 - RGB :人眼的色彩空间 - OpenCV默认使用BGR - HSV/HSB/HSL #### HSV Hue:色相,即色彩,如红色,蓝色 Saturation:饱和度,颜色的纯度 Value:名度 ![1646738947761](assets/1646738947761.png) #### HSL Hue:色相,即色彩,如红色,蓝色 Saturation:饱和度,颜色的纯度 Ligthness:亮度 ![1646743391264](assets/1646743391264.png) ### 通道的分离与合并 1. **通道分离** *split(mat)* 2. **通道合并** *merge((ch1,ch2,ch3,…))* ```python #07.sp_merge.py import cv2 import numpy as np img = np.zeros((480, 640, 3), np.uint8) #通道分离 b, g, r = cv2.split(img) b[10:100, 10:100] = 255 g[10:100, 10:100] = 255 #通道合并 img2 = cv2.merge((b, g, r)) cv2.imshow('img', img) cv2.imshow('b', b) cv2.imshow('g', g) cv2.imshow('img2',img2) cv2.waitKey(0) ``` ### 图形绘制 1. **绘制直线** *cv2.line(img,(x,y),(x,y),(b,g,r),width)* 2. **绘制矩形** *cv2.rectangle(img,(x,y),(x,y),(b,g,r),width)* 3. **绘制圆** *cv2.circle(img,(x,y), r, (B,G,R), width)* 4. **绘制椭圆** *cv2.ellipse(img,(x,y),(a,b),角度,0,360,(0,124,123),3)* - 0,360: 从0度开始,顺时针画到360度 5. **绘制多边形** *cv2.polylines(img, [pts], True, (112, 150, 123))* pts点集 6. **绘制文本** *cv2.putText(img,字符串,起始点,字体,字号……)* ```python #08.draw.py import cv2 from cv2 import FONT_HERSHEY_COMPLEX import numpy as np # 创建背景图片 img = np.zeros((480, 640, 3), np.uint8) # 画线,坐标为(x,y)) cv2.line(img, (10, 20), (300, 400), (0, 0, 255), 1) cv2.line(img, (10, 100), (300, 100), (0, 230, 25), 5) # 画矩形 cv2.rectangle(img, (320, 200), (500, 100), (0, 0, 255), 5) # 画圆 cv2.circle(img, (320, 240), 100, (0, 0, 255)) cv2.circle(img, (320, 240), 27, (255, 0, 0), 1) # 画椭圆 # 度是按顺时针计算的 # 0度是从左侧开始的 cv2.ellipse(img, (320, 240), (100, 50), 0, 0, 360, (0, 124, 123), 3) cv2.ellipse(img, (320, 240), (200, 100), 90, 0, 360, (0, 124, 123), 3) # 画多边形 # 三个点 pts = np.array([(300, 10), (150, 100), (450, 100)], np.int32) cv2.polylines(img, [pts], True, (112, 150, 123)) # 填充多边形 cv2.fillPoly(img, [pts], (255, 255, 0)) #绘制文本 cv2.putText(img, 'hello,world',(40,200),cv2.FONT_HERSHEY_COMPLEX,3,(255,255,0)) cv2.imshow('img', img) cv2.waitKey(0) ``` ![1646809958434](assets/1646809958434.png) ### 小案例—使用鼠标画简易图形 ```python # 基本功能 # 可以通过鼠标进行基本图形的绘制 # 1. 可以画线,当用户按下l键,即选择了画线。此时,滑动鼠标即可画线 # 2. 可以画矩形,当用户按下r键,即选择了画矩形。此时,滑动鼠标即可画矩形 # 3. 可以画线,当用户按下c键,即选择了画圆。此时,滑动鼠标即可画圆 # ..... # curshape: 0-drawline, 1-drawrectangle, 2-drawcircle import cv2 import numpy as np curshape = 0 startpos = (0, 0) # 显示窗口和背景 img = np.zeros((480, 640, 3), np.uint8) # 鼠标回调函数 def mouse_callback(event, x, y, flags, userdata): global curshape, startpos print(event, x, y, flags, userdata) if event & cv2.EVENT_LBUTTONDOWN == cv2.EVENT_LBUTTONDOWN: startpos = (x, y) elif event & cv2.EVENT_LBUTTONUP == cv2.EVENT_LBUTTONUP: if curshape == 0: # line cv2.line(img, startpos, (x, y), (0, 0, 255), 3) elif curshape == 1: # rectangle cv2.rectangle(img, startpos, (x, y), (255, 0, 0), 3) elif curshape == 2: # circle a = (x-startpos[0]) b = (y-startpos[1]) r = int((a**2+b**2)**0.5) cv2.circle(img, startpos, r, (0, 255, 0)) else: print('error:no shape') # 创建窗口 cv2.namedWindow('drawshape', cv2.WINDOW_NORMAL) # 设置鼠标回调 cv2.setMouseCallback('drawshape', mouse_callback) while True: cv2.imshow('drawshape', img) key = cv2.waitKey(1) & 0xFF if key == ord('q'): break elif key == ord('l'): # line curshape = 0 elif key == ord('r'): # rectangle curshape = 1 elif key == ord('c'): # circle curshape = 2 cv2.destroyAllWindows() ``` ![1646809927906](assets/1646809927906.png) ## 三、图像运算 ### 图像相加 *add(a,b)* ```python #11.img_add.py import cv2 import numpy as np # 加载图像 tx = cv2.imread('.//src//tx1.jpg') # 图的加法运算就是矩阵的加法运算 # 两张图尺寸必须相等 # 问题 图片路径不能包含中文?? # 见00.Chinese_Path.py print(tx.shape) #生成一张背景图 img = np.ones((640, 640, 3), np.uint8) * 50 #加法运算 result = cv2.add(tx, img) cv2.imshow('car', tx) cv2.imshow('img', img) cv2.imshow('result', result) cv2.waitKey(0) ``` ![1646834364179](assets/1646834364179.png) ### 图像相减 *subtract(a,b) a减b* ```python #12.img_sub.py #下图一 在加法的基础上减去背景图,还原 orig_1 = cv2.subtract(tx, img) #下图二 在原图上减去背景图,更暗 orig_1 = cv2.subtract(tx, img) cv2.imshow('orig_1', orig_1) ``` ![1646871498338](assets/1646871498338.png) ![1646871652138](assets/1646871652138.png) ### 图像乘除 *multiply(A,B)* **乘法** 升的更快 *divide(A,B)* **除法 ** 降的更快 ### 图像融合 **图像溶合** *addWeighted(A,alpha,B,bate,gamma)* - alpha 和 beta是权重 - gamma 静态权重 ```python # 图像溶合 # addWeighted(A,alpha,B,bate,gamma) # alpha 和 beta是权重 # gamma 静态权重 # 13.img_fus.py import cv2 import numpy as np smile = cv2.imread('.//src//tx2.jpg') boy = cv2.imread('.//src//tx1.jpg') # 只有两张图的属性是一样的才可以进行溶合 print(smile.shape) print(boy.shape) result = cv2.addWeighted(smile,0.7,boy,0.3,0) cv2.imshow('add',result) cv2.waitKey(0) ``` ![1646872961979](assets/1646872961979.png) ### 其他操作 ```python # 14.img_bitwise.py import cv2 from cv2 import bitwise_and import numpy as np # 创建一张图片 img1 = np.zeros((400, 400), np.uint8) img2 = np.zeros((400, 400), np.uint8) img1[50:250, 50:250] = 255 img2[200:350, 200:350] = 255 # new_img = cv2.bitwise_not(img1) #非运算 # new_img = cv2.bitwise_and(img1,img2) #与运算 # new_img = cv2.bitwise_or(img1, img2) # 或运算 new_img = cv2.bitwise_xor(img1, img2) # 异或运算 cv2.imshow('img1', img1) cv2.imshow('img2', img2) cv2.imshow('result', new_img) cv2.waitKey(0) ``` #### 非操作 *cv2.bitwise_not(img1)* ![1646874105065](assets/1646874105065.png) #### 与运算 *new_img = cv2.bitwise_and(img1,img2)* ![1646874667984](assets/1646874667984.png) #### 或与异或 *cv2.bitwise_or(img1, img2)* ![1646874778298](assets/1646874778298.png) *cv2.bitwise_xor(img1, img2)* ![1646874853367](assets/1646874853367.png) #### 小作业—图像加水印 1. 引入一张图片 2. 要有一个logo,可自己创建 3. 计算图片在什么地方,在添加的地方变成黑色 4. 利用add,将logo与图处叠加到一起 ```python # 1. 引入一张图片, # 2. 要有一个logo,可自己创建 # 3. 计算图片在什么地方,在添加的地方变成黑色 # 4. 利用add,将logo与图处叠加到一起 import cv2 import numpy as np # 导入图片 tx = cv2.imread('.//src//tx1.jpg') print(tx.shape) # 创建logo logo = np.zeros((200, 200, 3), np.uint8) mask = np.zeros((200, 200), np.uint8) # 绘制logo logo[20:120, 20:120] = [0, 0, 255] logo[80:180, 80:180] = [0, 255, 0] mask[20:120, 20:120] = 255 mask[80:180, 80:180] = 255 # 对mask按位求反 m = cv2.bitwise_not(mask) # 选择添加logo的位置 roi = tx[0:200, 0:200] # 与m进行与操作 roi是三通道的 tmp = cv2.bitwise_and(roi, roi, mask=m) # 添加水印 logo dst = cv2.add(tmp, logo) tx[0:200, 0:200] = dst cv2.imshow('logo', logo) cv2.imshow('mask', mask) cv2.imshow('m', m) cv2.imshow('dst', dst) cv2.imshow('tmp', tmp) cv2.imshow('tx', tx) cv2.waitKey(0) ``` ![1646883122839](assets/1646883122839.png) ## 四、图像变换 ### 图像缩放 *resize(src, dst, dsize,fx,fy,interpolation)* - src:源图像 - dst:目的图像(输出参数) python直接赋值 - dsize:目标大小(缩放多少) - fx,fy:x轴/y轴的缩放因子 (与dsize冲突,保留一个即可,默认dsize) - interpolation:缩放算法 - INTER_NEAREST:临近插值,速度快,效果差 - INTER_LINEAR:双线性插值,原图中的4个点 - INTER_CUBIC:三次插值,原图中的16个点 - INTER_AREA:效果更好,速度最慢 ```python #16.img_scale.py import cv2 from cv2 import INTER_NEAREST import numpy as np tx = cv2.imread('.//src//tx1.jpg') print(tx.shape) #图像缩放 new = cv2.resize(tx, (800, 800), interpolation=cv2.INTER_AREA) # new = cv2.resize(tx,None,fx=0.3,fy=0.3) cv2.imshow('tx', tx) cv2.imshow('new', new) cv2.waitKey(0) ``` ### 图像翻转 *flip(img,flipCode)* - flipCpde == 0上下, >0左右,<0上下+左右 ```python # 17.img_flip.py import cv2 import numpy as np tx = cv2.imread('.//src//tx1.jpg') print(tx.shape) # 图像翻转 # new = cv2.flip(tx, 0) # 上下 # new = cv2.flip(tx,1) #左右 new = cv2.flip(tx, -1) # 上下 左右 cv2.imshow('tx', tx) cv2.imshow('new', new) cv2.waitKey(0) ``` ![1646894843224](assets/1646894843224.png) ![1646894864680](assets/1646894864680.png) ![1646894905196](assets/1646894905196.png) ### 图像旋转 *rotate(img, reteateCode)* - rotateCode - ROTATE_90_CLOCKWISE 顺时针90° - ROTATE_180 180° - ROTATE_90_COUNTERCLOCKWISE 顺指针270° = 逆时针9° ```python #18.img_rotate.py import cv2 import numpy as np tx = cv2.imread('.//src//tx1.jpg') print(tx.shape) # 图像旋转 # new = cv2.rotate(tx, cv2.ROTATE_180) # 180度 # new = cv2.rotate(tx, cv2.ROTATE_90_CLOCKWISE) # 90度 new = cv2.rotate(tx, cv2.ROTATE_90_COUNTERCLOCKWISE) # 270度 cv2.imshow('tx', tx) cv2.imshow('new', new) cv2.waitKey(0) ``` ![1646895358965](assets/1646895358965.png) ![1646895381537](assets/1646895381537.png) ![1646895436806](assets/1646895436806.png) ### 图像的仿射变换 仿射变换是图像旋转、缩放、平移的总称。 #### 仿射API - warpAffine(sec, M, dsize, flags, mode, value) - M变换矩阵 - dsize:输出尺寸大小 - flag:与resize中的插值算法一致 - mode:边界外推法标志 - value:填充边界的值 #### 平移矩阵 ​ 矩阵中的每个像素由 (x,y)组成,因此其变换矩阵式2x2的矩阵,平移向量为2x1的向量,所在平移矩阵为2x3矩阵。 ```python # 19.img_affine.py import cv2 import numpy as np tx = cv2.imread('.//src//tx1.jpg') h, w, ch = tx.shape # 生成变换矩阵 M = np.float32([[1, 0, 100], [0, 1, 0]]) ## [1,0,平移量] ## [1,0][0,1] 2x2矩阵 new = cv2.warpAffine(tx, M, (w, h)) cv2.imshow('tx', tx) cv2.imshow('new', new) cv2.waitKey(0) ``` ![1646898933394](assets/1646898933394.png) #### 变换矩阵(一) - getRotationMatrix2D(center, angle, scale) - center 中心点 - angle 角度 - scale 缩放比例 ```python # 在 19.img_affine.py 的基础上将M改为如下 # 对图像进行中心旋转、缩放 # 旋转角度为逆时针 # 中心点是(x,y) M = cv2.getRotationMatrix2D((w/2, h/2), 45, 0.3) ``` ![1646899674265](assets/1646899674265.png) #### 变换矩阵(二) ![1646899216093](assets/1646899216093.png) 要达到如图所示的变换,可用如下api - getAffineTransform(src[], dst[]) 通过三个点来确认变换位置 ```python # 在 19.img_affine.py 的基础上将M改为如下 #src变换前三个点 #dst变换对应的三个点 src= np.float32([[400,300],[500,300],[400,600]]) dst = np.float32([[200,400],[300,500],[100,610]]) M = cv2.getAffineTransform(src, dst) ``` ![1646900232803](assets/1646900232803.png) ### 透视变换 ![1646900384922](assets/1646900384922.png) ​ 将一种坐标系变成另外一种角度,达到我们想要的结果。通过透视变换,可以进行图片矫正。 - warpPerspective(img,M,dsize,…) 参数说明同仿射变换 ★关键在于 M - getPersectiveTransform(src,dst) 需要四个点(图形的四个角) 获取变换矩阵 ```python # 20.img_presective.py import cv2 import numpy as np img = cv2.imread('.//src//page.png') print(img.shape) # 设置对应点 src = np.float32([[20, 195], [380, 190], [0, 715], [450, 690]]) dst = np.float32([[0, 0], [475, 0], [0, 595], [475, 595]]) # 获取变换矩阵 M = cv2.getPerspectiveTransform(src, dst) # 进行透视变换 new = cv2.warpPerspective(img, M, (475, 595)) cv2.imshow('img', img) cv2.imshow('new', new) cv2.waitKey(0) ``` ![1646902771272](assets/1646902771272.png) ## 五、图像滤波 ### 滤波相关概念 滤波的作用:一幅图像通过**滤波器**得到另一幅图像,其中滤波器又称为**卷积核**,滤波的过程成为**卷积**。 ![卷积](assets/1646903054719.png) ![1646904250526](assets/1646904250526.png) ![img](assets/v2-d6893085ec1fe4a0ad20f68589be1768_b.gif) - 卷积核的大小 - 锚点 - 边界扩充 - 步长 #### 卷积 - 卷积核的大小 ​ 卷积核一般为**奇数**,如3x3, 5x5, 7x7等。一方面是增加padding的原因;另一方面是保证**锚点**在中间,防止位置发生偏移。 - 卷积核大小的影响 ​ 在在深度学习中,卷积核越大,看到的(**感受野**)越多,提取的**特征越好**,同时计算量也就越大。 - 计算公式: #### 锚点 锚点即卷积核的中心 #### 边界扩充 - 当卷积核大于1且不进行边界扩充,输出尺寸将相应缩小 - 当卷积核以标准方式进行边界扩充,则输出数据的空间尺寸将与输入相等 ![1646903978440](assets/1646903978440.png) 计算公式: N = (W - F + 2P ) / S + 1 - N 输出图像大小 - W 源图大小 ; F 卷积核大小 ; P 扩充尺寸 - S 步长大小 #### 步长 ![1646904373951](assets/1646904373951.png) ### [实战] 图像卷积 #### 低通滤波与高通滤波 ![1646904547936](assets/1646904547936.png) - 低通滤波:低于某个阈值,可以去除噪音或平滑图像 - 高通滤波:高于某个阈值,可以帮助查找图像的边缘 #### 图像卷积示例 **卷积**cv2.filter2D(src, ddepth, kernel, anchor, delta, borderType)* - ddepth:位深 默认原彩 -1 - keinel:卷积核 - anchor:锚点 默认(-1,-1) 根据核的尺寸寻找锚点 - borderType:边界类型 ```python # 21.img_filter.py import cv2 import numpy as np img = cv2.imread('.//src//ZH-CN7993615424_UHD_4k.jpg') # 生成一个5*5的矩阵 值为 1/25 kernal = np.ones((5, 5), np.float32) / 25 dst = cv2.filter2D(img, -1, kernal) cv2.imshow('dst', dst) cv2.imshow('img', img) cv2.waitKey(0) ``` ![1646914434210](assets/1646914434210.png) ### 低通滤波 主要作用:去噪; #### 方盒滤波与均值滤波卷积核 ![1646914564006](assets/1646914564006.png) - 与均值滤波类似,主要区别为方盒滤波有参数 - 参数a的作用 - normalize = true , a = 1 / W * H - normalize = false , a =1 - 当normalize == true 时,方盒滤波=均值滤波 **方盒滤波** *cv2.boxFilter(src, ddepth, ksize, anchor, normalize, borderType)* - ksize:卷积核大小 - anchor:锚点 默认(-1,-1) 根据核的尺寸寻找锚点 - normalize: - normalize = true , a = 1 / W * H - normalize = false , a =1 - borderType:边界 **均值滤波** *cv2.blur(src, ksize, anchor, borderType)* ```python # 21.img_blur.py import cv2 import numpy as np img = cv2.imread('.//src//ZH-CN7993615424_UHD_4k.jpg') #均值滤波 dst = cv2.blur(img, (5,5)) cv2.imshow('dst', dst) cv2.imshow('img', img) cv2.waitKey(0) ``` ![1646914434210](assets/1646914434210.png) #### 高斯滤波 ![1646916372870](assets/1646916372870.png) ![1646916453105](assets/1646916453105.png) 中心点值可能不是最大,但占比权重最大;周围点值可能很大,但占比权重小。如下图所示。 ![1646916502124](assets/1646916502124.png) 优点:**高斯滤波**主要解决***高斯噪点***。 **API** *GanssianBlur(img, kernal, sigmaX, sigmaY, …)* - sigmaX、sigmaY:中型滤波延展的宽度,也是到中心点的差距 ![1646916897860](assets/1646916897860.png) **sigma**越大,图像越模糊,即平滑程度越大。 ![1646916969493](assets/1646916969493.png) 当没有**sigma**时,则以卷积核大小为基准。 ```python # 23.img_GanssBlur.py import cv2 import numpy as np img = cv2.imread('.//src//Ganss.png') # 高斯滤波 dst = cv2.GaussianBlur(img, (9,9), sigmaX=1) cv2.imshow('dst', dst) cv2.imshow('img', img) cv2.waitKey(0) ``` ![1646918084615](assets/1646918084615.png) #### 中值滤波 ​ 假设有一个数组[1556789],取其中的**中间值**作为卷积后的结果值。 ​ 优点:对**胡椒噪音**效果明显 ![1646918136092](assets/1646918136092.png) **API** *medianBlur(img, ksize)* ```python # 24.img_medianBlur.py import cv2 import numpy as np img = cv2.imread('.//src//Pepper_noise.png') # 中值滤波 dst = cv2.medianBlur(img, 9) cv2.imshow('dst', dst) cv2.imshow('img', img) cv2.waitKey(0) ``` ![1646918463213](assets/1646918463213.png) #### 双边滤波 ​ 优点:可以保留**边缘**,同时可以对边缘内的区域进行平滑处理。 ​ 主要作用是**进行美颜** ​ ![1646918685370](assets/1646918685370.png) **API** *bilaterFilter(img, d, sigmaColor, sigmaSpace,…)* - d:核的大小 - sigmaColor:控制图像灰度变化权重。设置的较大的时候,灰度可以变化的范围也就是越大,那么灰度差异越大也能得到比较高的权重值,细小的边缘因此会被影响掉,不能有很好的保边效果。 - sigmaSpace:控制空间距离的权重变化。当该值较大时,其有效的空间范围就越大,当较小的时候,有效的空间范围就越小,当窗口一定的情况下,sigmaS越大,那么其边缘点也能得到较大的权重,去噪效果明显。 ​ 图像中灰度变化在一定的范围内,两者的灰度差异越大的话,得到的权重应该越小,但是要保证灰度级差异大,也能得到一个有效的权重,就是说,灰度级的差异要落在有效的概率范围内,可以选择3sigma或者2sigma有效范围。因此,sigmaS的设置要根据设定的滤波窗口的大小设置。若Half_size为4,可以选用2sigma原则,设置为2 ```python # 25.img_bilateralfilter.py import cv2 import numpy as np img = cv2.imread('.//src//lena.png') # 中值滤波 dst = cv2.bilateralFilter(img, 7, 20,50) cv2.imshow('dst', dst) cv2.imshow('img', img) cv2.waitKey(0) ``` ![1646919121683](assets/1646919121683.png) ### 高通滤波 - 作用:检测边缘 - 常见的高通滤波: - Sobel(索贝尔)(高斯):抗噪音性比较强 - Scharr(沙尔):可以检测出更细的边缘线,但不可以改变卷积核大小。索贝尔size为-1时即沙尔滤波 - Laplacian(拉普拉斯):不需要单独求x,y的边缘,可以直接检测边缘,但是对于噪音比较敏感,无法进行降噪 #### Sobel算子 - 先向x方向求导,然后再y方向求导,最终结果:| G | = | Gx | + | Gy | **API** *Sobel(src, ddepth, dx, dy, ksize = 3, scale = 1(缩放), delta = 0, borderType = BORDER_DEFAUTLT)* - 当 ksize = 1时,即为Scharr(沙尔)滤波 ```python # 26.img_Sobel.py import cv2 import numpy as np img = cv2.imread('.//src//chess.png') # Sobel滤波 # 索贝尔算子y方向边缘 d1 = cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize = 5) # 索贝尔算子x方向边缘 d2 = cv2.Sobel(img, cv2.CV_64F, 0, 1, ksize = 5) dst = d1 + d2 #dst = cv2.add(d1, d2) #cv2.imshow('d1',d1) #cv2.imshow('d2',d2) cv2.imshow('dst', dst) cv2.imshow('img', img) cv2.waitKey(0) ``` ![1646987201429](assets/1646987201429.png) #### Scharr算子 - 与Sobel类似,只不过使用的kernel值不同 - Scharr只能求某一个方向x/y的边缘 **API** *Scharr(src, ddepth, dx, dy, scale = 1, delta = 0, borderType = BORDER_DEFAUTLT)* ```python # 在26.img_Sobel.py中修改 dst = cv2.Scharr(img, cv2.CV_64F, 1, 0) ``` #### 拉普拉斯算子 - 可以同时求两个方向的边缘 - 对噪音敏感,一般需要先进行去噪再调用拉普拉斯 **API** *Laplacian(img, ddepth, ksize=1, scale=1, borderType = BORDER_DEFAULT)* ```python # 在26.img_Sobel.py中修改 dst = cv2.Laplacian(img, cv2.CV_64F, ksize = 5) ``` ![1646988817396](assets/1646988817396.png) ### 边缘检测Canny - 使用5x5 高斯滤波消除噪声 - 计算图像梯度方向(0°/45°/90°/135°) - 取局部极大值 - 阈值计算 ![1646989229445](assets/1646989229445.png) 如果超过maxVal一定是超过边缘,如果低于maxVal一定不是边缘。 如果在maxVal与minVal之间,看所确定的值是否是连续的。如A是边缘,C在maxVal与minVal之间且与A是连续的(在同一条线上);而B就不被认为是边缘。 **API** *Canny(img, minVal, maxVal,…)* ```python # 27.img_Canny.py import cv2 import numpy as np img = cv2.imread('.//src//lena.png') # Canny边缘检测 dst = cv2.Canny(img, 100, 200) # 图一 # dst = cv2.Canny(img, 50, 150) # 图二 cv2.imshow('dst', dst) cv2.imshow('img', img) cv2.waitKey(0) ``` ![1646990149813](assets/1646990149813.png) ![1646990251122](assets/1646990251122.png) ### Kirsch算子和Robinson算子 #### Kirsch算子 Kirsch算子由以下8个卷积核组成,图像与每一个卷积核进行卷积,然后取**绝对值**作为对应方向上的边缘强度的量化。对8个卷积结果取绝对值,然后取**最大值**作为最后输出的边缘强度。 ![](assets/kirsch.svg) #### Robinson算子 与Kirsch算子类似,Robinson算子也是由8个卷积核组成,其检测过程和Kirsch是一样的。 ![](assets/robinson.svg) ​ Kirsch算子使用了8个方向上的卷积核,所以其检测的边缘比标准的Prewitt算子和Sobel算子检测到的边缘会显得更加丰富。 ## 六、 形态学图像处理 - 基于图像形态进行处理的一些基本方法 - 这些处理方法基本是对二进制图像进行处理 - **卷积核**则决定着图像处理后的效果。 **处理的基本方法**: - **腐蚀**与**膨胀** - 开运算:先做腐蚀再做膨胀 - 闭运算:先做膨胀再做腐蚀 - 顶帽 - 黑帽 * …… ### 图像二值化 ​ 将图像的每个像素变成两种值,如0,255 - 全局二值化:对于全部区域 - 局部二值化:选定某区域 #### 全局二值化 **API** *threshod(img, thresh, maxVal, type)* - 两个返回值:ret:True或False,代表有没有读到图片;dst: 目标图像; - img:图像,最好是灰度图 - thresh:阈值 - maxVal:超过阈值,替换成maxVal - type: - THRESH_BINARY 和 THRESH_BINARY_INV - THRESH_TRUNC - THRESH_TOZERO 和 THRESH_TOZERO_INV ![1646995968734](assets/1646995968734.png) ```python # 28.img_binary.py import cv2 img = cv2.imread('.//src//tx1.jpg') # 转换为灰度图像 tmp = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 二值化 # 超过阈值使用maxVal代替 如下图一 ret, dst = cv2.threshold(tmp, 180, 255, cv2.THRESH_BINARY) # 低于阈值使用maxVal代替 如下图二 ret, dst = cv2.threshold(tmp, 180, 255, cv2.THRESH_BINARY_INV) # 低于阈值保留,高于的使用阈值替代 ret, dst = cv2.threshold(tmp, 180, 255, cv2.THRESH_TRUNC) # 高于阈值保留,高于的使用0替代 ret, dst = cv2.threshold(tmp, 180, 255, cv2.THRESH_TOZERO) # 低于阈值保留,高于的使用0替代 ret, dst = cv2.threshold(tmp, 180, 255, cv2.THRESH_TOZERO_INV) print(dst.shape) cv2.imshow('img', img) cv2.imshow('tmp', tmp) cv2.imshow('dst', dst) cv2.waitKey(0) ``` ![1646995536540](assets/1646995536540.png) ![1646995578087](assets/1646995578087.png) #### 自适应阈值 二值化缺点:由于**光照不均匀**以及阴影的存在,只有一个阈值会使得在阴影处的白色被**二值化成黑色/白色**。如下图所示。 ![1646996685925](assets/1646996685925.png) 对于这种图像,则适用于**自适应阈值**。 **API** ***adaptiveThreshold(img, maxVal, adaptiveMethod, type, blockSize, C)*** - adaptiveMethod:计算阈值的方法 - ADAPTIVE_THRESH_MEAN_C:计算临近区域的平均值 - ADAPTIVE_THRESH_GAUSSIAN_C:高斯窗口加权平均值(一般使用) - Type: - THRESH_BINARY - THRESH_BINARY_INV - blockSize:临近区域的大小 - C:常量,应从计算出的平均值或加权平均值中减去 ```python # 29.img_adaptive.py import cv2 img = cv2.imread('.//src//math.png') # 转换为灰度图像 tmp = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 二值化 # 超过阈值使用maxVal代替 ret1, dst1 = cv2.threshold(tmp, 180, 255, cv2.THRESH_BINARY) # 自适应阈值 dst2 = cv2.adaptiveThreshold( tmp, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 0) cv2.imshow('img', img) cv2.imshow('tmp', tmp) cv2.imshow('dst1', dst1) cv2.imshow('dst2', dst2) cv2.waitKey(0) ``` 结果如下:图一为原始图像;图二为对应灰度图像;图三为二值化图像;图四为自适应阈值后的图像。 ![1646997293373](assets/1646997293373.png) ### 处理的基本方法 #### 腐蚀 通过腐蚀,可以使图像体积小很多。 ![1646997717607](assets/1646997717607.png) 当卷积核全部位于白色区域时,卷积核中心才是白色,否则为黑色。 ![1646997601142](assets/1646997601142.png) 一般情况下,腐蚀的卷积核为全 **1**。 ![1646997962353](assets/1646997962353.png) ![1646998033476](assets/1646998033476.png) **API** *erode(img, kernel, iterations = 1)* - iterations :腐蚀次数 ```python # 30.img_erode.py import cv2 import numpy as np img = cv2.imread('.//src//j.png') # 创建一个3x3的卷积核 kernel = np.ones((3, 3), np.uint8) # 腐蚀 dst = cv2.erode(img, kernel, iterations=5) cv2.imshow('img', img) cv2.imshow('dst', dst) cv2.waitKey(0) ``` ![3x3 腐蚀5次](assets/1646998311916.png) **卷积核的类型** **API:获取卷积核** *getStructuringElement(type, size)* - size: (3, 3) (5, 5) …… - type: - MORPH_RECT:矩形 - MORPH_ELLIPSE:圆形 - MORPH_CROSS:交叉 ```python # 31.img_getkernel.py import cv2 import numpy as np img = cv2.imread('.//src//j.png') # 获取一个卷积核 kernel1 = cv2.getStructuringElement(cv2.MORPH_RECT, (7, 7)) # 矩形 kernel2 = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (7, 7)) # 圆形 kernel3 = cv2.getStructuringElement(cv2.MORPH_CROSS, (7, 7)) # 交叉(十字架) print(kernel1, kernel2, kernel3) # 腐蚀 dst1 = cv2.erode(img, kernel1, iterations=3) dst2 = cv2.erode(img, kernel2, iterations=3) dst3 = cv2.erode(img, kernel3, iterations=3) # cv2.imshow('img', img) cv2.imshow('dst1', dst1) cv2.imshow('dst2', dst2) cv2.imshow('dst3', dst3) cv2.waitKey(0) ``` ![1646998875329](assets/1646998875329.png) #### 膨胀 即腐蚀的逆运算。 ![1646999112477](assets/1646999112477.png) 只要卷积核中心不为 **0**,其周边全部变为非 0 。 膨胀的快慢与卷积核大小有关。 ![1646999246649](assets/1646999246649.png) **API** *dilate(img, kernel, iterations = 1)* ```python # 32.img_dilate.py import cv2 import numpy as np img = cv2.imread('.//src//j.png') # 创建一个卷积核 kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5)) # 矩形 # 腐蚀 dst1 = cv2.erode(img, kernel, iterations=1) # 膨胀 dst2 = cv2.dilate(img, kernel, iterations=1) cv2.imshow('img', img) cv2.imshow('dst1', dst1) cv2.imshow('dst2', dst2) cv2.waitKey(0) ``` ![1646999497114](assets/1646999497114.png) 思考: - 如果是白底黑字,进行腐蚀和膨胀会怎样? - 卷积核是否可以设置为全 **0**? #### 开运算 **开运算** = **腐蚀** + **膨胀** ![1646999653110](assets/1646999653110.png) **作用**:消除字体外部噪点。 实现一:先腐蚀,再膨胀 ```python # 修改32.img_dilate.py import cv2 img = cv2.imread('.//src//dotJ.png') # 创建一个卷积核 kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (7, 7)) # 矩形 # 腐蚀 dst1 = cv2.erode(img, kernel, iterations=2) # 膨胀 dst2 = cv2.dilate(dst1, kernel, iterations=2) cv2.imshow('img', img) cv2.imshow('dst1', dst1) cv2.imshow('dst2', dst2) cv2.waitKey(0) ``` ![1647000048928](assets/1647000048928.png) 实现二:调用API **API** *morphologyEx(img, MORPH_OPEN), kernel)* ```python # 33.img_open.py import cv2 img = cv2.imread('.//src//dotJ.png') # 创建一个卷积核 kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 9)) # 矩形 # 开运算 dst = cv2.morphologyEx(img, cv2.MORPH_OPEN, kernel) cv2.imshow('img', img) cv2.imshow('dst1', dst) cv2.waitKey(0) ``` ![1647000278911](assets/1647000278911.png) #### 闭运算 **闭运算** = **膨胀** + **腐蚀** ![1647000394320](assets/1647000394320.png) **作用**: 消除图像内部噪点。 实现:调用API **API** *morphologyEx(img, MORPH_CLOSE), kernel)* ```python # 34.img_close.py import cv2 img = cv2.imread('.//src//dotinJ.png') # 创建一个卷积核 kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 9)) # 矩形 # 闭运算 dst = cv2.morphologyEx(img, cv2.MORPH_CLOSE, kernel) cv2.imshow('img', img) cv2.imshow('dst1', dst) cv2.waitKey(0) ``` ![1647000727537](assets/1647000727537.png) #### 形态学梯度 **梯度** = **原图** - **腐蚀** ![1647001061398](assets/1647001061398.png) **作用**: 求图像边沿。 **API** *morphologyEx(img, MORPH_GRADIENT), kernel)* ```python # 35.img_gradient.py import cv2 img = cv2.imread('.//src//j.png') # 创建一个卷积核 kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (9, 9)) # 矩形 # 梯度运算 dst = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel) cv2.imshow('img', img) cv2.imshow('dst1', dst) cv2.waitKey(0) ``` ![1647001447556](assets/1647001447556.png) #### 顶帽运算 **顶帽** = **原图** - **开运算** **作用**: 可以得到图像外的噪声。 ![1647001627122](assets/1647001627122.png) **API** *morphologyEx(img, MORPH_TOPHAT), kernel)* ```python # 36.img_tophat.py import cv2 img = cv2.imread('.//src//tophat.png') # 创建一个卷积核 kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (19, 19)) # 矩形 # 顶帽运算 dst = cv2.morphologyEx(img, cv2.MORPH_TOPHAT, kernel) cv2.imshow('img', img) cv2.imshow('dst1', dst) cv2.waitKey(0) ``` ![1647001938421](assets/1647001938421.png) #### 黑帽运算 **黑帽** = **闭运算** - **原图** **作用**: 可以得到图像内的噪声。 ![1647002147208](assets/1647002147208.png) **API** *morphologyEx(img, MORPH_BLACKHAT), kernel)* ```python # 37.img_blackhat.py import cv2 img = cv2.imread('.//src//dotinJ.png') # 创建一个卷积核 kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (7, 7)) # 矩形 # 黑帽运算 dst = cv2.morphologyEx(img, cv2.MORPH_BLACKHAT, kernel) cv2.imshow('img', img) cv2.imshow('dst1', dst) cv2.waitKey(0) ``` ![1647002292562](assets/1647002292562.png) #### 小结 - 开运算,先腐蚀在膨胀,去除大图形外的小图形 - 闭运算,线膨胀在腐蚀,去除大图形内的小图形 - 梯度,求图形边缘 - 顶帽,原图减去开运算,得到大图形外的小图形 - 黑帽,闭运算减去原图,得到大图形内的小图形 ## 七、图像轮廓 具有相同**颜色**或**强度**的**连续点**的曲线。 ![1647051587947](assets/1647051587947.png) **作用:** - 可以用于图形分析 - 物体的识别与检测 **注意点:** - 为了检测的准确性,需要先对图像进行二值化或者Canny操作 - 画轮廓时会修改原始输入的图像 ### 查找轮廓 **API** ***findContours(img,mode, ApproximationMode…)*** - 两个返回值:contours [轮廓列表] 和 hierarchy [层级] - **mode**: - RETR_EXTERNAL = 0 ,表示只检测外轮廓 - RETR_LIST = 1, 检测的轮廓不建立等级关系 :从里到外,从右到左 - RETR_CCOMP = 2, 每层对多两级 - RETR_TREE = 3, 按树形存储轮廓:从大到小,从右到左 - ApproximationMode - CHAIN_APPROX_NONE,保存所有轮廓上的点 - CHAIN_APPROX_SIMPLE,只保存角点 ![1647052026428](assets/1647052026428.png) ![1647054170973](assets/1647054170973.png) ![1647054234869](assets/1647054234869.png) ![1647055836081](assets/1647055836081.png) ![1647056031048](assets/1647056031048.png) ```python # 38.img_contours.py import cv2 img = cv2.imread('.//src//contours.png') print(img.shape) # 转化为灰度图 ---单通道 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) print(gray.shape) # 二值化 ret, binary = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY) # 轮廓查找 contours, hirarchy = cv2.findContours( binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # contours, hirarchy = cv2.findContours( # binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) # 打印轮廓列表 print(contours) # cv2.imshow('img', img) # cv2.imshow('gray', gray) cv2.imshow('bin', binary) cv2.waitKey(0) ``` ![1647070410904](assets/1647070410904.png) ### 绘制轮廓 **API** ***drawContours(img, contours, contourIdx, color)*** - img:目标图像 - contours:轮廓点集 - contourIdx , -1:表示绘制所有轮廓 - color:颜色(0,0,255) - thickness:线宽,-1 是全部填充 ```python # 在38.img_contours.py基础上修改 import cv2 img = cv2.imread('.//src//contours.png') print(img.shape) # 转化为灰度图 ---单通道 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) print(gray.shape) # 二值化 ret, binary = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY) # 轮廓查找 contours, hirarchy = cv2.findContours( binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) # 轮廓列表 print(contours) # 绘制轮廓 会在原图上进行修改 cv2.drawContours(img, contours, -1, (0, 0, 255), 1) cv2.imshow('img', img) cv2.imshow('bin', binary) cv2.waitKey(0) ``` ![1647070947227](assets/1647070947227.png) ### 轮廓的面积和周长 #### 计算面积 **API** ***contourArea(contour)*** - 结果为图像的面积 - contour:轮廓 #### 计算周长 **API** ***arcLength(curve,closed)*** - curve:轮廓 - closed:是否是闭合的轮廓 #### Code ```python # 在38.img_contours.py基础上修改 import cv2 img = cv2.imread('.//src//contours.png') print(img.shape) # 转化为灰度图 ---单通道 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) print(gray.shape) # 二值化 ret, binary = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY) # 轮廓查找 contours, hirarchy = cv2.findContours( binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) # 绘制轮廓 cv2.drawContours(img, contours, -1, (0, 0, 255), 1) # 计算面积 area = cv2.contourArea(contours[0]) print("area=%d" % (area)) # 计算周长 len = cv2.arcLength(contours[0], True) print('len=%d' % (len)) cv2.imshow('img', img) cv2.waitKey(0) ``` ![1647071749964](assets/1647071749964.png) ### 多边形逼近与凸包 - **逼近** :减少数据量,通过特征点将目标描绘出来,如下图左图。 - **凸包** :描绘整个‘大’轮廓,如下图右图。 ![1647071891839](assets/1647071891839.png) #### 逼近 **API** ***approxPolyDP(curve, epsilon, closed)*** - curve:轮廓 - epsilon: 精度 - closed:是否是闭合的轮廓 #### 凸包 **API** ***convexHull(points, clockwise,…)*** - points:轮廓 - clockwise:顺时针绘制 #### Code ```python # 39.img_approx_hull.py import cv2 # 连接直线形成闭合 def drawShape(src, points): i = 0 while i < len(points): if i == len(points) - 1: # 处理最后一个点,与第一个点连形成闭合 x, y = points[i][0] x1, y1 = points[0][0] cv2.line(src, (x, y), (x1, y1), (0, 0, 255), 2) else: x, y = points[i][0] # 连接点集 x1, y1 = points[i+1][0] cv2.line(src, (x, y), (x1, y1), (0, 0, 255), 2) i += 1 img = cv2.imread('.//src//hand.png') # 转化为灰度图 ---单通道 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 二值化 ret, binary = cv2.threshold(gray, 100, 255, cv2.THRESH_BINARY) # 轮廓查找 contours, hirarchy = cv2.findContours( binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) # 绘制轮廓 cv2.drawContours(img, contours, 0, (0, 255, 0), 3) # 多边形逼近 e = 20 approx = cv2.approxPolyDP(contours[0], e, True) # 多边形凸包 hull = cv2.convexHull(contours[0]) drawShape(img, approx) drawShape(img, hull) cv2.imshow('img', img) cv2.waitKey(0) ``` ![1647073201053](assets/1647073201053.png) ### 外接矩形 - 最小外接矩形: 能够涵盖图像所有轮廓的最小矩形 - 最大外接矩形: 能够涵盖图像所有轮廓的最大矩形 ![1647073487507](assets/1647073487507.png) #### 最小外接矩形 **API** ***minAreaRect(points)*** - points:轮廓 - 返回值:RotatedRect - x,y - width,height - angle #### 最大外接矩形 **API** ***boundingRect(array)*** - array:轮廓 - 返回值:Rect ```python # 40.img_mbRect.py import cv2 import numpy as np img = cv2.imread('.//src//hello.png') print(img.shape) # 转化为灰度图 ---单通道 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) print(gray.shape) # 二值化 ret, binary = cv2.threshold(gray, 150, 255, cv2.THRESH_BINARY) # 轮廓查找 contours, hirarchy = cv2.findContours( binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) # 绘制轮廓 cv2.drawContours(img, contours, -1, (0, 0, 255), 1) # 最小外接矩形 r = cv2.minAreaRect(contours[1]) box = cv2.boxPoints(r) # box为float类型,需要转换为int型 box = np.int0(box) cv2.drawContours(img, [box], 0, (0, 0, 255), 3) # 最大外接矩阵 x, y, w, h = cv2.boundingRect(contours[1]) cv2.rectangle(img, (x, y), (x+w, y+h), (255, 0, 0), 2) cv2.imshow('img', img) cv2.waitKey(0) ``` ![1647079310117](assets/1647079310117.png) ## 八、基于形态学的车辆识别与统计 对于车辆识别来讲,主要分为以下几个步骤: - 加载视频 - 通过形态学识别车辆 - 对车辆进行统计 - 显示车辆统计信息 ### 加载视频 加载视频,在OpenCV的基本操作中已经做过阐述,详见`从摄像头/视频文件读取视频帧` ```python import cv2 import numpy as np # 加载视频 cap = cv2.VideoCapture('.//src//video.mp4') while True: ret, frame = cap.read() if ret == True: cv2.imshow('video', frame) key = cv2.waitKey(1) & 0xFF if key == ord('q'): break cap.release() cv2.destroyAllWindows() ``` ### 去背景 ​ 对视频来讲,大部分场景,背景是相同的不变的,差别在于运动的物体,因此运动的物体可视为前景,而静止的物体则被视为背景,通过去除背景,就可以将运动的物体提取出来。 ​ 在视频中某一帧,某一特定区域内,它的像素在整个时间轴的范围内没有发生变化,则可以认为是背景。 **API** ***creatBackgroundSubtractorMOG(…)*** - history = 200 在最新的OpenCV v4中 API修改为*createBackgroundSubtractorMOG2()*,BackgroundSubtractorMOG2用于动态目标检测,用到的是基于自适应混合高斯背景建模的背景减除法,相对于BackgroundSubtractorMOG,其具有更好的抗干扰能力,特别是光照变化。 详见:https://docs.opencv.org/3.2.0/d7/d7b/classcv_1_1BackgroundSubtractorMOG2.html ```python bgsubmog = cv2.createBackgroundSubtractorMOG2() # 去背景 mask = bgsubmog.apply(dst) ``` ### 形态处理 ```python # 灰度化 cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 高斯降噪 blur = cv2.GaussianBlur(frame, (7, 7), 7) # 中值滤波 dst = cv2.medianBlur(blur, 5) # 腐蚀 erode = cv2.erode(mask, kernel) # 膨胀 dilate = cv2.dilate(erode, kernel, 3) # 闭操作 close = cv2.morphologyEx(dilate, cv2.MORPH_CLOSE, kernel) close = cv2.morphologyEx(close, cv2.MORPH_CLOSE, kernel) close = cv2.morphologyEx(close, cv2.MORPH_CLOSE, kernel) # 获取轮廓 cnts, h = cv2.findContours( close, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) cv2.line(frame, (10, line_high), (1200, line_high), (255, 255, 0), 2) ``` ![1647175405891](assets/1647175405891.png) ### 显示车辆统计信息 ```python for (x, y) in cars: if((y < line_high - offset) and (y > line_high)+offset): carno += 1 cars.remove((x, y)) print(carno) cv2.putText(frame, 'Car nums:'+str(carno), (500, 60), cv2.FONT_HERSHEY_SCRIPT_COMPLEX, 2, (255, 0, 0), 5) ``` ![1647343220479](assets/1647343220479.png) ### 完整代码 ```python import cv2 from cv2 import createBackgroundSubtractorMOG2 from cv2 import line import numpy as np # 加载视频 cap = cv2.VideoCapture('.//src//video.mp4') bgsubmog = createBackgroundSubtractorMOG2() # 创建一个卷积核 kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (7, 7)) # 矩形 # 设置最小宽高 min_w = 150 min_h = 150 # 检测线的高度 line_high = 500 # 线的偏移量 offset = 4 # 统计车的数量 carno = 0 # 存放有效车辆数组 cars = [] def center(x, y, w, h): x1 = int(w/2) y1 = int(h/2) cx = x + x1 cy = y+y1 return cx, cy while True: ret, frame = cap.read() if ret == True: # 灰度化 cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) # 高斯降噪 blur = cv2.GaussianBlur(frame, (7, 7), 7) # 中值滤波 dst = cv2.medianBlur(blur, 5) # 去背景 mask = bgsubmog.apply(dst) # 腐蚀 erode = cv2.erode(mask, kernel) # 膨胀 dilate = cv2.dilate(erode, kernel, 3) # 闭操作 close = cv2.morphologyEx(dilate, cv2.MORPH_CLOSE, kernel) close = cv2.morphologyEx(close, cv2.MORPH_CLOSE, kernel) close = cv2.morphologyEx(close, cv2.MORPH_CLOSE, kernel) close = cv2.morphologyEx(close, cv2.MORPH_CLOSE, kernel) # 获取轮廓 cnts, h = cv2.findContours( close, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) cv2.line(frame, (10, line_high), (1200, line_high), (255, 255, 0), 2) # 绘制轮廓 for (i, c) in enumerate(cnts): (x, y, w, h) = cv2.boundingRect(c) # 对车辆的宽高进行判断 # 以验证是否是有效的车辆 isValid = (w >= min_w) and (h >= min_h) if not isValid: continue # 绘制矩形 cv2.rectangle(frame, (x, y), (x+w, y+h), (0, 0, 255), 5) # 对车进行统计 cpoint = center(x, y, w, h) cars.append(cpoint) print(cpoint) # 要有一条线 # 有范围 6 # 从数组中减去 for (x, y) in cars: if((y < line_high - offset) and (y > line_high)+offset): carno += 1 cars.remove((x, y)) print(carno) cv2.putText(frame, 'Car nums:'+str(carno), (500, 60), cv2.FONT_HERSHEY_SCRIPT_COMPLEX, 2, (255, 0, 0), 5) cv2.imshow('video', frame) key = cv2.waitKey(20) & 0xFF if key == ord('q'): break cap.release() cv2.destroyAllWindows() ``` ![1647174325154](assets/1647174325154.png) ### 问题 ​ 由于是基于形态学的轮廓识别,对于视频的拍摄要较高的要求,如道路等背景必须完全静止,拍摄晃动会导致不能正确识别前景、起风等不可控天气因素造成的树木、小草等晃动会被CV“过度”识别,误以为是车辆,或表现为或大或小的噪点增多……为了解决这种问题,使用深度学习、人工智能等实现车辆识别更加准确,以后将慢慢学习更新。 ## 九、特征匹配 **什么是特征** ​ **图像特征**就是指有意义的图像区域,具有独特性、易于识别性,比如角点、斑点以及高密度区。 ### 角点 - 在特征中最重要的是角点 - **灰度梯度**的**最大值**对应的**像素** - 两条线的**交点** - **极值点**(一阶导数最大值,但二阶导数为0) #### Harris角点 ![1647314542933](assets/1647314542933.png) 1. 当窗口在平面上任何一个方向进行移动时,只要没有像素的变化,Harris检测就认为是平坦的 2. 当窗口在边沿上上下移动时,即使边沿两侧像素点是不同的,但由于窗口一直在边沿上运动,是没有像素变化的 3. 当窗口在边沿上左右移动时,这时候像素点就会发生变化,那么这里就是边沿 4. 当窗口处于角点位置时,无论窗口朝哪个方向移动,像素值都会发生变化,那么这时就检测到一个角点 **Harris点总结** - 光滑地区,无论向哪里移动,衡量系数不变 - 边缘地址,垂直边缘移动时,衡量系统变化剧烈 - 在交点处,往哪个方向移动,衡量系统都剧烈变化 **API** ***cornerHarris(img, dst, blockSize, ksize, k)*** - blockSize:检测窗口大小 - ksize:Sobel的卷积核 - k:权重系数,经验值,一般取0.02~0.04之间 ```python # 41.img_harris.py import cv2 import numpy as np # harris blockSize = 2 ksize = 3 k = 0.04 img = cv2.imread('.//src//chess.png') # 灰度化 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # Harris角点检测 dst = cv2.cornerHarris(gray, blockSize, ksize, k) # Harris角点的展示 img[dst > 0.01*dst.max()] = [0, 0, 255] cv2.imshow('harris', img) cv2.waitKey(0) ``` ![1647342523589](assets/1647342523589.png) #### Shi-Tomasi角点★ ​ Shi-Tomasi是Harris角点检测的改进,Harris角点检测的稳定性和**k**有关,而**k**是个经验值,不好设定最佳值,对于不同的图片需要设定不同的最佳**k**值。 **API** ***goodFeaturesToTracck(img, maxCorners, …)*** - maxCoerners:角点的最大数,值为0表示无限制 - qualityLevel:小于1.0的正数,一般在0.01~0.1之间 - minDistance:角之间最小欧式距离,忽略小于此距离的点 - mask:感兴趣的区域 - blockSize:检测窗口 - useHarrisDetector:是否使用Harris算法,默认False - k:默认0.04,当设置使用Harris时,需要设置k值 ```python # 42.img_Shi-Tomasi.py import cv2 import numpy as np # Shi-Tomasi参数 maxCorners = 1000 ql = 0.01 minDistance = 10 img = cv2.imread('.//src//chess.png') # 灰度化 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # Shi-Tomasi角点检测 corners = cv2.goodFeaturesToTrack(gray, maxCorners, ql, minDistance) corners = np.int0(corners) # Shi-Tomasi绘制角点 for i in corners: x, y = i.ravel() cv2.circle(img, (x, y), 3, (255, 0, 0), -1) cv2.imshow('harris', img) cv2.waitKey(0) ``` ![1647342347270](assets/1647342347270.png) ### 关键点检测和计算描述算子 #### SIFT检测 ##### **SIFT(Scale-Incariant Feature Transform)** **出现的原因** - Harris角点具有旋转不变的特性 - 但缩放后,原来的角点有可能就不是角点了,如下图 ![1647343383859](assets/1647343383859.png) ​ 我们知道,一条线是由许多像素点连续拼接而成的,对于缩放前的角被Harris识别为角点,但放大后,之前识别的角点被许多连续的像素点替代,这时Harris就无法正确的识别为角点,而是用SIFT关键点检测依然可以正确识别到角点。 **使用SIFT步骤** 1. 创建SIFT对象 2. 进行检测,***kp = sift.detect(img,……)*** 3. 绘制关键点,***drawKeypoints(gray, kp, img)*** ```python # 43.img_sift.py import cv2 # 读文件 img = cv2.imread('.//src//chess.png') # 灰度化 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 创建sift对象 sift = cv2.SIFT_create() # 进行检测 kp, des = sift.detectAndCompute(gray, None) # 绘制keypoints cv2.drawKeypoints(gray, kp, img) cv2.imshow('img', img) cv2.waitKey(0) ``` ![1647344737352](assets/1647344737352.png) ##### **SIFT计算描述算子** **关键点和描述子** - 关键点:位置,大小和方向 - 关键点描述子:记录了关键点周围对其有贡献的像素点的一组向量值,其不受仿射变换、关照变换等影响 **只计算描述子** - ***kp, des = sift.compute(img, ip)*** - 其**作用**是进行**特征匹配** **同时计算关键点和描述子** - ***kp, des = sift.detectAndCompute(img,……)*** - mask:指明对img中哪个区域进行**计算** #### SURF检测 **SURF(Speeded-Up Robust Features)** **SURF的优点** SIFT最大的问题是速度慢,如果对于一系列的图片进行特征点检测,SIFT则显得比较吃力,因此才有SURF,SURF主要解决SIFT的速度问题。 **使用SURF的步骤** - ***surf = cv2.SURF_create()*** - ***kp, des = surf.datectAndCompute(img, mask)*** ```python # 44.img_ruft.py import cv2 import numpy as np # 读文件 img = cv2.imread('.//src//chess.png') # 灰度化 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 创建surf对象 surf = cv2.xfeatures2d.SURF_create() # 使用surf进行检测 kp, des = surf.detectAndCompute(gray, None) # 绘制keypoints cv2.drawKeypoints(gray, kp, img) #cv2.imshow('img', img) cv2.waitKey(0) ``` ![1647349771559](assets/1647349771559.png) 通过编程可能会有的小伙伴遇到如下这个问题: ![1647349810412](assets/1647349810412.png) ​ 这是由于在最新版本中,OpenCV已经没有SURF的版权,将opencv版本退到3.4.2即可解决,卸载之前的包,然后重新安装。 ```powershell pip uninstall opencv-python pip install opencv-python==3.4.2.16 pip install opencv-contrib-python==3.4.2.16 ``` 然后使用即可解决。 ```python surf = cv2.xfeatures2d.SURF_create() ``` #### ORB检测 **优势** - FAST可以做到描述点的实时监测,BRIEF是对已检测到的特征点进行描述,他加快了特征描述符建立的速度,同时也极大的降低了特征匹配的时间。 - ORB = Oriented FAST + Rotated BRIEF **使用ORB的步骤** - ***orb = cv2.ORB_create()*** - ***kp, des = orb.detectAndCompute(img, mask)*** ```python # 45.img_ord.py import cv2 #读文件 img = cv2.imread('.//src//chess.png') #灰度化 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) #创建ORB对象 orb = cv2.ORB_create() #orb进行检测 kp, des = orb.detectAndCompute(gray, None) #绘制keypoints cv2.drawKeypoints(gray, kp, img) cv2.imshow('img', img) cv2.waitKey(0) ``` ![1647350595506](assets/1647350595506.png) ​ 通过ORB算法与其他算法比较发现,ORB算子得到的特征点比较少,这也是ORB速度快的原因:通过减少数据量,降低计算量,达到快速度。 ### 特征匹配 #### 暴力特征匹配 **BF(Brute-Force),暴力特征匹配方法** **原理** ​ 它使用第一组中的**每个特征**的描述子,与第二组中的**所有特征描述子**进行匹配。计算它们之间的差距,然后将最接近一个匹配数据返回。 **OpenCV特征匹配步骤** - 创建匹配器,***BFMatcher(normType, corssCheck)*** - 进行特征匹配,***bf.match(des1, des2)*** - 绘制匹配点,***cv2.drawMatches(img1, kp1, img2, kp2, ……)*** **API** ***BFMatcher()*** - normType:NORM_L1, **NORM_L2**, HAMMING1…… - crossCheck:是否进行交叉匹配,默认为false **API** ***match(des1, des2)*** - 参数为SIFT、SURF、OBR等计算的描述子 - 对两幅图的描述子进行计算 **API** ***drawMatches()*** - 搜索img, kp - 匹配img, kp - match()方法返回匹配结果 ```Python #46.img_bfmatch.py import cv2 # 读文件 img1 = cv2.imread('.//src//opencv_search.png') img2 = cv2.imread('.//src//opencv_orig.png') # 灰度化 g1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY) g2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY) # 创建sift对象 sift = cv2.xfeatures2d.SIFT_create() # 进行检测 kp1, des1 = sift.detectAndCompute(g1, None) kp2, des2 = sift.detectAndCompute(g2, None) # 创建bf暴力匹配对象 bf = cv2.BFMatcher(cv2.NORM_L1) match = bf.match(des1, des2) img3 = cv2.drawMatches(img1, kp1, img2, kp2, match, None) cv2.imshow('img3', img3) cv2.waitKey(0) ``` ![1647511052505](assets/1647511052505.png)   通过观察我们可以看到,大多数的特征点是可以一一对应、相互匹配到的,而对于一些匹配不到的特征点则是按照相似度最高进行匹配。 #### FLANN匹配 **FLANN优缺点** - 在进行批量特征匹配时,FLANN速度更**快** - 由于它使用的是**邻近近似值**,所以精度较差 **FLANN特征匹配步骤** - 创建FLANN匹配器,***FlannBasedMatcher(……)*** - 进行特征匹配,***flann.match/knnMatch(……)*** - 绘制匹配点,***cv2.drawMatches/drawMatchersKnn(……)*** **API** ***FlannBasedMatcher(index_params, search_params)*** - index_params 字典: 匹配算法KDTREE、LSH - search_params 字典:指定KDTREE算法中遍历树的次数 - KDTREE 设置方法 - `index_params = dict(algorithm = FLANN_INDEX_KDTREE, tree = 5)` ,经验值:5 ,比较快 - `search_params = dict(checks = 50)` **API** ***knnMatch(des1, des2, k)*** - 参数为SIFT、SURF、ORB等计算的描述子 - k,表示取欧式距离最近的前k个关键点 **API** ***DMatch()*** - distance,描述子之间的距离,值越低越好 - queryIdx,第一个图像的描述子索引值 - trainIdx,第二个图像的描述子索引值 - imgIdx,第二图的索引值 **API** ***drawMatchesKnn()*** - 搜索img, kp - 匹配img, kp - match()方法返回匹配结果 ```python # 47.img_flann.py import cv2 # 打开两个文件 img1 = cv2.imread('.//src//opencv_search.png') img2 = cv2.imread('.//src//opencv_orig.png') # 灰度化 g1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY) g2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY) # 他建SIFT特征检测器 sift = cv2.xfeatures2d.SIFT_create() # 计算描述子与特征点 kp1, des1 = sift.detectAndCompute(g1, None) kp2, des2 = sift.detectAndCompute(g2, None) # 创建匹配器 index_params = dict(algorithm=1, trees=5) search_params = dict(checks=50) flann = cv2.FlannBasedMatcher(index_params, search_params) # 对描述子进行匹配计算 matchs = flann.knnMatch(des1, des2, k=2) good = [] for i, (m, n) in enumerate(matchs): if m.distance < 0.7 * n.distance: good.append(m) ret = cv2.drawMatchesKnn(img1, kp1, img2, kp2, [good], None) cv2.imshow('result', ret) cv2.waitKey() ``` ![1647515861342](assets/1647515861342.png) ### [实战]图像查找 **特征匹配 + 单应性矩阵** ![](assets/1647516044572.png) 对于同一个物体,从不同的方向看去,物体的同一点在不同的图像上位置不同,如planar_surface上的 `X`点在image1上为 `x` ,在image2上为 `x'` ,图像中的 `H` 则是图像与真实物体之间变换的单应性矩阵。 ![1647516188214](assets/1647516188214.png) ![1647516217568](assets/1647516217568.png) ```python # 48.img_find.py import cv2 import numpy as np # 打开两个文件 img1 = cv2.imread('.//src//opencv_search.png') img2 = cv2.imread('.//src//opencv_orig.png') # 灰度化 g1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY) g2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY) # 他建SIFT特征检测器 sift = cv2.xfeatures2d.SIFT_create() # 计算描述子与特征点 kp1, des1 = sift.detectAndCompute(g1, None) kp2, des2 = sift.detectAndCompute(g2, None) # 创建匹配器 index_params = dict(algorithm=1, trees=5) search_params = dict(checks=50) flann = cv2.FlannBasedMatcher(index_params, search_params) # 对描述子进行匹配计算 matchs = flann.knnMatch(des1, des2, k=2) good = [] # 存放角点 for i, (m, n) in enumerate(matchs): if m.distance < 0.7 * n.distance: good.append(m) if len(good) >= 4: srcPts = np.float32([kp1[m.queryIdx].pt for m in good]).reshape(-1, 1, 2) dstPts = np.float32([kp2[m.trainIdx].pt for m in good]).reshape(-1, 1, 2) # 查找单应性矩阵 H, _ = cv2.findHomography( srcPts, dstPts, cv2.RANSAC, 5.0) # cv2.RANSAC 随机抽样一致算法 h, w = img1.shape[:2] pts = np.float32([[0, 0], [0, h-1], [w-1, h-1], [w-1, 0]] ).reshape(-1, 1, 2) dst = cv2.perspectiveTransform(pts, H) # 对找到的图像进行圈框 cv2.polylines(img2, [np.int32(dst)], True, (0, 0, 255)) else: print('the number of good is less than 4.') exit() ret = cv2.drawMatchesKnn(img1, kp1, img2, kp2, [good], None) cv2.imshow('result', ret) cv2.waitKey() ``` ![1647517251425](assets/1647517251425.png) 左边的OpenCV logo在右侧图像中找到并全框起来。 ### [实战]图像拼接 ![1647667347552](assets/1647667347552.png)   对于同一景色,从不同方向进行观测/拍照,得到两张图片则会有相关联的景色,如上图山体,那么将 这两张图像按照相同部分进行拼接,就可以得到一份不会重叠而又更加宽阔的景色,如下图。 ![1647667461737](assets/1647667461737.png) ​ **图像合并的步骤** - 读取文件并重置尺寸 - 根据特征点和计算描述子,得到单应性矩阵 - 图像变换 - 图像拼接,并输出图像 ```python # 49.img_stitch.py # 整体思路 # 第一步,读取文件,将图片设置成一样大小640*480 # 第二步,找特征点,描述子,计算单应性矩阵 # 第三步,根据单应性矩阵对图像进行变换,然后平移★ # 第四步,拼接并输出最终结果 import cv2 import numpy as np # 计算单应性矩阵 def get_homo(img1, img2): # 1. 创建特征转换对象 # 2. 通过特征转换对象获得特征点和描述子 # 3. 创建特征匹配器 # 4. 进行特征匹配 # 5. 过滤特征,找出有效的特征匹配点 # 创建特征转换对象 sift = cv2.SIFT_create() # 获取特征点和描述子 k1, d1 = sift.detectAndCompute(img1, None) k2, d2 = sift.detectAndCompute(img2, None) # 创建特征匹配器 bf = cv2.BFMatcher() matches = bf.knnMatch(d1, d2, k=2) # 过滤特征,找出有效的特征匹配点 # 设置阈值 verify_tatio = 0.8 verify_matches = [] for m1, m2 in matches: if m1.distance < verify_tatio * m2.distance: verify_matches.append(m1) # 设置最小匹配点个数 min_match = 8 if len(verify_matches) > min_match: img1_pts = [] img2_pts = [] for m in verify_matches: img1_pts.append(k1[m.queryIdx].pt) img2_pts.append(k2[m.trainIdx].pt) # [(x1,y1),(x2,y2),……] # [[x1,y1],[x2,y2],……] img1_pts = np.float32(img1_pts).reshape(-1, 1, 2) img2_pts = np.float32(img2_pts).reshape(-1, 1, 2) # 获取单应性矩阵 H, mask = cv2.findHomography(img1_pts, img2_pts, cv2.RANSAC, 5.0) return H else: print('err:Not enough matches!') exit() # 图像拼接 def stitch_img(img1, img2, H): # 1. 获得每张图片的四个角点 # 2. 对图片进行变换(单应性矩阵使图进行旋转,平移) # 3. 创建一张大图,将两张图拼接到一起 # 4. 将结果输出 # 获得原始图的高/宽 h1, w1 = img1.shape[:2] h2, w2 = img2.shape[:2] # 获得四个角点 img1_dims = np.float32( [[0, 0], [0, h1], [w1, h1], [w1, 0]]).reshape(-1, 1, 2) img2_dims = np.float32( [[0, 0], [0, h2], [w2, h2], [w2, 0]]).reshape(-1, 1, 2) img1_transform = cv2.perspectiveTransform(img1_dims, H) result_dims = np.concatenate((img2_dims, img1_transform), axis=0) [x_min, y_min] = np.int32(result_dims.min(axis=0).ravel()-0.5) [x_max, y_max] = np.int32(result_dims.max(axis=0).ravel()+0.5) # 平移的距离 transform_dist = [-x_min, -y_min] transform_array = np.array([[1, 0, transform_dist[0]], [0, 1, transform_dist[1]], [0, 0, 1]]) # 投影变换 result_img = cv2.warpPerspective( img1, transform_array.dot(H), (x_max-x_min, y_max-y_min)) # cv2.imshow('1', result_img) # 图像拼接 result_img[transform_dist[1]:transform_dist[1]+h2, transform_dist[0]:transform_dist[0]+w2] = img2 return result_img # 读取图像 img1 = cv2.imread('.//src//map1.png') img2 = cv2.imread('.//src//map2.png') # 修改尺寸 img1 = cv2.resize(img1, (640, 480)) img2 = cv2.resize(img2, (640, 480)) inputs = np.hstack((img1, img2)) # 获得单应性矩阵 H = get_homo(img1, img2) # 进行图像拼接 result_img = stitch_img(img1, img2, H) cv2.imshow('input', inputs) cv2.imshow('result', result_img) cv2.waitKey(0) ``` ![1647672304105](assets/1647672304105.png) ​ · ——学习ing 2022.03.17 ## OpenCV特征的场景 - 图像搜索,如以图搜图 - 拼图游戏 - 图像拼接,将两长有关联的图拼接到一起 ![1647313977164](assets/1647313977164.png) ### 拼图方法 1. 寻找特征 2. 特征是唯一的 3. 可追踪的 4. 能比较的 ### 总结 - 平坦部分很难找到它在原图中的位置 - 边缘相比平坦要好找一些,但也不能一下确定 - 角点可以一下就能找到其在原图的位置 ### 更新ing ## 十、图像分割 待学习... ## 十一、机器学习 待学习...