【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…...
一起笨笨的学C ——16链表基础
目录 目录 前言 正文 链表定义: 基本创建链表程序: 链表结点插入: 对角线记忆法: 画图理解法: 链表结点删除: 链表销毁: 后语 前言 链表理解方法分享,愿你的大脑也能建立一个…...
信息学奥赛一本通1917:【01NOIP普及组】装箱问题
1917:【01NOIP普及组】装箱问题 时间限制: 1000 ms 内存限制: 65536 KB 提交数: 4178 通过数: 2473 【题目描述】 有一个箱子容量为VV(正整数,0≤V≤200000≤V≤20000),同时有n个物品(0≤n≤300≤n≤30),…...
android user 版本如何手动触发dump
项目需要在android user版本增加手动触发dump方法,用以确认user版本发生dump后系统是重启还是真正发生dump卡机! 本文以qcom平台项目为例描述所做的修改,留下足迹以备后忘。 闲言少叙,开整上干货: 一、修改bin文件 …...
RedHat Linux 7.5 安装 mssql-server
RedHat Linux 7.5 安装 mssql-server 1、安装部署所需的依赖包 [rootlocalhost ~]# yum -y install libatomic bzip2 gdb cyrus-sasl cyrus-sasl-gssapi Loaded plugins: ulninfo Resolving Dependencies --> Running transaction check ---> Package bzip2.x86_64 0:1…...
Vue的SSR和预渲染:提升首屏加载速度与SEO效果
引言 在现代Web应用开发中,首屏加载速度和搜索引擎优化(SEO)是衡量应用性能的重要指标。Vue.js 作为流行的前端框架,提供了服务器端渲染(SSR)和预渲染(prerendering)两种技术来提升这些指标。本文将深入探讨如何使用 Vue 的 SSR 和预渲染技术,提供详细的代码示例和最…...
若依ruoyi+AI项目二次开发(智能售货机运营管理系统)
(一) 帝可得 - 产品原型 - 腾讯 CoDesign (qq.com)...
【SpringBoot】 4 Thymeleaf
官网 https://www.thymeleaf.org/ 介绍 Thymeleaf 是一个适用于 Web 和独立环境的现代服务器端 Java 模板引擎。 模板引擎:为了使用户界面和业务数据分离而产生的,它可以生成特定格式的文档,用于网站的模板引擎会生成一个标准的 html 文档…...
动静资源的转发操作
目录 Nginx中的location指令 静态资源的转发 动态资源的转发 注意事项 深入研究 如何在Nginx中实现对特定后缀文件的静态资源进行反向代理? Nginx中location指令的优先级是怎样确定的? 为什么在使用proxy_pass时要区分是否带有斜杠? N…...
Windows系统安全加固方案:快速上手系统加固指南(上)
无论是个人用户、小型企业还是大型机构,都需要采取措施保护其计算机系统免受各种威胁、系统加固常见的应用场景有个人用户、 AWD 比赛、公共机构以及企业环境等等 文档目录 一、Windows常用命令二、Windows常见端口三、账户安全3.1 默认账户安全3.2 按照用户分配账户…...
git连接远程仓库
一、本地新建代码,上传到远程仓库 1.git init #初始化本地仓库 2.git remote -v #查看当前仓库的远程地址 3.git remote add origin 远程仓库的URL 4.git branch master / git branch dev 创建 主分支或者 dev 分支 5.git checkout master/dev. 切换到主分支或者dev 分支…...
算法-----递归~~搜索~~回溯(宏观认识)
目录 1.什么是递归 1.1二叉树的遍历 1.2快速排序 1.3归并排序 2.为什么会用到递归 3.如何理解递归 4.如何写好一个递归 5.什么是搜索 5.1深度(dfs)优先遍历&优先搜索 5.2宽度(bfs)优先遍历&优先搜索 6.回溯 1.什…...
【云原生】Docker搭建知识库文档协作平台Confluence
目录 一、前言 二、企业级知识库文档工具部署形式 2.1 开源工具平台 2.1.1 开源工具优点 2.1.2 开源工具缺点 2.2 私有化部署 2.3 混合部署 三、如何选择合适的知识库平台工具 3.1 明确目标和需求 3.2 选择合适的知识库平台工具 四、Confluence介绍 4.2 confluence特…...
序列化与反序列化的本质
1. 将对象存储到本地 假如有一个student类,我们定义了好几个对象,想要把这些对象存储下来,该怎么办呢 from typing import List class Student:name: strage: intphones: List[str] s1 Student("xiaoming",10,["huawei&quo…...
飞牛爬虫FlyBullSpider 一款简单方便强大的爬虫,限时免费 特别适合小白!用它爬下Boss的2024年7月底Java岗位,分析一下程序员就业市场行情
一、下载安装FlyBullSpider 暂时支持Window,现在只在Win11上做过测试 1 百度 点击百度网盘 下载 链接:https://pan.baidu.com/s/1gSLKYuezaZgd8iqrXhk8Kg 提取码:Fly6 2 csdn https://download.csdn.net/download/fencer911/89584687 二、体验初…...
EXCEL 排名(RANK,COUNTIFS)
1.单列排序 需求描述:如有下面表格,需要按笔试成绩整体排名。 解决步骤: 我们使用RANK函数即可实现单列整体排名。 Number 选择第一列。 Ref 选择这一整列(CtrlShift向下箭头、再按F4)。 "确定"即可计算…...
【踩坑系列-JS】iframe中的url参数获取
Author:赵志乾 Date:2024-07-24 Declaration:All Right Reserved!!! 1. 问题描述 系统A的页面中以iframe的方式嵌入了系统B的页面,并需要将A页面url中的参数传递给B页面。 最初的实现方式是&am…...
测试工作中常听到的名词解释 : )
背景 很多名称其实看字面意思都挺抽象的,有时看群里的测试大佬在不停蹦这类术语,感觉很高大上,但其实很多你应该是知道的,只不过没想到别人是这样叫它的。又或者你的主编程语言不是 Java,所以看不懂他们在讲啥&#x…...
Linux内网离线用rsync和inotify-tools实现文件夹文件单向同步和双向同步
lsyncd实现方式可参考:https://www.jianshu.com/p/c075ccf89516 安装文件下载:相关文件下载 rsync默认都有,所以没有提供。 服务端和客户端均操作 服务端:双向同步其实都是服务端,只是单向同步时稍有区别 客户端&am…...
Spring Security学习笔记(二)Spring Security认证和鉴权
前言:本系列博客基于Spring Boot 2.6.x依赖的Spring Security5.6.x版本 上一篇博客介绍了Spring Security的整体架构,本篇博客要讲的是Spring Security的认证和鉴权两个重要的机制。 UsernamePasswordAuthenticationFilter和BasicAuthenticationFilter是…...
产品经理NPDP好考吗?
NPDP是新产品开发专业人员的资格认证,对于希望在产品管理领域取得认可的专业人士来说,NPDP认证是一项重要的资格。 那么,产品经理考取NPDP资格认证究竟难不难呢? 首先,NPDP考试的难易程度取决于考生的背景和准备情况…...
【C++】:红黑树的应用 --- 封装map和set
点击跳转至文章:【C】:红黑树深度剖析 — 手撕红黑树! 目录 前言一,红黑树的改造1. 红黑树的主体框架2. 对红黑树节点结构的改造3. 红黑树的迭代器3.1 迭代器类3.2 Begin() 和 End() 四,红黑树相关接口的改造4.1 Find…...
unity美术资源优化(资源冗余,主界面图集过多)
图片资源冗余: UPR unity的性能优化工具检查资源 1.检查纹理读/写标记 开启纹理资源的读/写标志会导致双倍的内存占用 检查Inspector -> Advanced -> Read/Write Enabled选项 2.检查纹理资源alpha通道 如果纹理的alpha通道全部为0,或者全部为2…...
【git】github中的Pull Request是什么
在 Git 中,"pull request"(简称 PR)是一种在分布式版本控制系统中使用的功能,特别是在使用 GitHub、GitLab、Bitbucket 等基于 Git 的代码托管平台时。Pull Request 允许开发者请求将他们的代码更改合并到另一个分支&am…...
gitlab查询分支API显示不全,只有20个问题
背景 gitlab查询分支API需要查询所有分支,且分支数量大于20,但目前调用接口返回的branch最多就显示了20个 解决方案 根据GitLab的文档,查询分支API默认最多返回20个分支。如果要一次性显示80个分支,可以使用分页参数来获取所有…...
vue3+vite 实现动态引入某个文件夹下的组件 - glob-import的使用
<template><div class"user-content"><HeaderTitle title"用户详情"></HeaderTitle><div class"main-content"><div><UserForm /></div><div><TableList></TableList></d…...
hhhhh
x torch.tensor([1.0,0.],[-1.,1.],requires_gradTrue) z x.pow(2).sum() z.backward() x.grad在这段代码中,我们利用 PyTorch 进行自动求梯度,下面详细解释代码的每一个部分及其在反向传播中的作用。同时,我们也将介绍函数对象和叶子节点的…...
扫雷小游戏纯后端版
package com.wind;import java.util.Random; import java.util.Scanner;public class ResultLei {static Random random new Random();public static void main(String[] args) {boolean end true;while (end) {System.out.println("请输入你选择的难度对应的数字&#…...
RuoYi-Vue-Plus(动态添加移除数据源)
一、添加数据 private final DynamicRoutingDataSource dynamicRoutingDataSource;private final DefaultDataSourceCreator dataSourceCreator;//添加一个dynamic的数据源@GetMapping("createDynamic")public void createDynamic() {DataSourceProperty property =…...
idea启动项目报:the command line via JAR manifest or via a classpath file and rerun.
解决方案 1.打开Edit Configurations,进去编辑,如下: 笔记配置 2.选择Modfiy options,点击Shorten command line 3.在新增的Shorten command line选项中选择JAR manifest或classpath file 4.点击保存后即可...
vue3 + ts中有哪些类型是由vue3提供的?
在 Vue 3 中结合 TypeScript 使用时,Vue 提供了一系列的类型帮助函数和接口,这些类型用于增强 TypeScript 的集成和提供类型安全。以下是一些由 Vue 3 提供的常用 TypeScript 类型: RefType: 用于标注一个 ref 返回的响应式引用类型。Reacti…...