拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

OpenCV基础入门:imread、imshow、imwrite三大核心操作详解

OpenCV基础入门:imread、imshow、imwrite三大核心操作详解 在接触OpenCV的初期很多人会纠结于复杂的算子、深度学习模型集成甚至是编译带CUDA的版本。但实际上读取图像、显示图像、写入图像这三板斧才是整个OpenCV图像处理项目的地基。几乎所有任务——人脸识别、颜色追踪、卡尺测量、缺陷检测——都逃不开“先读进来看一眼对不对处理完存下去”这个循环。我见过太多人一上来就研究相机标定或GrabCut结果连imread的路径坑都没绕过去白白浪费了大量时间。所以这期“基础”系列第一篇就专门把这三个核心操作彻底讲透。内容不追求高深而是把每个函数背后的机制、参数的选择依据、以及新手最容易踩的坑全部摊开来讲。这篇文章适合刚装好OpenCV但不知道从哪下手的人也适合已经写过一些脚本但对某些细节比如waitKey(0)和waitKey(1)到底啥区别、中文路径为什么读不进来一知半解的人。读完以后你会发现这几个最基础的操作其实藏着不少值得琢磨的细节把这些细节吃透后面学什么都顺。1. 环境准备先把OpenCV装明白再谈其他1.1 三种安装方式怎么选在写第一行代码之前环境必须搞定。国内用户最常见的装法基本是这三种pip直接安装、Anaconda的conda安装、以及源码编译。我个人的建议是新手老老实实用 pip其次用 conda绝对不要碰源码编译。很多人被网上的教程带偏一上来就编译带 CUDA 的 OpenCV结果卡在 CMake 配置上好几个小时。实际上对于“读取、显示、写入图像”这一步预编译包已经完全足够了。哪怕你后续要做人脸识别、轮廓分析opencv-python这个预编译包也都能胜任。只有当你需要用到 DNN 模块的 CUDA 加速、或者需要修改 OpenCV 源码本身时才值得去折腾编译。# 普通 Python 环境 pip install opencv-python # 如果需要额外模块如 xfeatures2d、aruco 等 pip install opencv-contrib-python如果你用的是 Anaconda也可以conda install -c conda-forge opencv但注意conda 默认源在国内经常慢得让人崩溃如果没配好镜像源建议直接用 pip。Anaconda Prompt 里没有 opencv 这个问题十有八九就是没激活虚拟环境或者装到了 base 环境之外的其他环境里。1.2 装完怎么验证装完之后验证一下是不是真的能用了。在终端或你惯用的 IDE 里执行import cv2 print(cv2.__version__)能输出版本号比如4.8.1就说明安装成功。如果报ModuleNotFoundError: No module named cv2最常见的几个原因按概率排序是当前使用的 Python 解释器不是安装 opencv 的那个环境尤其是 PyCharm 没配置对解释器这是重灾区pip 安装时权限不足装到了系统目录而当前用户不可读在 Jupyter Notebook 里kernel 对应的环境不对排查思路就一句话在同一个终端里先运行pip list | findstr opencvWindows或pip list | grep opencvLinux/macOS确认包存在再运行python进入解释器试导入。如果终端里能导入而 PyCharm 里不能那就是解释器指向的问题去设置里把 Project Interpreter 换成 conda 环境或系统 Python 路径即可。2. 读取图像imread 的细节与坑2.1 第一个坑中文路径和相对路径安装验证通过后我们正式开始。打开编辑器第一段代码几乎所有人最早接触到的都是这个import cv2 img cv2.imread(test.jpg) print(img)这段代码看似简单但坑相当多。打印出来如果是None九成是路径不存在或读取失败。先从路径说起。Windows 下cv2.imread(D:\data\test.jpg)这种写法会出错因为\d、\t在字符串里会被转义成特殊字符。建议用正斜杠D:/data/test.jpg或者使用cv2.imread(rD:\data\test.jpg)这种原始字符串写法。另一个高频问题中文路径。如果你把图片放在D:/测试/图片.jpg这种带中文的目录里cv2.imread在 Windows 上很可能直接返回None。为什么因为 OpenCV 的底层imread函数调用的是 C 标准库的文件操作接口而 Windows 上的文件路径解析走的是多字节字符集MBCS路径对中文编码支持相当不友好。解决方案有两个最省事把涉及代码的路径都改为纯英文目录。我在实际项目里连用户名都尽量用英文因为不止 OpenCV很多 C 库都栽在中文路径上。想硬刚中文路径先读成字节流再用imdecode解码import numpy as np import cv2 def imread_unicode(filepath): stream open(filepath, rb) bytes_data bytearray(stream.read()) np_array np.asarray(bytes_data, dtypenp.uint8) img cv2.imdecode(np_array, cv2.IMREAD_COLOR) return img这个方法确实能绕过中文路径问题但能不折腾就别折腾程序里统一用英文路径是最省心的。2.2 读取参数怎么选彩色、灰度还是原样imread的第二个参数是读取标志位常用的是这几个参数数值作用cv2.IMREAD_COLOR1默认值读成 BGR 三通道彩色图透明通道会被忽略cv2.IMREAD_GRAYSCALE0读成单通道灰度图cv2.IMREAD_UNCHANGED-1读成原始格式包含 alpha 透明通道如果有的话这里有个理解上的小陷阱。很多人以为IMREAD_COLOR是“按原样读入彩色图片”但在 OpenCV 里它其实是强制转成三通道 BGR 格式。如果你用手机拍了一张 RGBA 的 PNG用默认参数imread读进来alpha 通道会被丢掉。真正“保持原样”的是IMREAD_UNCHANGED。实际使用中我的建议是只做边缘检测、阈值分割这类不依赖颜色的操作直接用IMREAD_GRAYSCALE既省内存又省计算时间需要做颜色识别、画彩色标注、或者后面要显示给用户看用IMREAD_COLOR需要保留透明背景比如抠图素材处理用IMREAD_UNCHANGED还有一点imread的第二个参数不传时默认就是IMREAD_COLOR。所以cv2.imread(a.jpg)和cv2.imread(a.jpg, 1)完全等价。但写代码时最好显式标注参数名或数值别人看代码的时候一眼就知道你的意图。2.3 读进来的图像到底是什么从矩阵到坐标读入成功后的img在 Python 里是一个numpy.ndarray数组。这是理解 OpenCV 一切操作的核心——图像在计算机里就是数字矩阵。打个比方一张 640x480 的彩色图片你可以把它理解成一个480 行 x 640 列的表格每个格子里装三个数字B、G、R注意顺序OpenCV 用的是 BGR 而不是常见的 RGB。灰度图则每个格子里只有一个数字表示亮度0 是纯黑255 是纯白。这里必须强烈提醒一个让无数初学者蒙圈的坐标系问题。人类的阅读习惯是(x, y)但 NumPy 数组的索引是(行, 列)对应到图像上就是(y, x)。所以在 OpenCV 的图像坐标系里图片左上角是原点(0, 0)x 轴向右y 轴向下。访问某个像素的正确方式是# 访问第 100 行第 200 列即坐标 (200, 100) 处的像素 b, g, r img[100, 200]很多后续问题——比如旋转方向反了、ROI 区域错位、轮廓坐标对不上——根源都在这个行列表述和坐标表述的混淆上。基础阶段就把这个习惯立好后面能少掉一半头发。2.4 查看图像基本信息拿到图像后第一件事通常是确认它的尺寸、通道数和数据类型print(img.shape) # (高度, 宽度, 通道数) print(img.size) # 总像素数 高 x 宽 x 通道数 print(img.dtype) # 通常是 uint8取值范围 0~255这三个属性在调试时极其常用。比如你想确认一张图是不是灰度图看shape是两维还是三维就知道。你想确认图像有没有正常读入img is None的判空操作就足够。3. 显示图像imshow 与 waitKey 的相爱相杀3.1 三行代码背后的窗口机制读取图像之后最自然的下一步就是显示出来看一眼。OpenCV 的显示代码长得非常固定cv2.imshow(window, img) cv2.waitKey(0) cv2.destroyAllWindows()第一行是把名为img的图像显示到标题为window的窗口里第二行是让程序暂停等待用户按键第三行是销毁所有窗口。这三行缺一不可尤其第二行waitKey如果你是第一次接触 GUI 编程大概率会困惑为什么我写了imshow之后窗口一闪而过或者根本不显示原因在于imshow不会真正把图像绘制到屏幕上才返回。它只是把图像数据交给窗口系统然后立即返回后面的代码紧接着继续执行。如果你没有waitKey让程序停下来程序瞬间就跑完退出了窗口根本来不及展示。你可以把imshow理解为“把照片递给了一个店员告诉他把照片挂到橱窗里”照片还没挂好你就转身走了店员也没法拉住你。waitKey就是那个让你的脚步停下来的机制。这组函数在 Jupyter Notebook 里特别容易出问题。因为 Notebook 的 cell 执行方式和普通的 Python 脚本不同imshow弹不出独立窗口属于常见现象。如果非要在 Notebook 里看图像建议直接用matplotlib显示或者把代码写成.py脚本用终端跑。3.2 waitKey 的参数到底怎么给waitKey的参数单位是毫秒。cv2.waitKey(0)表示无限期等待直到你按下任意键才继续cv2.waitKey(1000)表示等待 1 秒不管你有没有按键1 秒后继续执行。热搜里有个特别典型的问题“opencv 库 waitkey 为啥没参数时会卡主”。这其实是个理解偏差。如果你写的是cv2.waitKey()括号里没传参数那么参数默认值就是0也就是无限期等待所以程序看起来像“卡住了”。这不是 bug而是你让它永久等下去。如果想让它等一小会儿比如 30 毫秒刷新一帧就必须显式写cv2.waitKey(30)。另外还有一个很多人没注意的返回值问题。waitKey会返回用户按下的按键的 ASCII 码比如按下 Esc 键ASCII 码 27返回 27如果没有按键而超时了返回 -1。利用这个特性可以写退出逻辑while True: cv2.imshow(frame, img) key cv2.waitKey(30) if key 27: # Esc 键退出 break cv2.destroyAllWindows()这是后面做视频处理、摄像头实时显示时最经典的循环框架。在“基础一”阶段先把这个逻辑看明白以后做实时任务就顺手了。3.3 显示窗口的大小与分辨率问题读入一张 4K 大图直接imshow的时候窗口可能比你的屏幕还大显示不全。这时候有人会想到用cv2.namedWindow重新设定窗口属性或者干脆先缩小图像再显示height, width img.shape[:2] resized cv2.resize(img, (width // 2, height // 2)) cv2.imshow(resized, resized) cv2.waitKey(0)这里需要提醒一个细节cv2.resize的第二个参数是(宽度, 高度)不是(高度, 宽度)和img.shape返回的顺序正好相反。这是 OpenCV 里一个极其容易踩反的高频错误。我测过太多次写代码的时候顺口就把shape的值直接填进去了结果图像横竖颠倒。如果不想手动缩放也可以先创建带属性的窗口再显示cv2.namedWindow(window, cv2.WINDOW_NORMAL) cv2.imshow(window, img) cv2.waitKey(0)WINDOW_NORMAL允许用户手动拉伸窗口大小而不是强制按图像原始尺寸显示。3.4 显示颜色不对BGR 与 RGB 的纠葛读入图像并用imshow显示时颜色一般是正常的。但如果你把图像交给matplotlib去显示麻烦就来了。因为matplotlib默认按 RGB 解析三通道图像而 OpenCV 读出来的是 BGR直接抛进去的话红蓝通道会互换画面看起来整体偏蓝、偏诡异。解决方案很简单显示前转换一下通道顺序import matplotlib.pyplot as plt rgb_img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) plt.imshow(rgb_img) plt.show()这个“BGR 还是 RGB”的问题是 OpenCV 新手最容易困惑的地方之一。核心就一句话OpenCV 读图和存储图像时老老实实按 BGR 处理不要试图改变它只有在显示环节发给 matplotlib、写入某些图像库才需要做转换。一旦你自己手动画了一个 RGB 纯红像素(255, 0, 0)写进img再用imshow显示你会发现它变成了蓝色——因为 OpenCV 把你的三个数按 BGR 解释了(255, 0, 0)表示“蓝色最强”。4. 写入图像imwrite 的参数与实战4.1 基本用法与文件格式推断显示看完之后如果把处理结果存下来就用imwritecv2.imwrite(output.jpg, img)OpenCV 会根据文件扩展名自动判断保存格式.jpg存成 JPEG、.png存成 PNG、.bmp存成 BMP。写完之后它返回一个布尔值True表示保存成功False表示失败。和imread一样它也无法正确处理中文路径因此输出路径同样建议使用纯英文。保存格式的选择上有两个常见需求保存灰度图或带透明通道的图用 PNG因为 JPEG 不支持 alpha 通道且是有损压缩保存彩色照片、中间调试结果用 JPG 比较省存储空间但如果后续还要继续处理尽量用 PNG 避免压缩带来的画质损失4.2 控制保存质量JPEG 压缩率与 PNG 压缩级别imwrite还有个不常被提到但非常实用的功能——通过第三个参数控制保存质量。参数是一个列表写法略绕但理解了以后能精准控制输出# 保存为高画质 JPEG质量 95 cv2.imwrite(output.jpg, img, [cv2.IMWRITE_JPEG_QUALITY, 95]) # 保存为无压缩 PNG cv2.imwrite(output.png, img, [cv2.IMWRITE_PNG_COMPRESSION, 0])IMWRITE_JPEG_QUALITY的取值范围是 0~100数值越大画质越好、文件越大默认 95。IMWRITE_PNG_COMPRESSION的取值范围是 0~9数值越大压缩率越高、文件越小、耗时越长默认 3。如果你的程序是循环保存很多帧图像比如视频抽帧建议用默认质量即可。如果是做图像采集、后续要训练模型用建议 JPEG 的 quality 至少设定到 90 以上不要省那点存储空间。压缩伪影会直接影响后续图像处理算法的精度尤其边缘检测、轮廓提取这类对细节敏感的任务伪影会被当成真实边缘提取出来。4.3 完整示例从读到写到显示一整套流程把前面的内容整合成一个最基础、也最完整的流程import cv2 # 读取图像 img cv2.imread(D:/images/test.png) if img is None: print(读取失败请检查路径) exit() # 转为灰度图 gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 显示原图和灰度图 cv2.imshow(original, img) cv2.imshow(gray, gray) # 等待按键Esc 退出 while True: key cv2.waitKey(0) if key 27: break cv2.destroyAllWindows() # 保存结果 cv2.imwrite(D:/images/test_gray.png, gray) print(保存完成)这段代码覆盖了“读取→变换→显示→保存”的基本闭环。实际项目里读取和显示往往是调试阶段才用到真正部署的时候只保留处理逻辑和写入逻辑因为生产环境没有窗口管理器给你弹窗。理解了这个闭环之后你可以开始往中间塞任何图像处理算法了。5. 常见问题从 ModuleNotFoundError 到图像发绿5.1 问题速查表下面这个表格把新手最常碰到的几个问题集中列出来大家可以按图索骥。现象可能原因解决办法import cv2报ModuleNotFoundError: No module named cv2pip 装错了环境或 PyCharm 解释器未切换在终端确认pip list里有 opencv检查 IDE 解释器路径cv2.imread返回None路径不对、中文路径、文件名拼写错误使用英文路径、用绝对路径、加print验证文件是否存在窗口一闪而过看不到图缺少waitKey(0)导致程序直接退出补上cv2.waitKey(0)cv2.waitKey()像卡死没传参默认值是 0表示无限等待明确传毫秒数比如cv2.waitKey(30)matplotlib 显示图像颜色发蓝/发绿OpenCV 存的是 BGRmatplotlib 认 RGB先用cv2.cvtColor(img, cv2.COLOR_BGR2RGB)转换使用cv2.resize后图像横竖颠倒第二个参数是(宽度, 高度)写反了改成(height, width)的正确映射窗口显示“图像过大无法完整显示”图像尺寸超过屏幕分辨率用cv2.namedWindow(..., WINDOW_NORMAL)或先resize保存的图片是全黑的处理后图像数据类型不对或范围溢出检查数组是否全 0、dtype 是否 uint8、是否忘记归一化5.2 排查逻辑先确认数据再确认代码这里分享一个排错心得。遇到问题别第一时间改代码先分清到底是“数据问题”还是“代码问题”。比如imread读不到图先在终端用 Python 的os.path.exists(D:/images/test.png)确认文件在不在再考虑是不是代码写错。再比如保存的图片是全黑的先打印一下img.min()和img.max()看看数组里的数值范围——如果是0~255之间的数据却保存成黑的很可能是数据类型不是uint8而是float64写出来的时候数值被截断成了 0。5.3 我踩过的几个典型坑最后说几个我自己反复踩出来的教训。第一个是RGB 和 BGR 的混用。早期做项目时我用 OpenCV 读图、用 matplotlib 调试每次看到颜色不对就怀疑是 OpenCV 读图失败折腾半天才发现是通道顺序的问题。后来我给自己立下一个规矩进 OpenCV 之前不碰 RGB出 OpenCV 之后再考虑通道转换整个流程干干净净再也没犯过糊涂。第二个是路径里的反斜杠。Windows 上复制路径时默认是C:\Users\name\...这样反斜杠的形式直接粘贴到代码里就会被转义。吃了几次亏之后我习惯性把所有路径都写成正斜杠不为别的就图个心里踏实。第三个是waitKey 返回值的使用。做视频逐帧处理时如果只想让程序每帧等待 1 毫秒却写了cv2.waitKey(0)视频会像幻灯片一样一帧一帧等你按键画面看起来就像彻底卡死。记住waitKey(0)是单帧调试用的实时视频流一般用waitKey(1)或waitKey(30)。这个参数虽然小但它决定了你的程序是“流畅显示视频”还是“卡成一帧一帧看”。回到文章开头那句话——OpenCV 的一切都始于读取、显示、写入这三个最基础的操作。把这三个函数用熟了理解了背后的数组本质和坐标系逻辑你就已经超过了相当一部分停留在“会抄代码但不懂原理”的入门者。下一篇基础系列我计划把图像的像素访问、ROI 裁剪和通道拆分合并讲透这些是后面所有图像处理算法的积木。到时候你再回头看会发现今天这篇关于imread、imshow、imwrite的细节全都在为后面的内容打底没有任何一步是白费的。
返回列表