当前位置: 首页 > wzjs >正文

广东住房和城乡建设厅网站王芃建筑公司排名前100

广东住房和城乡建设厅网站王芃,建筑公司排名前100,网站建设工作稳定吗,网页超链接怎么做步骤文章目录 添加噪点的案例图像像素值1、访问图像属性2、像素访问方法 at灰度图像彩色图像 3、OpenCV 的向量类型4、 图像传递方式 The cv::Mat_ 类1、作用及优点2、使用 cv::Mat_ 简化像素访问 用指针扫描图像背景算法案例原理1. 图像数据存储的基本结构2、行填充(Pa…

文章目录

    • 添加噪点的案例
    • 图像像素值
      • 1、访问图像属性
      • 2、像素访问方法 at
        • 灰度图像
        • 彩色图像
      • 3、OpenCV 的向量类型
      • 4、 图像传递方式
    • The cv::Mat_ 类
      • 1、作用及优点
      • 2、使用 cv::Mat_ 简化像素访问
    • 用指针扫描图像
      • 背景
      • 算法
      • 案例
      • 原理
        • 1. 图像数据存储的基本结构
        • 2、行填充(Padding)与有效宽度
        • 3、计算每行的像素值数量
        • 4、使用指针运算访问图像数据
      • 颜色缩减方案
        • 1、方法一:整数除法
        • 2、方法二:取模运算
        • 3、方法三:位运算
      • 参数的输入与输出
        • 1、原地处理(In-place Transformation)
        • 2. 提供灵活性的函数设计
        • 3. 灵活函数的实现
      • 高效扫描连续图像
        • 优点
        • 适用场景
      • 低级指针运算
        • 核心概念
          • 1、图像数据的起始地址
          • 2、行与列的偏移
          • 3、像素地址计算
        • 优点
        • 缺点
      • 使用迭代器扫描图像
        • 核心思想
          • 1、迭代器的声明:
          • 2、迭代器的使用:
          • 3、颜色缩减:
      • 编写高效的图像扫描循环
      • 通过邻域访问扫描图像
        • 准备工作
        • 实现方法
        • 锐化滤波
      • 执行简单图像算术
        • 图像加法
        • 图像减法
        • 乘法和除法
        • 逐通道操作
        • 重载图像操作
          • 分割图像通道
        • 重映射图像
          • 简单实现

添加噪点的案例

#include "base_function_image.h"
#include <iostream>
#include <random>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>#define IMAGE_1 "1.jpeg"
#define IMAGE_LOGO "logo.jpg"
#define IMAGE_LOGO_2 "logo_2.jpeg"void salt(cv::Mat &image, int n) 
{// 检查输入图像是否为空if (image.empty()) {std::cerr << "Error: Input image is empty!" << std::endl;return;}// C++11 随机数生成器std::default_random_engine generator(std::random_device{}());std::uniform_int_distribution<int> randomRow(0, image.rows - 1);std::uniform_int_distribution<int> randomCol(0, image.cols - 1);for (int k = 0; k < n; ++k) {// 随机生成图像坐标int i = randomCol(generator); // 列索引int j = randomRow(generator); // 行索引// 根据图像类型设置像素值if (image.type() == CV_8UC1) { // 灰度图像(单通道)image.at<uchar>(j, i) = 255; // 设置为白色} else if (image.type() == CV_8UC3) { // 彩色图像(三通道)image.at<cv::Vec3b>(j, i)[0] = 255; // B通道image.at<cv::Vec3b>(j, i)[1] = 255; // G通道image.at<cv::Vec3b>(j, i)[2] = 255; // R通道} else {std::cerr << "Error: Unsupported image type!" << std::endl;return;}}
}int main() 
{// 加载图像cv::Mat image = cv::imread(IMAGE_1);if (image.empty()) {std::cerr << "Error: Could not load the image!" << std::endl;return -1;}// 显示原始图像cv::imshow("Original Image", image);// 添加盐噪声int numSaltNoisePoints = 1000; // 噪声点数量salt(image, numSaltNoisePoints);// 显示处理后的图像cv::imshow("Image with Salt Noise", image);// 保存结果cv::imwrite("salt_noise_image.jpg", image);// 等待用户按键后退出cv::waitKey(0);return 0;
}

在这里插入图片描述

图像像素值

1、访问图像属性

在 OpenCV 中,cv::Mat 类提供了多种方法来访问图像的不同属性。其中,cols 和 rows 是两个公共成员变量,用于获取图像的列数和行数。

int numCols = image.cols; // 获取图像的列数
int numRows = image.rows; // 获取图像的行数// 如果图像大小为 640x480,则 image.cols 返回 640,image.rows 返回 480。

2、像素访问方法 at

为了访问图像中的像素,cv::Mat 提供了模板方法 at(int y, int x),其中:

  • x 是列索引(水平方向)。
  • y 是行索引(垂直方向)。
  • T 是像素的数据类型。

由于 cv::Mat 可以存储任意类型的元素,因此程序员需要显式指定返回类型。例如:

灰度图像

对于单通道灰度图像,每个像素是一个 8 位无符号整数(uchar),可以这样访问:

image.at<uchar>(j, i) = 255; // 将第 j 行、第 i 列的像素值设置为 255(白色)
彩色图像

对于三通道彩色图像,每个像素是一个包含三个 8 位无符号整数的向量(蓝色、绿色和红色)。OpenCV 定义了一个专门的类型 cv::Vec3b 来表示这种短向量。

image.at<cv::Vec3b>(j, i)[0] = 255; // 设置蓝色通道值为 255
image.at<cv::Vec3b>(j, i)[1] = 255; // 设置绿色通道值为 255
image.at<cv::Vec3b>(j, i)[2] = 255; // 设置红色通道值为 255

或者,可以直接使用 cv::Vec3b 向量赋值:

image.at<cv::Vec3b>(j, i) = cv::Vec3b(255, 255, 255); // 设置像素为白色

3、OpenCV 的向量类型

OpenCV 提供了一系列向量类型,用于表示不同长度和数据类型的向量。这些类型基于模板类 cv::Vec<T, N>,其中:

  • T 是元素类型。
  • N 是向量的长度。

常见类型
2 元素向量:cv::Vec2b(2 个字节)、cv::Vec2f(2 个浮点数)、cv::Vec2i(2 个整数)。
3 元素向量:cv::Vec3b(3 个字节,常用于 RGB 颜色)。
4 元素向量:cv::Vec4b(4 个字节,常用于 RGBA 颜色)。

命名规则
最后一个字母表示数据类型:
b:8 位无符号整数(unsigned char)。
f:单精度浮点数(float)。
s:短整型(short)。
i:整型(int)。
d:双精度浮点数(double)。

4、 图像传递方式

在 OpenCV 中,即使通过值传递图像对象,它们仍然共享相同的图像数据。这是因为 cv::Mat 内部使用引用计数机制管理数据。

以下函数通过值传递图像参数,并修改其内容:

void modifyImage(cv::Mat image) {for(int i = 300; i < 600; ++i){for(int j = 300; j < 600; ++j){image.at<uchar>(i, j) = 255; // 修改像素值}}}int main() {cv::Mat img = cv::imread(IMAGE_LOGO);modifyImage(img); // 调用函数cv::imshow("Modified Image", img); // 显示修改后的图像cv::waitKey(0);return 0;
}

在这里插入图片描述

尽管 modifyImage 函数的参数是通过值传递的,但由于 cv::Mat 的内部机制,原始图像的内容也会被修改。

The cv::Mat_ 类

1、作用及优点

在 OpenCV 中,cv::Mat 是一个通用的矩阵类,可以存储任意类型的元素。然而,使用 cv::Mat 的 at 方法访问像素时,需要显式指定模板参数(如 uchar 或 cv::Vec3b),这有时会显得繁琐。

为了简化操作,OpenCV 提供了一个模板子类 cv::Mat_,它继承自 cv::Mat。通过 cv::Mat_,可以在创建变量时指定矩阵元素的类型,从而避免每次调用 at 方法时重复指定类型。

优点

  • 减少冗余:在频繁访问像素时,cv::Mat_ 可以避免每次都指定模板参数。
  • 提高可读性:使用 operator() 的代码更短、更直观。
  • 兼容性:cv::Mat_ 是 cv::Mat 的子类,两者可以无缝转换。例如,您可以将 cv::Mat 对象直接赋值给 cv::Mat_ 对象,反之亦然。

2、使用 cv::Mat_ 简化像素访问

cv::Mat_ 提供了一个额外的操作符 operator(),可以直接访问矩阵元素。与 cv::Mat 的 at 方法相比,operator() 更加简洁,因为类型在创建 cv::Mat_ 对象时已经确定。

#include <opencv2/opencv.hpp>
#include <iostream>int main() {// 加载图像cv::Mat image = cv::imread("input.jpg", cv::IMREAD_GRAYSCALE); // 灰度图像if (image.empty()) {std::cerr << "Error: Could not load the image!" << std::endl;return -1;}// 转换为 cv::Mat_<uchar> 类型cv::Mat_<uchar> img(image);// 使用 operator() 访问像素img(50, 100) = 0; // 将第 50 行、第 100 列的像素值设置为 0(黑色)// 显示修改后的图像cv::imshow("Modified Image", img);cv::waitKey(0);return 0;
}

用指针扫描图像

由于像素数量庞大,需要高效地实现扫描。

背景

彩色图像由 3 通道像素组成(红、绿、蓝),每个通道是一个 8 位无符号整数(0-255)。因此,总颜色数为 256 × 256 × 256 种颜色。
为了简化分析,有时需要减少图像中的颜色数量。一种简单的方法是将 RGB 颜色空间划分为等大小的立方体。例如,如果每个维度的颜色数量减少为原来的 1/8,则总颜色数将变为 32 × 32 × 32 = 32768 种颜色。

算法

设 N 为颜色缩减因子:
1、对每个像素的每个通道值进行整数除法:value / N。
2、再乘以 N:(value / N) * N,得到小于或等于原值的最大 N 的倍数。
3、加上 N/2,使结果位于区间的中心位置:(value / N) * N + N/2。
重复上述步骤对每个通道(R、G、B)进行处理后,颜色总数将减少为 (256/N) × (256/N) × (256/N) 种。

案例

定义了一个用于颜色缩减的函数 colorReduce

/*
cv::Mat image:输入图像(彩色或灰度图像)。
int div = 64:每个通道的颜色缩减因子,默认值为 64。
*/
void colorReduce(cv::Mat image, int div = 64) {int nl = image.rows; // 图像的行数int nc = image.cols * image.channels(); // 每行的总元素数(列数 × 通道数)for (int j = 0; j < nl; j++) { // 遍历每一行uchar* data = image.ptr<uchar>(j); // 获取第 j 行的指针for (int i = 0; i < nc; i++) { // 遍历当前行的所有像素// 对每个像素进行处理data[i] = data[i] / div * div + div / 2;}}
}
int main() {// 加载图像cv::Mat image = cv::imread("boldt.jpg");if (image.empty()) {std::cerr << "Error: Could not load the image!" << std::endl;return -1;}// 处理图像colorReduce(image, 64);// 显示结果cv::namedWindow("Reduced Color Image", cv::WINDOW_AUTOSIZE);cv::imshow("Reduced Color Image", image);// 等待用户按键后退出cv::waitKey(0);return 0;
}

在这里插入图片描述

原理

1. 图像数据存储的基本结构

在 OpenCV 中,彩色图像的数据存储遵循以下规则:
1、每个像素由 3 个字节组成,分别对应蓝色(B)、绿色(G)和红色(R)通道。
2、图像数据按行优先存储:

  • 第一行的第一个像素对应图像左上角,其数据是 3 个字节(BGR 值)。
  • 第二个像素是第一行的第二个像素,依此类推。
    3、一个宽度为 W、高度为 H 的彩色图像需要的内存大小为:W × H × 3 字节。
2、行填充(Padding)与有效宽度

为了提高效率,OpenCV 有时会在每一行末尾填充额外的字节。这些填充字节的作用包括:

  • 对齐内存:使每行的长度对齐到特定的边界(如 8 字节对齐),以更好地利用硬件特性。
  • 性能优化:某些图像处理算法在对齐的内存上运行得更快。
    尽管有填充字节,这些额外的数据并不会显示或保存,实际图像的宽度仍然保持不变。

相关属性

  • 真实宽度:image.cols 返回图像的真实列数。
  • 有效宽度:image.step 返回每行的实际字节数(包括填充字节)。
    如果没有填充,image.step 等于 image.cols × image.elemSize()。
  • 像素元素大小:image.elemSize() 返回单个像素占用的字节数。
    例如,对于 3 通道的短整型矩阵(CV_16SC3),每个像素占用 6 字节(3 × 2 字节)。
  • 总像素数:image.total() 返回图像中像素的总数(即矩阵元素数)。
3、计算每行的像素值数量

每行的像素值数量可以通过以下公式计算:

// image.cols 是图像的列数。
// image.channels() 是每个像素的通道数(灰度图像为 1,彩色图像为 3)。
int nc = image.cols * image.channels();
4、使用指针运算访问图像数据

以下是一个典型的双层循环实现,用于遍历图像的所有像素:

for (int j = 0; j < image.rows; j++) { // 遍历每一行uchar* data = image.ptr<uchar>(j); // 获取第 j 行的指针for (int i = 0; i < nc; i++) {     // 遍历当前行的所有像素data[i] = data[i] / div * div + div / 2; // 处理每个像素}
}

如果希望进一步简化指针操作,可以在处理过程中直接移动指针。例如:

for (int j = 0; j < image.rows; j++) {uchar* data = image.ptr<uchar>(j);for (int i = 0; i < nc; i++) {*data++ = *data / div * div + div / 2; // 使用指针运算}
}
  • *data++ 表示先访问 data 指向的值,然后将指针向前移动一个字节。
  • 这种方式避免了显式的索引操作,但需要注意指针的边界。

颜色缩减方案

1、方法一:整数除法

通过整数除法将像素值映射到最近的区间中心位置:

  • data[i] / div:将像素值整除 div,得到最接近的倍数。
  • (data[i] / div) * div:恢复到该倍数。
    • div / 2:偏移到区间的中心位置。
// 假设 div = 64,像素值范围为 [0, 255]
// 将像素值分组为若干区间(如 [0, 63], [64, 127], [128, 191], [192, 255])
// 每个区间内的像素值会被映射到该区间的中心位置(如 [0, 63] 映射到 32)
data[i] = (data[i] / div) * div + div / 2;
2、方法二:取模运算

通过取模运算找到最接近的倍数,并调整到区间的中心位置:

  • data[i] % div:计算当前像素值相对于 div 的余数。
  • data[i] - data[i] % div:得到小于或等于当前像素值的最大倍数。
    • div / 2:偏移到区间的中心位置。
/* 取模运算可以快速找到像素值所属的区间
例如,当 div = 64 时,像素值 100 的处理过程如下:100 % 64 = 36,计算余数。100 - 36 = 64,得到最接近的倍数。64 + 32 = 96,偏移到区间的中心位置。
*/ 
data[i] = data[i] - data[i] % div + div / 2;
3、方法三:位运算

如果 div 是 2 的幂(即 div = pow(2, n)),可以使用位运算高效地完成颜色缩减:

  • mask = 0xFF << n:生成一个掩码,用于屏蔽最低的 n 位。
  • *data &= mask:通过按位与操作保留高阶位,丢弃低阶位。
  • *data += div >> 1:加上 div / 2,偏移到区间的中心位置。
/*
假设 div = 16,则 n = 4(因为 16 = 2^4)。
掩码 mask = 0xFF << 4 = 0xF0(十六进制表示为 11110000)。
对于像素值 100 的处理过程如下:100 & 0xF0 = 96,屏蔽低 4 位。96 + 8 = 104,偏移到区间的中心位置。
*/
uchar mask = 0xFF << n; // e.g., for div=16, mask=0xF0
*data &= mask;          // 屏蔽低 n 位
*data++ += div >> 1;    // 加上 div/2
  • 效率高:位运算是硬件级的操作,比整数除法和取模运算更快。
  • 适用场景:当 div 是 2 的幂时,位运算是最佳选择。
方法操作优点缺点
整数除法(data[i] / div) * div + div / 2简单直观,适用于任意 div运算速度较慢
取模运算data[i] - data[i] % div + div / 2计算逻辑清晰速度略优于整数除法,但仍较慢
位运算*data &= mask; *data++ += div >> 1极其高效,适合 div 为 2 的幂不适用于非 2 的幂的 div
  • 实时处理:位运算因其高效性,特别适合需要高性能的应用场景(如视频处理)。
  • 通用性:整数除法和取模运算适用于任意缩减因子,灵活性更高。
  • 内存优化:位运算减少了不必要的计算开销,适合嵌入式设备或资源受限的环境。

参数的输入与输出

1、原地处理(In-place Transformation)

在颜色缩减的例子中,我们直接对输入图像进行修改,这被称为原地处理。
然而,在某些应用场景中,用户可能希望保留原始图像不变。此时,用户需要在调用函数前手动复制一份图像。例如:

// 读取图像
cv::Mat image = cv::imread("boldt.jpg");// 克隆图像
cv::Mat imageClone = image.clone();// 对克隆图像进行处理,保持原始图像不变
colorReduce(imageClone);// 显示处理后的图像
cv::namedWindow("Image Result");
cv::imshow("Image Result", imageClone);

通过调用 clone() 方法,可以轻松创建一个图像的深拷贝(Deep Copy),从而避免修改原始图像

2. 提供灵活性的函数设计

为了避免用户手动复制图像,我们可以设计一个更灵活的函数,允许用户选择是否进行原地处理。该函数如下:

void colorReduce(const cv::Mat &image, // 输入图像cv::Mat &result,      // 输出图像int div = 64);        // 颜色缩减因子  默认值为 64
3. 灵活函数的实现

OpenCV 提供了一个便捷的方法 create,用于确保输出矩阵具有与输入矩阵相同的大小和类型。如果输出矩阵已经满足要求,则不会重新分配内存。

void colorReduce(const cv::Mat &image, cv::Mat &result, int div = 64) {// 确保输出图像具有正确的大小和类型result.create(image.rows, image.cols, image.type());int nl = image.rows; // 图像的行数int nc = image.cols * image.channels(); // 每行的总元素数for (int j = 0; j < nl; j++) { // 遍历每一行const uchar* data_in = image.ptr<uchar>(j); // 获取输入图像第 j 行的指针uchar* data_out = result.ptr<uchar>(j);     // 获取输出图像第 j 行的指针for (int i = 0; i < nc; i++) { // 遍历每个像素// 颜色缩减处理data_out[i] = data_in[i] / div * div + div / 2;}}
}

高效扫描连续图像

在 OpenCV 中,如果图像没有填充额外的字节(即每行末尾没有多余像素),它实际上可以被视为一个一维数组。这种特性可以通过 isContinuous 方法检测,或者通过检查 image.step == image.cols * image.elemSize() 来验证。

void colorReduce(cv::Mat image, int div = 64) {int nl = image.rows; // 行数int nc = image.cols * image.channels(); // 每行总元素数// 检查图像是否连续// 如果图像连续,则将其视为一个长的一维数组,减少外层循环次数。// if (image.isContinuous()) {nc = nc * nl; // 总像素数nl = 1;       // 将图像视为一维数组// image.reshape(1, 1); // 调整为单行矩阵 (另一方案)}// 计算掩码和 div/2// 使用掩码 mask 和右移操作快速完成颜色缩减int n = static_cast<int>(log(static_cast<double>(div)) / log(2.0) + 0.5);uchar mask = 0xFF << n; // 掩码uchar div2 = div >> 1;  // div/2// 扫描图像for (int j = 0; j < nl; j++) {uchar* data = image.ptr<uchar>(j); // 获取第 j 行指针for (int i = 0; i < nc; i++) {*data &= mask;       // 屏蔽低 n 位*data++ += div2;     // 偏移到区间中心}}
}
优点
  • 提高扫描效率:避免不必要的外层循环。
  • 灵活性强:支持连续性和非连续性图像
适用场景
  • 大规模图像处理任务。
  • 需要高效内存访问的应用场景。

低级指针运算

在 OpenCV 的 cv::Mat 类中,图像数据存储在一个连续的内存块中,数据类型通常为 unsigned char。通过直接操作指针,可以高效地访问和处理图像数据。

核心概念
1、图像数据的起始地址
  • 使用 image.data 获取图像数据块的起始地址。
  • image.data 返回一个指向图像第一个像素的 unsigned char* 指针。
2、行与列的偏移
  • 图像的每一行可能包含填充字节,因此每行的实际字节数由 image.step 表示。
  • 列的偏移量由每个像素的大小(image.elemSize())决定
3、像素地址计算

任意像素 (j, i) 的地址可以通过以下公式计算

/*
j 是行号。
i 是列号。
image.step 是每行的总字节数(包括填充字节)。
image.elemSize() 是每个像素的字节大小
*/
data = image.data + j * image.step + i * image.elemSize();
void colorReduce(cv::Mat image, int div = 64) {uchar* data = image.data; // 获取图像数据的起始地址for (int j = 0; j < image.rows; j++) { 	// 遍历每一行uchar* row = image.ptr<uchar>(j); // 获取第 j 行的指针for (int i = 0; i < image.cols * image.channels(); i++) { // 遍历每个像素row[i] = row[i] / div * div + div / 2; // 处理像素}}
}
优点

低级指针运算提供了对图像数据的完全控制,适合性能要求极高的场景。

缺点
  • 容易出错,尤其是在处理多通道图像或填充字节时。
  • 可读性差,代码维护困难。

使用迭代器扫描图像

cv::Mat 提供了迭代器类(cv::MatIterator_),可以方便地遍历图像的每个像素。迭代器隐藏了底层实现细节,使代码更简洁、安全。

核心思想
1、迭代器的声明:
  • 使用 cv::Mat_cv::Vec3b::iterator 声明迭代器。
  • cv::Vec3b 表示彩色图像的每个像素(包含 BGR 三个通道)。
2、迭代器的使用:
  • 使用 image.begincv::Vec3b() 和 image.endcv::Vec3b() 获取起始和结束迭代器。
  • 遍历图像时,通过解引用操作符 *it 访问当前像素。
3、颜色缩减:
  • 对每个像素的 BGR 通道值进行位运算和偏移操作。
void colorReduce(cv::Mat image, int div = 64) 
{// 确保 div 是 2 的幂int n = static_cast<int>(log(static_cast<double>(div)) / log(2.0) + 0.5);uchar mask = 0xFF << n; // 掩码uchar div2 = div >> 1;  // div/2// 获取迭代器// cv::Vec3b 表示每个像素的 BGR 通道值cv::Mat_<cv::Vec3b>::iterator it = image.begin<cv::Vec3b>();cv::Mat_<cv::Vec3b>::iterator itend = image.end<cv::Vec3b>();// 遍历所有像素for (; it != itend; ++it) {// 使用 (*it)[i] 访问第 i 个通道(B=0, G=1, R=2)(*it)[0] &= mask; (*it)[0] += div2; // 蓝色通道(*it)[1] &= mask; (*it)[1] += div2; // 绿色通道(*it)[2] &= mask; (*it)[2] += div2; // 红色通道}
}

编写高效的图像扫描循环

OpenCV 提供了一个方便的函数 cv::getTickCount()。该函数返回自计算机启动以来的时钟周期数。通过在代码执行前后分别获取时钟周期数,可以计算出代码的执行时间。
要将执行时间转换为秒,可以使用另一个方法 cv::getTickFrequency(),它返回每秒的时钟周期数(假设 CPU 频率固定,尽管现代处理器不一定如此)。

const int64 start = cv::getTickCount(); // 获取起始时钟周期
colorReduce(image);                     // 调用函数
// 计算执行时间(秒)
double duration = (cv::getTickCount() - start) / cv::getTickFrequency();

通过邻域访问扫描图像

在图像处理中,经常需要根据像素的邻域值计算每个像素的新值。当邻域包含前一行和后一行的像素时,就需要同时扫描图像的多行。本节将展示如何实现这一操作。

准备工作

图像锐化的原理是:从图像中减去拉普拉斯算子的结果,可以增强图像边缘,使图像更清晰。
锐化后的像素值计算公式如下:

// left 是当前像素左侧的像素
// up 是上一行对应的像素
sharpened_pixel = 5 * current - left - right - up - down;
实现方法

由于需要访问邻域像素,无法在原图上直接进行处理,必须提供一个输出图像。
使用三个指针分别指向当前行、上一行和下一行。此外,由于每个像素的计算需要邻域信息,无法处理图像的第一行、最后一行以及第一列和最后一列的像素。循环代码如下:

void sharpen(const cv::Mat &image, cv::Mat &result) 
{// 如果需要,分配输出图像result.create(image.size(), image.type());int nchannels = image.channels(); // 获取通道数// 遍历所有行(除第一行和最后一行)for (int j = 1; j < image.rows - 1; j++) {const uchar* previous = image.ptr<const uchar>(j - 1); // 上一行const uchar* current = image.ptr<const uchar>(j);      // 当前行const uchar* next = image.ptr<const uchar>(j + 1);     // 下一行uchar* output = result.ptr<uchar>(j);                  // 输出行// 遍历所有列(除第一列和最后一列)for (int i = nchannels; i < (image.cols - 1) * nchannels; i++) {// 应用锐化算子*output++ = cv::saturate_cast<uchar>(5 * current[i] - current[i - nchannels] -current[i + nchannels] - previous[i] - next[i]);}}// 将未处理的像素设置为 0// 无法处理第一行、最后一行、第一列和最后一列的像素,因此将这些像素设置为 0result.row(0).setTo(cv::Scalar(0));               // 第一行result.row(result.rows - 1).setTo(cv::Scalar(0)); // 最后一行result.col(0).setTo(cv::Scalar(0));               // 第一列result.col(result.cols - 1).setTo(cv::Scalar(0)); // 最后一列
}
锐化滤波
0  -1  0
-1  5 -1
0  -1  0

为了满足锐化滤波器的要求,当前像素的四个水平和垂直邻居被乘以-1,而当前像素本身则乘以5。
将核应用于图像不仅是方便的表示方法,它是信号处理中卷积概念的基础。
OpenCV定义了一个执行此任务的特殊函数:cv::filter2D 函数。只需定义一个核(以矩阵形式),然后用图像和核调用该函数,它返回滤波后的图像。利用这个函数,重新定义我们的锐化函数如下:

void sharpen2D(const cv::Mat &image, cv::Mat &result) {// 构造核(所有元素初始化为0)cv::Mat kernel(3, 3, CV_32F, cv::Scalar(0));// 赋值给核kernel.at<float>(1, 1) = 5.0;kernel.at<float>(0, 1) = -1.0;kernel.at<float>(2, 1) = -1.0;kernel.at<float>(1, 0) = -1.0;kernel.at<float>(1, 2) = -1.0;// 应用滤波cv::filter2D(image, result, image.depth(), kernel);
}

此实现产生的结果与之前的实现完全相同(且效率相同)。如果输入的是彩色图像,则相同的核会被应用到所有三个通道。 当使用较大的核时,使用
filter2D 函数特别有利,因为它在这种情况下会使用更高效的算法。

执行简单图像算术

由于图像是规则的矩阵,因此可以对它们进行加法、减法、乘法或除法运算。

图像加法

可以通过cv::add函数实现,也可以直接通过矩阵操作如image1 + image2来完成。
当像素值相加后超过255(对于8位无符号图像),需要使用饱和处理,即超过255的值会被截断为255。

cv::Mat result;
cv::add(image1, image2, result); // 使用add函数
// 或者
result = image1 + image2; // 直接相加

指定权重作为标量乘数参与运算

// c[i] = k1 * a[i] + k2 * b[i] + k3;
cv::addWeighted(imageA, k1, imageB, k2, k3, resultC);

指定一个掩码(mask)

// if (mask[i]) c[i] = a[i] + b[i];
cv::add(imageA, imageB, resultC, mask);

如果应用了掩码,则操作仅对掩码值非零的像素执行(掩码必须是单通道的)。可以查看 cv::subtract、cv::absdiff、cv::multiply 和 cv::divide 等函数的不同形式。

OpenCV还提供了按位操作符(对像素的二进制表示逐位操作):cv::bitwise_and、cv::bitwise_or、cv::bitwise_xor和 cv::bitwise_not。cv::min 和 cv::max 操作也非常有用,它们分别计算元素级别的最小值和最大值。

在所有情况下,都会使用 cv::saturate_cast 函数,以确保结果保持在定义的像素值范围内(即避免溢出或下溢)。

图像必须具有相同的大小和类型(如果输出图像的大小与输入不匹配,则会重新分配)。由于操作是逐元素进行的,因此可以将其中一个输入图像用作输出。

还有一些接受单张图像作为输入的操作符可用,例如:

  • cv::sqrt(平方根)
  • cv::pow(幂运算)
  • cv::abs(绝对值)
  • cv::cuberoot(立方根)
  • cv::exp(指数运算)
  • cv::log(对数运算)
    在这里插入图片描述在这里插入图片描述
    在这里插入图片描述
图像减法

可以通过cv::subtract函数或直接减法操作完成。这有助于检测图像之间的差异。

cv::Mat result;
cv::subtract(image1, image2, result); // 使用subtract函数
// 或者
result = image1 - image2; // 直接相减
乘法和除法

图像乘法和除法也能以类似的方式完成,分别使用cv::multiply和cv::divide函数,或者直接使用*和/操作符。

cv::Mat result;
cv::multiply(image1, image2, result); // 使用multiply函数
// 或者
result = image1 * image2; // 直接相乘cv::divide(image1, image2, result); // 使用divide函数
// 或者
result = image1 / image2; // 直接相除

在图像融合时可能需要用到加法操作;在比较两个相似图像的不同之处时,则可能用到减法操作。同时,考虑到数值溢出或下溢的问题,合理利用OpenCV提供的函数(如cv::addWeighted用于带权重的加法)可以帮助更有效地处理这些问题

逐通道操作
std::vector<cv::Mat> channels;
cv::split(image, channels); // 分离通道
channels[0] = channels[0] * 2.0; // 对第一个通道进行操作
cv::merge(channels, image); // 合并通道回原图像
重载图像操作

大多数算术函数都有对应的运算符重载。意味着可直接使用C++的运算符来代替调用特定的OpenCV函数,使代码更加紧凑和易读。例如,cv::addWeighted函数可以这样写:

result = 0.7 * image1 + 0.9 * image2;

许多C++运算符都被重载了,包括按位运算符&, |, ^, 和 ~; 最小值、最大值和绝对值函数;以及比较运算符<, <=, ==, !=, >, 和 >=(返回8位二进制图像)。你还可以找到矩阵乘法m1 * m2(其中m1和m2都是cv::Mat实例),矩阵求逆m1.inv(),转置m1.t(),行列式m1.determinant(),向量范数v1.norm(),叉积v1.cross(v2),点积v1.dot(v2)等。当适用时,相应的复合赋值运算符也被定义了(如+=)。

image = (image & cv::Scalar(mask, mask, mask)) + cv::Scalar(div / 2, div / 2, div / 2);

使用cv::Scalar是因为我们处理的是彩色图像。利用这些图像运算符可以使代码变得非常简单,极大地提高了编程效率,因此在多数情况下都应考虑使用它们。

分割图像通道

有时可能希望独立地处理图像的不同通道。
例如,可能只想对图像的一个通道执行某些操作。虽然可以在扫描图像像素的循环中完成这一任务,但也可以使用cv::split函数将一个彩色图像的三个通道复制到三个独立的cv::Mat实例中。
假设想要仅向蓝色通道添加另一张图像,可以按照以下步骤操作:

// 创建包含3个图像的vector
std::vector<cv::Mat> planes;// 将一个3通道图像拆分为3个单通道图像
cv::split(image1, planes);// 向蓝色通道添加另一张图像
planes[0] += image2;// 将3个单通道图像合并为一个3通道图像
// cv::merge函数执行相反的操作,即从三个单通道图像创建一个彩色图像
cv::merge(planes, result);
重映射图像

通过移动图像中的像素来改变其外观。
这个过程中像素的值不会改变,而是每个像素的位置被重新映射到一个新的位置。这种方法可用于创建图像的特殊效果或纠正由镜头引起的图像失真。

简单实现

为了使用OpenCV的remap函数,首先需要定义重映射过程中要使用的映射图,然后将此映射应用于输入图像。
显然,定义映射的方式决定了最终产生的效果。定义了一个变换函数,该函数将在图像上创建波动效果:

// 通过创建波浪效果进行图像重映射
void wave(const cv::Mat &image, cv::Mat &result) {// 映射函数cv::Mat srcX(image.rows, image.cols, CV_32F); // x映射cv::Mat srcY(image.rows, image.cols, CV_32F); // y映射// 创建映射for (int i = 0; i < image.rows; i++) {for (int j = 0; j < image.cols; j++) {// 像素(i,j)的新位置srcX.at<float>(i, j) = j; // 保持在同一列// 原本在第i行的像素现在跟随正弦波移动srcY.at<float>(i, j) = i + 5 * sin(j / 10.0);}}// 应用映射cv::remap(image,   // 源图像result,  // 目标图像srcX,    // x方向映射srcY,    // y方向映射cv::INTER_LINEAR // 插值方法);
}

原始位于(i, j)的像素点,在重映射后,其x坐标保持不变(即仍然在原来的列),而y坐标则根据一个正弦函数变化,这样就会产生一种波动的效果。
通过调整正弦函数的参数,可以控制波动的幅度和频率。
在这里插入图片描述

cv::remap函数接受源图像、目标图像以及两个映射矩阵(分别对应于x和y方向上的映射)作为输入,并允许指定插值方法以确定如何计算新位置处的像素值。在例子中,使用了线性插值(cv::INTER_LINEAR)来平滑过渡像素值的变化。


文章转载自:

http://rWfsm13L.kmwsz.cn
http://OjmjfD7f.kmwsz.cn
http://RHaWFwRY.kmwsz.cn
http://tAEuE6ro.kmwsz.cn
http://ltzFo1Wb.kmwsz.cn
http://lNDghutV.kmwsz.cn
http://f8ZyBPh2.kmwsz.cn
http://CxEOCIN0.kmwsz.cn
http://QHzUhJ9W.kmwsz.cn
http://bgDJgsnX.kmwsz.cn
http://IujAgA42.kmwsz.cn
http://QmbXMUKi.kmwsz.cn
http://MdwiTNgb.kmwsz.cn
http://BqS7NkLy.kmwsz.cn
http://5jnxg8xC.kmwsz.cn
http://VH7oJzwm.kmwsz.cn
http://n92kJS5D.kmwsz.cn
http://blwe4gnq.kmwsz.cn
http://HO0QzmG3.kmwsz.cn
http://2EGY0aIj.kmwsz.cn
http://PmF9moLz.kmwsz.cn
http://a5GAC2Jr.kmwsz.cn
http://U94hC9zO.kmwsz.cn
http://QFy7bIhW.kmwsz.cn
http://28F7j90a.kmwsz.cn
http://poxiECCM.kmwsz.cn
http://GRE9modQ.kmwsz.cn
http://pfpQUmh0.kmwsz.cn
http://jWosHtvA.kmwsz.cn
http://Ku9kyBad.kmwsz.cn
http://www.dtcms.com/wzjs/748619.html

相关文章:

  • 推广 高端网站设计四川住房和建设厅官网
  • 网站租用服务器多少钱宁波论坛天一楼市
  • 大型门户网站最担心的威胁是产品做网站推广
  • 网站建设公众象山县住房和城乡建设局网站
  • 企业营销网站建设步骤wordpress微博主题
  • f型网站如何用wordpress站群
  • 海口网站建设王道下拉棒西宁手机网站微站建设
  • 郑州 制造 网站郑州妇科医院排行榜前十名
  • 烟台有哪些网站建站推广公司大前端 wordpress
  • 网络营销的优势有哪些seo对网络推广的作用是什么?
  • 泰州做网站淘宝企业网站数据库
  • 国外创意网站设计欣赏最近一周热点回顾
  • 医院营销型网站建设重庆孝爱之家网站建设
  • 书画院网站模板昆山建设工程招标网站
  • 制作一个景点介绍的网站html郑州市招投标信息网
  • 南宁网站推广排名公司网站建设推广方案
  • 江西个人网站备案做论坛西安推荐企业网站制作平台
  • 太仓网站制作书生网站后台不能粘贴
  • 网站建立需要什么条件上海已经开始二次感染
  • 优质做网站公司陕西渭南富平建设局网站
  • 福州网站制作维护渝北网站制作
  • 做pc端网站哪家好查询网址域名ip地址
  • 免费下载ppt模板网站有哪些做淘宝的人就跟做网站一样
  • 哪个网站做任务钱给得多重庆购物网站建设
  • 家具做网站北京建设网站兼职普工
  • 北京app网站建设做网站服务销售
  • dede做电影网站wordpress附件大小
  • 长沙做个网站多少钱小程序开发平台哪家比较被大家认可
  • 郑州免费网站制作郑州发布会最新消息
  • 家装设计效果图专业网站长春网站制作wang