第一章:图像基础与OpenCV入门
各位同学,欢迎来到《工业视觉算法从零到部署实战》的第一章。
说实话,每次带新人入行,我都要先问一个问题:你真的理解图像吗?很多人一上来就调库、跑模型,结果连图像在内存里长什么样都不清楚。这不行。工业视觉讲究的是稳、准、狠,基础不牢,后面全是坑。
这一章,我们就从最底层的像素讲起,把OpenCV这个趁手的兵器装好,然后亲手读写一张图。嗯,别急,慢慢来。
1.1 像素与色彩空间
图像是什么?说白了,就是一堆数字排成的矩阵。
你拿手机拍一张照片,放大再放大,最后看到的是一个一个的小方格。每个小方格就是一个像素。每个像素里存着颜色信息。
1.1.1 像素的表示
在计算机眼里,一张灰度图就是一个二维数组。每个元素的值是0到255,0代表纯黑,255代表纯白。中间的数字就是不同程度的灰色。
彩色图呢?稍微复杂一点。它有三个通道——红、绿、蓝。每个通道也是一张灰度图,叠在一起就形成了彩色。
我个人习惯把彩色图想象成三张透明胶片叠在一起。红色胶片管红色分量,绿色管绿色,蓝色管蓝色。三个一叠加,五彩斑斓的世界就出来了。
重要概念:图像在内存中的存储顺序是 BGR,不是 RGB。这是OpenCV的历史遗留问题。很多新手在这里翻车,我当年也中过招。
1.1.2 常见的色彩空间
除了RGB,工业视觉里常用的还有这么几种:
| 色彩空间 | 特点 | 工业应用场景 |
|---|---|---|
| RGB | 最直观,三通道 | 显示、常规检测 |
| 灰度 | 单通道,计算快 | 边缘检测、模板匹配 |
| HSV | 色调、饱和度、明度 | 颜色分割、目标跟踪 |
| Lab | 感知均匀,与设备无关 | 颜色校准、高精度检测 |
为什么工业视觉里经常用HSV而不是RGB?你想想看,RGB三个通道跟亮度强相关。光照一变,RGB值全变了。但HSV里,色调H相对稳定。我在做手机外壳划痕检测时,就靠HSV空间把不同光照下的缺陷稳定提取出来。
我的经验:做颜色相关的检测,优先试HSV。做形状相关的,灰度图往往就够了。别一上来就搞三通道,计算量翻三倍,效果不一定好。
1.2 OpenCV环境搭建
OpenCV,全称Open Source Computer Vision Library。工业视觉圈里,几乎人手一份。我建议你用Python版本,开发快,调试方便。生产环境再用C++重写。
1.2.1 Python环境安装
我个人习惯用conda管理环境,干净、隔离、不打架。
# 创建虚拟环境
conda create -n industrial_vision python=3.9
# 激活环境
conda activate industrial_vision
# 安装OpenCV
pip install opencv-python
# 如果需要额外模块(如特征匹配)
pip install opencv-contrib-python
装完之后,验证一下:
import cv2
print(cv2.__version__)
# 输出类似 4.8.0 就对了
避坑指南:我曾经在客户现场遇到过一个问题——OpenCV装好了,但imshow弹不出窗口。后来发现是服务器没有图形界面。工业现场很多机器是无头模式(headless),这时候要用 opencv-python-headless 版本。
1.2.2 C++环境配置(选读)
如果你后面要做嵌入式部署,C++版本是绕不开的。这里简单提一下:
# Ubuntu下安装
sudo apt-get install libopencv-dev
# Windows下建议用vcpkg
vcpkg install opencv
配置好之后,记得在CMakeLists.txt里加上:
find_package(OpenCV REQUIRED)
target_link_libraries(your_project ${OpenCV_LIBS})
1.3 图像读取、显示与保存
好了,环境搭好了,我们来亲手操作一张图。这是每个视觉工程师的"Hello World"。
1.3.1 读取图像
import cv2
# 读取图像
img = cv2.imread('example.jpg')
# 检查是否读取成功
if img is None:
print("图片没读到,检查路径")
else:
print(f"图片读取成功,尺寸:{img.shape}")
这里有个细节:imread默认以彩色BGR模式读取。如果你想读成灰度图:
img_gray = cv2.imread('example.jpg', cv2.IMREAD_GRAYSCALE)
注意:路径中不要有中文。OpenCV底层用的是C++的fopen,对中文支持不好。我在一个工厂项目里吃过这个亏,排查了半天,最后发现是文件夹名字带了"测试"两个字。
1.3.2 显示图像
cv2.imshow('窗口标题', img)
cv2.waitKey(0) # 等待按键
cv2.destroyAllWindows()
waitKey(0)的意思是无限等待,直到你按任意键。如果你想让窗口自动关闭,可以设置毫秒数,比如waitKey(3000)就是等3秒。
为什么会这样设计?因为OpenCV的显示是事件驱动的。没有waitKey,窗口一闪就没了,你根本看不到。
1.3.3 保存图像
cv2.imwrite('output.jpg', img)
就这么一行。你可以指定保存格式,OpenCV会根据文件后缀自动判断。比如:
cv2.imwrite('output.png', img) # 保存为PNG
cv2.imwrite('output.bmp', img) # 保存为BMP
我的习惯:中间结果保存为PNG,无损压缩。最终交付用JPG,文件小。调试阶段用BMP,读写最快。
1.4 基本图像属性
拿到一张图,第一件事就是看它的属性。就像你拿到一个零件,先量尺寸一样。
1.4.1 shape属性
print(img.shape)
# 灰度图输出:(height, width)
# 彩色图输出:(height, width, channels)
举个例子:
img = cv2.imread('example.jpg')
h, w, c = img.shape
print(f"高度:{h},宽度:{w},通道数:{c}")
1.4.2 dtype属性
print(img.dtype)
# 通常是 uint8,范围0-255
工业相机有时候会输出16位深度的图像,dtype就是uint16。这时候要注意,很多OpenCV函数默认只支持uint8,需要做类型转换。
1.4.3 像素访问
# 访问某个像素
pixel = img[100, 200] # 第100行,第200列
print(pixel)
# 修改像素
img[100, 200] = [0, 0, 255] # 改成红色(BGR顺序)
注意:行是y坐标,列是x坐标。跟数学里的坐标系不一样,别搞反了。
性能提醒:用Python循环逐像素访问非常慢。一张1920x1080的图,两百万个像素,循环一次要好几秒。工业现场要求毫秒级响应,千万别这么干。要用numpy的矩阵操作或者OpenCV的内置函数。
1.4.4 ROI(感兴趣区域)
# 截取图像的一部分
roi = img[100:300, 200:400] # 行范围100-300,列范围200-400
# 显示ROI
cv2.imshow('ROI', roi)
ROI在工业视觉里太常用了。比如你要检测电路板上的某个焊点,先框出焊点的大致区域,再在这个小区域里做精细检测。速度快,干扰少。
本章小结
这一章我们干了三件事:
- 理解了像素和色彩空间——图像的本质是数字矩阵
- 搭好了OpenCV环境——Python和C++两种方式
- 学会了图像的读写显示——这是视觉工程师的基本功
下一章,我们会深入图像的基本操作:裁剪、缩放、旋转、翻转。这些操作在工业视觉里天天用,熟练了能省不少事。
嗯,今天就到这里。记得动手敲代码,光看是学不会的。有问题随时交流。
公众号:蓝海资料掘金营,微信deep3321