目录
[1.4.void* userdata参数的作用](#1.4.void* userdata参数的作用)
[2.1.1. NORM_MINMAX ------ "按比例缩放"](#2.1.1. NORM_MINMAX —— “按比例缩放”)
[2.1.2. NORM_L1 ------ "总金额 1 元"](#2.1.2. NORM_L1 —— “总金额 1 元”)
[2.1.3. NORM_L2 ------ "方向不变,长度变 1"](#2.1.3. NORM_L2 —— “方向不变,长度变 1”)
[2.1.4. NORM_INF ------ "削平最高的山头"](#2.1.4. NORM_INF —— “削平最高的山头”)
一.鼠标操作与响应
OpenCV 的鼠标操作,简单来说就是通过 setMouseCallback 函数,为特定的显示窗口"绑定"一个回调函数。之后,你在这个窗口上的每一次鼠标点击、移动或释放,系统都会自动调用这个函数,让你的程序能"看到"并响应鼠标的动作。
⚙️ 核心机制与函数:setMouseCallback
在 OpenCV 中,鼠标交互的核心函数是 cv::setMouseCallback。它的原型如下:
void setMouseCallback(const String& winname, MouseCallback onMouse, void* userdata = 0);
参数说明:
- winname: **目标窗口的名称。**必须是已经通过 cv::namedWindow() 创建的窗口。
- onMouse: **用户定义的回调函数指针。**当指定窗口发生鼠标事件时,该函数会被自动调用。
- userdata: 用户自定义数据的指针(默认为0),用于向回调函数传递额外的数据,例如要操作的图像指针等。
回调函数 MouseCallback 有固定的格式:
void onMouse(int event, int x, int y, int flags, void* userdata);
参数说明:
- event: 核心参数,表示发生的具体鼠标事件。
- x, y: 鼠标事件发生时,鼠标在窗口坐标系中的坐标(左上角为原点 (0,0))。
- flags: 事件的附加状态,常用于判断鼠标移动时是否有其他按键(Ctrl/Shift等)被同时按下。
- userdata: 与 setMouseCallback 中传递的 userdata 对应。
📋 事件大全:event 参数
event 参数是回调函数的灵魂,OpenCV 定义了多种事件常量,涵盖了基本操作。
事件常量 (C++中为 EVENT_*) 描述 数值常量
- EVENT_MOUSEMOVE 鼠标移动 0
- EVENT_LBUTTONDOWN 鼠标左键按下 1
- EVENT_RBUTTONDOWN 鼠标右键按下 2
- EVENT_MBUTTONDOWN 鼠标中键按下 3
- EVENT_LBUTTONUP 鼠标左键释放 4
- EVENT_RBUTTONUP 鼠标右键释放 5
- EVENT_MBUTTONUP 鼠标中键释放 6
- EVENT_LBUTTONDBLCLK 鼠标左键双击 7
- EVENT_RBUTTONDBLCLK 鼠标右键双击 8
- EVENT_MBUTTONDBLCLK 鼠标中键双击 9
- EVENT_MOUSEWHEEL 鼠标滚轮垂直滚动 10
⌨️ 按键信息:flags 参数
flags 参数用于获取 event 发生时,鼠标或键盘的"额外状态"。
鼠标按钮状态:
- EVENT_FLAG_LBUTTON (1):按下左键
- EVENT_FLAG_RBUTTON (2):按下右键
- EVENT_FLAG_MBUTTON (4):按下中键
功能键状态:
- EVENT_FLAG_CTRLKEY (8):按下 Ctrl 键
- EVENT_FLAG_SHIFTKEY (16):按下 Shift 键
- EVENT_FLAG_ALTKEY (32):按下 Alt 键
我们直接看例子
例子一
cpp
#include <opencv2/opencv.hpp>
#include <iostream>
void onMouse(int event, int x, int y, int flags, void* userdata) {
// 根据 event 类型打印信息
switch (event) {
case cv::EVENT_MOUSEMOVE:
std::cout << "鼠标移动: (" << x << ", " << y << ")" << std::endl;
break;
case cv::EVENT_LBUTTONDOWN:
std::cout << "左键按下: (" << x << ", " << y << ")" << std::endl;
break;
case cv::EVENT_LBUTTONUP:
std::cout << "左键释放: (" << x << ", " << y << ")" << std::endl;
break;
case cv::EVENT_RBUTTONDOWN:
std::cout << "右键按下: (" << x << ", " << y << ")" << std::endl;
break;
case cv::EVENT_RBUTTONUP:
std::cout << "右键释放: (" << x << ", " << y << ")" << std::endl;
break;
case cv::EVENT_MBUTTONDOWN:
std::cout << "中键按下: (" << x << ", " << y << ")" << std::endl;
break;
case cv::EVENT_MBUTTONUP:
std::cout << "中键释放: (" << x << ", " << y << ")" << std::endl;
break;
// 可以继续添加其他事件(如双击、滚轮等)
default:
break;
}
}
int main() {
// 创建一个简单的窗口
cv::namedWindow("Mouse Test");
// 绑定鼠标回调
cv::setMouseCallback("Mouse Test", onMouse);
// 显示一张空白图像(或者任何图像,这里用纯黑图)
cv::Mat img = cv::Mat::zeros(400, 600, CV_8UC3);
cv::imshow("Mouse Test", img);
std::cout << "请在窗口中移动鼠标或点击按键,按 ESC 退出" << std::endl;
while (true) {
int key = cv::waitKey(10);
if (key == 27) // ESC 键
break;
}
return 0;
}

非常厉害!!
1.1.鼠标双击
然后我们看看鼠标双击的情况
cpp
#include <opencv2/opencv.hpp>
#include <iostream>
void onMouse(int event, int x, int y, int flags, void* userdata) {
switch (event) {
case cv::EVENT_LBUTTONDOWN:
std::cout << "左键按下: (" << x << ", " << y << ")" << std::endl;
break;
case cv::EVENT_LBUTTONUP:
std::cout << "左键释放: (" << x << ", " << y << ")" << std::endl;
break;
case cv::EVENT_RBUTTONDOWN:
std::cout << "右键按下: (" << x << ", " << y << ")" << std::endl;
break;
case cv::EVENT_RBUTTONUP:
std::cout << "右键释放: (" << x << ", " << y << ")" << std::endl;
break;
case cv::EVENT_MBUTTONDOWN:
std::cout << "中键按下: (" << x << ", " << y << ")" << std::endl;
break;
case cv::EVENT_MBUTTONUP:
std::cout << "中键释放: (" << x << ", " << y << ")" << std::endl;
break;
case cv::EVENT_LBUTTONDBLCLK:
std::cout << "左键双击: (" << x << ", " << y << ")" << std::endl;
break;
case cv::EVENT_RBUTTONDBLCLK:
std::cout << "右键双击: (" << x << ", " << y << ")" << std::endl;
break;
case cv::EVENT_MBUTTONDBLCLK:
std::cout << "中键双击: (" << x << ", " << y << ")" << std::endl;
break;
default:
break;
}
}
int main() {
cv::namedWindow("Mouse Test");
cv::setMouseCallback("Mouse Test", onMouse);
cv::Mat img = cv::Mat::zeros(400, 600, CV_8UC3);
cv::imshow("Mouse Test", img);
std::cout << "请在窗口中移动鼠标或点击按键(包括双击),按 ESC 退出" << std::endl;
while (true) {
int key = cv::waitKey(10);
if (key == 27) // ESC
break;
}
return 0;
}

我们发现,双击出现之前,必定会触发一次单击!!!
1.2.滚轮轮动
我们看看鼠标滚轮的情况
cpp
#include <opencv2/opencv.hpp>
#include <iostream>
void onMouse(int event, int x, int y, int flags, void* userdata) {
if (event == cv::EVENT_MOUSEWHEEL)
{
// 获取滚轮滚动量(正:向上滚动,负:向下滚动)
int wheel_delta = cv::getMouseWheelDelta(flags);
std::cout << "鼠标滚轮: (" << x << ", " << y << ") 滚动量 = " << wheel_delta << std::endl;
}
}
int main() {
cv::namedWindow("Mouse Wheel Test");
cv::setMouseCallback("Mouse Wheel Test", onMouse);
cv::Mat img = cv::Mat::zeros(400, 600, CV_8UC3);
cv::imshow("Mouse Wheel Test", img);
std::cout << "请在窗口中滚动鼠标滚轮,按 ESC 退出" << std::endl;
while (true) {
int key = cv::waitKey(10);
if (key == 27) // ESC
break;
}
return 0;
}

我们往前滚动,就是120,往后滚动就是-120
1.3.flag参数
cpp
#include <opencv2/opencv.hpp>
#include <iostream>
void onMouse(int event, int x, int y, int flags, void*) {
// 只在鼠标移动时检查 flags 状态(你也可以在其他事件中检查)
if (event == cv::EVENT_MOUSEMOVE) {
std::cout << "鼠标移动到 (" << x << ", " << y << ") flags = " << flags << " -> ";
// 检查各个标志位
if (flags & cv::EVENT_FLAG_LBUTTON)
std::cout << "[左键按住] ";
if (flags & cv::EVENT_FLAG_RBUTTON)
std::cout << "[右键按住] ";
if (flags & cv::EVENT_FLAG_MBUTTON)
std::cout << "[中键按住] ";
if (flags & cv::EVENT_FLAG_CTRLKEY)
std::cout << "[Ctrl键] ";
if (flags & cv::EVENT_FLAG_SHIFTKEY)
std::cout << "[Shift键] ";
if (flags & cv::EVENT_FLAG_ALTKEY)
std::cout << "[Alt键] ";
std::cout << std::endl;
}
}
int main() {
cv::namedWindow("Flags Test");
cv::setMouseCallback("Flags Test", onMouse);
cv::Mat img = cv::Mat::zeros(400, 600, CV_8UC3);
cv::imshow("Flags Test", img);
std::cout << "请移动鼠标,可以同时按住鼠标按钮或 Ctrl/Shift/Alt 键" << std::endl;
std::cout << "按 ESC 退出" << std::endl;
while (true) {
if (cv::waitKey(10) == 27) break;
}
return 0;
}

非常的厉害!!
1.4.void* userdata参数的作用
void* userdata 是一个可以向回调函数传递任意自定义数据的指针。你可以把任何类型的数据(例如 Mat 图像指针、整数、结构体等)通过它传入,这样回调函数就不再依赖全局变量,更加灵活和模块化。
下面是一个最简单的例子:通过 userdata 把一张图像传给鼠标回调函数,在鼠标左键点击时,在图像上画一个红色圆点。
这个参数非常厉害的!!!
cpp
#include <opencv2/opencv.hpp>
#include <iostream>
// 鼠标回调函数
void onMouse(int event, int x, int y, int flags, void* userdata) {
// 将 userdata 转换回 cv::Mat 指针
cv::Mat* img = (cv::Mat*)userdata;
if (event == cv::EVENT_LBUTTONDOWN) {
// 在传入的图像上画一个蓝色小圆点
cv::circle(*img, cv::Point(x, y), 3, cv::Scalar(0, 0, 255), -1);
cv::imshow("Window", *img);
std::cout << "点击坐标 (" << x << ", " << y << "),已画圆" << std::endl;
}
}
int main() {
// 创建一张黑色图像
cv::Mat img = cv::Mat::zeros(400, 600, CV_8UC3);
cv::namedWindow("Window");
// 把图像指针作为 userdata 传入
cv::setMouseCallback("Window", onMouse, &img);
cv::imshow("Window", img);
std::cout << "在窗口内点击鼠标左键,会画圆。按 ESC 退出。" << std::endl;
while (true) {
int key = cv::waitKey(10);
if (key == 27) break;
}
return 0;
}

1.5.小例子
下面是一个完整的例子:鼠标左键按下时确定圆心,拖动鼠标时实时显示一个半径不断变化的圆(圆心到鼠标当前点的距离),松开左键后这个圆就固定在图像上。
cpp
#include <opencv2/opencv.hpp>
#include <iostream>
cv::Mat original; // 原始图像(已固定的内容)
cv::Mat temp; // 临时图像(用于绘制橡皮筋圆)
bool drawing = false;
cv::Point center;
void onMouse(int event, int x, int y, int flags, void*) {
if (event == cv::EVENT_LBUTTONDOWN) {
// 左键按下:开始绘制,记录圆心
drawing = true;
center = cv::Point(x, y);
}
else if (event == cv::EVENT_MOUSEMOVE && drawing) {
// 鼠标移动且正在绘制中:实时显示圆
int radius = (int)cv::norm(cv::Point(x, y) - center); // 圆心到当前点的距离
// 复制原始图像到临时图像
original.copyTo(temp);//原图是啥也没有的,这里就相当于将tmp图像归零
// 在临时图像上绘制圆(绿色,线宽2)
cv::circle(temp, center, radius, cv::Scalar(0, 255, 0), 2);
// 显示临时图像
cv::imshow("Draw Circle", temp);
}
else if (event == cv::EVENT_LBUTTONUP && drawing) {
// 左键释放:固定圆到原始图像
int radius = (int)cv::norm(cv::Point(x, y) - center);
// 在原始图像上绘制圆(绿色,线宽2)
cv::circle(original, center, radius, cv::Scalar(0, 255, 0), 2);
// 更新显示
cv::imshow("Draw Circle", original);
// 绘制结束
drawing = false;
}
}
int main() {
// 创建一张白色背景的图像(或读取一张图片)
original = cv::Mat::zeros(480, 640, CV_8UC3);
original.setTo(cv::Scalar(255, 255, 255)); // 白色背景
cv::namedWindow("Draw Circle");
cv::setMouseCallback("Draw Circle", onMouse);
cv::imshow("Draw Circle", original);
std::cout << "操作说明:按住鼠标左键并拖动,实时显示圆,释放鼠标后固定圆。按ESC退出。\n";
while (true) {
int key = cv::waitKey(10);
if (key == 27) // ESC
break;
}
return 0;
}
这个还是非常简单的
二.图像像素类型转换与归一化
2.1.归一化------cv::normalize
归一化的本质:给数据"统一标准"
想象一下:你手里有三把尺子,一把量厘米,一把量英寸,一把量市寸。你想比较它们测出的长度,但单位不同,数值没法直接比。怎么办呢?你把它们全部换算成同一个单位,比如"米"。这样,所有数值都变成了统一的参照系,可以直接比较了。这个"换算到统一标准"的过程,就是归一化。
在计算机里,图像、矩阵或任意一组数字也经常遇到这个问题:
-
有的数字很大(比如 0 到 10000)
-
有的数字很小(比如 0 到 1)
-
有的数字有正有负(比如 -100 到 +100)
你想把它们放在一起处理,或者想用同一个规则去衡量它们,就需要"归一化"。
最常见的归一化:把数据压到 0, 1 区间
比如你有一组考试成绩:60分,75分,90分。你想知道每个人的相对水平,而不要绝对分数。你可以这样做:
-
找出最低分 60,最高分 90。
-
把每个分数减去最低分,再除以(最高分-最低分)。
-
60 变成 0
-
75 变成 0.5
-
90 变成 1
-
结果就是 0、0.5、1。这组新数字都在 0 到 1 之间,而且保留了原来的大小顺序。这就是 线性归一化 (NORM_MINMAX)。在 OpenCV 里,你可以把一张很暗的图像(像素值 0~50)拉伸到 0~255,让图像变亮,本质上就是这种归一化。
另一种归一化:让数据"长度"变成 1
想象你把一个向量(一组数字)看作是从原点指向某个点的箭头。这个箭头有长度。如果你不想改变箭头的方向,只是想比较不同箭头的方向,就需要把所有的箭头都"拉长或缩短"到单位长度(长度 = 1)。这样,不同长度的箭头就变成了只代表方向的"单位向量"。这就是 L2 归一化 (NORM_L2)。它常用在机器学习里:比较两个特征向量是不是相似,先把他们归一化成单位长度,然后计算夹角。
cv::normalize 函数
opencv是借助了cv::normalize 函数来实现图像的归一化
cpp
void cv::normalize(
InputArray src, // 输入数组(原图像或矩阵)
InputOutputArray dst, // 输出数组(可与 src 相同)
double alpha, // 归一化后的最小值(或 L1/L2/INF 的目标范数值)
double beta, // 归一化后的最大值(仅对 NORM_MINMAX 有效)
int norm_type, // 归一化类型(NORM_MINMAX, NORM_L1, NORM_L2, NORM_INF)
int dtype = -1, // 输出数组的数据类型(-1 表示与 src 相同)
InputArray mask = noArray() // 可选掩膜
);
OpenCV 主要提供了 4 种 像素归一化方法,均通过 cv::normalize 函数的 norm_type 参数指定:
- NORM_MINMAX
- NORM_L1
- NORM_L2
- NORM_INF
2.1.1. NORM_MINMAX ------ "按比例缩放"
将像素值线性缩放到指定的范围(例如 0, 1 或 0, 255)。这是最常用的方法,用于对比度拉伸或统一不同图像的数值区间。
- 类比:班级里考试,最高分 90 分,最低分 10 分。你想把所有成绩换算成 0 到 1 之间的"相对分",方便看出每个人的水平差距。
- 做法:10 分变成 0,90 分变成 1,中间的分按比例变成 0.x。
- 结果:所有数都落在 0,1 或你指定的区间内,但原来谁大谁小、差距比例不变。
- 在图像中的用处:一张很暗的照片(所有像素值 30~80),拉伸到 0~255 后,暗部变黑,亮部变白,对比度提升。
cpp
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
// 1. 创建一个简单的 3x3 矩阵(模拟图像像素值)
cv::Mat src = (cv::Mat_<float>(3, 3) << 10, 20, 30,
40, 50, 60,
70, 80, 90);
std::cout << "原始矩阵:\n" << src << std::endl;
// 2. 使用 NORM_MINMAX 将数值线性缩放到 [0, 1] 区间
cv::Mat dst_minmax;
cv::normalize(src, dst_minmax, 0, 1, cv::NORM_MINMAX);
std::cout << "\nNORM_MINMAX 归一化到 [0, 1]:\n" << dst_minmax << std::endl;
// 3. 使用 NORM_MINMAX 缩放到 [0, 255](常用于图像显示)
cv::Mat dst_255;
cv::normalize(src, dst_255, 0, 255, cv::NORM_MINMAX);
std::cout << "\nNORM_MINMAX 归一化到 [0, 255]:\n" << dst_255 << std::endl;
return 0;
}

2.1.2. NORM_L1 ------ "总金额 1 元"
将所有像素的绝对值之和 (L1 范数)**归一化为 1。**即每个像素除以全图像素绝对值总和,使得新图像的像素绝对值总和等于 1。常用于概率分布或稀疏表示。
- 类比:你手里有一堆零钱:1 角、2 角、7 角,总共 1 元。现在你只想保留"每种零钱占总金额的比例",就把所有金额除以总金额(1 元)。
- 结果:0.1 元、0.2 元、0.7 元,相加刚好 1 元。
- 在图像中的用处:像素值变成"概率分布"(比如每个像素占图像总亮度的比例),常用于需要累加和为 1 的场合(如直方图匹配)。
cpp
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
// 创建一个简单的 3x3 矩阵(包含正数和负数,演示绝对值求和)
cv::Mat src = (cv::Mat_<float>(3, 3) << 1, 2, 3,
-4, 5, -6,
7, -8, 9);
std::cout << "原始矩阵:\n" << src << std::endl;
// 计算原始矩阵的 L1 范数(所有元素绝对值之和)
double l1_original = cv::norm(src, cv::NORM_L1);
std::cout << "原始矩阵的 L1 范数(绝对值之和): " << l1_original << std::endl;
// 使用 NORM_L1 归一化,使新的 L1 范数等于 1.0
cv::Mat dst_l1;
cv::normalize(src, dst_l1, 1.0, 0, cv::NORM_L1);
std::cout << "\nNORM_L1 归一化后(目标范数 = 1.0):\n" << dst_l1 << std::endl;
// 验证归一化后的 L1 范数是否为 1
double l1_new = cv::norm(dst_l1, cv::NORM_L1);
std::cout << "归一化后的 L1 范数: " << l1_new << std::endl;
return 0;
}

这些数值的代数和(正负数直接相加)确实不等于 1,实际上大约是 0.2。但是 NORM_L1 归一化要求的是所有元素绝对值之和等于 1,而不是代数和。
让我验证一下你给出的这个矩阵的绝对值之和:
矩阵元素及其绝对值:
- 0.022222223 → 0.022222223
- 0.044444446 → 0.044444446
- 0.06666667 → 0.06666667
- -0.088888891 → 0.088888891
- 0.11111111 → 0.11111111
- -0.13333334 → 0.13333334
- 0.15555556 → 0.15555556
- -0.17777778 → 0.17777778
- 0.2 → 0.2
现在计算绝对值之和:
- 0.022222223 + 0.044444446 = 0.066666669
- +0.06666667 = 0.133333339
- +0.088888891 = 0.22222223
- +0.11111111 = 0.33333334
- +0.13333334 = 0.46666668
- +0.15555556 = 0.62222224
- +0.17777778 = 0.80000002
- +0.2 = 1.00000002 ≈ 1
所以绝对值之和确实等于 1,这就是 NORM_L1 归一化的含义。
此外,我们在代码里面还看到了一个东西
cv::norm函数
给定一个向量(一组数字,比如 (1,2,3) 或者一张图像里的所有像素值),你可以用不同的"尺子"去量它的大小:
-
L1 范数 :把每个数字的绝对值加起来。
比如 (3, -4) 的 L1 范数 = |3| + |-4| = 7。
这就像曼哈顿街区距离:你只能沿着街道走,不能斜穿。
-
L2 范数 :每个数字平方,加起来,再开方。
比如 (3, -4) 的 L2 范数 =
。
这就是我们最熟悉的直线距离。
-
无穷范数 :只看绝对值最大的那个数字。
比如 (3, -4, 8) 的无穷范数 = max(3,4,8) = 8。
这就像在问:这支队伍里最壮的那个人有多壮?
cv::norm 是 OpenCV 中用于计算矩阵或向量的范数的函数。
在这里,我们只需要了解三种范数
- NORM_L1 L1 范数
- NORM_L2 L2 范数
- NORM_INF 无穷范数
2.1.3. NORM_L2 ------ "方向不变,长度变 1"
将所有像素的平方和的开方 (L2 范数,欧几里得范数)归一化为 1。 即每个像素除以 L2 范数,使得新图像的像素平方和等于 1。常用于特征向量归一化、机器学习预处理等。
- 类比:你画一个箭头,从原点指向 (3,4)。这个箭头的长度是 5(勾股定理)。你只想保留箭头的"指向",但把长度缩短为 1。
- 做法:所有坐标除以 5,得到 (0.6, 0.8),这就是一个单位向量。
- 在图像中的用处:把整张图像看作一个高维向量,归一化后,不同的图像只看"方向"(即像素值的相对比例),忽略整体亮度的影响。常用于机器学习特征比较(比如人脸识别中,比较两个特征的余弦相似度)。
cpp
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
// 创建一个简单的 3x3 矩阵(可以看作一个9维向量)
cv::Mat src = (cv::Mat_<float>(3, 3) << 3, 4, 0,
0, 0, 0,
0, 0, 0);
std::cout << "原始矩阵:\n" << src << std::endl;
// 计算原始矩阵的 L2 范数(平方和开方)
double l2_original = cv::norm(src, cv::NORM_L2);
std::cout << "原始矩阵的 L2 范数(欧几里得长度): " << l2_original << std::endl;
// 使用 NORM_L2 归一化,使新的 L2 范数等于 1.0
cv::Mat dst_l2;
cv::normalize(src, dst_l2, 1.0, 0, cv::NORM_L2);
std::cout << "\nNORM_L2 归一化后(目标范数 = 1.0):\n" << dst_l2 << std::endl;
// 验证归一化后的 L2 范数是否为 1
double l2_new = cv::norm(dst_l2, cv::NORM_L2);
std::cout << "归一化后的 L2 范数: " << l2_new << std::endl;
return 0;
}

2.1.4. NORM_INF ------ "削平最高的山头"
将所有像素绝对值的最大值 (无穷范数)**归一化为 1。**即每个像素除以全图像素绝对值的最大值,使得新图像的最大绝对值为 1。常用于将数值范围限制在 -1, 1 区间,防止溢出。
- 类比:你有一组数:3, -5, 10, 2。绝对值最大的是 10。你希望所有数的绝对值不超过 1,同时保持符号和相对大小。
- 做法:每个数都除以 10,得到 0.3, -0.5, 1, 0.2。现在最大的绝对值是 1。
- 在图像中的用处:把像素值范围压缩到 -1, 1 之间,避免计算时溢出;或者只看"相对于最强响应的比例"。
cpp
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
// 创建一个包含正数和负数的矩阵
cv::Mat src = (cv::Mat_<float>(3, 3) << 3, -5, 10,
2, -8, 4,
-12, 6, -1);
std::cout << "原始矩阵:\n" << src << std::endl;
// 计算原始矩阵的无穷范数(绝对值最大的元素)
double inf_original = cv::norm(src, cv::NORM_INF);
std::cout << "原始矩阵的无穷范数(最大绝对值): " << inf_original << std::endl;
// 使用 NORM_INF 归一化,使新的无穷范数等于 1.0
cv::Mat dst_inf;
cv::normalize(src, dst_inf, 1.0, 0, cv::NORM_INF);
std::cout << "\nNORM_INF 归一化后(目标范数 = 1.0):\n" << dst_inf << std::endl;
// 验证归一化后的无穷范数是否为 1
double inf_new = cv::norm(dst_inf, cv::NORM_INF);
std::cout << "归一化后的无穷范数: " << inf_new << std::endl;
return 0;
}

2.2.类型转换------cv::convertTo
它的主要作用是在不同的数值类型之间转换,比如把 CV_8U(0~255 的 uint8)转换为 CV_32F(浮点数),或者反过来。
cpp
void cv::Mat::convertTo(
OutputArray dst, // 输出矩阵
int rtype, // 目标数据类型(如 CV_32F, CV_8U)
double alpha = 1, // 缩放因子(乘数)
double beta = 0 // 偏移量(加数)
) const;
实际转换公式为:
cpp
dst(x,y) = saturate_cast<目标类型>( src(x,y) * alpha + beta )
saturate_cast 会确保结果值落在目标数据类型的有效范围内(比如 uint8 会截断到 0~255)。
cv::convertTo 的第二个参数 rtype 指定目标矩阵的数据类型,可以是以下常见的 OpenCV 类型宏(以单通道为例,也可以扩展为多通道,如 CV_8UC3):
- CV_8U:8 位无符号整数(0~255),最常用的图像类型。
- CV_8S:8 位有符号整数(-128~127)。
- CV_16U:16 位无符号整数(0~65535),常用于深度图。
- CV_16S:16 位有符号整数(-32768~32767)。
- CV_32S:32 位有符号整数(-2^31~2^31-1)。
- CV_32F:32 位单精度浮点数。
- CV_64F:64 位双精度浮点数。
我们看个简单的例子
cpp
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
// 创建一个 8 位无符号的矩阵(数值范围 0-255)
cv::Mat src = (cv::Mat_<uchar>(2, 2) << 10, 20, 30, 40);
std::cout << "原始矩阵 (uint8):\n" << src << std::endl;
// 1. 转换为 float 类型,数值不变(alpha=1, beta=0)
cv::Mat dst1;
src.convertTo(dst1, CV_32F, 1.0, 0.0);
std::cout << "转 float,数值不变:\n" << dst1 << std::endl;
// 2. 转换为 float,同时缩放到 [0,1] 区间(除以 255)
cv::Mat dst2;
src.convertTo(dst2, CV_32F, 1.0/255.0, 0.0);
std::cout << "转 float,缩放 [0,1]:\n" << dst2 << std::endl;
// 3. 转换为 int16,同时平移(加 100)
cv::Mat dst3;
src.convertTo(dst3, CV_16S, 1.0, 100.0);
std::cout << "转 int16,加 100:\n" << dst3 << std::endl;
return 0;
}

很不错!!!