OpenCV 图像基本操作
OpenCV快速通关
第一章:OpenCV 简介与环境搭建
第二章:OpenCV 图像基本操作
OpenCV 图像基本操作
- OpenCV快速通关
- 第二章:OpenCV 图像基本操作
- 一、相关结构体与函数介绍
- (一)cv::Mat 结构体
- (二)cv::imread 函数
- (三)cv::namedWindow 函数
- (四)cv::imshow 函数
- (五)cv::waitKey 函数
- (六)cv::destroyAllWindows 函数
- (七)cv::add 函数
- (八)cv::subtract 函数
- (九)cv::multiply 函数
- (十)cv::divide 函数
- (十一)cv::remap 函数
- 二、访问像素值
- (一)单通道图像像素值访问
- (二)多通道图像像素访问
- 二、用指针扫描图像
- (一)单通道图像指针扫描
- (二)多通道图像指针扫描
- 三、用迭代器扫描图像
- (一)单通道图像迭代器扫描
- (二)多通道图像迭代器扫描
- 四、编写高效的图像扫描循环
- (一)避免循环内无关计算
- (二)多线程并行处理
- 五、扫描图像并访问相邻像素
- 六、实现简单的图像运算
- (一)图像加法
- (二)图像减法
- (三)图像乘法
- (四)图像除法
- 七、图像重映射
- 总结
第二章:OpenCV 图像基本操作
一、相关结构体与函数介绍
(一)cv::Mat 结构体
cv::Mat
是 OpenCV 中用于表示图像和矩阵的核心数据结构。
- 成员变量:
rows
:表示矩阵的行数,对于图像来说就是图像的高度。例如,对于一个 480 行的图像,rows
的值为 480。cols
:表示矩阵的列数,即图像的宽度。若图像宽度为 640 像素,则cols
为 640。channels
:表示图像的通道数。对于灰度图像,其值为 1;对于常见的 BGR 彩色图像,值为 3。例如,在处理彩色图像时,可以通过channels
的值来确定如何访问每个像素的不同颜色通道。data
:指向存储图像数据的内存地址的指针。通过这个指针,可以直接访问图像的像素值。例如,在一些对性能要求极高且确定不会越界的情况下,可以利用data
指针快速遍历图像像素,但需要开发者自行谨慎处理指针运算,确保不出现越界等错误。
- 构造函数:
cv::Mat::Mat()
:默认构造函数,创建一个空的矩阵,可以后续通过create
函数等方法分配内存并初始化。例如,可以先创建一个空的cv::Mat
对象,然后根据实际需求确定其大小和类型后再进行初始化。cv::Mat::Mat(int rows, int cols, int type)
:创建一个指定行数、列数和数据类型的矩阵。例如cv::Mat(3, 3, CV_8UC1)
创建一个 3x3 的单通道 8 位无符号字符类型(即灰度图像)的矩阵。这里CV_8UC1
表示 8 位无符号字符类型,单通道。如果要创建一个 3 通道的 8 位无符号字符类型矩阵,可以使用CV_8UC3
。cv::Mat::Mat(cv::Size size, int type)
:使用cv::Size
结构指定矩阵的大小(cv::Size(width, height)
)和数据类型来创建矩阵。例如cv::Mat(cv::Size(100, 200), CV_16SC3)
创建一个宽 100 像素,高 200 像素的 3 通道 16 位有符号整数类型的矩阵。cv::Mat::Mat(int rows, int cols, int type, const void* data)
:使用指定的已存在数据指针来创建矩阵,可用于将外部数据包装成cv::Mat
结构进行处理。比如,如果有一个已经在内存中分配好的图像数据缓冲区,可以使用这个构造函数将其转换为cv::Mat
对象以便后续使用 OpenCV 的函数进行处理。
(二)cv::imread 函数
- 函数原型:
cv::Mat cv::imread(const std::string& filename, int flags = cv::IMREAD_COLOR)
- 参数说明:
filename
:要读取的图像文件的路径和文件名。例如,如果图像文件名为test.jpg
且位于当前项目目录下,可以传入"test.jpg"
作为参数。flags
:指定图像读取的方式,常用的取值有:cv::IMREAD_COLOR
(默认值):以彩色图像方式读取,对于彩色图像,会忽略图像的透明度通道(如果有的话),将图像解码为三通道的 BGR 格式。这是最常用的读取彩色图像的方式,适用于大多数不需要处理透明度信息的场景。cv::IMREAD_GRAYSCALE
:以灰度图像方式读取,将图像转换为单通道灰度图像。在一些只关注图像亮度信息,不需要颜色信息的应用中,如某些图像边缘检测算法的预处理阶段,使用该标志可以减少数据量并简化后续处理。cv::IMREAD_UNCHANGED
:按图像的原始格式读取,包括图像的颜色通道、透明度通道等信息。如果图像具有透明度通道或者是一些特殊格式的图像,使用该标志可以完整地读取图像数据。
- 返回值:
- 如果图像读取成功,返回一个
cv::Mat
对象,表示读取到的图像数据。如果读取失败(例如文件不存在、文件格式不支持等原因),则返回一个空的cv::Mat
对象(可以通过mat.empty()
函数判断)。例如,在读取图像后,可以使用if (img.empty())
来检查图像是否成功读取,如果为空,则可以输出错误信息并进行相应的错误处理。
- 如果图像读取成功,返回一个
(三)cv::namedWindow 函数
- 函数原型:
void cv::namedWindow(const std::string& winname, int flags = cv::WINDOW_AUTOSIZE)
- 参数说明:
winname
:窗口的名称,用于标识创建的窗口。例如,可以传入"My Image"
作为窗口名称,后续在显示图像等操作时可以通过这个名称来引用该窗口。flags
:指定窗口的属性,常用的取值有:cv::WINDOW_AUTOSIZE
(默认值):窗口大小会自动根据图像大小调整,用户不能手动改变窗口大小。这种模式适用于只需要展示图像原始大小,不需要用户交互调整窗口的情况。cv::WINDOW_NORMAL
:窗口大小可以由用户手动调整。在一些需要用户仔细观察图像细节,可能需要放大或缩小窗口的应用场景中,如图像编辑软件中的图像查看功能,可以使用该标志创建可调整大小的窗口。
- 功能:创建一个用于显示图像的窗口。在使用
cv::imshow
函数显示图像之前,需要先创建一个窗口来承载图像。
(四)cv::imshow 函数
- 函数原型:
void cv::imshow(const std::string& winname, InputArray mat)
- 参数说明:
winname
:要显示图像的窗口名称,该名称必须与之前使用cv::namedWindow
创建的窗口名称一致。例如,如果之前创建了名为"My Image"
的窗口,这里就需要传入"My Image"
。mat
:要显示的cv::Mat
类型的图像数据。例如,可以将读取到的图像cv::Mat
对象传入该函数,以在指定窗口中显示图像。
- 功能:在指定的窗口中显示图像。它是将图像数据可视化的关键函数,通过将图像数据与窗口关联起来,实现图像的展示。
(五)cv::waitKey 函数
- 函数原型:
int cv::waitKey(int delay = 0)
- 参数说明:
delay
:等待按键的时间(以毫秒为单位)。如果delay
为 0,则表示无限等待,直到用户按下任意键;如果delay
为一个正整数,例如 1000,则表示等待 1000 毫秒(即 1 秒),如果在这段时间内用户按下了键,则函数立即返回,否则在等待时间结束后返回。
- 返回值:返回用户按下的键的 ASCII 码值。例如,如果用户按下了空格键,可能会返回 32(空格的 ASCII 码)。可以根据返回值来判断用户的操作,从而进行相应的后续处理,如根据不同的按键来切换显示不同的图像或者执行不同的图像处理操作。
(六)cv::destroyAllWindows 函数
- 功能:销毁所有由 OpenCV 创建的窗口。在程序结束或者不再需要显示图像时,可以调用该函数来释放窗口资源,关闭所有打开的图像显示窗口。例如,在一个图像处理程序中,当完成所有图像的处理和展示后,可以调用该函数来清理界面,避免窗口资源的浪费。
(七)cv::add 函数
- 函数原型:
void cv::add(InputArray src1, InputArray src2, OutputArray dst, InputArray mask = noArray(), int dtype = -1)
- 参数说明:
src1
和src2
:要相加的两个图像(或矩阵)。它们必须具有相同的大小和类型。例如,可以是两个cv::Mat
类型的图像对象,分别表示要进行加法运算的两个图像数据。dst
:输出结果图像,用于存储加法运算的结果。其大小和类型与src1
和src2
相同(如果dtype
参数未指定特殊类型)。mask
:可选的掩码图像,用于指定哪些像素需要进行加法运算(如果不指定,则对所有像素进行运算)。掩码图像是一个与src1
等大小的单通道图像,其中非零像素对应的位置才会进行加法运算,零像素对应的位置则保持目标图像dst
原来的值不变。dtype
:输出图像的数据类型,如果为 -1,则根据输入图像自动确定。例如,如果src1
和src2
都是 8 位无符号整数类型的图像,且dtype
为 -1,则dst
也将是 8 位无符号整数类型。可以通过指定dtype
来改变输出图像的数据类型,如将结果转换为 16 位整数类型等,以满足不同的计算需求。
- 功能:实现图像加法运算,将
src1
和src2
对应像素的值相加,并将结果存储在dst
中。该函数会进行饱和运算,即当相加结果超过数据类型的最大值时,会将结果截断为最大值。例如,对于 8 位无符号整数类型,相加结果超过 255 时,结果将被设置为 255。
(八)cv::subtract 函数
- 函数原型:
void cv::subtract(InputArray src1, InputArray src2, OutputArray dst, InputArray mask = noArray(), int dtype = -1)
- 参数说明:
- 与
cv::add
函数类似,src1
和src2
是要相减的两个图像(或矩阵),dst
是输出结果图像,mask
是可选的掩码图像,dtype
是输出图像的数据类型(默认 -1,自动确定)。
- 与
- 功能:实现图像减法运算,将
src1
中每个像素的值减去src2
对应像素的值,并将结果存储在dst
中。同样会进行饱和运算,当减法结果小于数据类型的最小值时,会被截断为最小值。例如,对于 8 位无符号整数类型,相减结果小于 0 时,结果将被设置为 0。
(九)cv::multiply 函数
- 函数原型:
void cv::multiply(InputArray src1, InputArray src2, OutputArray dst, double scale = 1, int dtype = -1)
- 参数说明:
src1
和src2
:要相乘的两个图像(或矩阵)。dst
:输出结果图像。scale
:是一个可选的缩放因子,用于在乘法运算后对结果进行缩放。例如,如果scale
为 0.5,则乘法运算后的结果会缩小一半。dtype
:输出图像的数据类型(默认 -1,自动确定)。
- 功能:实现图像乘法运算,将
src1
和src2
对应像素的值相乘,并根据scale
因子进行缩放后存储在dst
中。可用于调整图像的亮度、对比度等,如通过创建一个与图像大小相同且元素值合适的系数矩阵与图像相乘,来改变图像的整体亮度或对比度效果。
(十)cv::divide 函数
- 函数原型:
void cv::divide(InputArray src1, InputArray src2, OutputArray dst, double scale = 1, int dtype = -1)
- 参数说明:
src1
和src2
:被除数和除数图像(或矩阵)。需要注意分母src2
不能为 0,否则会导致错误结果或异常。dst
:输出结果图像。scale
:可选的缩放因子。dtype
:输出图像的数据类型。
- 功能:实现图像除法运算,将
src1
中每个像素的值除以src2
对应像素的值,再根据scale
因子进行缩放后存储在dst
中。在一些特定的图像处理任务中,如对图像进行归一化处理或者根据某种比例关系调整图像像素值时可能会用到。
(十一)cv::remap 函数
- 函数原型:
void cv::remap(InputArray src, OutputArray dst, InputArray map1, InputArray map2, int interpolation, int borderMode = BORDER_CONSTANT, const Scalar& borderValue = Scalar())
- 参数说明:
src
:源图像,即要进行重映射的原始图像,是一个cv::Mat
对象。dst
:目标图像,用于存储重映射后的图像结果,其大小和类型与src
相关,由重映射过程确定。map1
和map2
:分别指定源图像中每个像素在目标图像中的新的 x 坐标和 y 坐标的映射矩阵,它们必须是单通道的cv::Mat
对象,且大小与源图像相同。例如,在进行图像旋转、缩放等几何变换时,需要根据变换公式计算出每个像素在目标图像中的新坐标,并存储在这两个映射矩阵中。interpolation
:插值方法,用于确定当映射后的坐标不是整数时如何计算像素值,常见的插值方法有:cv::INTER_LINEAR
(双线性插值):通过对相邻像素值进行线性插值来计算非整数坐标处的像素值,在图像缩放、旋转等操作中能提供较好的视觉效果,是一种常用的插值方法。cv::INTER_NEAREST
(最近邻插值):直接取离非整数坐标最近的像素值作为结果,计算速度相对较快,但可能会导致图像边缘出现锯齿状,在对图像质量要求不高或者计算资源有限的情况下可以使用。
borderMode
:边界处理模式,用于指定当映射后的坐标超出源图像范围时如何处理边界像素,常见的取值有:BORDER_CONSTANT
(默认值):用指定的常数填充边界像素,borderValue
参数用于指定这个常数。例如,可以将边界像素设置为黑色(Scalar(0, 0, 0)
)或白色(Scalar(255, 255, 255)
)等。BORDER_REPLICATE
:复制边界像素的值来填充超出范围的区域。
borderValue
:当borderMode
为BORDER_CONSTANT
时,用于指定边界填充的值。
- 功能:根据给定的映射矩阵
map1
和map2
对源图像src
进行重映射,得到目标图像dst
,常用于图像矫正、几何变换等操作。例如,可以通过计算特定的映射矩阵来实现图像的翻转、旋转、透视变换等效果。
二、访问像素值
在 OpenCV 中,cv::Mat
是用于存储图像数据的核心数据结构。访问图像像素值是图像处理的基础操作之一,其方式取决于图像的数据类型和通道数。
(一)单通道图像像素值访问
对于单通道图像(如灰度图像),可以通过 at
方法来访问像素值。例如,对于一个名为 gray_img
的单通道图像,其像素值访问方式如下:
#include <opencv2\core.hpp>
#include <opencv2\highgui.hpp>
#include <iostream>int main(int argc, char** argv[])
{// 读取灰度图像cv::Mat gray_img = cv::imread("E:/pro/cv_code/res/test_img.png", cv::IMREAD_GRAYSCALE);// 检查图像是否成功读取if (gray_img.empty()) {std::cerr << "read gray_img failed!" << std::endl;return -1;}// 遍历图像的每个像素并反转灰度值for (int i = 0; i < gray_img.rows; i++) {for (int j = 0; j < gray_img.cols; j++) {// 使用 at 方法访问像素值,数据类型为 uchar (单通道 8 位无符号整数)uchar pixel_value = gray_img.at<uchar>(i, j);// 反转灰度值gray_img.at<uchar>(i, j) = 255 - pixel_value;}}// 保存反转后的图像"E:/pro/cv_code/res/inverted_gray_img.png"cv::imwrite("E:/pro/cv_code/res/inverted_gray_img.png", gray_img);return 0;
}
在上述代码中,首先使用 cv::imread
函数以灰度模式读取图像,并通过 empty
方法检查图像是否成功读取。然后,通过两层嵌套循环遍历图像的每一个像素,使用 at
方法获取像素值,并对其进行反转,最后保存。
这里的 at
方法是 cv::Mat
类的模板成员函数,它提供了一种安全的方式来访问矩阵元素(包括图像像素)。其模板参数指定了要访问的元素的数据类型,对于单通道图像,通常为 uchar
。at
方法会进行边界检查,如果访问的坐标超出了图像的范围,会抛出异常。这有助于在开发过程中及时发现错误,但在一些对性能要求极高且确定不会越界的情况下,可以考虑使用其他更高效但不进行边界检查的方法。
(二)多通道图像像素访问
对于多通道图像(如常见的 BGR 彩色图像),at
方法的使用略有不同。例如,对于一个名为 bgr_img
的 BGR 彩色图像:
#include <opencv2\core.hpp>
#include <opencv2\highgui.hpp>
#include <iostream>int main(int argc, char** argv[]) {// 读取彩色图像cv::Mat bgr_img = cv::imread("E:/pro/cv_code/res/test_img.png");// 检查图像是否成功读取if (bgr_img.empty()) {std::cerr << "read bgr_img failed!" << std::endl;return -1;}// 遍历图像的每个像素并将蓝色通道设为0for (int i = 0; i < bgr_img.rows; i++) {for (int j = 0; j < bgr_img.cols; j++) {// 访问 B 通道像素值uchar blue_value = bgr_img.at<cv::Vec3b>(i, j)[0];// 访问 G 通道像素值uchar green_value = bgr_img.at<cv::Vec3b>(i, j)[1];// 访问 R 通道像素值uchar red_value = bgr_img.at<cv::Vec3b>(i, j)[2];// 蓝色通道值设为0bgr_img.at<cv::Vec3b>(i, j)[0] = 0;}}// 保存修改后的图像cv::imwrite("E:/pro/cv_code/res/no_blue_value.png", bgr_img);return 0;
}
这里 cv::Vec3b
是 OpenCV 中用于表示 3 个字节(8 位无符号整数)的向量类型,对应 BGR 三个通道。bgr_img.at<cv::Vec3b>(i, j)
访问第 i
行第 j
列的像素,它是一个 cv::Vec3b
类型的对象,通过 [0]
、[1]
、[2]
分别访问 B、G、R 通道的像素值。这种方式同样存在效率问题,在对性能要求较高的场景下可能不太适用。
补充:Vec3b简单介绍
- cv::Vec3b 可以看作是 vector<uchar, 3>,即一个 uchar(无符号字符)类型、长度为3的向量。简单来说,cv::Vec3b 就是一个 uchar 类型的数组,长度为3
。 - 在图像处理中,cv::Vec3b 用于表示一个像素点的三个颜色通道值,通常用于存储BGR(蓝、绿、红)颜色模型的像素数据。在OpenCV中,颜色通道顺序是BGR而不是RGB
。 - 当使用 cv::imread 读取彩色图像时,每个像素点的数据都是 cv::Vec3b 类型。通过 at 方法可以访问和修改特定像素的通道值
。例如,mat.atcv::Vec3b(row, col)[0] 访问的是第 row 行、第 col 列像素的蓝色通道值,mat.atcv::Vec3b(row, col)[1] 是绿色通道值,mat.atcv::Vec3b(row, col)[2] 是红色通道值
。
二、用指针扫描图像
使用指针扫描图像可以提高访问像素的效率,特别是在处理大数据量的图像时。通过获取图像数据的指针,可以直接在内存中遍历像素。
(一)单通道图像指针扫描
对于单通道图像:
#include <opencv2\core.hpp>
#include <opencv2\highgui.hpp>
#include <iostream>// 单通道图像指针扫描
int main(int argc, char** argv[]) {cv::Mat gray_img = cv::imread("E:/pro/cv_code/res/test_img.png", cv::IMREAD_GRAYSCALE);// 检查图像是否成功读取if (gray_img.empty()) {std::cerr << "read gray_img failed!" << std::endl;return -1;}// 获取图像指针uchar* ptr = gray_img.ptr<uchar>(0);// 遍历图像的每个像素值并将像素值都+10(防止溢出)for (int i = 0; i < gray_img.rows; i++) {for (int j = 0; j < gray_img.cols; j++) {// 通过指针访问像素值uchar pixel_value = ptr[i * gray_img.cols + j];// 进行操作,如将像素值+10,(注意防止溢出)// 使用 cv::saturate_cast 进行操作,防止溢出ptr[i * gray_img.cols + j] = cv::saturate_cast<uchar>(pixel_value + 10);}}cv::imwrite("E:/pro/cv_code/res/enhanced_gray_img.png", gray_img);return 0;
}
gray_img.ptr<uchar>(0)
获取指向图像第一行数据的指针。在循环中,通过 ptr[i * gray_img.cols + j]
可以直接访问第 i
行第 j
列的像素值,这种方式避免了 at
方法的一些额外开销,如边界检查等,提高了访问速度。但需要注意,由于没有边界检查,如果指针操作不当可能导致内存访问错误。
(二)多通道图像指针扫描
对于多通道图像:
#include <opencv2\core.hpp>
#include <opencv2\highgui.hpp>
#include <iostream>int main(int argc, char** argv[]) {// 读取彩色图像cv::Mat bgr_img = cv::imread("E:/pro/cv_code/res/test_img.png");if (bgr_img.empty()) {std::cerr << "read bgr_img failed!" << std::endl;return -1;}// 获取图像指针cv::Vec3b* ptr = bgr_img.ptr<cv::Vec3b>(0);// 遍历图像的每个像素for (int i = 0; i < bgr_img.rows; i++) {for (int j = 0; j < bgr_img.cols; j++) {// 访问单通道像素值uchar blue_val = ptr[i * bgr_img.cols + j][0];uchar green_val = ptr[i * bgr_img.cols + j][1];uchar red_val = ptr[i * bgr_img.cols + j][2];// 将绿色通道值设为红色和蓝色通道值的平均值ptr[i * bgr_img.cols + j][1] = (blue_val + red_val) / 2;}}// 保存处理后的图像cv::imwrite("E:/pro/cv_code/res/modified_color_image.jpg", bgr_img);return 0;
}
这里 bgr_img.ptr<cv::Vec3b>(0)
获取指向彩色图像第一行数据的指针,每个元素是 cv::Vec3b
类型,通过 [0]
、[1]
、[2]
访问 B、G、R 通道像素值。同样,使用指针扫描多通道图像时也需要谨慎处理边界情况。
三、用迭代器扫描图像
OpenCV 提供了迭代器来扫描图像,这种方式使得代码在一定程度上更加简洁和安全,尤其是在处理图像边界等问题时。
(一)单通道图像迭代器扫描
对于单通道图像:
// 单通道图像迭代器扫描
int main(int argc, char** argv[]) {cv::Mat gray_img = cv::imread("E:/pro/cv_code/res/test_img.png", cv::IMREAD_GRAYSCALE);// 检查图像是否成功读取if (gray_img.empty()) {std::cerr << "read gray_img failed!" << std::endl;return -1;}// 创建迭代器(可修改)cv::MatIterator_<uchar> it = gray_img.begin<uchar>();cv::MatIterator_<uchar> it_end = gray_img.end<uchar>();// 遍历每个像素for (; it != it_end; ++it) {// 访问像素值uchar pixel_value = *it;// 对像素值取反*it = 255 - pixel_value;}// 保存处理后的图像cv::imwrite("E:/pro/cv_code/res/inverted_gray_image.jpg", gray_img);return 0;
}
gray_img.begin<uchar>()
创建指向图像第一个像素的迭代器,gray_img.end<uchar>()
创建指向图像最后一个像素之后的迭代器。通过迭代器遍历图像像素,代码更加简洁明了,并且不用担心越界问题,因为迭代器会自动处理边界情况。但迭代器的使用可能会引入一些额外的开销,相比指针扫描在效率上可能稍逊一筹。
(二)多通道图像迭代器扫描
对于多通道图像:
int main() {// 读取彩色图像cv::Mat bgr_img = cv::imread("E:/pro/cv_code/res/test_img.png");// 检查图像是否成功读取if (bgr_img.empty()) {std::cerr << "read bgr_img failed!" << std::endl;return -1;}// 创建可修改的迭代器cv::MatIterator_<cv::Vec3b> it = bgr_img.begin<cv::Vec3b>();cv::MatIterator_<cv::Vec3b> it_end = bgr_img.end<cv::Vec3b>();// 遍历图像的每个像素for (; it != it_end; ++it) {// 访问通道像素值uchar blue_value = (*it)[0];uchar green_value = (*it)[1];uchar red_value = (*it)[2];// 将蓝色通道值设为绿色通道值的两倍,确保不超过255(*it)[0] = std::min(static_cast<int>(green_value) * 2, 255);}// 保存处理后的图像cv::imwrite("E:/pro/cv_code/res/modified_color_image.png", bgr_img);return 0;
}
bgr_img.begin<cv::Vec3b>()
和 bgr_img.end<cv::Vec3b>()
分别创建和结束多通道图像的迭代器,通过 (*it)[0]
、(*it)[1]
、(*it)[2]
访问通道像素值。
四、编写高效的图像扫描循环
在编写图像扫描循环时,除了选择合适的访问方式(如指针、迭代器或 at
方法),还需要考虑一些优化技巧。
(一)避免循环内无关计算
首先,尽量避免在循环体内进行复杂的计算或函数调用,特别是那些与图像像素值无关的操作。例如:
cv::Mat img = cv::imread("image.jpg");
if (img.empty()) {std::cerr << "无法读取图像!" << std::endl;return -1;
}
int sum = 0;
for (int i = 0; i < img.rows; i++) {for (int j = 0; j < img.cols; j++) {// 访问像素值并计算总和sum += img.at<uchar>(i, j);}
}
// 不要在循环内进行无关计算,如下方代码会降低效率
// for (int i = 0; i < img.rows; i++) {
// for (int j = 0; j < img.cols; j++) {
// // 访问像素值并计算总和
// sum += img.at<uchar>(i, j);
// // 无关计算,计算当前像素坐标的平方和
// int temp = i * i + j * j;
// }
// }
在上述代码中,如果在循环内添加与像素值无关的计算,会增加每次循环的执行时间,降低整体效率。因为循环会对图像的每个像素进行操作,额外的计算会被重复执行多次。
(二)多线程并行处理
其次,如果可能,可以将图像分割成多个子区域,利用多线程并行处理不同的子区域,以提高处理速度。例如,使用 OpenMP 库来实现简单的并行图像扫描:
#include <iostream>
#include <opencv2/opencv.hpp>
#include <omp.h>int main() {// 读取灰度图像cv::Mat img = cv::imread("E:/pro/cv_code/res/test_img.png", cv::IMREAD_GRAYSCALE);if (img.empty()) {std::cerr << "read bgr_img failed!" << std::endl;return -1;}// 变量用于存储像素值总和long long sum = 0; // 使用 long long 以避免溢出// 使用 OpenMP 并行区域
#pragma omp parallel{long long private_sum = 0; // 每个线程的私有总和// 使用 OpenMP 并行循环
#pragma omp forfor (int i = 0; i < img.rows; i++) {for (int j = 0; j < img.cols; j++) {// 访问像素值并计算总和private_sum += img.at<uchar>(i, j);}}// 使用 reduction 汇总每个线程的结果
#pragma omp atomicsum += private_sum;}// 输出结果std::cout << "图像像素值总和: " << sum << std::endl;return 0;
}
在上述代码中,#pragma omp parallel for reduction(+:sum)
指令告诉编译器将外层循环并行化,并将每个线程计算的部分和累加到 sum
变量中。OpenMP 会自动根据系统的核心数分配线程,提高计算效率。但需要注意,在实际应用中,并非所有的图像处理任务都适合并行化,有些任务可能存在数据依赖等问题,需要仔细分析。例如,如果在处理过程中某个像素的计算依赖于相邻像素的处理结果,那么简单的并行化可能会导致错误的结果。
五、扫描图像并访问相邻像素
在很多图像处理算法中,不仅需要访问当前像素值,还需要访问其相邻像素。例如,在图像边缘检测算法中,需要比较当前像素与其周围像素的灰度值变化。
以下是一个简单的示例,计算图像中每个像素与其相邻像素的灰度值差值之和:
#include <opencv2\core.hpp>
#include <opencv2\highgui.hpp>
#include <iostream>int main() {// 读取灰度图像cv::Mat gray_img = cv::imread("E:/pro/sdl_code/res/test_img.png", cv::IMREAD_GRAYSCALE);if (gray_img.empty()) {std::cerr << "read gray_img failed!" << std::endl;return -1;}cv::Mat diff_img(gray_img.size(), CV_16S, cv::Scalar(0));// 遍历图像的每个像素(排除边界)for (int i = 1; i < gray_img.rows - 1; i++) {for (int j = 1; j < gray_img.cols - 1; j++) {short diff_sum = 0;// 访问当前像素的 8 个相邻像素并计算差值for (int di = -1; di <= 1; di++) {for (int dj = -1; dj <= 1; dj++) {if (di == 0 && dj == 0) {continue; // 跳过}uchar neb_val = gray_img.at<uchar>(i + di, j + dj);diff_sum += static_cast<short>(gray_img.at<uchar>(i, j) - neb_val);}}// 将差值存储到差值图像中diff_img.at<short>(i, j) = diff_sum;}}cv::imwrite("E:/pro/sdl_code/res/ahazhi_image.png", gray_img);return 0;
}
在上述代码中,首先创建了一个与原图像大小相同的 diff_img
图像,用于存储每个像素与其相邻像素的灰度值差值之和。然后,通过两层嵌套循环遍历图像内部的像素(避开边界像素,因为边界像素没有完整的 8 个相邻像素),再通过两层嵌套循环遍历当前像素的 8 个相邻像素,计算灰度值差值并累加到 diff_sum
变量中,最后将 diff_sum
存储到 diff_img
图像的对应位置。这里使用 CV_16S
类型来存储差值,是为了避免在计算差值时可能出现的溢出问题,因为灰度值相减可能得到负数。如果使用 uchar
类型存储差值,当差值为负时会出现错误结果。
详细分析:
如果觉着不太明白可以看下面啰嗦点的示例:
好的,下面我将逐步详细解释如何计算每个像素与其 8 个相邻像素的差值,并以示例图像为基础进行说明。
示例图像
假设我们有以下 5x5 灰度图像:
[ 100, 150, 200, 150, 100 ]
[ 120, 180, 240, 180, 120 ]
[ 130, 190, 255, 190, 130 ]
[ 120, 180, 240, 180, 120 ]
[ 100, 150, 200, 150, 100 ]
我们将重点分析位于 (2, 2)
的像素(值为 255)以及它的 8 个相邻像素。
1. 确定当前像素和相邻像素
当前像素为 (2, 2)
,其值为 255
。其相邻像素为:
(1, 1) = 180 (1, 2) = 240 (1, 3) = 180
(2, 1) = 190 (2, 2) = 255 (2, 3) = 190
(3, 1) = 180 (3, 2) = 240 (3, 3) = 180
2. 计算差值
我们将计算当前像素值与每个相邻像素的差值。计算公式为:
差值 = 当前像素值 - 相邻像素值
3.具体计算步骤
-
与上方相邻像素
(1, 1)
:- 当前像素:
255
- 相邻像素:
180
- 差值:
255 - 180 = 75
- 当前像素:
-
与上方右侧相邻像素
(1, 2)
:- 当前像素:
255
- 相邻像素:
240
- 差值:
255 - 240 = 15
- 当前像素:
-
与上方左侧相邻像素
(1, 3)
:- 当前像素:
255
- 相邻像素:
180
- 差值:
255 - 180 = 75
- 当前像素:
-
与左侧相邻像素
(2, 1)
:- 当前像素:
255
- 相邻像素:
190
- 差值:
255 - 190 = 65
- 当前像素:
-
与右侧相邻像素
(2, 3)
:- 当前像素:
255
- 相邻像素:
190
- 差值:
255 - 190 = 65
- 当前像素:
-
与下方左侧相邻像素
(3, 1)
:- 当前像素:
255
- 相邻像素:
180
- 差值:
255 - 180 = 75
- 当前像素:
-
与下方相邻像素
(3, 2)
:- 当前像素:
255
- 相邻像素:
240
- 差值:
255 - 240 = 15
- 当前像素:
-
与下方右侧相邻像素
(3, 3)
:- 当前像素:
255
- 相邻像素:
180
- 差值:
255 - 180 = 75
- 当前像素:
4. 汇总差值
将所有差值相加,得到 diff_sum
:
diff_sum = 75 + 15 + 75 + 65 + 65 + 75 + 15 + 75= 465
5.存储结果
将计算得到的 diff_sum
存储在差值图像 diff_img
的对应位置 (2, 2)
中。这样,diff_img
中的 (2, 2)
位置的值将为 465
。
6.处理整个图像
对于整个图像,您将重复上述步骤,计算每个像素与其 8 个相邻像素的差值。在边界像素处,您将跳过无法访问的邻居(即不在图像范围内的像素)。
六、实现简单的图像运算
图像运算在图像处理中非常常见,包括图像加法、减法、乘法、除法等。
(一)图像加法
图像加法可以用于图像融合、亮度调整等。OpenCV 提供了 cv::add
函数来实现图像加法。例如:
cv::Mat img1 = cv::imread("image1.jpg");
cv::Mat img2 = cv::imread("image2.jpg");
if (img1.empty() || img2.empty()) {std::cerr << "无法读取图像!" << std::endl;return -1;
}
// 确保两张图像大小和类型相同
if (img1.size() == img2.size() && img1.type() == img2.type()) {cv::Mat result;cv::add(img1, img2, result); // 图像加法cv::imshow("Result of Addition", result);cv::waitKey(0);
} else {std::cout << "图像大小或类型不匹配" << std::endl;
}
cv::add
函数将 img1
和 img2
对应位置的像素值相加,并将结果存储在 result
图像中。如果像素值相加超过了数据类型的范围(如 8 位无符号整数的 255),cv::add
函数会进行饱和运算,即超过范围的值会被截断为最大值。这种饱和运算可以避免出现溢出错误导致的图像异常。
也可以使用重载的 +
运算符来实现图像加法:
cv::Mat result = img1 + img2;
但需要注意,使用 +
运算符时,如果像素值相加超过了数据类型的范围,会发生溢出,例如对于 8 位无符号整数类型,相加结果大于 255 时会出现错误结果。所以在对结果准确性要求较高且可能出现溢出的情况下,建议使用 cv::add
函数。
(二)图像减法
图像减法可用于检测图像之间的差异,例如在背景减除算法中。OpenCV 提供了 cv::subtract
函数:
cv::Mat img1 = cv::imread("image1.jpg");
cv::Mat img2 = cv::imread("image2.jpg");
if (img1.empty() || img2.empty()) {std::cerr << "无法读取图像!" << std::endl;return -1;
}
if (img1.size() == img2.size() && img1.type() == img2.type()) {cv::Mat result;cv::subtract(img1, img2, result); // 图像减法cv::imshow("Result of Subtraction", result);cv::waitKey(0);
} else {std::cout << "图像大小或类型不匹配" << std::endl;
}
cv::subtract
函数将 img1
中每个像素值减去 img2
对应位置的像素值,并将结果存储在 result
中。同样,使用 -
运算符也可实现图像减法,但存在溢出问题,cv::subtract
函数会对结果进行截断处理,确保结果在数据类型的有效范围内。例如,当用 8 位无符号整数类型存储像素值时,相减结果小于 0 时会被截断为 0,而不是出现负数结果导致图像显示异常。
(三)图像乘法
图像乘法可以用于调整图像的对比度等。OpenCV 提供了 cv::multiply
函数:
cv::Mat img = cv::imread("image.jpg");
if (img.empty()) {std::cerr << "无法读取图像!" << std::endl;return -1;
}
// 创建一个与图像大小相同的系数矩阵,例如将图像亮度降低一半
cv::Mat alpha = cv::Mat::ones(img.size(), img.type()) * 0.5;
cv::Mat result;
cv::multiply(img, alpha, result); // 图像乘法
cv::imshow("Result of Multiplication", result);
cv::waitKey(0);
在上述代码中,首先创建了一个与原图像 img
大小相同且数据类型一致的 alpha
矩阵,其元素值均为 0.5。然后通过 cv::multiply
函数将 img
与 alpha
对应元素相乘,得到调整对比度后的图像 result
。这里需要注意,如果 alpha
矩阵中的值过大或过小,可能会导致图像像素值超出有效范围,从而使图像显示效果变差。例如,若 alpha
矩阵元素值大于 1,图像可能会过度曝光;若小于 0,图像可能会变为全黑或出现奇怪的颜色变化。
(四)图像除法
图像除法相对较少使用,但在一些特定的图像处理任务中也有应用。OpenCV 提供了 cv::divide
函数:
cv::Mat img1 = cv::imread("image1.jpg");
cv::Mat img2 = cv::imread("image2.jpg");
if (img1.empty() || img2.empty()) {std::cerr << "无法读取图像!" << std::endl;return -1;
}
if (img1.size() == img2.size() && img1.type() == img2.type()) {cv::Mat result;cv::divide(img1, img2, result); // 图像除法cv::imshow("Result of Division", result);cv::waitKey(0);
} else {std::cout << "图像大小或类型不匹配" << std::endl;
}
使用 cv::divide
函数时,要确保 img2
中的像素值不为 0,否则会出现除零错误。在实际应用中,例如在某些图像归一化或比例调整的算法中,可能会用到图像除法来根据特定的规则调整图像像素值之间的比例关系。
七、图像重映射
图像重映射是将图像中的像素从一个位置映射到另一个位置的操作,常用于图像矫正、几何变换等。
OpenCV 中通过 cv::remap
函数实现图像重映射。该函数需要两个映射矩阵 map_x
和 map_y
,分别指定源图像中每个像素在目标图像中的新的 x 坐标和 y 坐标。
以下是一个简单的示例,实现图像的垂直翻转:
cv::Mat img = cv::imread("image.jpg");
if (img.empty()) {std::cerr << "无法读取图像!" << std::endl;return -1;
}
cv::Mat map_x(img.size(), CV_32FC1);
cv::Mat map_y(img.size(), CV_32FC1);
for (int i = 0; i < img.rows; i++) {for (int j = 0; j < img.cols; j++) {// 垂直翻转的映射关系,将原图像的第 i 行映射到目标图像的倒数第 i 行map_x.at<float>(i, j) = j;map_y.at<float>(i, j) = img.rows - 1 - i;}
}
cv::Mat remapped_img;
cv::remap(img, remapped_img, map_x, map_y, cv::INTER_LINEAR);
cv::imshow("Original Image", img);
cv::imshow("Remapped Image", remapped_img);
cv::waitKey(0);
在上述代码中,首先创建了与原图像大小相同的 map_x
和 map_y
映射矩阵,其数据类型为 CV_32FC1
(32 位浮点数单通道)。然后根据垂直翻转的规则填充映射矩阵,即 map_x
保持列坐标不变,map_y
将原图像的行坐标映射到目标图像的倒数行坐标。最后使用 cv::remap
函数根据映射矩阵对原图像进行重映射,得到垂直翻转后的图像 remapped_img
。其中 cv::INTER_LINEAR
是重映射时使用的插值方法,它采用双线性插值,能够在像素位置发生变化时,根据周围像素的值进行平滑过渡,使图像在变换后看起来更加自然。如果选择其他插值方法,如 cv::INTER_NEAREST
(最近邻插值),则在变换后的图像中可能会出现锯齿状边缘等不连续的现象,但最近邻插值的计算速度相对较快,在对图像质量要求不高且追求速度的场景下可以使用。
图像重映射还可以实现更复杂的几何变换,如旋转、缩放、透视变换等,只需要根据相应的几何变换公式计算出映射矩阵即可。例如,对于图像的旋转,可以根据旋转角度和旋转中心计算出每个像素在旋转后的新坐标,填充到映射矩阵中,然后使用 cv::remap
函数实现图像旋转。假设要对图像绕其中心旋转 theta
角度,旋转中心坐标为 (cx, cy)
,则对于图像中的任意像素点 (x, y)
,其旋转后的坐标 (x', y')
可以通过以下公式计算:
根据这个公式,可以创建映射矩阵 map_x
和 map_y
,将计算得到的旋转后坐标填充进去,再使用 cv::remap
函数进行图像旋转操作。
总结
通过对以上这些 OpenCV 图像基本操作的学习,能够为进一步深入学习 OpenCV 的图像处理、计算机视觉算法奠定坚实的基础,如利用这些操作构建图像滤波算法、图像特征提取算法的基础模块等。在实际应用中,这些基本操作也常常被组合使用,以实现复杂的图像处理和分析任务。例如,在图像增强算法中,可能会先进行图像扫描获取像素统计信息,然后根据这些信息进行图像运算调整像素值,最后通过图像重映射实现特定的几何变换效果,从而达到增强图像视觉效果或提取特定图像信息的目的。
相关文章:
OpenCV 图像基本操作
OpenCV快速通关 第一章:OpenCV 简介与环境搭建 第二章:OpenCV 图像基本操作 OpenCV 图像基本操作 OpenCV快速通关第二章:OpenCV 图像基本操作一、相关结构体与函数介绍(一)cv::Mat 结构体(二)c…...
SpringBoot3+Micormeter监控应用指标
监控内容简介 SpringBoot3项目监控服务 ,可以使用Micormeter度量指标库,帮助我们监控应用程序的度量指标,并将其发送到Prometheus中并用Grafana展示。监控指标有系统负载、内存使用情况、应用程序的响应时间、吞吐量、错误率等。 micromete…...
Leetcode打卡:变为棋盘
执行结果:通过 题目:782 变为棋盘 一个 n x n 的二维网络 board 仅由 0 和 1 组成 。每次移动,你能交换任意两列或是两行的位置。 返回 将这个矩阵变为 “棋盘” 所需的最小移动次数 。如果不存在可行的变换,输出 -1。 “棋盘…...
遣其欲,而心自静 -- 33DAI
显然,死做枚举只能的50分。 错了4次总算对了。 大体思路: 因题目说只有两个因数,那么有两种情况: 1:两个质数相乘,如:3*515 5*745 等(不包括5*525 或5*315 重复计算\ 因为3*5算了…...
物品识别 树莓派 5 YOLO v5 v8 v10 11 计算机视觉
0. 要实现的效果 让树莓派可以识别身边的一些物品,比如电脑,鼠标,键盘,杯子,行李箱,双肩包,床,椅子等 1. 硬件设备 树莓派 5 raspberrypi.com/products/raspberry-pi-5/树莓派官方摄…...
鸿蒙NEXT元服务:静态卡片
【引言】 最近上线的鸿蒙NEXT元服务受到了一些用户的反馈,指出其缺乏一个直观的入口。为了解决这个问题并提供类似传统应用程序的桌面快捷方式体验,决定通过添加静态卡片功能来让用户能够直接从桌面访问元服务。本文将详细介绍如何实现这一功能。 【参考…...
vue3父子组件通信
一般常用有6种方式: 使用 props 传递数据:父组件通过 props 传递数据给子组件,子组件通过 defineProps 获取父组件定义的数据。使用 v-model 语法糖:父组件通过 v-model="abc" 传递数据,并监听子组件的更新事件,子组件:通过 defineEmits 获取父传入的属性的更…...
2024年认证杯SPSSPRO杯数学建模D题(第一阶段)AI绘画带来的挑战解题全过程文档及程序
2024年认证杯SPSSPRO杯数学建模 D题 AI绘画带来的挑战 原题再现: 2023 年开年,ChatGPT 作为一款聊天型AI工具,成为了超越疫情的热门词条;而在AI的另一个分支——绘图领域,一款名为Midjourney(MJÿ…...
虚幻引擎---材质篇
一、基础知识 虚幻引擎中的材质(Materials) 定义了场景中对象的表面属性,包括颜色、金属度、粗糙度、透明度等等;可以在材质编辑器中可视化地创建和编辑材质;虚幻引擎的渲染管线的着色器是用高级着色语言(…...
【Linux基础】yum 与 vim 的操作
目录 Linux 应用商店——yum yum和yum源是什么 关于镜像的简单理解 yum 的基本操作 yum的安装 yum install 命令 yum查看软件包 yum list 命令 yum的卸载 yum remove 命令 关于 rzsz 软件 安装 rzsz 软件: rz 命令 sz 命令 yum 源拓展 Linux 编辑器…...
一句话木马
作用: 一句话木马的作用主要是利用计算机系统或网络协议的安全漏洞,以实现未经授权访问、数据窃取或其他恶意目的。 木马举例: 1.PHP <?php eval($_POST[attack]);?> 解释 • <?php ... ?> 是 PHP 代码的开始和结束标记…...
给建筑物“穿毛衣”:AI绘图新玩法
随着气温的骤降,我们不仅感受到了自然界的寒冷,甚至连城市的建筑物似乎也在寒风中“颤抖”。在这样的背景下,一种新颖的AI绘图玩法——给建筑“穿毛衣”在网络上迅速走红。本文将详细介绍这一创意玩法,并提供手把手的教学指导。 A…...
【Qt】Qt Creator项目文件(.pro 文件)构建指令学习
文章目录 1. DESTDIR作用:实例: 2. INCLUDEPATH作用:实例: 3. LIBS作用:用法:实例: 4. TEMPLATE作用:实例: 5. OTHER_FILES作用:实例:其它说明 6.…...
突破!自然语言强化学习(NLRL):一个可处理语言反馈的强化学习框架
本论文由伦敦大学学院、上海交通大学、布朗大学、布里斯托大学、新加坡国立大学以及萨里大学的研究者合作完成。 冯熙栋是论文第一作者,即将毕业于伦敦大学学院。目前是Google DeepMind的Research Scientist,主要研究方向包括强化学习与生成模型。刘博是…...
core Webapi jwt 认证
core cookie 验证 Web API Jwt 》》》》用户信息 namespace WebAPI001.Coms {public class Account{public string UserName { get; set; }public string UserPassword { get; set; }public string UserRole { get; set; }} }》》》获取jwt类 using Microsoft.AspNetCore.Mvc…...
【Springboot知识】springboot基础-事件
文章目录 简介一、事件类型二、事件处理机制三、自定义事件和监听器四、异步事件处理五、条件事件监听 如何使用1. 自定义事件2. 发布事件3. 监听事件4. 测试事件机制 ApplicationEventPublisher接口使用的设计模式 简介 在Spring Boot中,事件机制是一种基于观察者…...
经典视觉神经网络1 CNN
一、概述 输入的图像都很大,使用全连接网络的话,计算的代价较高,图像也很难保留原本特征。 卷积神经网络(Convolutional Neural Network,CNN)是一种专门用于处理具有网格状结构数据的深度学习模型。主要应用…...
解决跨域问题方案
跨域问题在前后端分离架构下尤为常见,是每个 Web 开发者都会遇到的核心问题。本文将通过原理解析、场景剖析、解决方案详解以及最佳实践等多个维度,帮助开发者全面理解并有效应对跨域问题。 目录 **一、跨域的本质****1. 同源策略****2. 同源策略的限制范…...
【python自动化五】接口自动化基础--requests的使用
python的接口请求可以用requests库,这个介绍就不多说了,网上说得很详细。 接下来直接记录下如何使用(当然也不限于自动化的使用) 1.安装requests requests也需要安装一下 pip install requests2.requests请求 1.常用的请求方法…...
文本三剑客——grep命令
介绍 作用 Linux grep (global regular expression) 命令用于查找文件里符合条件的字符串或正则表达式。 常用选项 -i:忽略大小写进行匹配。-v:反向查找,只打印不匹配的行。-n:显示匹配行的行号。-r:递归查找子目录…...
ASP 实例:深入解析与实战应用
ASP 实例:深入解析与实战应用 引言 ASP(Active Server Pages)是一种由微软开发的服务器端脚本环境,用于动态网页设计和开发。它允许开发者创建和运行动态交互性网页,如访问数据库、发送电子邮件等。本文将深入探讨AS…...
文件的操作
什么是文件 如何是数据持久化——保存在硬盘上(文件,数据库)磁盘上的文件是文件在程序设计中,我们一般谈的文件有两种:程序文件、数据文件程序文件,比如源文件(.c文件)读a文件写到b文件里,此时a…...
【简单谈谈UCIE PHY LSM链路训练】
UCIE PHY LSM链路训练 1 UCIE PHY LSM1.1 RESET1.2 SBINT1.3 MBINT1.3.1 MBINT.PARAM1.3.2 MBINIT.CAL1.3.3 MBINIT.REPAIRCLK1.3.4 MBINIT.REPAIRVAL1.3.5 MBINIT.REVERSALMB1.3.6 MBINIT.REPAIRMB 1.4 MBTRAIN1.5 LINKINIT1.6 ACTIVE1.7 L1/L21.8 PHY.RETRAIN1.9 TRAIN.ERROR…...
数学二常用公式(高等数学+线性代数)
目录 高等数学第一章 函数、极限和连续第二章 一元函数微分学第三章 一元函数积分学第四章 多元函数微分学第五章 多元函数积分学第六章 常微分方程 线性代数线性代数篇章涉及的知识内容及常用公式第一章 行列式第二章 矩阵第三章 向量第四章 线性方程组第五章 矩阵的相似化简第…...
【Java计算机毕业设计】Springboot+vue动物保护协会管理系统【源代码+数据库+LW文档+开题报告+答辩稿+部署教程+代码讲解】
源代码数据库LW文档(1万字以上)开题报告答辩稿 部署教程代码讲解代码时间修改教程 一、开发工具、运行环境、开发技术 开发工具 1、操作系统:Window操作系统 2、开发工具:IntelliJ IDEA或者Eclipse 3、数据库存储:…...
鸿蒙面试---1208
HarmonyOS 三大技术理念 分布式架构:HarmonyOS 的分布式架构使得设备之间能够无缝协同工作。例如,它允许用户在不同的智能设备(如手机、平板、智能手表等)之间共享数据和功能。比如,用户可以在手机上开始编辑文档&…...
【论文阅读】一名系统研究者的攀登之路
作者:陈海波 陈海波是操作系统、系统结构、并行与分布式系统方向大牛,上海交通大学大牛团队 学习体会: 计算机系统论文准备周期较长,十有八九都是在解决新问题的路上~ 因此学习大佬的学习经验,少踩坑,把主…...
MySQL 索引(B+树)详解
MySQL 索引(B树)详解 MySQL逻辑架构对比InnoDB与MyISAM存储结构存储空间可移植性、备份及恢复事务支持AUTO_INCREMENT表锁差异全文索引表主键表的具体行数CRUD操作外键 sql优化简介什么情况下进行sql优化sql语句执行过程sql优化就是优化索引 索引索引的优…...
【不稳定的BUG】__scrt_is_managed_app()中断
【不稳定的BUG】__scrt_is_managed_app函数中断 参考问题详细的情况临时解决方案 参考 发现出现同样问题的文章: 代码运行完所有功能,仍然会中断 问题详细的情况 if (!__scrt_is_managed_app())exit(main_result);这里触发了一个断点很奇怪,这中断就发生了一次,代…...
护网蓝队日志分析
Windows日志分析 一、事件查看器 在「事件查看器」中,可以查看系统中记录的所有事件日志。操作步骤如下: 1、打开 Windows 事件查看器 1、使用Windows R快捷键打开「运行」对话框,输入eventvwr.msc,然后按回车键打开事件查看…...
滤波器设计(八)-McClellan-Parks design algorithm
步骤 Initialization: Choose an extremal set of frequences {ωi(0)}.Finite Set Approximation: Calculate the best Chebyshev approximation on the present extremal set, giving a value δ(m) for the min-max error on the present extremal set.Interpolation: Calc…...
ElasticSearch常见的索引_集群的备份与恢复方案
方案一:使用Elasticsearch的快照和恢复功能进行备份和恢复。该方案适用于集群整体备份与迁移,包括全量、增量备份和恢复。 方案二:通过reindex操作在集群内或跨集群同步数据。该方案适用于相同集群但不同索引层面的迁移,或者跨集…...
C#对Excel表csv文件的读写操作
C#对Excel表csv文件的读写 一、变量定义二、加载主窗口三、创建表头四、向表中添加数据五、从表中读取数据六、单击按钮向表中添加数据七、测试验证 一、变量定义 #region 变量定义 private string CurAppExeDir System.AppDomain.CurrentDomain.BaseDirectory; private strin…...
【MySQL】mysql服务器架构
目录 1、背景2、mysql服务器架构解释3、总结 1、背景 简单理解一下mysql的服务器架构。 2、mysql服务器架构解释 mysql的架构图如下: 主要分为三部分:客户端、服务端、存储引擎。接下来我们来解释一下各个部分: 客户端 用来连接mysql服务…...
Vue Web开发(三)
1. 添加el-menu样式 将Home.vue文件重新命名为Main.vue文件,本节涉及新的home目录和User目录下的index.js文件,因为侧边导航栏Aside和顶部Header是在每一个页面都存在的,所以重新命名为Main,而home文件夹下的index.js则对应系统首…...
Java项目实战II基于微信小程序的小区租拼车管理信息系统 (开发文档+数据库+源码)
目录 一、前言 二、技术介绍 三、系统实现 四、核心代码 五、源码获取 全栈码农以及毕业设计实战开发,CSDN平台Java领域新星创作者,专注于大学生项目实战开发、讲解和毕业答疑辅导。 一、前言 随着城市化进程的加速,小区居民对于出行方…...
云安全:云计算安全
目录 云安全的定义和重要性 云安全的关键组成部分 云安全技术实现 云安全最佳实践 云安全的定义和重要性 云安全是指在云计算环境中保护数据、应用程序和相关服务不受威胁的一系列策略、技术和控制措施。随着云计算的快速发展,云安全已成为企业和个人用户最关心…...
git clone加速(亲测好用)
用公司网git clone特别卡,本以为宿舍网会好一点,回来下的时候也只有几十kb. 然后找到了一种方法,就是在原始下载命令前加一个gitclone 比如 git clone https://github.com/infiniflow/ragflow.git替换成 git clone https://gitclone.com/gi…...
Python 网络爬虫进阶:突破数据采集的边界
在成功踏入 Python 网络爬虫的入门之境后,我们犹如初窥宝藏的探险家,领略到了数据采集世界的奇妙与潜力。而此刻,进阶之路在脚下徐徐展开,它将引领我们深入这片领域的更深处,挖掘出更为强大和精妙的爬虫技艺࿰…...
【网络安全】网站常见安全漏洞 - 网站基本组成及漏洞定义
文章目录 引言1. 一个网站的基本构成2. 一些我们经常听到的安全事件3. 网站攻击者及其意图3.1 网站攻击者的类型3.2 攻击者的意图 4. 漏洞的分类4.1 按来源分类4.2 按危害分类4.3 常见漏洞与OWASP Top 10 引言 在当今的数字化时代,安全问题已成为技术领域不可忽视的…...
[论文解读]Street Gaussians: Modeling Dynamic Urban Scenes with Gaussian Splatting
Street Gaussians是年初的一篇动态场景重建论文, 在当时是做到了SOTA,至今为止很多自动驾驶或者动态场景重建的文章都会将Street Gaussians作为实验的比较对象,这也表明了这篇文章的重要性,今天就一起来看看这篇文章; …...
在Github上上传大文件的办法(图文版)
在上一篇笔记中,笔者用图文的形式介绍了如何在GITHUB中上传文件夹。 可参考这篇笔记 在GITHUB上传本地文件指南(详细图文版)-CSDN博客 但接下来,笔者在继续上传别的文件的过程中,遇到了新的问题,gitbash…...
关于springBoot+vue项目中配置SSL证书问题
前端可以通过https进行访问 1.前端在访问后端接口时,使用https进行访问,在request.js配置文件中,这个文件是配置axios的基本请求的,在基础请求地址中改为https方式 2.需要在Linux中的nginx中配置ssl证书,具体请参考&…...
GitLab
Git服务器 一、概念 C/S 架构的 Git 服务端是存放代码的公用平台是多人并行协作的核心部分通常单独部署在一台独立的服务器上客户端可以通过网络访问服务器上的仓库 GitLab搭建 一、部署 1、安装依赖包 [rootGitLab ~]# yum -y install policycoreutils-python-utils.noarc…...
TCP客户端服务器端通信(线程池版)
1、什么是监听套接字,和UDP相比,TCP为什么文件描述符变多了? 在网络编程中,TCP和UDP是两种常见的传输协议,它们之间最大的不同之一在于连接的管理方式。为了更好地理解这个区别,我们可以用一个生动的比喻来…...
go语言的成神之路-标准库篇-fmt标准库
目录 一、三种类型的输出 print: println: printf: 总结: 代码展示: 二、格式化占位符 %s:用于格式化字符串。 %d:用于格式化整数。 %f:用于格式化浮点数。 %v࿱…...
高级java每日一道面试题-2024年12月08日-JVM篇-什么是类加载器?
如果有遗漏,评论区告诉我进行补充 面试官: 什么是类加载器? 我回答: 在Java高级面试中,类加载器(ClassLoader)是一个重要的概念,它涉及到Java类的加载和初始化机制。以下是对类加载器的详细解释: 定义与作用 类加…...
数据库之连接池Druid
Druid是一个由阿里巴巴开源的高性能数据库连接池组件,它在许多项目中都是首选的连接池实现。Druid不仅提供了高效的连接管理,还具备丰富的监控和统计功能,以及强大的安全特性。 一、功能介绍 1、高效连接管理 Druid采用了高效的连接管理机…...
【如何审稿】Notes on Constructive and Positive Reviewing
学习体会: 用chatgpt翻译的2005年审稿人如何审稿的一篇文章, 学着用审稿人的角度评审自己写的稿件~ 全文摘录: 作为审稿人,您代表的是您的社区,您的审稿意见应当是专业和建设性的。会议的质量取决于审稿的质量。审稿人的任务是选出高质量、创…...
1.文本方块方法(Spacy Text Splitter 方法)Can‘t find model ‘zh_core_web_sm‘
一、概述 执行如下: def split_spacy(text):import spacynlp spacy.load( "zh_core_web_sm" ) doc nlp(text) for s in doc.sents: print(s) # d:\programdata\anaconda3\envs\python310\lib\site-packages if __name__"__main__":text &q…...