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

【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,就是用空格隔开的两个参数。注意第一个参数可以不用输入,默认为项目程序名。
输入main函数参数
我这里使用的th.jpg,是一个1920*1200的图片。这是为了测试处理比较大的图片的时候各种方法的性能。
调试运行结果为:
图片扫描结果

结论

从运行结果可以看到用时最短的是LUT方法,这是因为OpenCV库使用了多线程方法加快了运行速度。但这并不代表LUT方法永远是最好的,其他方法的优缺点如下:

  • 给简单的小图片写扫描程序的时候,用C风格的数组方法更好,因为没必要动用多线程;
  • 迭代器方法更安全,但是相对较慢
  • 坐标的方法需要获取动态引用,是在调试模式中耗时最多的方法,但是在发行模式中可能会比迭代器方法更快;不过肯定没有迭代器方法安全

** 文章较长,不免有遗漏或笔误,欢迎大家指正!**

相关文章:

【OpenCV C++20 学习笔记】扫描图片数据

扫描图片数据 应用情景图像数据扫描的难点颜色空间缩减&#xff08;color space reduction&#xff09;查询表 扫描算法计算查询表统计运算时长连续内存3种扫描方法C风格的扫描方法迭代器方法坐标方法LUT方法 算法效率对比结论 应用情景 图像数据扫描的难点 在上一篇文章《基…...

LeetCode:爬楼梯(C语言)

1、问题概述&#xff1a;每次可以爬 1 或 2 个台阶。有多少种不同的方法可以爬到楼顶 2、示例 示例 1&#xff1a; 输入&#xff1a;n 2 输出&#xff1a;2 解释&#xff1a;有两种方法可以爬到楼顶。 1. 1 阶 1 阶 2. 2 阶 示例 2&#xff1a; 输入&#xff1a;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题第一个错误的版本

题目&#xff1a; 题解&#xff1a; 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挂钩的加密货币稳定币

据京东科技集团旗下公司京东币链科技(香港)官网信息&#xff0c;京东稳定币是一种基于公链并与港元(HKD) 1:1挂钩的稳定币&#xff0c;将在公共区块链上发行&#xff0c;其储备由高度流动且可信的资产组成&#xff0c;这些资产安全存放于持牌金融机构的独立账户中&#xff0c;通…...

Vue 实现电子签名并生成签名图片

目录 前言项目结构代码实现 安装依赖创建签名画布组件生成签名图片 总结相关阅读 1. 前言 电子签名在现代Web应用中越来越普遍&#xff0c;例如合同签署、确认表单等。本文将介绍如何使用Vue.js实现一个简单的电子签名功能&#xff0c;并将签名生成图片。 2. 项目结构 项…...

Visual Studio 2022美化

说明&#xff1a; VS版本&#xff1a;Visual Studio Community 2022 背景美化 【扩展】【管理扩展】搜索“ClaudiaIDE”&#xff0c;【下载】&#xff0c;安装完扩展要重启VS 在wallhaven下载壁纸图片作为文本编辑器区域背景图片 【工具】【选项】搜索ClaudiaIDE&#xff…...

[CISCN2019 华东南赛区]Web11

进来先做信息收集&#xff0c;右上角显示当前ip&#xff0c;然后有api的调用地址和请求包的格式以及最重要的是最下面的smarty模版&#xff0c;一看到这个就得想到smarty模版注入 测试了一下两个api都无法访问 直接切到数据包看看能不能通过XFF来修改右上角ip 成功修改&#x…...

【图形图像-1】SDF

在图形图像处理中&#xff0c;SDF&#xff08;Signed Distance Field&#xff0c;带符号的距离场&#xff09;是一种表示图形轮廓和空间距离的数学结构。它通常用于计算机图形学、文本渲染、碰撞检测和物理模拟等领域。 SDF&#xff08;Signed Distance Field&#xff0c;带符号…...

苍穹外卖01

0. 配置maven (仅一次的操作 1.项目导入idea 2. 保证nginx服务器运行 &#xff08;nginx.exe要在非中文的目录下&#xff09; 开启服务&#xff1a; start nginx 查看任务进程是否存在&#xff1a; tasklist /fi "imagename eq nginx.exe" 关闭ngi…...

ElasticSearch(三)—文档字段参数设置以及元字段

一、 字段参数设置 analyzer&#xff1a; 指定分词器。elasticsearch 是一款支持全文检索的分布式存储系统&#xff0c;对于 text类型的字段&#xff0c;首先会使用分词器进行分词&#xff0c;然后将分词后的词根一个一个存储在倒排索引中&#xff0c;后续查询主要是针对词根…...

ARM功耗管理之压力测试和PM_DEBUG实验

安全之安全(security)博客目录导读 ARM功耗管理精讲与实战汇总参见&#xff1a;Arm功耗管理精讲与实战 思考&#xff1a;睡眠唤醒实验&#xff1f;压力测试&#xff1f;Suspend-to-Idle/RAM/Disk演示&#xff1f; 1、实验环境准备 2、软件代码准备 3、唤醒源 4、Suspend-…...

ESP8266用AT指令实现连接MQTT

1准备工作 硬件&#xff08;ESP8266&#xff09;连接电脑 硬件已经烧入了MQTT透传固件 2实现连接 2-1&#xff08;进入AT模式&#xff09; 打开串口助手发送如下指令 AT 2-2&#xff08;复位&#xff09; ATRST 2-3&#xff08;开启DHCP&#xff0c;自动获取IP&#x…...

人工智能与机器学习原理精解【5】

文章目录 最优化基础理论特征值&#xff08;Eigenvalue&#xff09;特征向量&#xff08;Eigenvector&#xff09;特征值和特征向量的重要性计算方法特征值一、特征值分解的定义二、特征值分解的算法三、特征值分解的例子 正定矩阵Hessian矩阵的特征值Hessian矩阵的含义Hessian…...

为什么用LeSS?

实现适应性 LeSS是一个产品开发的组织系统&#xff0c;旨在最大化一个组织的适应性。关于适应性&#xff08;或者敏捷性&#xff0c;也就是敏捷开发的初衷&#xff09;我们是指优化&#xff1a; 以相对低的成本改变方向的能力&#xff0c;主要是基于通过频繁交付产生的探索。从…...

力扣高频SQL 50题(基础版)第七题

文章目录 力扣高频SQL 50题&#xff08;基础版&#xff09;第七题1068. 产品销售分析 I题目说明思路分析实现过程准备数据&#xff1a;实现方式&#xff1a;结果截图:总结&#xff1a; 力扣高频SQL 50题&#xff08;基础版&#xff09;第七题 1068. 产品销售分析 I 题目说明 …...

【音视频】一篇文章区分直播与点播、推流与拉流

文章目录 前言直播和点播的概念及区别直播是什么点播是什么 直播和点播的区别举例说明推流与拉流推流是什么拉流是什么 推流与拉流的区别举例说明 总结 前言 在音视频领域&#xff0c;直播、点播、推流和拉流是常见的概念&#xff0c;每个术语都有其特定的含义和应用场景。了解…...

3d动画软件blender如何汉化?(最新版本4.2)

前言 Blender是一个非常强大的3d动画软件&#xff0c;总能受到大量工作者的青睐。 但是&#xff0c;对于新手来说&#xff08;尤其是英语学渣&#xff09;&#xff0c;语言是个难事。大部分blender打开时都是英文&#xff0c;对新手使用具有一定的障碍。因此&#xff0c;我们需…...

C++学习笔记04-补充知识点(问题-解答自查版)

前言 以下问题以Q&A形式记录&#xff0c;基本上都是笔者在初学一轮后&#xff0c;掌握不牢或者频繁忘记的点 Q&A的形式有助于学习过程中时刻关注自己的输入与输出关系&#xff0c;也适合做查漏补缺和复盘。 本文对读者可以用作自查&#xff0c;答案在后面&#xff0…...

Vue el-table的自定义排序返回值为null,设置刷新页面保持排序标志,导航时elementui组件不更新

自定义排序使用sort-change"sortChange"监听&#xff0c;表列需设置为sortable“custom”&#xff08;自定义&#xff09; <el-table:data"tableData"bordersort-change"sortChange":default-sort"{prop:sortProp,order:sortOrder}&quo…...

一起笨笨的学C ——16链表基础

目录 目录 前言 正文 链表定义&#xff1a; 基本创建链表程序&#xff1a; 链表结点插入&#xff1a; 对角线记忆法&#xff1a; 画图理解法&#xff1a; 链表结点删除&#xff1a; 链表销毁&#xff1a; 后语 前言 链表理解方法分享&#xff0c;愿你的大脑也能建立一个…...

信息学奥赛一本通1917:【01NOIP普及组】装箱问题

1917&#xff1a;【01NOIP普及组】装箱问题 时间限制: 1000 ms 内存限制: 65536 KB 提交数: 4178 通过数: 2473 【题目描述】 有一个箱子容量为VV(正整数&#xff0c;0≤V≤200000≤V≤20000&#xff09;&#xff0c;同时有n个物品&#xff08;0≤n≤300≤n≤30),…...

android user 版本如何手动触发dump

项目需要在android user版本增加手动触发dump方法&#xff0c;用以确认user版本发生dump后系统是重启还是真正发生dump卡机&#xff01; 本文以qcom平台项目为例描述所做的修改&#xff0c;留下足迹以备后忘。 闲言少叙&#xff0c;开整上干货&#xff1a; 一、修改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 模板引擎。 模板引擎&#xff1a;为了使用户界面和业务数据分离而产生的&#xff0c;它可以生成特定格式的文档&#xff0c;用于网站的模板引擎会生成一个标准的 html 文档…...

动静资源的转发操作

目录 Nginx中的location指令 静态资源的转发 动态资源的转发 注意事项 深入研究 如何在Nginx中实现对特定后缀文件的静态资源进行反向代理&#xff1f; Nginx中location指令的优先级是怎样确定的&#xff1f; 为什么在使用proxy_pass时要区分是否带有斜杠&#xff1f; N…...

Windows系统安全加固方案:快速上手系统加固指南(上)

无论是个人用户、小型企业还是大型机构&#xff0c;都需要采取措施保护其计算机系统免受各种威胁、系统加固常见的应用场景有个人用户、 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深度&#xff08;dfs&#xff09;优先遍历&优先搜索 5.2宽度&#xff08;bfs&#xff09;优先遍历&优先搜索 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类&#xff0c;我们定义了好几个对象&#xff0c;想要把这些对象存储下来&#xff0c;该怎么办呢 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 百度 点击百度网盘 下载 链接&#xff1a;https://pan.baidu.com/s/1gSLKYuezaZgd8iqrXhk8Kg 提取码&#xff1a;Fly6 2 csdn https://download.csdn.net/download/fencer911/89584687 二、体验初…...

EXCEL 排名(RANK,COUNTIFS)

1.单列排序 需求描述&#xff1a;如有下面表格&#xff0c;需要按笔试成绩整体排名。 解决步骤&#xff1a; 我们使用RANK函数即可实现单列整体排名。 Number 选择第一列。 Ref 选择这一整列&#xff08;CtrlShift向下箭头、再按F4&#xff09;。 "确定"即可计算…...

【踩坑系列-JS】iframe中的url参数获取

Author&#xff1a;赵志乾 Date&#xff1a;2024-07-24 Declaration&#xff1a;All Right Reserved&#xff01;&#xff01;&#xff01; 1. 问题描述 系统A的页面中以iframe的方式嵌入了系统B的页面&#xff0c;并需要将A页面url中的参数传递给B页面。 最初的实现方式是&am…...

测试工作中常听到的名词解释 : )

背景 很多名称其实看字面意思都挺抽象的&#xff0c;有时看群里的测试大佬在不停蹦这类术语&#xff0c;感觉很高大上&#xff0c;但其实很多你应该是知道的&#xff0c;只不过没想到别人是这样叫它的。又或者你的主编程语言不是 Java&#xff0c;所以看不懂他们在讲啥&#x…...

Linux内网离线用rsync和inotify-tools实现文件夹文件单向同步和双向同步

lsyncd实现方式可参考&#xff1a;https://www.jianshu.com/p/c075ccf89516 安装文件下载&#xff1a;相关文件下载 rsync默认都有&#xff0c;所以没有提供。 服务端和客户端均操作 服务端&#xff1a;双向同步其实都是服务端&#xff0c;只是单向同步时稍有区别 客户端&am…...

Spring Security学习笔记(二)Spring Security认证和鉴权

前言&#xff1a;本系列博客基于Spring Boot 2.6.x依赖的Spring Security5.6.x版本 上一篇博客介绍了Spring Security的整体架构&#xff0c;本篇博客要讲的是Spring Security的认证和鉴权两个重要的机制。 UsernamePasswordAuthenticationFilter和BasicAuthenticationFilter是…...

产品经理NPDP好考吗?

NPDP是新产品开发专业人员的资格认证&#xff0c;对于希望在产品管理领域取得认可的专业人士来说&#xff0c;NPDP认证是一项重要的资格。 那么&#xff0c;产品经理考取NPDP资格认证究竟难不难呢&#xff1f; 首先&#xff0c;NPDP考试的难易程度取决于考生的背景和准备情况…...

【C++】:红黑树的应用 --- 封装map和set

点击跳转至文章&#xff1a;【C】&#xff1a;红黑树深度剖析 — 手撕红黑树&#xff01; 目录 前言一&#xff0c;红黑树的改造1. 红黑树的主体框架2. 对红黑树节点结构的改造3. 红黑树的迭代器3.1 迭代器类3.2 Begin() 和 End() 四&#xff0c;红黑树相关接口的改造4.1 Find…...

unity美术资源优化(资源冗余,主界面图集过多)

图片资源冗余&#xff1a; UPR unity的性能优化工具检查资源 1.检查纹理读/写标记 开启纹理资源的读/写标志会导致双倍的内存占用 检查Inspector -> Advanced -> Read/Write Enabled选项 2.检查纹理资源alpha通道 如果纹理的alpha通道全部为0&#xff0c;或者全部为2…...

【git】github中的Pull Request是什么

在 Git 中&#xff0c;"pull request"&#xff08;简称 PR&#xff09;是一种在分布式版本控制系统中使用的功能&#xff0c;特别是在使用 GitHub、GitLab、Bitbucket 等基于 Git 的代码托管平台时。Pull Request 允许开发者请求将他们的代码更改合并到另一个分支&am…...

gitlab查询分支API显示不全,只有20个问题

背景 gitlab查询分支API需要查询所有分支&#xff0c;且分支数量大于20&#xff0c;但目前调用接口返回的branch最多就显示了20个 解决方案 根据GitLab的文档&#xff0c;查询分支API默认最多返回20个分支。如果要一次性显示80个分支&#xff0c;可以使用分页参数来获取所有…...

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在这段代码中&#xff0c;我们利用 PyTorch 进行自动求梯度&#xff0c;下面详细解释代码的每一个部分及其在反向传播中的作用。同时&#xff0c;我们也将介绍函数对象和叶子节点的…...

扫雷小游戏纯后端版

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&#xff0c;进去编辑&#xff0c;如下&#xff1a; 笔记配置 2.选择Modfiy options,点击Shorten command line 3.在新增的Shorten command line选项中选择JAR manifest或classpath file 4.点击保存后即可...

vue3 + ts中有哪些类型是由vue3提供的?

在 Vue 3 中结合 TypeScript 使用时&#xff0c;Vue 提供了一系列的类型帮助函数和接口&#xff0c;这些类型用于增强 TypeScript 的集成和提供类型安全。以下是一些由 Vue 3 提供的常用 TypeScript 类型&#xff1a; RefType: 用于标注一个 ref 返回的响应式引用类型。Reacti…...