找到
4
篇与
opencv
相关的结果
-
Python OpenCV学习第四天:Canny边缘检测+角点检测+轮廓检测 Python OpenCV学习第四天:终于能自动框出物体了 昨天把几何变换和滤镜玩明白了,今天开始学更有意思的——边缘检测、角点检测和轮廓检测。一开始看这些概念觉得挺抽象的,什么梯度、非极大值抑制,头都大了,结果实际敲代码才发现,原来就是调几个参数的事,而且效果特别直观,能直接把图片里的物体边缘和角点标出来,最后写的自动框物体的小程序,跑出来的时候真的有点小激动。 今日目标完成情况 Canny边缘检测和Sobel算子全搞懂 Harris和Shi-Tomasi角点检测都会用 轮廓检测和轮廓特征计算(面积、周长、外接矩形)掌握 完成了实用小项目:自动筛选并框出图片中的物体 一、边缘检测:找到物体的轮廓线 边缘检测就是把图片中物体的边界找出来,是识别物体的第一步,常用的就是Canny和Sobel。 1. Canny边缘检测:最常用也最好用 Canny虽然步骤听起来多(高斯滤波→计算梯度→非极大值抑制→双阈值检测),但OpenCV都封装好了,一行代码就能搞定: import cv2 import numpy as np img = cv2.imread("test.jpg") gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # Canny边缘检测:(灰度图, 低阈值, 高阈值) # 低阈值和高阈值比例一般是1:2或1:3,效果最好 edges = cv2.Canny(gray, 50, 150) cv2.imshow("原图", img) cv2.imshow("Canny边缘", edges) cv2.waitKey(0) cv2.destroyAllWindows()canny边缘图片 效果这里要特别注意: 低阈值:低于这个值的边缘直接扔掉 高阈值:高于这个值的边缘保留 中间的:如果和高阈值边缘连在一起就保留,否则扔掉 我自己测试了一下,50和150这个组合对大多数图片都够用,要是边缘太多就把阈值调高点,太少就调低点。 2. Sobel算子:分方向计算梯度 Sobel是分别计算x方向(水平)和y方向(垂直)的梯度,然后合并起来: # Sobel算子:(灰度图, 输出深度, x方向阶数, y方向阶数, 卷积核大小) # 输出深度用cv2.CV_64F,不然负数会被截断,后面要转回来 sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3) sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3) # 转成uint8,不然显示出来是黑的 sobelx = cv2.convertScaleAbs(sobelx) sobely = cv2.convertScaleAbs(sobely) # 合并x和y方向的梯度 sobel = cv2.addWeighted(sobelx, 0.5, sobely, 0.5, 0)我对比了一下,Canny的效果比Sobel更干净,边缘更连续,所以平时用Canny更多,Sobel适合需要单独看某个方向边缘的情况。 二、角点检测:找到图片中“尖尖”(头发也行)的地方 角点就是两条边的交点,比如桌子的角、正方形的四个角,这些点在图像拼接、目标跟踪里特别有用。 1. Harris角点检测:经典但参数多 原理大概是:移动一个小窗口,如果窗口里的灰度变化很大,那就是角点。代码如下: # Harris角点检测需要先把灰度图转成float32 gray_float = np.float32(gray) # 参数:(灰度图, 窗口大小, Sobel卷积核大小, k值) # k值一般取0.04-0.06,别改太大 dst = cv2.cornerHarris(gray_float, 2, 3, 0.04) # 膨胀一下,让角点更明显 dst = cv2.dilate(dst, None) # 标记角点:大于最大值1%的地方都标红 img[dst > 0.01 * dst.max()] = [0, 0, 255]踩坑:Harris的参数调起来有点麻烦,窗口大小和k值改一点,结果就差很多,而且标出来的角点有时候会连在一起。 2. Shi-Tomasi角点检测:改进版,更省心 这是Harris的优化版,效果更好,而且可以直接指定检测多少个角点,不用调那么多参数: # 参数:(灰度图, 检测角点数量, 质量水平, 最小距离) # 质量水平一般0.01-0.1,最小距离是角点之间的最小间隔 corners = cv2.goodFeaturesToTrack(gray, 100, 0.01, 10) # 转成整数 corners = np.int0(corners) # 画角点:画个小圆圈 for corner in corners: x, y = corner.ravel() cv2.circle(img, (x, y), 3, [0, 0, 255], -1)这个真的太好用了!指定100个角点就出来100个,而且分布很均匀,比Harris省心太多,平时用这个就行。 三、轮廓检测:终于能框物体了 轮廓检测是今天的重点!找到物体的轮廓后,就能计算它的面积、周长,还能画外接矩形把它框起来。 1. 找轮廓的步骤 注意:找轮廓前一定要先做二值化或者Canny边缘检测,不然找出来的轮廓会很乱: # 步骤1:转灰度图 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 步骤2:高斯滤波去噪(可选,但推荐) blur = cv2.GaussianBlur(gray, (5, 5), 0) # 步骤3:二值化(或者用Canny边缘检测) ret, thresh = cv2.threshold(blur, 127, 255, cv2.THRESH_BINARY) # 步骤4:找轮廓 # 参数:(二值图, 轮廓检索模式, 轮廓近似方法) # 注意:OpenCV 4.x返回两个值(轮廓和层级),3.x返回三个,这里用4.x的写法 contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 步骤5:画轮廓 # 参数:(原图, 轮廓列表, 要画的轮廓索引(-1表示画所有), 颜色, 线宽) cv2.drawContours(img, contours, -1, (0, 255, 0), 2)2. 轮廓特征计算 找到轮廓后,就能算它的各种特征了,最常用的就是面积、周长和外接矩形: # 遍历所有轮廓 for contour in contours: # 1. 计算面积 area = cv2.contourArea(contour) # 筛选:只保留面积大于500的轮廓,过滤掉小噪点 if area < 500: continue # 2. 计算周长(True表示闭合轮廓) perimeter = cv2.arcLength(contour, True) # 3. 画外接矩形(最常用!) x, y, w, h = cv2.boundingRect(contour) cv2.rectangle(img, (x, y), (x+w, y+h), (0, 0, 255), 2) # 4. 画最小外接圆(可选) (x_circle, y_circle), radius = cv2.minEnclosingCircle(contour) center = (int(x_circle), int(y_circle)) radius = int(radius) cv2.circle(img, center, radius, (255, 0, 0), 2)筛选面积这步太关键了! 我一开始没加筛选,结果画出来一堆小框框,都是噪点,加了之后只保留大的物体,看起来清爽多了。 四、今日小项目:自动框出图片中的物体 把今天学的整合起来,写一个能自动找到图片中物体并画外接矩形的小程序: import cv2 import numpy as np def main(): img = cv2.imread("test.jpg") if img is None: print("图片读取失败!") return # 复制一份原图,用来画框 img_copy = img.copy() # 转灰度、滤波、二值化 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) blur = cv2.GaussianBlur(gray, (5, 5), 0) ret, thresh = cv2.threshold(blur, 127, 255, cv2.THRESH_BINARY) # 找轮廓 contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 遍历轮廓,筛选并画框 for contour in contours: area = cv2.contourArea(contour) if area > 500: # 面积阈值根据自己的图片调整 x, y, w, h = cv2.boundingRect(contour) cv2.rectangle(img_copy, (x, y), (x+w, y+h), (0, 0, 255), 2) # 把面积写在框上面 cv2.putText(img_copy, f"Area:{int(area)}", (x, y-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1) # 显示结果 cv2.imshow("原图", img) cv2.imshow("检测结果", img_copy) cv2.waitKey(0) cv2.destroyAllWindows() if __name__ == "__main__": main()我用自己拍的一张桌上放了几个杯子的照片测试,居然真的把每个杯子都框出来了,还标了面积,太有成就感了! 五、今日学到的关键点 Canny边缘检测的双阈值比例一般1:2或1:3,50和150是通用组合 Shi-Tomasi角点检测比Harris好用,直接指定数量就行,不用调复杂参数 找轮廓前一定要先二值化或Canny,不然结果会很乱 轮廓筛选按面积来,能过滤掉大部分噪点,面积阈值根据图片实际情况调 OpenCV 4.x的findContours返回两个值,3.x返回三个,别搞混了 六、明天要学的 ORB特征点检测与匹配(用来找两张图里的同一个物体) 直方图均衡化(让图片更清晰) 模板匹配(在大图里找小图) 写一个简单的物体识别小程序 今天学了大概三个小时,主要是轮廓筛选那里花了点时间调阈值,不过看到最后框出来的结果,觉得一切都值了。现在终于能对图片做一些“有用”的处理了,不再是只能加加滤镜,越来越有意思了。 -
Python OpenCV学习第三天:几何变换+阈值处理+图像滤波 Python OpenCV学习第三天:给图片加滤镜原来这么简单 昨天把图像读写和摄像头那点事搞明白了,今天继续往下啃几何变换和图像预处理。本来以为这些都是套公式的函数调用,结果写代码的时候才发现好多细节没注意到,比如旋转个图片居然会被裁掉一半,滤波参数调错了效果差十万八千里。不过今天写出来的滤镜小程序还挺有意思的,能给图片加模糊、锐化、黑白这些效果,终于有点做东西的实感了。 今日目标完成情况 图像几何变换(缩放、旋转、平移、翻转)全掌握 搞懂简单阈值和自适应阈值的区别及适用场景 学会4种常用图像滤波的用法和各自的特点 完成了第一个交互型小项目:键盘控制的图像滤镜 一、图像几何变换:想怎么转就怎么转 几何变换就是改变图片的大小、位置和角度,都是后面做目标检测、图像拼接的基础。 1. 缩放:cv2.resize() 最简单也最常用,注意不同插值方法的区别,用错了图片会糊: import cv2 import numpy as np img = cv2.imread("test.jpg") # 按比例缩放(推荐) img_resized = cv2.resize(img, None, fx=0.5, fy=0.5, interpolation=cv2.INTER_AREA) # fx=水平缩放比例,fy=垂直缩放比例,None表示不指定固定尺寸 # 指定尺寸缩放 img_fixed = cv2.resize(img, (400, 300), interpolation=cv2.INTER_CUBIC)这里要特别说明:插值方法选不对会严重影响效果 缩小图片:用cv2.INTER_AREA,效果最自然 放大图片:用cv2.INTER_CUBIC(慢但清晰)或cv2.INTER_LINEAR(快但稍糊) 默认是INTER_LINEAR,一般够用 2. 翻转:cv2.flip() 一行代码搞定,做数据增强的时候特别有用: img_flip_h = cv2.flip(img, 1) # 水平翻转(左右反过来) img_flip_v = cv2.flip(img, 0) # 垂直翻转(上下反过来) img_flip_both = cv2.flip(img, -1) # 同时水平+垂直翻转3. 旋转:最容易出问题的一个 我一开始以为传个角度就行,结果转完发现图片缺了一半!原来OpenCV的旋转是保持画布大小不变,超出画布的部分直接被裁掉了。 基础旋转(会裁边) # 获取图片中心坐标 h, w = img.shape[:2] center = (w//2, h//2) # 获取旋转矩阵:(旋转中心, 旋转角度, 缩放比例) # 角度是逆时针为正,比如90就是逆时针转90度 M = cv2.getRotationMatrix2D(center, 45, 1.0) # 执行旋转 img_rotated = cv2.warpAffine(img, M, (w, h))不裁边的旋转(进阶) 如果不想裁边,就要自己计算旋转后的新画布大小,我找了个通用写法,直接抄就行: def rotate_image(img, angle): h, w = img.shape[:2] center = (w//2, h//2) # 获取旋转矩阵 M = cv2.getRotationMatrix2D(center, angle, 1.0) # 计算旋转后的新尺寸 cos = np.abs(M[0, 0]) sin = np.abs(M[0, 1]) new_w = int(h * sin + w * cos) new_h = int(h * cos + w * sin) # 调整旋转矩阵的平移量,让图片居中 M[0, 2] += (new_w / 2) - center[0] M[1, 2] += (new_h / 2) - center[1] # 执行旋转 return cv2.warpAffine(img, M, (new_w, new_h))4. 平移 和旋转类似,需要先构造平移矩阵: # 平移矩阵:[[1, 0, 水平平移量], [0, 1, 垂直平移量]] # 正数向右/向下平移,负数向左/向上平移 M = np.float32([[1, 0, 50], [0, 1, 30]]) img_translated = cv2.warpAffine(img, M, (w, h))二、图像阈值处理:把图片变成黑白 阈值处理就是把彩色图转成二值图(只有黑和白),是提取目标物体的第一步。 1. 简单阈值:cv2.threshold() 全局用同一个阈值,适合光照均匀的图片: # 先转成灰度图 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 简单阈值:(灰度图, 阈值, 最大值, 阈值类型) # 阈值类型: # cv2.THRESH_BINARY:大于阈值变255(白),小于变0(黑) # cv2.THRESH_BINARY_INV:反过来,大于阈值变0,小于变255 ret, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)2. 自适应阈值:cv2.adaptiveThreshold() 解决光照不均匀的问题!比如拍的书本照片,中间亮两边暗,用简单阈值会有一大块黑的,自适应阈值就不会。 thresh_adaptive = cv2.adaptiveThreshold( gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, # 自适应方法:高斯加权平均 cv2.THRESH_BINARY, 11, # 块大小:必须是奇数,越大处理越粗糙 2 # 常数:减去这个值,微调阈值 )我自己测试了一下,用一张窗边拍的照片,简单阈值出来的效果惨不忍睹,自适应阈值就清晰很多,这个真的太有用了。 三、图像滤波:给图片“磨个皮” 滤波主要用来去噪,不同的滤波对应不同的噪声类型,别用混了。 1. 均值滤波:cv2.blur() 最简单的滤波,取周围像素的平均值,去噪的同时会把边缘也弄模糊: img_blur = cv2.blur(img, (5, 5)) # (5,5)是卷积核大小,必须是奇数2. 高斯滤波:cv2.GaussianBlur() 最常用的滤波,对高斯噪声(比如图片上的颗粒感)效果很好,比均值滤波保留更多边缘: img_gaussian = cv2.GaussianBlur(img, (5, 5), 0) # 0表示自动计算标准差3. 中值滤波:cv2.medianBlur() 去椒盐噪声神器! 就是图片上那种随机的白点黑点,用这个效果最好: img_median = cv2.medianBlur(img, 5) # 卷积核大小必须是奇数4. 双边滤波:cv2.bilateralFilter() 最智能的滤波,去噪的同时能保持边缘清晰,就是速度慢一点: img_bilateral = cv2.bilateralFilter(img, 9, 75, 75)四、今日小项目:键盘控制的图像滤镜 把今天学的所有功能整合起来,写一个能通过键盘切换不同滤镜的小程序,按不同的键就能看到不同的效果: import cv2 import numpy as np def main(): img = cv2.imread("test.jpg") if img is None: print("图片读取失败!") return cv2.imshow("原图", img) while True: key = cv2.waitKey(0) & 0xFF if key == ord('q'): break elif key == ord('1'): # 均值模糊 res = cv2.blur(img, (5, 5)) cv2.imshow("滤镜效果", res) elif key == ord('2'): # 高斯模糊 res = cv2.GaussianBlur(img, (5, 5), 0) cv2.imshow("滤镜效果", res) elif key == ord('3'): # 中值模糊 res = cv2.medianBlur(img, 5) cv2.imshow("滤镜效果", res) elif key == ord('4'): # 双边滤波 res = cv2.bilateralFilter(img, 9, 75, 75) cv2.imshow("滤镜效果", res) elif key == ord('5'): # 二值化 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) ret, res = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY) cv2.imshow("滤镜效果", res) elif key == ord('6'): # 自适应阈值 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) res = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2) cv2.imshow("滤镜效果", res) elif key == ord('r'): # 重置为原图 cv2.imshow("滤镜效果", img) cv2.destroyAllWindows() if __name__ == "__main__": main()mo8loxdo.png图片 运行之后按1-6就能切换不同的滤镜,按r重置,按q退出,特别方便。 五、今日学到的关键点 缩放图片的时候,缩小用INTER_AREA,放大用INTER_CUBIC,效果最好 默认的旋转会裁掉边缘,不想裁边就要自己计算新的画布大小 简单阈值适合光照均匀的图片,光照不均一定要用自适应阈值 不同的滤波对应不同的噪声:椒盐噪声用中值滤波,高斯噪声用高斯滤波 所有卷积核的大小都必须是奇数,这个是硬性规定 六、明天要学的 Canny边缘检测和Sobel算子 Harris和Shi-Tomasi角点检测 轮廓检测和轮廓特征计算 写一个能自动框出图片中物体的小程序 今天学了大概两个半小时,主要是旋转不裁边的那个函数花了点时间理解,不过搞懂之后就觉得很简单了。现在终于能对图片做一些有意思的处理了. -
Python OpenCV学习第二天:图像读写+BGR颜色空间+摄像头调用 Python OpenCV学习第二天:读个图颜色居然是反的 昨天把环境和NumPy基础搞定了,今天信心满满地开始学图像读写,本来以为就是调几个函数的事,结果第一张图读出来直接给我整懵了——好好的蓝天变成了深蓝,红花变成了蓝花,折腾了半天才搞明白是OpenCV那个反人类的BGR颜色空间搞的鬼。今天踩的坑比昨天还多,不过好歹把核心操作都摸透了。 今日目标完成情况 图像的读取、显示、保存全流程搞定 搞懂了BGR和RGB的区别及转换方法 学会了通道分离与合并 成功调用电脑摄像头实时显示画面 完成了第一个小项目:简易图像查看器 一、图像基本操作:读、显、存(坑最多的部分) 这三个函数看起来简单,但新手能踩的坑一个都不少,我挨个说。 1. 读取图像:cv2.imread() 函数原型:cv2.imread(文件路径, 读取模式) import cv2 import numpy as np # 最常用的读取方式:彩色图模式(默认) img = cv2.imread("test.jpg") # 其他读取模式 img_gray = cv2.imread("test.jpg", cv2.IMREAD_GRAYSCALE) # 灰度图模式 img_unchanged = cv2.imread("test.jpg", cv2.IMREAD_UNCHANGED) # 包含透明通道 路径绝对不能有中文! 这是90%新手第一个踩的坑。如果路径里有中文(虽然我用的是test.jpg),imread()不会报错,但会返回None,后面所有操作都会报错。解决方法:把图片放到纯英文路径下,或者用下面的兼容写法: # 中文路径兼容写法 img = cv2.imdecode(np.fromfile("测试图片.jpg", dtype=np.uint8), cv2.IMREAD_COLOR) 图片不存在/路径写错时,会出警告但不会直接崩溃! 第一步:它会先弹出一串黄色警告(Warning): [ WARN:0@0.032] ... can't open/read file ... mo6rtsk4.png图片 (我把文件名字写错了)意思是“找不到图片了,但我先不跟你玩命,提醒你一下”。 第二步:程序不会停止,继续往下运行,但会把 img 变量赋值为 None(空的)。 第三步:如果你没加判断,后面直接用 img(比如 cv2.imshow),这时候才会弹出红色报错(Error),然后程序直接崩溃闪退。 所以必须加这个判断! 把问题扼杀在摇篮里: if img is None: print("图片读取失败!请检查路径/文件名/是否有中文") else: print("图片读取成功") mo6ryxne.png图片 文件路径错误调用后爆炸 读取模式参数:默认是cv2.IMREAD_COLOR(值为1),会自动忽略透明通道;如果需要保留透明通道(PNG图片),必须用cv2.IMREAD_UNCHANGED(值为-1)。 2. 显示图像:cv2.imshow() 函数原型:cv2.imshow(窗口名, 图像数组) # 显示图像 cv2.imshow("img", img) # ↓必须加这两行!不然窗口会一闪而过 cv2.waitKey(0) # 等待任意按键按下,0表示无限等待 cv2.destroyAllWindows() # 关闭所有窗口注意: 不写waitKey()和destroyAllWindows()会导致程序卡死,窗口关不掉,只能强制结束进程。 waitKey(0)里的数字是等待时间(毫秒),如果写waitKey(1000)就是等待1秒后自动关闭窗口。 多个窗口的话,窗口名不能重复,否则后面的会覆盖前面的。 3. 保存图像:cv2.imwrite() 函数原型:cv2.imwrite(保存路径, 图像数组) # 保存图像 cv2.imwrite("output.jpg", img) # 中文路径兼容写法 cv2.imencode(".jpg", img)[1].tofile("输出图片.jpg")注意:保存格式由后缀名决定,支持jpg、png、bmp等常见格式。保存jpg时可以指定压缩质量(0-100,默认95): cv2.imwrite("output.jpg", img, [cv2.IMWRITE_JPEG_QUALITY, 100]) # 最高质量二、BGR颜色空间:OpenCV最反人类的设计 终于说到今天最大的坑了!为什么我读出来的图颜色不对?因为OpenCV默认的彩色图像格式是BGR,而不是我们平时用的RGB! 也就是说,普通图片的像素顺序是(红、绿、蓝),但OpenCV读进来之后会变成(蓝、绿、红),所以红色会变成蓝色,蓝色会变成红色,绿色不变。 1. BGR转RGB 解决方法很简单,用cv2.cvtColor()转换一下就行: # BGR转RGB img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) # 其他转换 img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # BGR转灰度图 img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) # BGR转HSV(后面颜色识别会用到)2. 通道分离与合并 有时候我们需要单独处理某一个颜色通道,比如只提取红色通道,这就需要用到通道分离和合并: # 分离通道(B、G、R三个通道) b, g, r = cv2.split(img) # 合并通道(注意顺序!) img_merged = cv2.merge([b, g, r]) # 合并成BGR图像 img_rgb_merged = cv2.merge([r, g, b]) # 合并成RGB图像偷鸡技巧:如果想单独显示某个通道,直接cv2.imshow("Red", r)会显示成灰度图,因为单通道数组会被当成灰度图处理。如果想显示成红色,需要把其他两个通道设为0: # 只显示红色通道(彩色,黑白没有红色) red_channel = np.zeros_like(img) red_channel[:, :, 2] = r # 红色通道是第三个通道(索引2) cv2.imshow("Red Channel", red_channel)三、视频与摄像头调用:比想象中简单(康养写过) OpenCV处理视频的逻辑很简单:把视频当成一帧一帧的图像,逐帧处理就行。 1. 读取本地视频 # 创建视频捕获对象,参数是视频文件路径 cap = cv2.VideoCapture("video.mp4") # 循环读取每一帧 while cap.isOpened(): # 读取一帧,ret表示是否读取成功,frame是当前帧的图像 ret, frame = cap.read() if not ret: print("视频读取完毕") break # 显示当前帧 cv2.imshow("Video", frame) # 按q键退出(等待1毫秒,这样视频才能正常播放) if cv2.waitKey(1) & 0xFF == ord('q'): break # 释放资源 cap.release() cv2.destroyAllWindows()2. 调用电脑摄像头 把上面的视频路径换成0就行,0表示默认摄像头: cap = cv2.VideoCapture(0) # 0是默认摄像头,1是第二个摄像头 while cap.isOpened(): ret, frame = cap.read() if not ret: break # 这里可以对frame进行处理,比如转成灰度图(不建议,因为会变成遗照🤔) #gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) cv2.imshow("摄像头", gray) if cv2.waitKey(1) & 0xFF == ord('q'): break cap.release() cv2.destroyAllWindows()踩坑:如果摄像头被其他程序占用,cap.isOpened()会返回False,关闭其他占用摄像头的程序就行。 四、今日小项目:简易图像查看器 把今天学的内容整合起来,写一个能打开、显示、保存图像的小程序: import cv2 import numpy as np def main(): # 读取图像(中文路径兼容) img_path = "test.jpg" img = cv2.imdecode(np.fromfile(img_path, dtype=np.uint8), cv2.IMREAD_COLOR) if img is None: print("图片读取失败!") return # 显示原图 cv2.imshow("原图", img) # 转成灰度图并显示 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) cv2.imshow("灰度图", gray) # 转成RGB并显示 rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB) cv2.imshow("RGB图", rgb) # 等待按键 key = cv2.waitKey(0) # 按s键保存灰度图 if key == ord('s'): cv2.imencode(".jpg", gray)[1].tofile("灰度图.jpg") print("灰度图已保存") # 关闭所有窗口 cv2.destroyAllWindows() if __name__ == "__main__": main()运行这个程序,就能同时看到原图、灰度图和RGB图的区别了,按s键还能保存灰度图。 五、今日踩坑总结 中文路径大坑:cv2.imread()和cv2.imwrite()不支持中文路径,必须用imdecode和imencode的兼容写法。 BGR颜色空间:OpenCV默认是BGR,和所有其他库(Matplotlib、PIL)都不一样,显示颜色不对先转RGB。 窗口关闭问题:必须写cv2.waitKey()和cv2.destroyAllWindows(),不然窗口会卡死。 视频播放速度:cv2.waitKey(1)里的数字控制视频播放速度,数字越小播放越快,一般用1-10毫秒。 摄像头权限:如果摄像头打不开,检查一下系统是否给了Python摄像头权限。 六、明天要学的 图像的几何变换:缩放、旋转、平移、翻转 图像阈值处理:简单阈值和自适应阈值 图像滤波:均值滤波、高斯滤波、中值滤波 写一个简单的图像滤镜小程序 今天学了大概两个小时,主要是颜色空间的坑浪费了不少时间,一开始还以为是我电脑出问题了,查了半天才知道是OpenCV的设计问题。不过搞懂之后就觉得其实也不难,都是固定的套路。果然学OpenCV就是不断踩坑然后填坑的过程,多敲代码多踩坑,自然就会了。 -
Python OpenCV学习第一天:环境搭建+NumPy核心基础 Python OpenCV学习第一天:环境搭到一半差点放弃 今天终于下定决心开始学OpenCV了!按照之前定的计划,第一天先搞环境和NumPy基础。本来以为半小时就能搞定环境,结果踩了两个大坑,折腾了快一个小时才弄好,真的服了。 今日目标完成情况 OpenCV环境搭建成功 NumPy核心操作过了一遍 搞懂了为什么OpenCV离不开NumPy 掌握OpenCV、NumPy基础语法,能看懂基础代码 一、环境搭建:别再只装opencv-python了! 真的要吐槽一下网上那些老教程,全都是让你pip install opencv-python,我装完之后才发现后面很多函数都用不了,查了半天才知道原来还有个完整版。而且我之前的python环境乱成狗屎,之前安装中断导致后续安装不上,折腾了好久才理顺。 正确安装方法 先把之前装的乱七八糟的版本全部卸载干净,不然会冲突(卸载时如果提示“跳过opencv-contrib-python,因为未安装”,属于正常情况,不用管,确保已安装的版本被卸载即可): pip uninstall opencv-python opencv-contrib-python -ymo5qcl22.png图片 然后直接装完整版,包含所有contrib模块,后面学特征检测、DNN什么的都不用再重新装了: pip install opencv-contrib-python -i https://pypi.tuna.tsinghua.edu.cn/simplemo5qfmkq.png图片 一定要加清华源!不加的话国外源慢到死,还经常超时(但是我加了也慢,又被校园网美式拦截了)。另外安装时还遇到了清华源网页访问失败的情况(疑似跑路),试了几次后才顺利下载,估计是临时网络波动,遇到这种情况多重试几次就好。 还有个小问题:安装时会提示“默认采用用户安装模式”,因为正常的site-packages目录没有写入权限,无需担心,不影响OpenCV和NumPy的正常使用,最终还是成功安装了opencv-contrib-python 4.13.0.92版本,NumPy也保持了适配的版本。 测试安装 新建个py文件,输入这几行,测试环境是否安装成功(代码含义后面语法板块会详细说): import cv2 import numpy as np print(f"OpenCV版本:{cv2.__version__}") print(f"NumPy版本:{np.__version__}")mo5r7t6a.png图片 我这里实际测试输出的是OpenCV 4.13.0和NumPy 2.2.6,之前写的版本是笔误,实际安装后一切正常。如果报错找不到模块,大概率是你的IDE解释器选错了,去设置里检查一下(比如PyCharm,打开设置→Project→Python Interpreter,选择自己的Python环境即可)。 二、基础语法板块(小白必看,逐行解析) 刚开始看代码肯定一脸懵,这里把今天用到的、后续常用的基础语法,逐行拆解,看不懂的地方对照着看,慢慢就能上手。 1. 模块导入语法(最基础,必须会) Python中使用第三方库(比如OpenCV、NumPy),必须先“导入”,才能使用里面的功能,常用导入方式有2种: # 方式1:导入整个模块,使用时需要加“模块名.功能”(推荐,不容易混淆) import cv2 # 导入OpenCV库,简写为cv2(行业通用写法,固定这么写) import numpy as np # 导入NumPy库,简写为np(行业通用写法,固定这么写) # 方式2:从模块中导入指定功能,使用时不用加模块名(不推荐,容易记混) # from cv2 import * # 导入OpenCV所有功能,不推荐,容易和其他库冲突 # from numpy import array # 只导入NumPy的array功能(创建数组) 补充说明:cv2、np都是行业约定俗成的简写,所有人都这么写,记下来就好,不用纠结为什么叫这个。 2. 打印输出语法(调试必备) 用来打印变量、版本信息、结果等,方便我们查看是否正确,今天用到的是f-string格式化输出(最简洁好用): # 基本格式:print(f"提示文字:{变量/功能}") print(f"OpenCV版本:{cv2.__version__}") # 打印OpenCV版本 print(f"NumPy版本:{np.__version__}") # 打印NumPy版本 # 拆解说明: # 1. print():打印函数,括号里放要打印的内容,必须加括号 # 2. f"":格式化字符串,前面加f,里面可以用 嵌入变量或功能 # 3. cv2.__version__:获取OpenCV的版本号,__version__是固定写法(两个下划线) # 4. 示例:如果想打印数组,也可以用print(f"数组内容:{arr}") 3. NumPy核心语法(OpenCV必备,逐行解析) 结合今天学的NumPy操作,把每个语法、每个参数都拆解开,看不懂就对照着看,多敲几遍就记住了。 (1)创建数组语法 import numpy as np # 先导入NumPy,必写 # 1. 创建普通数组:np.array(数据, dtype=数据类型) # 拆解:np.array() 是创建数组的核心函数,括号里放要创建的数据 gray = np.array([[1,2,3], [4,5,6], [7,8,9]], dtype=np.uint8) # 逐行解析: # gray:变量名,自己起(建议见名知意,gray=灰度图) # np.array():创建数组的函数 # [[1,2,3], [4,5,6], [7,8,9]]:数组数据,二维数组(3行3列),对应灰度图的像素 # dtype=np.uint8:指定数据类型为uint8(重点!图像专用,范围0-255) # 2. 创建全0数组:np.zeros(形状, dtype=数据类型) # 拆解:np.zeros() 生成所有元素都是0的数组,适合创建全黑图像 color = np.zeros((100, 100, 3), dtype=np.uint8) # 逐行解析: # (100, 100, 3):数组形状(高, 宽, 通道数),100行100列,3个通道(对应彩色图) # 其他参数和上面一致 # 3. 创建全1数组:np.ones(形状, dtype=数据类型) white = np.ones((200, 200), dtype=np.uint8) * 255 # 逐行解析: # np.ones() 生成所有元素都是1的数组,乘以255后,所有元素变成255(对应全白图像) # (200, 200):二维数组(200行200列),对应灰度图 # 4. 创建随机数组:np.random.randint(最小值, 最大值, 形状, dtype=数据类型) noise = np.random.randint(0, 256, (200, 200), dtype=np.uint8) # 逐行解析: # np.random.randint():生成随机整数的函数 # 0, 256:随机数范围是0~255(因为最大值256是开区间,不包含256) # (200, 200):数组形状,二维数组,对应灰度图的随机噪声(2)数组索引和切片语法(修改图像必备) 核心:数组索引是「行, 列」(y, x),和我们平时说的「x, y」相反,一定要记牢! import numpy as np # 先创建一个5x5的数组(方便演示) arr = np.array([[1,2,3,4,5], [6,7,8,9,10], [11,12,13,14,15], [16,17,18,19,20], [21,22,23,24,25]]) # 1. 获取单个元素:数组名[行索引, 列索引](索引从0开始,不是从1开始!) print(arr[2, 3]) # 输出14 # 解析:第3行(索引2)、第4列(索引3)的元素,就是14 # 2. 获取切片(一块区域):数组名[行范围, 列范围] # 范围写法:start:end(包含start,不包含end),省略start表示从0开始,省略end表示到最后 print(arr[0:3, 0:3]) # 获取左上角3x3区域(行0-2,列0-2) # 解析:行0到3(不包含3,即0、1、2行),列0到3(不包含3,即0、1、2列) # 3. 修改切片区域:数组名[行范围, 列范围] = 目标值 arr[0:3, 0:3] = 0 # 把左上角3x3区域全部改成0 print(arr) # 打印修改后的数组 (3)数组形状查看/变换语法 import numpy as np arr = np.array([[1,2,3,4,5], [6,7,8,9,10], [11,12,13,14,15], [16,17,18,19,20], [21,22,23,24,25]]) # 1. 查看数组形状:数组名.shape(固定写法) print(arr.shape) # 输出(5, 5),表示5行5列的二维数组 # 补充:如果是彩色图数组,会输出(高, 宽, 3),比如(100, 100, 3) # 2. 变换数组形状:数组名.reshape(新形状) # 注意:变换后的元素总数,必须和原来的一致(5x5=25,所以可以改成25个元素的一维数组) arr_flat = arr.reshape(25) # 改成一维数组(25个元素) print(arr_flat.shape) # 输出(25,),逗号表示一维数组 4. 常见报错/警告语法解析(避坑必备) 今天遇到的警告,拆解一下,以后遇到就知道怎么回事了: 警告1:WARNING: Skipping opencv-contrib-python as it is not installed. 解析:提示“跳过opencv-contrib-python,因为未安装”,正常情况,说明之前没装过这个包,不用处理。 警告2:Defaulting to user installation because normal site-packages is not writeable 解析:提示“默认采用用户安装模式,因为正常的site-packages目录没有写入权限”,不用管,不影响使用,是系统权限问题,不是我们操作错了。 报错:找不到cv2/np模块 解析:要么是没安装成功,要么是IDE解释器选错了,去IDE设置里切换到自己的Python环境即可。 三、NumPy:原来这才是OpenCV的本体 之前就听人说OpenCV学到最后其实是在学NumPy(豆包说的),今天算是有点体会了。原来OpenCV里的图像根本不是什么特殊的东西,就是个三维的NumPy数组而已!所有对图像的操作(比如修改颜色、裁剪、缩放),本质上都是对NumPy数组的操作。 数组和列表的区别(小白必懂) 很多人刚开始会把NumPy数组和Python列表搞混,这里用通俗的话讲清楚,不用记复杂概念: 列表:什么类型都能放(比如数字、字符串、列表),比如[1, "a", [2,3]],但运算很慢,处理图像(大数据)会卡死。 数组:只能放同一种类型(比如全是数字),运算速度比列表快几百倍,专门用来处理图像这种大数据。 数组支持向量化运算:不用写一堆for循环,比如想把数组里所有元素加1,直接arr+1就行,列表则需要写for循环逐个修改。 今天必须记住的几个操作(结合语法,再巩固一遍) 这些都是后面天天要用的,结合上面的语法解析,边敲代码边理解,记起来更快。 1. 创建数组(图像基础) import numpy as np # 灰度图就是二维数组(高×宽) gray = np.array([[1,2,3], [4,5,6], [7,8,9]], dtype=np.uint8) # 彩色图是三维数组(高×宽×3通道) color = np.zeros((100, 100, 3), dtype=np.uint8) # 快速创建全黑、全白、随机噪声图 black = np.zeros((200, 200), dtype=np.uint8) white = np.ones((200, 200), dtype=np.uint8) * 255 noise = np.random.randint(0, 256, (200, 200), dtype=np.uint8) 超级重要提醒:图像的数据类型一定要用np.uint8!范围是0-255,超出的话会溢出,显示出来的颜色会完全不对,这个坑我已经提前踩过了。 2. 索引和切片(修改图像必备) # 创建一个5x5的数组 arr = np.array([[1,2,3,4,5], [6,7,8,9,10], [11,12,13,14,15], [16,17,18,19,20], [21,22,23,24,25]]) # 获取单个元素(行, 列) print(arr[2, 3]) # 14 # 获取左上角3x3区域 print(arr[0:3, 0:3]) # 把左上角3x3区域全部改成0 arr[0:3, 0:3] = 0 print(arr) 3. 形状变换(图像缩放/调整必备) # 查看数组形状 print(arr.shape) # (5, 5) # 改变形状(元素总数要一样) arr_flat = arr.reshape(25) print(arr_flat.shape) # (25,) 四、今日总结 环境一定要装opencv-contrib-python,别装基础版!别装基础版!别装基础版!重要的事情说三遍,基础版缺少很多功能。 图像数据类型必须是np.uint8,0-255,超出范围会导致图像颜色异常。 数组索引是(行, 列),也就是(y, x),和我们平时说的(x, y)是反的,这个很容易搞混,多练几次就能记住。 清华源可能会出现网页访问失败的情况,遇到时多重试几次,大概率是临时网络波动导致的。 安装时可能会提示“默认采用用户安装模式”,因为正常的site-packages目录没有写入权限,无需担心,不影响使用。 卸载旧版本时,可能会提示“跳过opencv-contrib-python,因为未安装”,属于正常情况,只需确保已安装的版本被成功卸载即可。 Python环境混乱会导致安装失败,遇到这种情况,先卸载相关包,再重新安装,实在不行就重新配置Python环境。 基础语法是重点,尤其是NumPy的数组创建、索引、形状变换,后续操作都要用到,一定要多敲代码练习。 五、明天要学的 图像的读取、显示和保存(结合今天学的语法,实操练习) OpenCV那个反人类的BGR颜色空间(为什么OpenCV读出来的颜色和实际不一样) 调用摄像头实时显示画面(简单实操,增加成就感) 写个最简单的图像查看器(综合运用今天学的知识) 今天学了大概一个半小时,感觉还可以。主要是环境浪费了点时间(python环境乱成狗屎,安装中断导致后续安装不上),中途遇到了清华源访问失败(疑似跑路)、权限不足导致的用户安装模式等小问题,慢慢排查后都解决了,后面学NumPy的时候边敲代码边理解,效率还挺高的。果然学编程不能只看视频,一定要自己动手敲,尤其是语法部分,多敲几遍自然就懂了。