ISP图像信号处理——白平衡校正和标定介绍以及C++实现

从数码相机直接输出的未经过处理过的RAW图到平常看到的JEPG图有一系列复杂的图像信号处理过程,称作ISP(Image Signal Processing)。这个过程会经过图像处理和压缩

参考文章1:http://t.csdn.cn/LvHH5

参考文章2:http://t.csdn.cn/c97t5

参考文章3:http://t.csdn.cn/UbAOu

http://t.csdn.cn/HuI67

参考文章4:http://t.csdn.cn/lwUAc

参考文章5:http://t.csdn.cn/HuI67

参考文章(下面整理的笔记):Understanding White Balance Control - 知乎 (zhihu.com)

人眼具有颜色恒常性,可以避免光源变化带来的颜色变化,但是图像传感器不具备这种特性,从而造成色偏,白平衡就是需要校正这个颜色的偏差。

颜色恒常性是指在照度发生变化的条件下人们对物体表面颜色的知觉趋于稳定的心理倾向。

色温描述的是具有一定表面温度的“黑体”(blackbody)的辐射光的光谱特性。简单的理解就是颜色随温度的变化规律,比如生铁就是黑色,加热会变成橘红色,继续加热到液态会呈现偏白的颜色,这种随温度而产生的颜色变化就光谱特性。

白平衡校正

  • 手动白平衡:在拍照前通过拍摄一个18度灰的卡片,然后计算出当时环境的白平衡增益值对后面的图片进行校正;

  • 自动白平衡:相机通过本身的算法,通过获取的图像自动计算出增益值对图像进行校正的方式。

AWB(Automatic White Balance),自动白平衡理解

为了将拍摄场景中的白色物体在显示时正确还原为白色,首先需要知道真实的白色物体在sensor RGB 空间中呈现什么颜色。常用的方法是通过实验标定标准白色在一些典型色温下呈现的sensor RGB 颜色,然后通过几个标定的色温点可以外推白色在所有可能色温下呈现的颜色,从而建立场景色温与sensor RGB 白色的对应关系,这个对应关系通常用白平衡增益来描述。

白平衡增益通常记为(R/G, B/G),即红色和蓝色相对于绿色的比例。目前对白平衡增益存在两种不同的定义:

1.一部分厂家将(R/G, B/G)定义为sensor捕捉到的图像中红色和蓝色的统计值,因此在高色温(如D65)下B/G大于R/G。

海思的光源的色温曲线(普朗克曲线)表现为下图所示的形状。

通过对不同sensor的色温曲线标定数据可知:在D50-D65之间 R/G、B/G的比值基本相近,相差不大。在低色温的时候,主要是G和B分量在改变,在高色温的时候主要是G和B分量在改变。

以海思的色温曲线为例:从直角双曲线的两个坐标方向来看,在D50-D65这个R/G、B/G为分界点,沿着R/G的方向看,最后应该会有一个B/G的饱和点。同理,沿B/G方向看,R/G也会有一个饱和点,饱和区域的时候其两个坐标方向上看应该呈现一条类似接近直线的曲线段。

或者也可以这么理解:

色温越低,R/G的比值越大,B/G的比值越小,当R/G达到某一阈值后,B/G则缓慢变小直至达到一个最小值而不再改变。

同理,色温越高,B/G的比值越大,R/G的比值越小,当B/G达到某一阈值后,R/G则缓慢变小直至达到一个最小值而不再改变。

直角双曲线靠近坐标的部分与坐标轴近似平行的直线段,比较符合色温曲线的变化规律。

根据G/R和G/B用直角双曲线函数拟合的图像和海思的普朗克曲线十分相似,如下图所示。

在这种定义下,D65光源下的白色具有较大的B/G统计值。

2.另一部分厂家则将(R/G, B/G)定义成为了使sensor捕捉的图像达到白平衡需要施加的增益系数,因此在高色温下B/G小于R/G。

在这种定义下,D65光源下的白色需要较大的R/G增益才能达到白平衡。

以R/G,B/G值代表增益系数,而非图像的统计值,在此定义下,白平衡标定的大致过程是:

  • 以标定好的若干个色温点(A, TL84, D50, D65, D75等)为核心可以拟合出一条色温曲线
  • 以色温曲线上的点为圆心,以一定半径画椭圆,可以得到一些列椭圆。
  • 所有椭圆所覆盖的(R/G,B/G)集合就认为是真实场景中的白色物体在全部色温下的可能值。不落在这个范围内的颜色不认为是白色。

某些特殊光源(如CWF)光谱特征偏离黑体光谱较大,因此需要单独标定。

一个好的白平衡算法需要能够检测出画面中存在的特殊场景并加以针对性的强化。点云分析是一种非常有效的提取图像特征的方法,但由于需要分析每一个像素的白平衡增益,所以计算量非常大。

理想的白平衡控制时序如下图所示,其基本流程是:

  1. 在第N帧图像结束时ISP硬件采集关于白平衡的统计信息
  2. 白平衡算法根据第N帧的统计数据预测第N+1帧的控制参数
  3. 摄像机固件在第N+1帧图像到达之前将白平衡控制参数写入ISP硬件寄存器
  4. 第N+1帧图像使用新的白平衡参数进行曝光

但是实际上由于两帧的时间间隔往往很小,不容易保证算法完成一系列的计算和配置,因此实际的白平衡控制往往采用隔帧生效的时序,其基本流程如下图所示

  1. 在第N帧图像结束时ISP硬件采集关于白平衡的统计信息
  2. 在第N+1帧开始后,白平衡算法根据第N帧的统计数据预测第N+2帧的控制参数,需要在第N+1帧结束前完成所有计算
  3. 摄像机固件在第N+2帧图像到达之前将新的白平衡控制参数写入ISP硬件寄存器
  4. 第N+2帧图像使用新的白平衡参数进行曝光

白平衡校正算法

1.灰度世界算法

基于一个假说:任一幅图像,当它有足够多的色彩变化,则它的RGB分量的均值会趋于相等。这是一个在自动白平衡方面应用极为广泛的理论。对此算法的流程如下:

  • 计算各个颜色通道的平均值;
  • 寻找一个参考值K,一般情况选取Gmean;
  • 计算Rgain = Gmean/Rmean, Bgain = Gmean/Bmean;
  • 对图像中的每个像素都乘以对应的gain值进行校正;
  • 以下C++代码,不知道本人缺少什么运行条件,在自己电脑上最后显示结果不太理想;大家也可以试试,该代码出自:http://t.csdn.cn/edydD
//自动白平衡  //灰度世界算法
void GrayWorldAlgorithm(Mat& src,Mat& dst)
{assert(3==src.channels());//求BGR分量均值auto mean = mean(src);//需要调整的BGR分量的增益float gain_B(0),gain_G(0),gain_R(0);float K = (mean[0]+mean[1]+mean[2])/3.0f;gain_B = K/mean[0];gain_G = K/mean[1];gain_R = K/mean[2];vector<Mat> channels;split(src,channels);//调整三个通道各自的值channels[0] = channels[0]*gain_B;channels[1] = channels[1]*gain_G;channels[2] = channels[2]*gain_R;//通道合并cv::merge(channels,dst);
}

下面的灰度世界法是本人自己根据理解写的,但是显示结果存疑。有点怀疑是不是BGR的赋值跟输入图像的拜尔分布有关,赋值顺序有误?有能力者找到错误的可以帮我纠出(感谢感谢):

//灰度世界法,输入三通道彩色图
Mat awbgray(Mat img_rgb8)
{Mat img_awb = Mat::zeros(height, width, CV_32FC3);Mat imgR = Mat::zeros(height, width, CV_8UC1);Mat imgG = Mat::zeros(height, width, CV_8UC1);Mat imgB = Mat::zeros(height, width, CV_8UC1);double Rsum=0, Gsum=0,Bsum=0;/*unsigned char* srcdata;unsigned char *dstdata;*/for (int row = 0; row < height; row++){//ptr得到行数据的头指针,得到row行指针/*uchar* data = img_awb.ptr<uchar>(row);*/Vec3b* inptr = img_rgb8.ptr<Vec3b>(row);for (int col = 0; col < width; col++){//BGGR分布?//imgB.at<Vec3b>(row, col)[0]   b通道/*uchar bdata = data[col * img_awb.channels() + 0];*/imgB.at<uchar>(row, col) = (*(inptr + col))[0];imgG.at<uchar>(row, col) = (*(inptr + col))[1];imgR.at<uchar>(row, col) = (*(inptr + col))[2];Bsum += imgB.at<uchar>(row, col);Gsum += imgG.at<uchar>(row, col);Rsum += imgR.at<uchar>(row, col);}}double Rmean=0, Gmean=0, Bmean = 0;Rmean = Rsum / (height * width);Gmean = Gsum / (height * width);Bmean = Bsum / (height * width);double K = (Rmean + Gmean + Bmean) / 3;cout << "Rmeanvalue:" << Rmean << endl;cout << "Gmeanvalue:" << Gmean << endl;cout << "Bmeanvalue:" << Bmean << endl;cout << "rgbmeanvalue:" << K << endl;double Rgain = K / Rmean;double Ggain = K / Gmean;double Bgain = K / Bmean;//重新调整计算RGB值for (int row = 0; row < height; row++){Vec3b* inptr = img_rgb8.ptr<Vec3b>(row);Vec3f* outptr = img_awb.ptr<Vec3f>(row);for (int col = 0; col < width; col++){(*(outptr + col))[2] = Rgain * (*(inptr + col))[2];(*(outptr + col))[1] = Ggain * (*(inptr + col))[1];(*(outptr + col))[0] = Bgain * (*(inptr + col))[0];}}//convertScaleAbs(img_awb, img_awb);//转为CV_8UC1/*imshow("img_awb", img_awb);waitKey(0);*/return img_awb;
}

该方法运行得到的结果,通过图像监视观察函数中的图像变化,放大可以查看像素值,没有在VS安装的可以自行安装Imagewatch,安装教程:VS2022安装Image Watch插件_image watch for visual studio 2022_Aqder的博客-CSDN博客

感觉处理后的结果不太理想,不知道是不是代码有误,可指出。

(左为输入,右为输出,不是一一对应的截图):

2.完美反射法

(perfect Reflector)基于这样一种假设,一幅图像中最亮的像素相当于物体有光泽或镜面上的点,它传达了很多关于场景照明条件的信息。如果景物中有纯白的部分,那么就可以直接从这些像素中提取出光源信息。因为镜面或有光泽的平面本身不吸收光线,所以其反射的颜色即为光源的真实颜色,这是因为镜面或有光泽的平面的反射比函数在很长的一段波长范围内是保持不变的。那么在这个假设下,图像中就一定存在一个纯白色的的像素或者最亮的点。

完美反射法就是利用这种特性来对图像进行调整。算法执行时,将待检测图像中亮度最高的像素作为参考白点,以此点为基础就可计算出gain值从而进行校正。完美反射算法流程如下:
  (1)遍历原始图像,统计RGB三通道之和的直方图;
  (2)遍历原始图像,找到RGB三通道各自的最大值Bmax、Gmax、Rmax
  (3)设定比例 r ,对RGB之和的直方图进行倒叙遍历,找到使白点像素个数超过总像素个数比例的阈值,T;
  (4)遍历原始图像,计算RGB之和大于 T 的像素,各个通道取平均,得到Bavg、Gavg、Ravg;
  (5)遍历原始图像,分别计算RGB三通道的调整值Aout=A / Aavg * Amax;
  (6)防溢出处理,这里可以采用简单的截断即可。

有看到其他博文资料不使用比例和阈值的完美反射法,但是运行结果和上面一样存疑。

设定比例r为10%,使用C++,代码如下:

​
//白平衡校正,完美反射法,使用比例r和阈值T,
Mat awbreflect2(Mat img_rgb8)
{int histrgbsum[255 * 3 + 1] = { 0 };double Rmax = 0, Gmax = 0, Bmax = 0;//uchar maxrgb[3] = { 0 };for (int row = 0; row < height; row++){const uchar* inptr = img_rgb8.ptr<uchar>(row);for (int col = 0; col < width; col++){//统计RGB三通道之和的直方图int sum = *(inptr + 3 * col) + *(inptr + 3 * col + 1) + *(inptr + 3 * col + 2);histrgbsum[sum]++;//找到RGB三通道各自的最大值Bmax、Gmax、RmaxBmax = max(Bmax, (double)*(inptr + 3 * col));Gmax = max(Gmax, (double)*(inptr + 3 * col + 1));Rmax = max(Rmax, (double)*(inptr + 3 * col + 2));/*maxrgb[0] = max(maxrgb[0], *(inptr + 3 * col));maxrgb[1] = max(maxrgb[1], *(inptr + 3 * col+1));maxrgb[2] = max(maxrgb[2], *(inptr + 3 * col+2));*/}}//设定比例r为10%double num = 0,ratio=0.1;int threshold = 0;int len = 0;len=sizeof(histrgbsum) / sizeof(histrgbsum[0]);//int len = end(histrgbsum) - begin(histrgbsum);cout << "histagram length:" << len << endl;for (len; len >= 0; len--){num += histrgbsum[len];//计算R+G+B的数量超过像素总数的ratio的像素值if (num > height * width * ratio){//使白点像素个数超过总像素个数的比例时,为阈值Tthreshold = len;break;}}//计算RGB之和大于 T 的像素,对大于阈值的像素各通道取平均,得到Bavg、Gavg、Ravg;double Rsum = 0, Gsum = 0, Bsum = 0;double Ravg = 0, Gavg = 0, Bavg = 0;int pixnum = 0;for (int row = 0; row < height; row++){const uchar* inptr = img_rgb8.ptr<uchar>(row);for (int col = 0; col < width; col++){//计算RGB之和,上面的局部变量又用一遍int sum = *(inptr + 3 * col) + *(inptr + 3 * col + 1) + *(inptr + 3 * col + 2);if (sum > threshold){Bsum += *(inptr + 3 * col);Gsum += *(inptr + 3 * col + 1);Rsum += *(inptr + 3 * col + 2);pixnum++;}}}Ravg = Rsum / (double)pixnum;Gavg = Gsum / (double)pixnum;Bavg = Bsum / (double)pixnum;//创建与输入图像一样大小类型的矩阵Mat img_awb = Mat::zeros(img_rgb8.size(), img_rgb8.type());//量化0-255,重新计算RGB值,分别计算RGB三通道的调整值Aout=A / Aavg * Amaxdouble Rout = 0, Gout = 0, Bout = 0;for (int row = 0; row < height; row++){const uchar* inptr = img_rgb8.ptr<uchar>(row);uchar* outptr = img_awb.ptr<uchar>(row);for (int col = 0; col < width; col++){Bout = (double)*(inptr + 3 * col) / Bavg * Bmax;Gout = (double)*(inptr + 3 * col+1) / Gavg * Gmax;Rout = (double)*(inptr + 3 * col+2) / Ravg * Rmax;Bout = min(max((double)0, Bout), (double)255);Gout = min(max((double)0, Gout), (double)255);Rout = min(max((double)0, Rout), (double)255);//将计算好的RGB值赋给新矩阵*(outptr + 3 * col) = (uchar)Bout;*(outptr + 3 * col+1) = (uchar)Gout;*(outptr + 3 * col+2) = (uchar)Rout;}}return img_awb;}​

断点运行之后,图像监视下白平衡处理后得到的结果(左输入,右输出,不是一一对应的截图):

3.动态阈值法

YUV颜色空间(亦称YCrCb)主要用于优化彩色视频信号的传输,Y表示亮度,U和V表示色度(色调和饱和度)。亮度是通过RGB输入信号来建立的,方法是将RGB信号的特定部分叠加到一起。“色度”则定义了颜色的两个方面─色调与饱和度,分别用Cr和Cb来表示。其中,Cr反映了RGB输入信号红色部分与RGB信号亮度值之间的差异。而Cb反映的是RGB输入信号蓝色部分与RGB信号亮度值之同的差异。

动态阈值算法通过将RGB变化到YCrCb颜色空间进行分析来确定白点,其选择参考白点的阈值是动态变化的。我们通过对图片的YCrCb坐标空间的分析,可以找到一个接近白色的区域,该区域是包含着参考白点的,通过设定一个阈值来规定某些点为参考白点。因此该算法是一个动态的自适应白平衡算法。

白平衡算法通常分为两步:白色点的检测,白色点的调整。本方法采用一个动态的阀值来检测白色点。详细算法过程参考如下:

(1)把图像w*h从RGB空间转换到YCrCb空间。转换公式如下:

(2)通过限定YUV的区域来判断是否为白点,通过四个限制条件俩限制白点,满足条件的点就是白点,参与后续的计算,否则,点直接舍弃。

首先,为了增强算法的鲁棒性,将图像分为12部分,把图像分成宽高比为4:3个块(块数可选)。

然后对每个块,分别计算Cr,Cb的平均值Mr,Mb。

再对每个块,根据Mr,Mb,用下面公式分别计算Cr,Cb的方差Dr,Db。

最后判定每个块的近白区域(near-white region)。判别准则为:

其中sign为符号函数,即正数返回1,负数返回0。

设一个“参考白色点”的亮度矩阵RL,大小为w*h。若符合判别式,则作为“参考白色点”,并把该点(i,j)的亮度(Y分量)值赋给RL(i,j);若不符合,则该点的RL(i,j)值为0。

上面几步为白点检测,下面几步为白点调整:

(1)选取参考“参考白色点”中最大的10%的亮度(Y分量)值,并选取其中的最小值Lu_min;

(2)调整RL,若RL(i,j)<Lu_min, RL(i,j)=0; 否则,RL(i,j)=1;

(3)分别把R,G,B与RL相乘,得到R2,G2,B2。 分别计算R2,G2,B2的平均值,得到Rav,Gav,Bav;

(4)得到调整增益: Ymax=double(max(max(Y));

Rgain=Ymax/Rav;

Ggain=Ymax/Gav;

Bgain=Ymax/Bav;

(5)调整原图像:Ro= R*Rgain; Go= G*Ggain; Bo= B*Bgain。

实现代码本人未尝试,C++可以参考(过程较为复杂):

OpenCV图像处理专栏十一 | IEEE Xplore 2015的图像白平衡处理之动态阈值法 (qq.com)

4.其他算法

除了上面比较常见的几种,还有基于模糊逻辑,基于色温,基于边缘和多方法融合法等等。

最后,再次想吐槽csdn的发布文章的编辑页面,类似换行,空格,撤消等等,编辑与图片接近的地方经常会跳转编辑处,还很容易误删图片。有时候看别人文章,里面的一些公式,格式,图片等等,可能因为不兼容或者乱码等问题影响观看和理解,也是学习中一方面的阻碍。

写出一篇文章没想到最后的困难居然是编辑,用多了word,真心觉得这里编辑功能太不智能,需要花费更多时间。还挂着中国开发者网络的头衔,内部工作人员能不能更新升级以下这个编辑发布文章里面的页面和功能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/147928.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

基于matlab创作简易表白代码

一、程序 以下是一个基于MATLAB的简单表白代码&#xff1a; % 表白代码 clc; % 清除命令行窗口 clear; % 清除所有变量 close all; % 关闭所有图形窗口 % 输入被表白者的名字 name input(请输入被表白者的名字&#xff1a;, s); % 显示表白信息 fprintf(\n); fprintf(亲爱的…

IDEA Rogstry中找不到compiler.automake.allow.when.app.running问题解决

网上大部分人教我们 先 File > Settings 然后 勾选 Build 下的 Compiler中的 Build project automatically 这些步骤都不会有问题 然后就会让我们 ctrl shift alt / 点 Rogstry 打开后 我人就麻了 根本没有什么 compiler.automake.allow.when.app.running 也不用慌 我们…

基于 SpringBoot 2.7.x 使用最新的 Elasticsearch Java API Client 之 ElasticsearchClient

1. 从 RestHighLevelClient 到 ElasticsearchClient 从 Java Rest Client 7.15.0 版本开始&#xff0c;Elasticsearch 官方决定将 RestHighLevelClient 标记为废弃的&#xff0c;并推荐使用新的 Java API Client&#xff0c;即 ElasticsearchClient. 为什么要将 RestHighLevelC…

Android 进阶——系统启动之BootLoader 及内核启动一(下)

文章大纲 引言一、Android 系统启动流程概述1、手机电源被打开时&#xff0c;首先是引导进入BootLoader分区2、BootLoader分区加载Linux 内核3、内核解析执行init.rc脚本并启动进程id为1 的init进程4、init进程初始化各种Android系统服务、ServiceManager以及Zygote 进程孵化器…

键盘上F1至F12键的作用

多年来&#xff0c;我们习惯了最上排的12个按键&#xff0c;从F1到F12&#xff0c;它们被称为“快速功能键”&#xff0c;可以让你更轻松地操作电脑&#xff1b;但是&#xff0c;很多人可能从未使用过它们&#xff0c;也从来不知道它们的用途。那么今天&#xff0c;就向大家科普…

Selenium 浏览器坐标转桌面坐标

背景&#xff1a; 做图表自动化项目需要做拖拽操作&#xff0c;但是selenium提供的拖拽API无效&#xff0c;因此借用pyautogui实现拖拽&#xff0c;但是pyautogui的拖拽是基于Windows桌面坐标实现的&#xff0c;另外浏览器中的坐标与windows桌面坐标并不是一比一对应的关系&am…

DevExpress ChartControl 画间断线

效果如下&#xff1a; 解决办法&#xff1a;数据源间断位置加入double.NaN demo下载

蓝桥杯每日一题2023.10.3

杨辉三角形 - 蓝桥云课 (lanqiao.cn) 题目描述 题目分析 40分写法&#xff1a; 可以自己手动构造一个杨辉三角&#xff0c;然后进行循环&#xff0c;用cnt记录下循环数的个数&#xff0c;看哪个数与要找的数一样&#xff0c;输出cnt #include<bits/stdc.h> using na…

大语言模型之十五-预训练和监督微调中文LLama-2

这篇博客是继《大语言模型之十二 SentencePiece扩充LLama2中文词汇》、《大语言模型之十三 LLama2中文推理》和《大语言模型之十四-PEFT的LoRA》 前面博客演示了中文词汇的扩充以及给予LoRA方法的预训练模型参数合并&#xff0c;并没有给出LoRA模型参数是如何训练得出的。 本篇…

VM装Windows虚拟机扩容

1.进入服务器CMD模式&#xff0c;输入diskpart&#xff0c;回车 2.查看卷 list volume 3.指定扩容的磁盘 select volume 1 4.查看磁盘 list disk 5.查看逻辑分区 list parttition 6.选择需要扩展的逻辑分区 select partition 1 7.扩展 extend 8.退出并查看磁盘大小

消息中间件(二)——kafka

文章目录 Apache Kafka综述什么是消息系统&#xff1f;点对点消息类型发布-订阅消息类型 什么是Kafka?优点关键术语Kafka基本原理用例 Apache Kafka综述 在大数据中&#xff0c;会使用到大量的数据。面对这些海量的数据&#xff0c;我们一是需要做到能够收集这些数据&#xf…

【Java 进阶篇】JDBC查询操作详解

在数据库编程中&#xff0c;查询是一项非常常见且重要的操作。JDBC&#xff08;Java Database Connectivity&#xff09;提供了丰富的API来执行各种类型的查询操作。本篇博客将详细介绍如何使用JDBC进行查询操作&#xff0c;包括连接数据库、创建查询语句、执行查询、处理结果集…

软件工程与计算总结(二)软件工程的发展

本章开始介绍第二节内容&#xff0c;主要是一些历史性的东西~ 一.软件工程的发展脉络 1.基础环境因素的变化及其对软件工程的推动 抽象软件实体和虚拟计算机都是软件工程的基础环境因素&#xff0c;它们能从根本上影响软件工程的生产能力&#xff0c;而且是软件工程无法反向…

231003-四步MacOS-iPadOS设置无线竖屏随航SideCar

Step 0&#xff1a;MacOS到iPad无线竖屏随航显示&#xff0c;最终效果 Step 1&#xff1a; 下载 Better Display Step 2&#xff1a;在设置中新建虚拟屏幕&#xff0c;创建虚拟屏幕 Step 3&#xff1a;进行如下设置 Step 4&#xff1a;注意事项 ⚠️ 设置后的虚拟屏幕与Sideca…

如何在 Google Earth 中创建轨迹、路线并制作动画

如何创建航迹 https://kurviger.de/en Google 地球飞行教程(天桥动画) 选择合适的点 &#xff08;可调整视图快照&#xff09;点击录制&#xff0c;依次点击图标即可

堆栈与堆(Stack vs Heap)有什么区别?

​编写有效的代码需要了解堆栈和堆内存&#xff0c;这使其成为学习编程的重要组成部分。不仅如此&#xff0c;新程序员或职场老手都应该完全熟悉堆栈内存和堆内存之间的区别&#xff0c;以便编写有效且优化的代码。 这篇博文将对这两种内存分配技术进行全面的比较。通过本文的…

selenium使用已经获取的cookies登录网站报错unable to set cookie的处理方式

用selenium半手动登录github获取其登录cookies后&#xff0c;保存到一个文件gtb_cookies.txt中。 然后用selenium使用这个cookies文件&#xff0c;免登录上github。但是报错如下&#xff1a;selenium.common.exceptions.UnableToSetCookieException: Message: unable to set co…

简化数据库操作:探索 Gorm 的约定优于配置原则

文章目录 使用 ID 作为主键数据库表名TableName临时指定表名列名时间戳自动填充CreatedAtUpdatedAt时间戳类型Gorm 采用约定优于配置的原则,提供了一些默认的命名规则和行为,简化开发者的操作。 使用 ID 作为主键 默认情况下,GORM 会使用 ID 作为表的主键: type User st…

喜讯 | 怿星科技获评SAE“优秀核心零部件企业”,测试软件平台工具广受赞誉

2023年9月22日-23日&#xff0c;SAE 2023汽车智能与网联技术国际学术会议成功举行。此次学术会议由SAE International与南昌智能新能源汽车研究院联合主办&#xff0c;大会汇聚了来自国内外智能网联领域的顶尖专家和学者。大会同期颁布的奖项旨在向行业推选出更多新时代涌现的杰…

OpenGLES:绘制一个混色旋转的3D圆锥

一.概述 1.1 对圆锥的拆解 上一篇博文讲解了绘制圆柱体&#xff0c;这一篇讲解绘制一个彩色旋转的圆锥 在绘制圆柱体时提到过&#xff0c;关键点是先将圆柱进行拆解&#xff0c;便于创建出顶点坐标数组 同样&#xff0c;绘制圆锥也先进行拆解 圆锥的拆解很简单&#xff0c…