前一个教程: 矩阵上掩模操作
下一个教程: 使用 OpenCV 添加(混合)两张图像
输入/输出
图像
从文件中加载图像
C++
Mat img = imread(filename);
Java
Mat img = Imgcodecs.imread(filename);
Python
CV_EXPORTS_W Mat imread(const String &filename, int flags=IMREAD_COLOR)
从文件中加载图像。
如果您读取 jpg 文件,默认会创建一个 3 通道图像。如果您需要灰度图像,请使用
C++
Mat img = imread(filename, IMREAD_GRAYSCALE);
Java
Mat img = Imgcodecs.imread(filename, Imgcodecs.IMREAD_GRAYSCALE);
Python
img =
cv.imread(filename, cv.IMREAD_GRAYSCALE)
- 注意
- 文件格式由其内容(前几个字节)决定。要将图像保存到文件
C++
Java
Imgcodecs.imwrite(filename, img);
Python
CV_EXPORTS_W bool imwrite(const String &filename, InputArray img, const std::vector< int > ¶ms=std::vector< int >())
将图像保存到指定文件中。
- 注意
- 文件格式由其扩展名决定。
- 使用 cv::imdecode 和 cv::imencode 从内存或文件中读取和写入图像,而不是文件。
图像基本操作
访问像素强度值
为了获取像素强度值,您必须知道图像的类型和通道数。对于单通道灰度图像(类型 8UC1)和像素坐标 x 和 y 来说,以下是一个示例
C++
Scalar intensity = img.at<
uchar>(y, x);
无符号 char uchar
定义 interface.h:51
Java
byte[] imgData = new byte[(int) (img.total() * img.channels())];
img.get(0, 0, imgData);
byte intensity = imgData[y * img.cols() + x];
Python
仅限 C++ 版本:intensity.val[0] 包含 0 至 255 的值。 请注意 x 和 y 的顺序。 由于在 OpenCV 中图像由与矩阵相同的结构表示,因此对两种情况我们都使用相同的惯例 - 基于 0 的行索引(或 y 坐标)在先,基于 0 的列索引(或 x 坐标)在后。 或者,可以使用以下符号(仅限 C++)
标量强度 = img.at<
uchar>(点(x, y));
现在让我们考虑一个具有 BGR 颜色顺序(imread 返回的默认格式)的三通道图像
C++代码
Vec3b intenstiy = img.at<Vec3b>(y, x);
uchar 蓝色 = intenstiy.val[0];
uchar 绿色 = intenstiy.val[1];
uchar 红色 = intenstiy.val[2];
Python Python
_蓝色 = img[y,x,0]
_绿色 = img[y,x,1]
_红色 = img[y,x,2]
对于浮点图像,可以使用相同的方法(例如,可以通过对 3 通道图像运行 Sobel 获得此类图像)(仅限 C++)
Vec3f 强度 = img.at<Vec3f>(y, x);
float 绿色 = intensity.val[1];
float 红色 = intensity.val[2];
_Tp val[m *n]
矩阵元素
定义 matx.hpp:218
可以使用相同的方法来更改像素强度
C++
img.at<
uchar>(y, x) = 128;
Java
byte[] imgData = new byte[(int) (img.total() * img.channels())];
imgData[y * img.cols() + x] = (字节) 128;
img.put(0, 0, imgData);
Python
OpenCV 中有函数,尤其是来自 calib3d 模块的函数,例如 cv::projectPoints,以 Mat 的形式接受阵列的 2D 或 3D 点。 矩阵应仅包含一列,每行对应一个点,矩阵类型应分别为 32FC2 或 32FC3。可以很容易地从 std::vector
(仅限 C++)构建此类矩阵
vector<Point2f> 点;
Mat 点矩阵 = 矩阵(点);
可以使用相同的方法 Mat::at
(仅限 C++)访问此矩阵中的点
Point2f 点 = 点矩阵.at<Point2f>(i, 0);
内存管理和引用计数
Mat 是一种可以保留矩阵/图像特征(行数和列数、数据类型等)并指向数据的结构。因此,同一数据对应于多个 Mat 实例不会受到任何限制。Mat 会保存一个引用计数,当销毁一个特定的 Mat 实例时,该计数会告知数据是否要解除分配。以下是一个创建 Mat 的示例,不会复制数据(仅限 C++)
std::vector<Point3f> points;
Mat pointsMat = Mat(points).reshape(1);
结果中,我们会得到一个 32FC1 矩阵,有 3 列,而不是 32FC3 矩阵,有 1 列。pointsMat
使用 points 的数据,并且在销毁时不会解除内存的分配。然而,在此特定实例中,开发者必须确保 points
的生命周期比 pointsMat
的生命周期更长。如果我们需要复制数据,可以使用 cv::Mat::copyTo 或 cv::Mat::clone
C++
Mat img = imread("image.jpg");
Mat img1 = img.clone();
Java
Mat img = Imgcodecs.imread("image.jpg");
Mat img1 = img.clone();
Python
可以向每个函数提供一个空输出 Mat。每个实现都会针对一个目标矩阵调用 Mat::create。如果矩阵为空,此方法会为矩阵分配数据。如果矩阵不为空,并且具有正确的大小和类型,则此方法不会执行任何操作。但是,如果大小或类型不同于输入参数,那么数据会解除分配(丢失),然后会分配新的数据。例如
C++
Mat img = imread("image.jpg");
Mat sobelx;
Sobel(img, sobelx,
CV_32F, 1, 0);
#define CV_32F
定义 interface.h:78
Java
Mat img = Imgcodecs.imread("image.jpg");
Mat sobelx = new Mat();
Imgproc.Sobel(img, sobelx, CvType.CV_32F, 1, 0);
Python
_sobelx =
cv.Sobel(img, cv.CV_32F, 1, 0)
void Sobel(InputArray src, OutputArray dst, int ddepth, int dx, int dy, int ksize=3, double scale=1, double delta=0, int borderType=BORDER_DEFAULT)
使用扩展的 Sobel 算子计算一阶、二阶、三阶或混合图像导数。
原始操作
在矩阵上定义了多个便捷的算子。例如,以下是如何从现有的灰度图像 img
中创建一幅黑色图像
C++
Java
byte[] imgData = new byte[(int) (img.total() * img.channels())];
Arrays.fill(imgData, (byte) 0);
img.put(0, 0, imgData);
Python
选择感兴趣的区域
C++
Rect r(10, 10, 100, 100);
Mat smallImg = img(r);
Java
Rect r = new Rect(10, 10, 100, 100);
Mat smallImg = img.submat(r);
Python
_smallImg = img[10:110,10:110]
从彩色转换为灰度
C++
Mat img = imread("image.jpg");
Mat grey;
cvtColor(img, grey, COLOR_BGR2GRAY);
Java
Mat img = Imgcodecs.imread("image.jpg");
Mat grey = new Mat();
Imgproc.cvtColor(img, grey, Imgproc.COLOR_BGR2GRAY);
Python
void cvtColor(InputArray src, OutputArray dst, int code, int dstCn=0)
将图片从一种色彩空间转换成另一种色彩空间。
转换图像类型从 8UC1 到 32FC1
C++
Java
src.convertTo(dst, CvType.CV_32F);
Python
_dst = src.astype(np.float32)
可视化图像
在开发过程中,查看算法的中间结果非常有用。OpenCV 提供了一种方便的方法可视化图像。可以使用以下方法显示一个 8U 图像
C++
Mat img = imread("image.jpg");
namedWindow("image", WINDOW_AUTOSIZE);
imshow("image", img);
waitKey();
Java
Mat img = Imgcodecs.imread("image.jpg");
HighGui.namedWindow("image", HighGui.WINDOW_AUTOSIZE);
HighGui.imshow("image", img);
HighGui.waitKey();
Python
void imshow(const String &winname, InputArray mat)
在指定窗口中显示图像。
int waitKey(int delay=0)
等待按键。
void namedWindow(const String &winname, int flags=WINDOW_AUTOSIZE)
创建窗口。
调用 waitKey() 会开启一个消息传递循环,等待在“image”窗口中按下按键。需要将 32F 图像转换成 8U 类型。例如
C++
Mat img = imread("image.jpg");
Mat grey;
cvtColor(img, grey, COLOR_BGR2GRAY);
Mat sobelx;
Sobel(grey, sobelx,
CV_32F, 1, 0);
double minVal, maxVal;
minMaxLoc(sobelx, &minVal, &maxVal);
Mat draw;
sobelx.convertTo(draw,
CV_8U, 255.0/(maxVal - minVal), -minVal * 255.0/(maxVal - minVal));
namedWindow("image", WINDOW_AUTOSIZE);
imshow("image", draw);
waitKey();
#define CV_8U
Definition interface.h:73
Java
Mat img = Imgcodecs.imread("image.jpg");
Mat grey = new Mat();
Imgproc.cvtColor(img, grey, Imgproc.COLOR_BGR2GRAY);
Mat sobelx = new Mat();
Imgproc.Sobel(grey, sobelx, CvType.CV_32F, 1, 0);
MinMaxLocResult res = Core.minMaxLoc(sobelx);
Mat draw = new Mat();
double maxVal = res.maxVal, minVal = res.minVal;
sobelx.convertTo(draw, CvType.CV_8U, 255.0 / (maxVal - minVal), -minVal * 255.0 / (maxVal - minVal));
HighGui.namedWindow("image", HighGui.WINDOW_AUTOSIZE);
HighGui.imshow(“image”, draw);
HighGui.waitKey();
Python
sobelx =
cv.Sobel(grey, cv.CV_32F, 1, 0)
minVal = np.amin(sobelx)
maxVal = np.amax(sobelx)
draw =
cv.convertScaleAbs(sobelx, alpha=255.0/(maxVal - minVal), beta=-minVal * 255.0/(maxVal - minVal))
void convertScaleAbs(InputArray src, OutputArray dst, double alpha=1, double beta=0)
缩放、计算绝对值,并将结果转换为 8 位。
- 注意
- 此处 cv::imshow。尽管如此,仍可使用它来更改窗口属性或使用