目标
- 学习如何访问图像属性
- 学习如何构建 Mat
- 学习如何复制 Mat
- 学习如何转换 Mat 的类型
- 学习如何使用 MatVector
- 学习如何访问和修改像素值
- 学习如何设置感兴趣区域 (ROI)
- 学习如何分割和合并图像
访问图像属性
图像属性包括行数、列数和大小、深度、通道数、图像数据类型。
let src = cv.imread("canvasInput");
console.log('图像宽度: ' + src.cols + '\n' +
'图像高度: ' + src.rows + '\n' +
'图像大小: ' + src.size().width + '*' + src.size().height + '\n' +
'图像深度: ' + src.depth() + '\n' +
'图像通道数: ' + src.channels() + '\n' +
'图像类型: ' + src.type() + '\n');
- 注意
- src.type() 在调试时非常重要,因为 OpenCV.js 代码中的大量错误是由无效的数据类型引起的。
如何构建 Mat
有 4 种基本构造函数
// 1. 默认构造函数
let mat = new cv.Mat();
// 2. 通过大小和类型构造二维数组
let mat = new cv.Mat(size, type);
// 3. 通过行数、列数和类型构造二维数组
let mat = new cv.Mat(rows, cols, type);
// 4. 通过行数、列数、类型和初始化值构造二维数组
let mat = new cv.Mat(rows, cols, type, new cv.Scalar());
有 3 个静态函数
// 1. 创建一个充满零的 Mat
let mat = cv.Mat.zeros(rows, cols, type);
// 2. 创建一个充满一的 Mat
let mat = cv.Mat.ones(rows, cols, type);
// 3. 创建一个单位矩阵 Mat
let mat = cv.Mat.eye(rows, cols, type);
有 2 个工厂函数
// 1. 使用 JS 数组构造 Mat。
// 例如: let mat = cv.matFromArray(2, 2, cv.CV_8UC1, [1, 2, 3, 4]);
let mat = cv.matFromArray(rows, cols, type, array);
// 2. 使用 imgData 构造 Mat
let ctx = canvas.getContext("2d");
let imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
let mat = cv.matFromImageData(imgData);
- 注意
- 当您不再想使用 cv.Mat 时,请不要忘记删除它。
如何复制 Mat
有 2 种方法复制 Mat
// 1. Clone
let dst = src.clone();
// 2. CopyTo (只有 mask 中指示的条目会被复制)
src.copyTo(dst, mask);
如何转换 Mat 的类型
我们使用函数:convertTo(m, rtype, alpha = 1, beta = 0)
- 参数
-
| m | 输出矩阵;如果在操作之前没有适当的大小或类型,则会重新分配。 |
| rtype | 所需的输出矩阵类型,或者更确切地说,是深度,因为通道数与输入相同;如果 rtype 为负,则输出矩阵将具有与输入相同的类型。 |
| alpha | 可选的比例因子。 |
| beta | 添加到缩放值的可选增量。 |
src.convertTo(dst, rtype);
如何使用 MatVector
let mat = new cv.Mat();
// 初始化 MatVector
let matVec = new cv.MatVector();
// 将 Mat 推入 MatVector
matVec.push_back(mat);
// 从 MatVector 获取 Mat
let cnt = matVec.get(0);
mat.delete(); matVec.delete(); cnt.delete();
- 注意
- 当您不再想使用 cv.Mat, cv.MatVector 和 cnt (您从 MatVector 获取的 Mat) 时,请不要忘记删除它们。
访问和修改像素值
首先,您应该知道以下类型关系
| 数据属性 | C++ 类型 | JavaScript 类型化数组 | Mat 类型 |
| data | I.at<uchar>(y, x) = saturate_cast<uchar>(r); | Uint8Array | Definition interface.h:74 |
| data8S | char | Int8Array | enum { CV_8U=0, CV_8S=1, CV_16U=2, CV_16S=3, CV_32S=4, CV_32F=5, CV_64F=6 }; |
| data16U | ushort | Uint16Array | Definition interface.h:76 |
| data16S | short | Int16Array | Definition interface.h:77 |
| data32S | int | Int32Array | Definition interface.h:73 |
| data32F | float | Float32Array | CV_32F |
| data64F | double | Float64Array | CV_64F |
1. data
let row = 3, col = 4;
let src = cv.imread("canvasInput");
if (src.isContinuous()) {
let R = src.data[row * src.cols * src.channels() + col * src.channels()];
let G = src.data[row * src.cols * src.channels() + col * src.channels() + 1];
let B = src.data[row * src.cols * src.channels() + col * src.channels() + 2];
let A = src.data[row * src.cols * src.channels() + col * src.channels() + 3];
}
- 注意
- 数据操作仅对连续 Mat 有效。您应该首先使用 isContinuous() 进行检查。
2. at
| Mat 类型 | At 操作 |
| Definition interface.h:74 | ucharAt |
| enum { CV_8U=0, CV_8S=1, CV_16U=2, CV_16S=3, CV_32S=4, CV_32F=5, CV_64F=6 }; | charAt |
| Definition interface.h:76 | ushortAt |
| Definition interface.h:77 | shortAt |
| Definition interface.h:73 | intAt |
| CV_32F | floatAt |
| CV_64F | doubleAt |
let row = 3, col = 4;
let src = cv.imread("canvasInput");
let R = src.ucharAt(row, col * src.channels());
let G = src.ucharAt(row, col * src.channels() + 1);
let B = src.ucharAt(row, col * src.channels() + 2);
let A = src.ucharAt(row, col * src.channels() + 3);
- 注意
- At 操作仅用于单通道访问,并且无法修改该值。
3. ptr
| Mat 类型 | Ptr 操作 | JavaScript 类型化数组 |
| Definition interface.h:74 | ucharPtr | Uint8Array |
| enum { CV_8U=0, CV_8S=1, CV_16U=2, CV_16S=3, CV_32S=4, CV_32F=5, CV_64F=6 }; | charPtr | Int8Array |
| Definition interface.h:76 | ushortPtr | Uint16Array |
| Definition interface.h:77 | shortPtr | Int16Array |
| Definition interface.h:73 | intPtr | Int32Array |
| CV_32F | floatPtr | Float32Array |
| CV_64F | doublePtr | Float64Array |
let row = 3, col = 4;
let src = cv.imread("canvasInput");
let pixel = src.ucharPtr(row, col);
let R = pixel[0];
let G = pixel[1];
let B = pixel[2];
let A = pixel[3];
mat.ucharPtr(k) 获取 mat 的第 k 行。 mat.ucharPtr(i, j) 获取 mat 的第 i 行和第 j 列。
图像 ROI
有时,您需要处理图像的特定区域。 对于图像中的眼睛检测,首先在整个图像上进行面部检测,并且在获得面部时,我们仅选择面部区域并在其中搜索眼睛,而不是搜索整个图像。 它可以提高准确性(因为眼睛总是在脸上)和性能(因为我们搜索的是一个小区域)
我们使用函数:roi (rect)
- 参数
-
尝试一下
分割和合并图像通道
有时您需要分别处理图像的 R、G、B 通道。 然后您需要将 RGB 图像拆分为单个平面。 或者另一次,您可能需要将这些单独的通道加入到 RGB 图像中。
let src = cv.imread("canvasInput");
let rgbaPlanes = new cv.MatVector();
// 分割 Mat
cv.split(src, rgbaPlanes);
// 获取 R 通道
let R = rgbaPlanes.get(0);
// 合并所有通道
cv.merge(rgbaPlanes, src);
src.delete(); rgbaPlanes.delete(); R.delete();
- 注意
- 当您不再想使用 cv.Mat, cv.MatVector 和 R (您从 MatVector 获取的 Mat) 时,请不要忘记删除它们。
为图像制作边框(填充)
如果您想在图像周围创建边框,例如相框,则可以使用 cv.copyMakeBorder() 函数。 但是它对于卷积运算、零填充等有更多的应用。 此函数采用以下参数
- src - 输入图像
- top、bottom、left、right - 相应方向上的边框宽度(以像素为单位)
- borderType - 定义要添加的边框类型的标志。 它可以是以下类型
- value - 如果边框类型是 cv.BORDER_CONSTANT,则为边框颜色
尝试一下