【OpenCV C++20 学习笔记】扫描图片数据
扫描图片数据
- 应用情景
- 图像数据扫描的难点
- 颜色空间缩减(color space reduction)
- 查询表
- 扫描算法
- 计算查询表
- 统计运算时长
- 连续内存
- 3种扫描方法
- C风格的扫描方法
- 迭代器方法
- 坐标方法
- LUT方法
- 算法效率对比
- 结论
应用情景
图像数据扫描的难点
在上一篇文章《基本图像容器——Mat》中,已经详细描述了OpenCV储存图像数据的形式(图像的每个像素储存为一个矩阵中的数据项,矩阵的每个数据项包括各个颜色通道的值,如RGB3通道包含红、绿、蓝共3个通道的颜色值)。所以,矩阵的值的列数为矩阵的列数乘以颜色通道数,如下图所示,OpenCV默认的BGR格式的数据有3个颜色通道,所以实际有m*3列数值;行数则不变:

如果我们使用uchar(8bits)类型去储存每个像素的值,那么像素的每个颜色通道可以有256个可能的值( 2 8 = 256 2^8=256 28=256),这样的话如果是3通道的数据,那每个数据项就有( 25 6 3 = 16 , 777 , 216 256^3=16,777,216 2563=16,777,216)种可能的颜色值了。如果矩阵很大,那就会给算法的执行带来很大压力。
颜色空间缩减(color space reduction)
为了减轻扫描图像数据的算法压力,可以将现有的颜色值除以某个值,从而缩小颜色值的值域。例如,将所有0-9的颜色值都用0替代,将所有10-19的颜色值都用10替代,依此类推。用数学公式来表示:
I n e w = ( I o l d 10 ) ∗ 10 I_{new} = ( \frac{I_{old}} {10})*10 Inew=(10Iold)∗10
- I n e w I_{new} Inew:缩减之后的颜色值,以下简称缩减值
- I o l d I_{old} Iold:缩减之前的原始颜色值,以下简称原始值
推而广之,如果想要应用其他的缩减率,比如2,也就是说,0-1都用0来代替,2-3都用1来代替,那除数就会变成2;将这个除数用 d d d来表示,并称之为缩减因子,则公式会变成:
I n e w = ( I o l d d ) ∗ d I_{new} = ( \frac{I_{old}} {d})*d Inew=(dIold)∗d - I n e w I_{new} Inew:缩减之后的颜色值,以下简称缩减值
- I o l d I_{old} Iold:缩减之前的原始颜色值,以下简称原始值
- d d d:缩减因子
注意:uchar类型被整数除了之后,得出的结果依然是uchar类型
但是对每个颜色值都执行上述的除法和乘法运算,仍然会消耗很多算力。然而,由于每个颜色值的值域是有限的,比如uchar类型是[0, 256],所以如果能直接计算出所有可能的缩减结果,并进行赋值运算,会节省很多算力。所以,就产生了“查询表”
查询表
查询表就是储存与原始值一一对应的缩减值的数组(一维或多维)。一旦数据类型确定,查询表的大小就确定不变了。比如,uchar类型的查询表就只有256个缩减值,因为原始值总共就只有256种可能。然后运用这个查询表将每个原始值都替换成查询表中对应的值,就不必对每一个原始值都进行缩减计算了,而只是简单的查询和赋值,这样就能节省算力。而且,原始值越多,节省效果越好。这就是查询表的优势。
扫描算法
该实例将3种算法放在一个main函数中,main函数接收一个参数数组argv[],其中有可以有3个或4个参数:
- 默认参数:程序名(调试时不需要用户指定)
- 图片文件路径
- 缩减因子:即上述公式中的 d d d
- 图片读取格式:以灰度格式读取,则传递“G”;如不指定该参数,则默认采用BGR格式读取
静态函数help()详细描述了该方法的使用:
static void help()
{std::cout<< "\n--------------------------------------------------------------------------" << endl<< "这个程序展示如何在OpenCV中扫描图片(cv::Mat)"<< "根据输入图片路径以及缩减因子(大于0的整数)" << endl<< "这个程序分别使用了C风格方法、迭代器方法、坐标方法和LUT方法进行扫描" << endl<< "参数输入:" << endl<< "程序名 <图片路径> <缩减因子> [G]" << endl<< "如果加了参数G,则使用灰度格式读入图片" << endl<< "--------------------------------------------------------------------------" << endl<< endl;
}
在main函数中,首先对输入的参数进行分析和处理:
if (argc < 3)
{//参数数量小于3个,则退出函数并输出提示cout << "Not enough parameters" << endl;return -1;
}Mat I, J;
if (argc == 4 && !strcmp(argv[3], "G")) //当参数数量为4且,最后一个参数是"G"时I = imread(argv[1], IMREAD_GRAYSCALE); //以灰度格式读取图片,并储存在Mat对象I中
elseI = imread(argv[1], IMREAD_COLOR); //否则以BGR格式读取图片,并储存在Mat对象I中if (I.empty())
{//如果读取的数据为空,则退出函数并输出提示cout << "The image" << argv[1] << " could not be loaded." << endl;return -1;
}
计算查询表
接下来,根据传入的参数数组中的第3个参数,即argv[2],计算查询表:
int divideWith { 0 }; //1-4行:将字符串转换成数字,并储存在变量divdeWith中作为缩减因子
stringstream s;
s << argv[2];
s >> divideWith;
if (!s || !divideWith)
{//如果无法接收第3个参数,或者参数为0,则退出函数,并输出提示cout << "Invalid number entered for dividing. " << endl;return -1;
}uchar table[256]; //用一个长度为256的一维数组来储存查询表
for (int i { 0 }; i < 256; ++i) //计算查询表,计算结果为uchar类型table[i] = static_cast<uchar>(divideWith * (i / divideWith));
在将字符串转换成数字的过程中使用了C++中的字符串流stringstream,字符串流对象s,接收参数字符串argv[2],然后将其传给整数变量divideWith,作为后面进行缩减运算的缩减因子。
计算查询表的for循环块,进行了256次循环,将[0, 255]中的所有整数依次进行了缩减运算,得出256个uchar类型的缩减值,并依次储存在一个uchar类型的数组中。
统计运算时长
这个程序为了比较不同扫描方法的速度,使用了使用了OpenCV中的cv::getTickCount()和cv::getTickFrequency()函数进行计时。前者返回某个运行节点的CPU的tick数(一个tick为CPU频率的倒数);后者返回CPU每秒的tick数。如果获取事件起始点和结束点的CPU的tick数,然后用它们的差除以CPU每秒的tick数就能得到事件起始点和结束点之间的时间差,单位为秒。具体代码如下:
double t { static_cast<double>getTickCount() }; //将返回值类型从原本的int型转换为double型
//扫描函数
t = (static_cast<double>getTickCount() - t)/getTickFrequency(); //转换为double型后,除法运算结果中的小数就会被保留
cout << "用时 " << t << " 秒" << endl;
这几行代码放在每个扫描方法的前后,从而能为每个扫描方法计算运行时间,便可比较它们的运行速度
连续内存
虽然储存图像数据的Mat对象可能是个二维甚至多维矩阵,但是在内存中矩阵是被按行分成若干个一维数组储存的。这些一维数组可能被放在一起,形成一个连续的内存空间,也可能被分开储存。OpenCV中的cv::Mat::isContinuous()可以判断矩阵在内存中是否是连续的。被储存在一个连续内存中的矩阵扫描起来会更快。
3种扫描方法
如果没耐心对比每种方法的思路,可以直接结论部分看每种方法的优缺点,然后找到相应方法的章节进行进一步阅读。
但是,除了LUT方法,前三种方法都需要自己编写将替换原始值的语句,该语句中对查询表的索引,思路比较绕,为了避免重复,本文只在第一种方法,即C风格的扫描方法中对此进行了详细解释,如果看不懂可以到该章节进行参考。
C风格的扫描方法
C风格的扫描方法少不了要进行C风格的二维数组的遍历操作,包括行指针、列指针等。
//! [scan-c]
Mat& ScanImageAndReduceC(Mat& I, const uchar* const table)
{//只接收uchar类型的矩阵CV_Assert(I.depth() == CV_8U); //depth函数返回每个通道的数值的类型,如CV_8U为8比特无符号字符串类型int channels { I.channels() }; //channels函数返回矩阵中的颜色通道数int nRows { I.rows };int nCols { I.cols * channels }; //实际列数为矩阵列数*颜色通道数if (I.isContinuous()){//如果是储存在连续空间,则按一维数组来处理nCols *= nRows; //一维数组的实际列数为原列数*行数nRows = 1; //一维数组行数为1}int i, j;uchar* p; //uchar类型的p指针用来储存扫描结果for (i = 0; i < nRows; ++i){p = I.ptr<uchar>(i); //ptr模板函数根据行数i返回矩阵第i行的行指针,并在尖括号中指定返回的指针类型for (j = 0; j < nCols; ++j){//原来的p[j]是个列指针,实际为矩阵i行j列的值,即颜色的原始值//因为查询表示按0-255的顺序排列的,//所以,以p[j]为下标访问查询表,正好能访问到原始值对应的缩减值p[j] = table[p[j]]; //将查询表中的缩减值赋值给p指针中对应的位置//由于p是指向Mat对象I中的矩阵的,所以I实际上也被更改了}}return I; //返回被更改的I
}
//! [scan-c]
注意,该函数传入的第2个参数为uchar类型的常量指针常量,即这个指针指向的对象不能被修改,指针的地址也不能被修改,这样才能保证查询表在函数运行结束之后仍然没变,可以被再次利用。
该函数最核心的部分就是对数组进行遍历操作的for循环语句。这里巧妙地使用列指针,即原始值,作为访问查询表数组的下标,从而找到对应的缩减值。具体思路写在注释里了,读者可以参阅。
当矩阵是被储存在连续的内存空间中的时候,实际上是对一个一维数组进行遍历,i为1,只循环1次。
如果确定矩阵是储存在连续内存空间中的,那么还有另外一种方法可以完成对它的遍历:
uchar* p { I.data }; //data是Mat类的public数据成员,它储存了Mat对象首行的行指针for(unsinged int i { 0 }; i < ncol*nrows; ++i)//行指针自增之后就变成了列指针,即一维数组第i列的地址//再进行解引用操作,就得到了第i列的值*p++ = table[*p];
迭代器方法
使用迭代器比用数组指针更加方便,因为不用考虑行指针、列指针的问题,没有嵌套的for循环,只需要一层for循环。
但是,迭代器只能代替矩阵中的数据项。也就是说,如果是单通道的灰度格式的图片数据,迭代器正好代替矩阵中的每个颜色值;但是,如果是3通道的BGR格式的图片数据,迭代器实际上代替的是一个长度为3的数组,其中包含了3个颜色值(分别为蓝、绿、红值),在这种情况下还必须对迭代器进行进一步的操作。
//! [scan-iterator]
Mat& ScanImageAndReduceIterator(Mat& I, const uchar* const table)
{CV_Assert(I.depth() == CV_8U);const int channels{ I.channels() };switch (channels){//switch语句实现对两种情况的分别处理case 1: //单通道的灰度图片数据{MatIterator_<uchar> it, end; //声明起始和终止迭代器for (it = I.begin<uchar>(), end = I.end<uchar>(); it != end; ++it)//begin和end函数分别返回指向第一个和最后一个数据项的迭代器*it = table[*it]; //对迭代器进行解引用操作可获得对应的数据项break;}case 3: //3通道的BGR图片数据{MatIterator_<Vec3b> it, end;for (it = I.begin<Vec3b>(), end = I.end<Vec3b>(); it != end; ++it){//还需要对迭代器中的3个值分别进行赋值操作//*it[0]为数据项在第一个颜色通道的值(*it)[0] = table[(*it)[0]];(*it)[1] = table[(*it)[1]];(*it)[2] = table[(*it)[2]];}}}return I;
}
//! [scan-iterator]
如果在3通道的情况,没有对迭代器中的数组进行进一步的操作的话,那改变的只是每个像素的蓝色值。因为OpenCV将RGB转换成了BGR,第一个值是蓝色值。
坐标方法
这种方法其实一般是用来确定需要某个数据项的行数和列数的,也称为随机获取,所以并不建议用来扫描图像数据。
这种方法也需要对单通道和3通道数据进行分情况的处理。
在单通道数据中,该方法运用了数据项的动态地址,并返回数据项的引用,这由函数cv::Mat::at()来实现;
在3通道数据中,该方法运用了Mat_类型达到了同样的目的。/Mat_类型为储存了每个数据项的类型信息的Mat类型;Mat_类型可以用(row, col)方式来访问;这种访问等同于在Mat类型中用at(row, col)来进行访问
具体解释见代码注释:
//! [scan-random]
Mat& ScanImageAndReduceRandomAccess(Mat& I, const uchar* const table)
{CV_Assert(I.depth() == CV_8U);const int channels{ I.channels() };switch (channels){case 1:{for (int i{ 0 }; i < I.rows; ++i)for (int j{ 0 }; j < I.cols; ++j)//at函数获取矩阵在i行j列的数据项的地址,并返回它的引用I.at<uchar>(i, j) = table[I.at<uchar>(i, j)];break;}case 3:{//Vec3b类型为OpenCV中定义的3字节数据类型,用来储存长度为3的uchar类型数组正好Mat_<Vec3b> _I = I; //这里不能用C++20的初始化语法,会报错:无法转化参数类型for (int i{ 0 }; i < I.rows; ++i)for (int j{ 0 }; j < I.cols; ++j){_I(i, j)[0] = table[_I(i, j)[0]];_I(i, j)[1] = table[_I(i, j)[1]];_I(i, j)[2] = table[_I(i, j)[2]];}I = _I;break;}}return I;
}
//! [scan-random]
LUT方法
在OpenCV的core模块,有一个专门用来修改图片数据矩阵中的值的方法,cv::LUT()。其具体用法如下:
//! [table-init]
Mat lookUpTable(1, 256, CV_8U); //用Mat构造函数创建uchar类型的1维矩阵
uchar* p = lookUpTable.ptr(); //获取矩阵的首地址
for (int i = 0; i < 256; ++i)p[i] = table[i]; //将之前计算的查询表中的数值复制到矩阵中
//! [table-init]//! [table-use]
LUT(I, lookUpTable, J);
//! [table-use]
cv::LUT()函数使用了3个参数
- 需要进行修改的原始矩阵,输入矩阵
- 查询表矩阵
- 接收修改结果的矩阵,输出矩阵
该函数没有返回值。只用一条语句,就实现了用查询表矩阵中的缩减值替换输入矩阵中的原始值,然后输出替换结果。
算法效率对比
将3种方法整合在一个cpp文件中,并统计每个方法的运算时长。整合后的代码如下:
#include <opencv2/core.hpp>
#include <opencv2/core/utility.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>import <iostream>;
import <sstream>;using namespace cv;
using namespace std;static void help()
{std::cout<< "\n--------------------------------------------------------------------------" << endl<< "这个程序展示如何在OpenCV中扫描图片(cv::Mat)"<< "根据输入图片路径以及缩减因子(大于0的整数)" << endl<< "这个程序分别使用了C风格方法、迭代器方法、坐标方法和LUT方法进行扫描" << endl<< "参数输入:" << endl<< "程序名 <图片路径> <缩减因子> [G]" << endl<< "如果加了参数G,则使用灰度格式读入图片" << endl<< "--------------------------------------------------------------------------" << endl<< endl;
}Mat& ScanImageAndReduceC(Mat& I, const uchar* table);
Mat& ScanImageAndReduceIterator(Mat& I, const uchar* table);
Mat& ScanImageAndReduceRandomAccess(Mat& I, const uchar* table);int main(int argc, char* argv[])
{help();if (argc < 3){std::cout << "缺少参数" << endl;return -1;}Mat I, J;if (argc == 4 && !strcmp(argv[3], "G"))I = imread(argv[1], IMREAD_GRAYSCALE);elseI = imread(argv[1], IMREAD_COLOR);if (I.empty()){std::cout << "图片" << argv[1] << "打不开。" << endl;return -1;}//! [dividewith]int divideWith{ 0 };stringstream s;s << argv[2];s >> divideWith;if (!s || !divideWith){std::cout << "无效缩减因子。 " << endl;return -1;}uchar table[256];for (int i{ 0 }; i < 256; ++i)table[i] = static_cast<uchar>(divideWith * (i / divideWith));//! [dividewith]const int times{ 100 };double t;t = static_cast<double>(getTickCount());for (int i{ 0 }; i < times; ++i){Mat clone_i{ I.clone() };J = ScanImageAndReduceC(clone_i, table);}t = 1000 * (static_cast<double>(getTickCount()) - t) / getTickFrequency();t /= times;std::cout << "C风格方法每运行"<< times << "次平均耗时: " << t << "毫秒。" << endl;t = static_cast<double>(getTickCount());for (int i{ 0 }; i < times; ++i){Mat clone_i{ I.clone() };J = ScanImageAndReduceIterator(clone_i, table);}t = 1000 * (static_cast<double>(getTickCount()) - t) / getTickFrequency();t /= times;std::cout << "迭代器方法每运行"<< times << "次平均耗时:" << t << "毫秒。" << endl;t = static_cast<double>(getTickCount());for (int i{ 0 }; i < times; ++i){Mat clone_i{ I.clone() };ScanImageAndReduceRandomAccess(clone_i, table);}t = 1000 * (static_cast<double>(getTickCount()) - t) / getTickFrequency();t /= times;std::cout << "坐标方法每运行"<< times << "次平均耗时:" << t << "毫秒。" << endl;//! [table-init]Mat lookUpTable(1, 256, CV_8U);uchar* p{ lookUpTable.ptr() };for (int i{ 0 }; i < 256; ++i)p[i] = table[i];//! [table-init]t = static_cast<double>(getTickCount());for (int i{ 0 }; i < times; ++i)//! [table-use]LUT(I, lookUpTable, J);//! [table-use]t = 1000 * (static_cast<double>(getTickCount()) - t) / getTickFrequency();t /= times;std::cout << "LUT方法每运行 "<< times << "次平均耗时:" << t << "毫秒。" << endl;return 0;
}//! [scan-c]
Mat& ScanImageAndReduceC(Mat& I, const uchar* const table)
{// accept only char type matricesCV_Assert(I.depth() == CV_8U);int channels{ I.channels() };int nRows{ I.rows };int nCols{ I.cols * channels };if (I.isContinuous()){nCols *= nRows;nRows = 1;}int i, j;uchar* p;for (i = 0; i < nRows; ++i){p = I.ptr<uchar>(i);for (j = 0; j < nCols; ++j){p[j] = table[p[j]];}}return I;
}
//! [scan-c]//! [scan-iterator]
Mat& ScanImageAndReduceIterator(Mat& I, const uchar* const table)
{// accept only char type matricesCV_Assert(I.depth() == CV_8U);const int channels{ I.channels() };switch (channels){case 1:{MatIterator_<uchar> it, end;for (it = I.begin<uchar>(), end = I.end<uchar>(); it != end; ++it)*it = table[*it];break;}case 3:{MatIterator_<Vec3b> it, end;for (it = I.begin<Vec3b>(), end = I.end<Vec3b>(); it != end; ++it){(*it)[0] = table[(*it)[0]];(*it)[1] = table[(*it)[1]];(*it)[2] = table[(*it)[2]];}}}return I;
}
//! [scan-iterator]//! [scan-random]
Mat& ScanImageAndReduceRandomAccess(Mat& I, const uchar* const table)
{// accept only char type matricesCV_Assert(I.depth() == CV_8U);const int channels{ I.channels() };switch (channels){case 1:{for (int i{ 0 }; i < I.rows; ++i)for (int j{ 0 }; j < I.cols; ++j)I.at<uchar>(i, j) = table[I.at<uchar>(i, j)];break;}case 3:{Mat_<Vec3b> _I = I;for (int i{ 0 }; i < I.rows; ++i)for (int j{ 0 }; j < I.cols; ++j){_I(i, j)[0] = table[_I(i, j)[0]];_I(i, j)[1] = table[_I(i, j)[1]];_I(i, j)[2] = table[_I(i, j)[2]];}I = _I;break;}}return I;
}
//! [scan-random]
要调试程序,需要输入参数的main函数。在VS中,可以在项目属性中提前输入参数,如下图中黑体的th.jpg 10,就是用空格隔开的两个参数。注意第一个参数可以不用输入,默认为项目程序名。

我这里使用的th.jpg,是一个1920*1200的图片。这是为了测试处理比较大的图片的时候各种方法的性能。
调试运行结果为:

结论
从运行结果可以看到用时最短的是LUT方法,这是因为OpenCV库使用了多线程方法加快了运行速度。但这并不代表LUT方法永远是最好的,其他方法的优缺点如下:
- 给简单的小图片写扫描程序的时候,用C风格的数组方法更好,因为没必要动用多线程;
- 迭代器方法更安全,但是相对较慢
- 坐标的方法需要获取动态引用,是在调试模式中耗时最多的方法,但是在发行模式中可能会比迭代器方法更快;不过肯定没有迭代器方法安全
** 文章较长,不免有遗漏或笔误,欢迎大家指正!**
相关文章:
【OpenCV C++20 学习笔记】扫描图片数据
扫描图片数据 应用情景图像数据扫描的难点颜色空间缩减(color space reduction)查询表 扫描算法计算查询表统计运算时长连续内存3种扫描方法C风格的扫描方法迭代器方法坐标方法LUT方法 算法效率对比结论 应用情景 图像数据扫描的难点 在上一篇文章《基…...
LeetCode:爬楼梯(C语言)
1、问题概述:每次可以爬 1 或 2 个台阶。有多少种不同的方法可以爬到楼顶 2、示例 示例 1: 输入:n 2 输出:2 解释:有两种方法可以爬到楼顶。 1. 1 阶 1 阶 2. 2 阶 示例 2: 输入:n 3 输出&a…...
银河麒麟(arm64)环境下通过docker安装postgis3,并实现数据整体迁移
银河麒麟(arm64)环境下通过docker安装postgis3,并实现数据整体迁移 硬件配置:麒麟9006C 系统环境:银河麒麟桌面版v10 sp1 数据库:postgresql11+postgis3.0 具体的步骤参考https://blog.csdn.net/qq_34817440/article/details/103914574 -----主要操作-----------------…...
C语言 | Leetcode C语言题解之第278题第一个错误的版本
题目: 题解: int firstBadVersion(int n) {int left 1, right n;while (left < right) { // 循环直至区间左右端点相同int mid left (right - left) / 2; // 防止计算时溢出if (isBadVersion(mid)) {right mid; // 答案在区间 [left, mid] 中…...
京东科技集团将在香港发行与港元1:1挂钩的加密货币稳定币
据京东科技集团旗下公司京东币链科技(香港)官网信息,京东稳定币是一种基于公链并与港元(HKD) 1:1挂钩的稳定币,将在公共区块链上发行,其储备由高度流动且可信的资产组成,这些资产安全存放于持牌金融机构的独立账户中,通…...
Vue 实现电子签名并生成签名图片
目录 前言项目结构代码实现 安装依赖创建签名画布组件生成签名图片 总结相关阅读 1. 前言 电子签名在现代Web应用中越来越普遍,例如合同签署、确认表单等。本文将介绍如何使用Vue.js实现一个简单的电子签名功能,并将签名生成图片。 2. 项目结构 项…...
Visual Studio 2022美化
说明: VS版本:Visual Studio Community 2022 背景美化 【扩展】【管理扩展】搜索“ClaudiaIDE”,【下载】,安装完扩展要重启VS 在wallhaven下载壁纸图片作为文本编辑器区域背景图片 【工具】【选项】搜索ClaudiaIDEÿ…...
[CISCN2019 华东南赛区]Web11
进来先做信息收集,右上角显示当前ip,然后有api的调用地址和请求包的格式以及最重要的是最下面的smarty模版,一看到这个就得想到smarty模版注入 测试了一下两个api都无法访问 直接切到数据包看看能不能通过XFF来修改右上角ip 成功修改&#x…...
【图形图像-1】SDF
在图形图像处理中,SDF(Signed Distance Field,带符号的距离场)是一种表示图形轮廓和空间距离的数学结构。它通常用于计算机图形学、文本渲染、碰撞检测和物理模拟等领域。 SDF(Signed Distance Field,带符号…...
苍穹外卖01
0. 配置maven (仅一次的操作 1.项目导入idea 2. 保证nginx服务器运行 (nginx.exe要在非中文的目录下) 开启服务: start nginx 查看任务进程是否存在: tasklist /fi "imagename eq nginx.exe" 关闭ngi…...
ElasticSearch(三)—文档字段参数设置以及元字段
一、 字段参数设置 analyzer: 指定分词器。elasticsearch 是一款支持全文检索的分布式存储系统,对于 text类型的字段,首先会使用分词器进行分词,然后将分词后的词根一个一个存储在倒排索引中,后续查询主要是针对词根…...
ARM功耗管理之压力测试和PM_DEBUG实验
安全之安全(security)博客目录导读 ARM功耗管理精讲与实战汇总参见:Arm功耗管理精讲与实战 思考:睡眠唤醒实验?压力测试?Suspend-to-Idle/RAM/Disk演示? 1、实验环境准备 2、软件代码准备 3、唤醒源 4、Suspend-…...
ESP8266用AT指令实现连接MQTT
1准备工作 硬件(ESP8266)连接电脑 硬件已经烧入了MQTT透传固件 2实现连接 2-1(进入AT模式) 打开串口助手发送如下指令 AT 2-2(复位) ATRST 2-3(开启DHCP,自动获取IP&#x…...
人工智能与机器学习原理精解【5】
文章目录 最优化基础理论特征值(Eigenvalue)特征向量(Eigenvector)特征值和特征向量的重要性计算方法特征值一、特征值分解的定义二、特征值分解的算法三、特征值分解的例子 正定矩阵Hessian矩阵的特征值Hessian矩阵的含义Hessian…...
为什么用LeSS?
实现适应性 LeSS是一个产品开发的组织系统,旨在最大化一个组织的适应性。关于适应性(或者敏捷性,也就是敏捷开发的初衷)我们是指优化: 以相对低的成本改变方向的能力,主要是基于通过频繁交付产生的探索。从…...
力扣高频SQL 50题(基础版)第七题
文章目录 力扣高频SQL 50题(基础版)第七题1068. 产品销售分析 I题目说明思路分析实现过程准备数据:实现方式:结果截图:总结: 力扣高频SQL 50题(基础版)第七题 1068. 产品销售分析 I 题目说明 …...
【音视频】一篇文章区分直播与点播、推流与拉流
文章目录 前言直播和点播的概念及区别直播是什么点播是什么 直播和点播的区别举例说明推流与拉流推流是什么拉流是什么 推流与拉流的区别举例说明 总结 前言 在音视频领域,直播、点播、推流和拉流是常见的概念,每个术语都有其特定的含义和应用场景。了解…...
3d动画软件blender如何汉化?(最新版本4.2)
前言 Blender是一个非常强大的3d动画软件,总能受到大量工作者的青睐。 但是,对于新手来说(尤其是英语学渣),语言是个难事。大部分blender打开时都是英文,对新手使用具有一定的障碍。因此,我们需…...
C++学习笔记04-补充知识点(问题-解答自查版)
前言 以下问题以Q&A形式记录,基本上都是笔者在初学一轮后,掌握不牢或者频繁忘记的点 Q&A的形式有助于学习过程中时刻关注自己的输入与输出关系,也适合做查漏补缺和复盘。 本文对读者可以用作自查,答案在后面࿰…...
Vue el-table的自定义排序返回值为null,设置刷新页面保持排序标志,导航时elementui组件不更新
自定义排序使用sort-change"sortChange"监听,表列需设置为sortable“custom”(自定义) <el-table:data"tableData"bordersort-change"sortChange":default-sort"{prop:sortProp,order:sortOrder}&quo…...
OpenLayers 可视化之热力图
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 热力图(Heatmap)又叫热点图,是一种通过特殊高亮显示事物密度分布、变化趋势的数据可视化技术。采用颜色的深浅来显示…...
内存分配函数malloc kmalloc vmalloc
内存分配函数malloc kmalloc vmalloc malloc实现步骤: 1)请求大小调整:首先,malloc 需要调整用户请求的大小,以适应内部数据结构(例如,可能需要存储额外的元数据)。通常,这包括对齐调整,确保分配的内存地址满足特定硬件要求(如对齐到8字节或16字节边界)。 2)空闲…...
7.4.分块查找
一.分块查找的算法思想: 1.实例: 以上述图片的顺序表为例, 该顺序表的数据元素从整体来看是乱序的,但如果把这些数据元素分成一块一块的小区间, 第一个区间[0,1]索引上的数据元素都是小于等于10的, 第二…...
前端导出带有合并单元格的列表
// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…...
MySQL用户和授权
开放MySQL白名单 可以通过iptables-save命令确认对应客户端ip是否可以访问MySQL服务: test: # iptables-save | grep 3306 -A mp_srv_whitelist -s 172.16.14.102/32 -p tcp -m tcp --dport 3306 -j ACCEPT -A mp_srv_whitelist -s 172.16.4.16/32 -p tcp -m tcp -…...
DingDing机器人群消息推送
文章目录 1 新建机器人2 API文档说明3 代码编写 1 新建机器人 点击群设置 下滑到群管理的机器人,点击进入 添加机器人 选择自定义Webhook服务 点击添加 设置安全设置,详见说明文档 成功后,记录Webhook 2 API文档说明 点击设置说明 查看自…...
Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...
mac 安装homebrew (nvm 及git)
mac 安装nvm 及git 万恶之源 mac 安装这些东西离不开Xcode。及homebrew 一、先说安装git步骤 通用: 方法一:使用 Homebrew 安装 Git(推荐) 步骤如下:打开终端(Terminal.app) 1.安装 Homebrew…...
从面试角度回答Android中ContentProvider启动原理
Android中ContentProvider原理的面试角度解析,分为已启动和未启动两种场景: 一、ContentProvider已启动的情况 1. 核心流程 触发条件:当其他组件(如Activity、Service)通过ContentR…...
Xela矩阵三轴触觉传感器的工作原理解析与应用场景
Xela矩阵三轴触觉传感器通过先进技术模拟人类触觉感知,帮助设备实现精确的力测量与位移监测。其核心功能基于磁性三维力测量与空间位移测量,能够捕捉多维触觉信息。该传感器的设计不仅提升了触觉感知的精度,还为机器人、医疗设备和制造业的智…...
