《Opencv3编程入门》学习笔记—第五章

news2024/12/24 9:15:42

《Opencv3编程入门》学习笔记

记录一下在学习《Opencv3编程入门》这本书时遇到的问题或重要的知识点。

第五章 core组件进阶

一、访问图像中的像素

(一)图像在内存之中的存储方式

图像矩阵的大小取决于所用的颜色模型,确切地说,取决于所用通道数。
灰度图像
在这里插入图片描述
多通道图像
在这里插入图片描述

(二)颜色空间缩减

若矩阵元素存储的是单通道像素,使用C或C++的无符号字符类型,那么像素可有256个不同值。但若是三通道图像,这种存储格式的颜色数就有256256256,确切的说有一千六百多万种。
作用:可以大大降低运算复杂度
做法:将现有颜色空间值除以某个输入值,以获得较少的颜色数。也就是“做减法”,比如颜色值0到9可取为新值0,10到19可取为10,以此类推。
简单的颜色空间缩减算法由下面两步组成:
(1)遍历图像矩阵的每一个像素
(2)对像素应用上述公式

 int deivideWith = 10;
 uchar table[256];
 for(int i = 0;i < 256;++i){
 	table[i] = deivideWith  * (i/deivideWith );
 p[j] = table[p[j]];

(三)LUT函数:Look up table操作(即滤镜)

作用:突出的有用信息,增强图像的光对比度的作用

cv2. LUT(src, lut, dst=None)有三个参数,分别为:
src:输入数据array,类型为8位整型(np.uin8)
lut:查找表,如果输入src是多通道的,例如是BGR三通到的图像,而查表是单通道的,则此时B、G、R三个通道使用的是同一个查找表
dst=None:输出数组,大小和通道数与src相同,而深度depth与lut相同

//首先我们建立一个mat型用于查表。
Mat lookUpTable(1,256,CV_8U);
uchar* p = lookUpTable.data;
//改变查找表的对应
for(int i = 0;i < 256;++i)
	p[i] = table[i];
//然后我们调用函数(I是输入,J是输出):
for (int i = 0;i < times;++i)
	LUT(I,lookUpTable,J);

(四)计时函数

  • getTickCount():返回CPU自某个时间如(启动电脑)以来走过的时钟周期数。
  • getTickFrequency()函数返回CPU一秒钟所走的时钟周期数。
double time0 = static_cast<double>(getTickCount()); //记录起始时间
//进行图像处理操作
time0 = ((double)getTickCount() - time0)/getTickFrequency();
cout<<"此方法运行时间为:"<<time0<<"秒"<<endl;  //输出运行时间

(五)访问图像中像素的三类方法

示例代码:减少图像中颜色的数量

#include <iostream>
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
 
void colorReduce(Mat& inputImage, Mat& outputImage, int div);

int main() {
    std::cout << "Hello, World!" << std::endl;
    //【1】创建原始图并显示
    Mat image = imread("D://lili/Desktop/jpg/opencv/1.jpg");
    cout << image.size() << endl;
    cout << image.rows << endl;
    cout << image.cols << endl;
    cout << image.channels() << endl;
    cout << image.type() << endl;
 
    //【2】按原始图的参数规格来创建效果图
    Mat dstImage;
    dstImage.create(image.rows, image.cols, image.type());
    //【3】记录起始时间
    double time0 = static_cast<double>(getTickCount());
    //【4】调用颜色空间缩减函数
    colorReduce(image, dstImage, 32);
    //【5】计算运行时间并输出
    time0 = ((double)getTickCount() - time0) / getTickFrequency();
    cout << time0 << endl;
    //【6】显示效果图
    imshow("效果图", dstImage);
    waitKey(0);
    return 0;
}
【方法一】指针访问:C操作符[]
void colorReduce(Mat& inputImage, Mat& outputImage, int div){
    outputImage = inputImage.clone();     // 复制实参到临时变量
    int rowNum = outputImage.rows;        // 图像的行数
    int colNum = outputImage.cols * outputImage.channels();   // 图像的列数 * 通道数 = 每一行元素的个数
 
    for (int i=0;i<rowNum;i++)
    {
//        获取第i行的首地址
        uchar* data = outputImage.ptr<uchar>(i);
        for (int j=0;j<colNum;j++)
        {
//            处理每个像素
            data[j] = data[j]/div *div + div/2;
        }
    }
}

在这里插入图片描述

【方法二】迭代器iterator
void colorReduce(Mat& inputImage, Mat& outputImage, int div){
    outputImage = inputImage.clone();
 
    Mat_<Vec3b>::iterator it = outputImage.begin<Vec3b>();
    Mat_<Vec3b>::iterator itend = outputImage.end<Vec3b>();
    //三个通道
    for (; it != itend; ++it) {
        (*it)[0] = (*it)[0] / div *div+div/2;
        (*it)[1] = (*it)[1] / div *div+div/2;
        (*it)[2] = (*it)[2] / div *div+div/2;
    }
}

在这里插入图片描述

【方法三】动态地址计算
void colorReduce(Mat& inputImage, Mat& outputImage, int div)
{
    outputImage = inputImage.clone();
    int rowNum = outputImage.rows;
    int colNum = outputImage.cols;
 
    for (int i=0; i<rowNum;i++)
    {
        for (int j=0;j<colNum;j++)
        {
            outputImage.at<Vec3b>(i, j)[0] = outputImage.at<Vec3b>(i, j)[0]/div*div + div/2;
            outputImage.at<Vec3b>(i, j)[1] = outputImage.at<Vec3b>(i, j)[1]/div*div +div/2;
            outputImage.at<Vec3b>(i, j)[2] = outputImage.at<Vec3b>(i, j)[2]/div*div + div/2;
        }
    }
}

在这里插入图片描述
其中指针访问的速度最快。

(六)示例程序

配套的示例程序
【21】用指针访问像素
【22】用迭代器访问像素
【23】用动态地址计算配合at访问像素
【24】遍历图像像素的14种方法

二、ROI区域图像叠加&图像混合

(一)感兴趣区域:ROI

定义ROI区域的两种方法
1、使用表示矩形区域的Rect。它指定矩形的左上角坐标和矩形的长宽,以定义一个矩形区域。

Mat imgeROI;
imageROI = image(Rect(500,250,logo.cols,logo.rows));

2、指定感兴趣行或列的范围,Range是指从起始索引(不包括终止索引)的一连段连续序列。cRange可以用来定义Range。

imageROI = image(Range(250,250+logoImage.rows),Range(200,200+logoImage.cols));

示例代码

/*------------------------------------------------------
      函数名:ROI_AddImage()
      描述: 利用感兴趣区域ROI实现图像叠加(把图像叠加到ROI区域中,而不是俩张图的直接叠加)
-------------------------------------------------------*/
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>

using namespace cv;

bool ROI_AddImage();
 
int main()
{
	system("color 5E");
 
	if (ROI_AddImage())
	{
		printf("运行成功");
	}
 
	waitKey(0);
	return 0;
}

bool ROI_AddImage()
{
    
    //【1】读入图像
    Mat srcImage1 = imread("D:/lili/Desktop/jpg/opencv/1.jpg");
    Mat logoImage = imread("D:/lili/Desktop/jpg/opencv/dotalogo.jpg");
    
    if (!srcImage1.data)
    {
        printf("fuck, read the picture is wrong!!! \n");
        return false;
    }

    if (!logoImage.data)
    {
        printf("fuck, read the picture is wrong!!! \n");
        return false;
    }

    //【2】定义一个Mat类型并给其设定ROI区域
    Mat imageROI = srcImage1(Rect(100,150,logoImage.cols,logoImage.rows));

    //【3】加载掩膜
    Mat mask = imread("D:/lili/Desktop/jpg/opencv/dotalogo.jpg",0);

    //【4】将掩膜拷贝到ROI
    logoImage.copyTo(imageROI,mask);

    //【5】显示结果
    namedWindow("1 利用ROI实现图像叠加示例窗口");
    imshow("1 利用ROI实现图像叠加示例窗口",srcImage1);

    return true;
}

运行效果
在这里插入图片描述
【拓展1】

image.copyTo(imageROI,mask)

作用是把mask和image重叠以后把mask中像素值为0(black)的点对应的image中的点变为透明,而保留其他点。(mask是掩膜)

(二)线性混合操作

(三)计算数组加权和:addWeighted()函数

void(InputArray src1, double alpha, InputArray src2, double beta, double gamma, OutputArray dst, int dtype=-1);

1、参数1:InputArray类型的src1,表示需要加权的第一个数组,一般是一个Mat;
2、参数2:double类型的alpha,表示第一个数组的权重
3、参数3:InputArray类型的src2,表示需要加权的第二个数组,它需要和第一个数组拥有相同的尺寸和通道数;
4、参数4:double类型的beta,表示第二个数组的权重;
5、参数5:double类型的gamma,加到权重总和上的标量值(类似于偏置);
6、参数6:OutputArray类型的dst,输出的数组,它和输入的两个数组有相同的尺寸和通道数;
7、参数7:int类型的dtype,输出阵列的可选深度,默认值是-1。当两个输入数组有相同的深度时,设置为-1.

//addWeighted()函数的作用的矩阵表达式
dst = src1[I]*alpha + src2[I}*beta + gamma;

示例代码

//---------------------------------【LinearBlending()函数】-------------------------------------
// 函数名:LinearBlending()
// 描述:利用cv::addWeighted()函数实现图像线性混合
//--------------------------------------------------------------------------------------------
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>

using namespace cv;

bool LinearBlending();
 
int main()
{
	system("color 5E");
 
	if (LinearBlending())
	{
		printf("运行成功");
	}
 
	waitKey(0);
	return 0;
}
bool LinearBlending()
{
       //【0】定义一些局部变量
       double alphaValue = 0.7;
       double betaValue;
       Mat srcImage2, srcImage3, dstImage;
 
       //【1】读取图像 ( 两幅图片需为同样的类型和尺寸 )
       srcImage2= imread("D://lili/Desktop/jpg/pptjpg/frame/test.jpg");
       srcImage3= imread("D://lili/Desktop/jpg/pptjpg/frame/kou.jpg");
 
       if(!srcImage2.data ) { printf("读取srcImage2错误~! \n"); return false; }
       if(!srcImage3.data ) { printf("读取srcImage3错误~! \n"); return false; }
 
       //【2】做图像混合加权操作
       betaValue= ( 1.0 - alphaValue );
       addWeighted(srcImage2, alphaValue, srcImage3, betaValue, 0.0, dstImage);
 
       //【3】创建并显示原图窗口
       namedWindow("<2>线性混合示例窗口【原图】", 1);
       imshow("<2>线性混合示例窗口【原图】", srcImage2 );
 
       namedWindow("<3>线性混合示例窗口【效果图】", 1);
       imshow("<3>线性混合示例窗口【效果图】", dstImage );
 
       return true;
      
}

运行效果
在这里插入图片描述

(四)综合示例:初级图像混合

示例代码

#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
 
 
//-----------------------------【全局函数声明部分】------------------------------
bool ROI_AddImage();
bool LinearBlending();
bool ROI_LinearBlending();
 
 
int main() {
	system("color 5A");
	if (ROI_AddImage() && LinearBlending() && ROI_LinearBlending())
		cout << "运行成功!得出需要的图像了!:)" << endl;
	waitKey(0);
	return 0;
}
 
 
//----------------------【ROI_AddImage()函数】--------------------------
bool ROI_AddImage() {//利用感兴趣区域ROI实现图像叠加
	//读入图像
	Mat srcImage1 = imread("D://lili/Desktop/jpg/opencv/dota.jpg");
	Mat logoImage = imread("D://lili/Desktop/jpg/opencv/dotalogo.jpg");
	if (!srcImage1.data) {
		cout << "读取srcImage1失败!" << endl;
		return false;
	}
	if (!logoImage.data) {
		cout << "读取logoImage失败!" << endl;
		return false;
	}
	//定义一个Mat类型并给其设定ROI区域
	Mat imageROI = srcImage1(Rect(560, 240, logoImage.cols, logoImage.rows));
	//加载掩膜(必须是灰度图)
	Mat mask = imread("D://lili/Desktop/jpg/opencv/dotalogo.jpg", 0);
	//将掩膜复制到ROI区域
	logoImage.copyTo(imageROI, mask);
	//显示效果
	namedWindow("<1>利用ROI实现图像叠加示例窗口");
	imshow("<1>利用ROI实现图像叠加示例窗口", srcImage1);
	return true;
}
 
 
//------------------------【LinearBlending()函数】----------------------------
bool LinearBlending() {//利用cv::addWeighted()函数实现图像线性混合
	//定义一些局部变量
	double alpha = 0.5;
	double beta;
	Mat srcImage2, srcImage3, dstImage;
	//读取图像
	srcImage2 = imread("D://lili/Desktop/jpg/pptjpg/frame/test.jpg");
	srcImage3 = imread("D://lili/Desktop/jpg/pptjpg/frame/kou.jpg");
 
 
	if (!srcImage2.data) {
		cout << "读取srcImage2错误!" << endl;
		return false;
	}
	if (!srcImage3.data) {
		cout << "读取srcImage3错误!" << endl;
		return false;
	}
	
	//进行图像混合加权操作
	beta = (1.0 - alpha);
	double gamma = 0;
	addWeighted(srcImage2, alpha, srcImage3, beta, gamma, dstImage);
	
	//创建并显示原图窗口
	namedWindow("<2>线性混合示例窗口【原图】");
	imshow("<2>线性混合示例窗口【原图】", srcImage2);
 
 
	namedWindow("<3>线性混合示例窗口【效果图】");
	imshow("<3>线性混合示例窗口【效果图】",dstImage);
 
 
	return true;
}
 
 
//----------------------【ROI_LinearBlending()函数】线性混合直接输出到感兴趣区域---------------------------------
bool ROI_LinearBlending() {//指定区域线性图像混合
	//读取图像
	Mat srcImage4 = imread("D://lili/Desktop/jpg/opencv/dota.jpg", 1);//载入三通道图像
	Mat logoImage = imread("D://lili/Desktop/jpg/opencv/dotalogo.jpg");//默认flags=1,载入三通道图像
 
 
	if (!srcImage4.data) {
		cout << "读入srcImage4失败!" << endl;
		return false;
	}
	if (!logoImage.data) {
		cout << "读入logoImage失败!" << endl;
		return false;
	}
 
 
	//定义一个Mat类型并给其设定ROI区域
	Mat imageROI;
	//imageROI = srcImage4(Rect(560, 240, logoImage.cols, logoImage.rows));
	imageROI = srcImage4(Range(240, 240 + logoImage.rows), Range(560, 560 + logoImage.cols));//方法二
	//注意:Range()方法是先给定上下范围,再给定左右范围!!
 
 
	//定义一些局部变量
	double alpha = 0.5, beta = 0.3, gamma = 0;
	//将logo加到原图上
	//将logo线性叠加到ROI区域并赋值给imageROI,而imageROI属于srcImage4,所以srcImage4为最终效果图
	addWeighted(imageROI, alpha, logoImage, beta, gamma, imageROI);//注意参数一三六必须尺寸相同&通道数相同
 
 
	//显示结果
	namedWindow("<4>区域线性图像混合示例窗口");
	imshow("<4>区域线性图像混合示例窗口", srcImage4);
 
 
	return true;
}

运行效果
在这里插入图片描述
在这里插入图片描述

三、分离颜色通道、多通道图像混合

(一)通道分离:split()函数

C++: void split(const Mat& src, Mat*mvbegin);
C++: void split(InputArray m,OutputArrayOfArrays mv);

1、参数1:InputArray类型的m或者const Mat&类型的src,填我们需要进行分离的多通道数组。
2、参数2:OutputArrayOfArrays类型的mv,填函数的输出数组或者输出的vector容器。

示例代码

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
 
using namespace cv;
using namespace std;
 
void main(){
    vector<Mat> channels;
    Mat imageBlueChannel;
    Mat imageGreenChannel;
    Mat imageRedChannel;
    Mat srcImage4 = imread("D://lili/Desktop/jpg/opencv/dota.jpg");
    imshow("【原始图】", srcImage4);
 
    //把一个3通道图像转换成3个单通道图像
    split(srcImage4, channels);
    imageBlueChannel = channels.at(0);
    imageGreenChannel = channels.at(1);
    imageRedChannel = channels.at(2);
 
    //显示单通道图像
    imshow("【BlueChannel】", imageBlueChannel);
    imshow("【GreenChannel】", imageGreenChannel);
    imshow("【RedChannel】", imageRedChannel);
 
    waitKey();
}

运行效果
在这里插入图片描述

(二)通道合并:merge()函数

C++: void merge(const Mat* mv, size_tcount, OutputArray dst)
C++: void merge(InputArrayOfArrays mv,OutputArray dst)

1、参数1:mv,填需要被合并的输入矩阵或vector容器的阵列,这个mv参数中所有的矩阵必须有着一样的尺寸和深度。
2、参数2:count,当mv为一个空白的C数组时,代表输入矩阵的个数,这个参数显然必须大于1.
3、参数3:dst,即输出矩阵,和mv[0]拥有一样的尺寸和深度,并且通道的数量是矩阵阵列中的通道的总数。

示例代码
根据(一)中代码,将分离后的通道进行合并。

#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
 
using namespace cv;
using namespace std;
 
void main(){
    vector<Mat> channels;
    Mat imageBlueChannel;
    Mat imageGreenChannel;
    Mat imageRedChannel;
    Mat srcImage4 = imread("D://lili/Desktop/jpg/opencv/dota.jpg");
    imshow("【原始图】", srcImage4);
 
    //把一个3通道图像转换成3个单通道图像
    split(srcImage4, channels);
    imageBlueChannel = channels.at(0);
    imageGreenChannel = channels.at(1);
    imageRedChannel = channels.at(2);
 
    //显示单通道图像
    imshow("【BlueChannel】", imageBlueChannel);
    imshow("【GreenChannel】", imageGreenChannel);
    imshow("【RedChannel】", imageRedChannel);

	Mat dstImage;
	//合并
	merge(channels,dstImage);
	imshow("【合并效果图】", dstImage);

    waitKey();
}

运行效果
在这里插入图片描述

(三)示例程序:多通道图像混合

示例代码

//-----------------------------------【程序说明】----------------------------------------------
//  程序名称::【OpenCV入门教程之四】分离颜色通道&多通道图像混合   配套源码
// VS2010版   OpenCV版本:2.4.8
//     2014年3月13 日 Create by 浅墨
//  图片素材出处:dota2原画 dota2logo 
//     浅墨的微博:@浅墨_毛星云
//------------------------------------------------------------------------------------------------
 
//-----------------------------------【头文件包含部分】---------------------------------------
//     描述:包含程序所依赖的头文件
//----------------------------------------------------------------------------------------------                                                                                    
#include <cv.h>
#include <highgui.h>
#include <iostream>
 
//-----------------------------------【命名空间声明部分】---------------------------------------
//     描述:包含程序所使用的命名空间
//-----------------------------------------------------------------------------------------------  
using namespace cv;
using namespace std;
 
 
//-----------------------------------【全局函数声明部分】--------------------------------------
//     描述:全局函数声明
//-----------------------------------------------------------------------------------------------
bool MultiChannelBlending();
 
//-----------------------------------【main( )函数】--------------------------------------------
//     描述:控制台应用程序的入口函数,我们的程序从这里开始
//-----------------------------------------------------------------------------------------------
int main(  )
{
       system("color5E");
 
       if(MultiChannelBlending())
       {
              cout<<endl<<"获得混合值图像";
       }
 
       waitKey(0);
       return 0;
}
 
 
//-----------------------------【MultiChannelBlending( )函数】--------------------------------
//     描述:多通道混合的实现函数
//-----------------------------------------------------------------------------------------------
bool MultiChannelBlending()
{
       //【0】定义相关变量
       Mat srcImage;
       Mat logoImage;
       vector<Mat>channels;
       Mat  imageBlueChannel;
 
       //=================【蓝色通道部分】=================
       //     描述:多通道混合-蓝色分量部分
       //============================================
 
       //【1】读入图片
       logoImage=imread("D://lili/Desktop/jpg/opencv/dotalogo.jpg",0);
       srcImage=imread("D://lili/Desktop/jpg/opencv/dota.jpg");

       if(!logoImage.data ) { printf("读取logoImage错误\n"); return false; }
       if(!srcImage.data ) { printf("读取srcImage错误\n"); return false; }
 
       //【2】把一个3通道图像转换成3个单通道图像
       split(srcImage,channels);//分离色彩通道
 
       //【3】将原图的蓝色通道引用返回给imageBlueChannel,注意是引用,相当于两者等价,修改其中一个另一个跟着变
       imageBlueChannel=channels.at(0);
       //【4】将原图的蓝色通道的(500,250)坐标处右下方的一块区域和logo图进行加权操作,将得到的混合结果存到imageBlueChannel中
       addWeighted(imageBlueChannel(Rect(500,250,logoImage.cols,logoImage.rows)),1.0,
              logoImage,0.5,0,imageBlueChannel(Rect(500,250,logoImage.cols,logoImage.rows)));
 
       //【5】将三个单通道重新合并成一个三通道
       merge(channels,srcImage);
 
       //【6】显示效果图
       namedWindow("<1>游戏原画+logo蓝色通道");
       imshow("<1>游戏原画+logo蓝色通道",srcImage);
 
 
       //=================【绿色通道部分】=================
       //     描述:多通道混合-绿色分量部分
       //============================================
 
       //【0】定义相关变量
       Mat  imageGreenChannel;
 
       //【1】重新读入图片
       logoImage=imread("D://lili/Desktop/jpg/opencv/dotalogo.jpg",0);
       srcImage=imread("D://lili/Desktop/jpg/opencv/dota.jpg");
 
       if(!logoImage.data ) { printf("读取logoImage错误!\n"); return false; }
       if(!srcImage.data ) { printf("读取srcImage错误!\n"); return false; }
 
       //【2】将一个三通道图像转换成三个单通道图像
       split(srcImage,channels);//分离色彩通道
 
       //【3】将原图的绿色通道的引用返回给imageBlueChannel,注意是引用,相当于两者等价,修改其中一个另一个跟着变
       imageGreenChannel=channels.at(1);
       //【4】将原图的绿色通道的(500,250)坐标处右下方的一块区域和logo图进行加权操作,将得到的混合结果存到imageGreenChannel中
       addWeighted(imageGreenChannel(Rect(500,250,logoImage.cols,logoImage.rows)),1.0,
              logoImage,0.5,0.,imageGreenChannel(Rect(500,250,logoImage.cols,logoImage.rows)));
 
       //【5】将三个独立的单通道重新合并成一个三通道
       merge(channels,srcImage);
 
       //【6】显示效果图
       namedWindow("<2>游戏原画+logo绿色通道");
       imshow("<2>游戏原画+logo绿色通道",srcImage);
 
 
 
       //=================【红色通道部分】=================
       //     描述:多通道混合-红色分量部分
       //============================================
      
       //【0】定义相关变量
       Mat  imageRedChannel;
 
       //【1】重新读入图片
       logoImage=imread("D://lili/Desktop/jpg/opencv/dotalogo.jpg",0);
       srcImage=imread("D://lili/Desktop/jpg/opencv/dota.jpg");
 
       if(!logoImage.data ) { printf("读取logoImage错误!\n"); return false; }
       if(!srcImage.data ) { printf("读取srcImage错误!\n"); return false; }
 
       //【2】将一个三通道图像转换成三个单通道图像
       split(srcImage,channels);//分离色彩通道
 
       //【3】将原图的红色通道引用返回给imageBlueChannel,注意是引用,相当于两者等价,修改其中一个另一个跟着变
       imageRedChannel=channels.at(2);
       //【4】将原图的红色通道的(500,250)坐标处右下方的一块区域和logo图进行加权操作,将得到的混合结果存到imageRedChannel中
       addWeighted(imageRedChannel(Rect(500,250,logoImage.cols,logoImage.rows)),1.0,
              logoImage,0.5,0.,imageRedChannel(Rect(500,250,logoImage.cols,logoImage.rows)));
 
       //【5】将三个独立的单通道重新合并成一个三通道
       merge(channels,srcImage);
 
       //【6】显示效果图
       namedWindow("<3>游戏原画+logo红色通道");
       imshow("<3>游戏原画+logo红色通道",srcImage);
 
       return true;
}

运行效果
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

四、图像对比度、亮度值调整

(一)理论依据

在opencv中控制图像的亮度和对比度的理论公式:

g(i,j)=a*f(i,j)+b
  • i和j表示像素位于第i行和第j列
  • f(x):表示源图像像素
  • g(x):表示输出图像像素
  • a:(满足a>0)被称为增益,常常被用来控制图像的对比度
  • b:通常被称为偏置(bias),常常被用来控制图像的亮度

(二)访问图片中的像素

访问像素的代码片段,执行g(i,j)=a*f(i,j)+b运算

//三个for循环,执行运算new_image(i,j) = a*image(i,j) + b
for(int y = 0;y < image.rows;y++){
	for(int x = 0;x < image.cols;x++){
		for(int c = 0;c < 3;c++){
			new_image.at<Vec3b>(y,x)[c] = saturate_cast<uchar>((g_nContrastValue*0.01)*(image.at<Vec3b>(y,x)[c])+g_nBrightValue);
		}
	}
}
  • y是像素所在行,x是像素所在的列,c是R、G、B(对应0、1、2)其中之一。

  • 为了访问图像的每一个像素,我们使用这样的语法: image.at<Vec3b>(y,x)[c]
    其中,y是像素所在的行, x是像素所在的列, c是R、G、B(对应0、1、2)其中之一。

  • 因为我们的运算结果可能超出像素取值范围(溢出),还可能是非整数(如果是浮点数的话),所以我们要用saturate_cast对结果进行转换,以确保它为有效值。

  • 这里的a也就是对比度,一般为了观察的效果,取值为0.0到3.0的浮点值,但是我们的轨迹条一般取值都会整数,所以在这里我们可以,将其代表对比度值的nContrastValue参数设为0到300之间的整型,在最后的式子中乘以一个0.01,这样就可以完成轨迹条中300个不同取值的变化。所以在式子中,我们会看到saturate_cast<uchar>( (g_nContrastValue*0.01)*(image.at<Vec3b>(y,x)[c] ) + g_nBrightValue )中的g_nContrastValue*0.01

(三)示例程序:图像对比度、亮度值调整

示例代码

//-----------------------------------【程序说明】----------------------------------------------
//  程序名称::【OpenCV入门教程之四】 创建Trackbar&图像对比度、亮度值调整 配套博文源码
//------------------------------------------------------------------------------------------------
#include <opencv2/core/core.hpp>
#include<opencv2/highgui/highgui.hpp>
#include"opencv2/imgproc/imgproc.hpp"
#include <iostream>
 
//-----------------------------------【命名空间声明部分】---------------------------------------
//     描述:包含程序所使用的命名空间
//-----------------------------------------------------------------------------------------------  
using namespace std;
using namespace cv;
 
 
//-----------------------------------【全局函数声明部分】--------------------------------------
//     描述:全局函数声明
//-----------------------------------------------------------------------------------------------
static void ContrastAndBright(int, void *);
 
//-----------------------------------【全局变量声明部分】--------------------------------------
//     描述:全局变量声明
//-----------------------------------------------------------------------------------------------
int g_nContrastValue; //对比度值
int g_nBrightValue;  //亮度值
Mat g_srcImage,g_dstImage;
//-----------------------------------【main( )函数】--------------------------------------------
//     描述:控制台应用程序的入口函数,我们的程序从这里开始
//-----------------------------------------------------------------------------------------------
int main(  )
{
       //改变控制台前景色和背景色
       system("color5F"); 
 
       //读入用户提供的图像
       g_srcImage= imread( "D://lili/Desktop/jpg/opencv/1.jpg");
              if(!g_srcImage.data ) { printf("读取g_srcImage图片错误!\n"); return false; }
       g_dstImage= Mat::zeros( g_srcImage.size(), g_srcImage.type() );
 
       //设定对比度和亮度的初值
       g_nContrastValue=80;
       g_nBrightValue=80;
 
       //创建窗口
       namedWindow("【效果图窗口】", 1);
 
       //创建轨迹条
       createTrackbar("对比度:", "【效果图窗口】",&g_nContrastValue,300,ContrastAndBright );
       createTrackbar("亮   度:","【效果图窗口】",&g_nBrightValue,200,ContrastAndBright );
      
       //调用回调函数
       ContrastAndBright(g_nContrastValue,0);
       ContrastAndBright(g_nBrightValue,0);
      
       //输出一些帮助信息
       cout<<endl<<"\t嗯。好了,请调整滚动条观察图像效果~\n\n"
                     <<"\t按下“q”键时,程序退出~!\n"
                     <<"\n\n\t\t\t\t";
 
       //按下“q”键时,程序退出
   while(char(waitKey(1)) != 'q') {}
       return 0;
}
 
 
//-----------------------------【ContrastAndBright( )函数】------------------------------------
//     描述:改变图像对比度和亮度值的回调函数
//-----------------------------------------------------------------------------------------------
static void ContrastAndBright(int, void *)
{
 
       //创建窗口
       namedWindow("【原始图窗口】", 1);
 
       //三个for循环,执行运算 g_dstImage(i,j) =a*g_srcImage(i,j) + b
       for(int y = 0; y < g_srcImage.rows; y++ )
       {
              for(int x = 0; x < g_srcImage.cols; x++ )
              {
                     for(int c = 0; c < 3; c++ )
                     {
                            g_dstImage.at<Vec3b>(y,x)[c]= saturate_cast<uchar>( (g_nContrastValue*0.01)*(g_srcImage.at<Vec3b>(y,x)[c] ) + g_nBrightValue );
                     }
              }
       }
 
       //显示图像
       imshow("【原始图窗口】", g_srcImage);
       imshow("【效果图窗口】", g_dstImage);
}

运行效果
在这里插入图片描述

五、离散傅里叶变换(DFT)

【?待学习】不知道傅里叶变换到底有什么作用

(一)离散傅里叶变换的原理

简单来说,对一张图像进行离散傅里叶变换就是将它分解成正弦和余弦两部分,也就是将图像从空间域转换到频域。这一转换的理论基础是:任一函数都可以表示成无数个正弦和余弦函数的和的形式。

二维图像的傅里叶变化用数学公式可以表示为:
在这里插入图片描述
式中的f是空间域值,F是频域值。
变换之后的频域值是复数,因此,显示傅里叶变换之后的结果需要使用实数图像(real image)加虚数图像(complex image),或者幅度图像(magitude image)加相位图像(phase image)形式。

(二)dft()函数详解

dft函数的作用是对一维或二维浮点数数组进行正向或反向离散傅里叶变换

C++: void dft(InputArray src, OutputArray dst, int flags=0, int nonzeroRows=0);
第一个参数,InputArray类型的src。输入矩阵,可以是实数或虚数。
第二个参数,OutputArray类型的dst,函数调用后的运算结果在这里,其尺寸和类型取决于标识符,也就是第三个参数flags
第三个参数,int类型的flags,转换的标识符,有默认值0,取值可以为下表。
第四个参数,int类型的nonzeroRows,默认值是0。

在这里插入图片描述

一个用dft函数计算两个二维实矩阵卷积的示例核心片段
(没看懂)

void consolveDFT(InputArray A, InputArray B, OutputArray C)
{
	//【1】初始化输出矩阵
	C.create(abs(A.rows - B.rows) + 1, abs(A.cols - B.cols) + 1, A.type());
	Size dftSize;

	//【2】计算DFT变换尺寸
	dftSize.width = getOptimalDFTSize(A.cols + B.cols - 1);
	dftSize.height = getOptimalDFTSize(A.rows + B.rows - 1);

	//【3】分配临时缓冲区并初始化置零
	Mat tempA(dftSize, A.type(), Scalar::all(0));
	Mat tempB(dftSize, B.type(), Scalar::all(0));

	//【4】分别复制A和B到tempA和tempB的左上角
	Mat roiA(tempA, Rect(0, 0, A.cols, A.rows));
	A.copyTo(roiA);
	Mat roiB(tempB, Rect(0, 0, B.cols, B.rows));
	B.copyTo(roiB);

	//【5】就地操作(in_place),进行快速傅里叶变换,并将nonzeroRows参数置为非零,以进行更快速的处理
	dft(tempA, tempA, 0, A.rows);
	dft(tempB, tempB, 0, B.rows);

	//【6】将得到的频谱相乘,结果存放于tempA中
	mulSpectrums(tempA, tempB, tempA);//计算两个傅里叶频谱的每个元素的乘法

	//【7】将结果变换为频域,且尽管结果行(result rows)都为非零,我们只需要其中的C.rows的第一行,所以采用nonzeroRows==C.rows
	dft(tempA, tempA, DFT_INVERSE + DFT_SCALE, C.rows);

	//【8】将结果复制到C中
	tempA(Rect(0, 0, C.cols, C.rows)).copyTo(C);

	//所有的临时缓冲区将被自动释放,所以无须收尾操作
}

(三)返回DFT最优尺寸大小:getOptimalDFTSize()函数

int getOptimalDFTSize(int vecsize)

vecsize:向量尺寸,即图片是我rows、cols

(四)扩充图像边界:copyMakeBorder()函数

作用:扩充图像边界

void copyMakeBorder(InputArray src, OutputArray dst, int top, int bottom, int left, int right, int borderType, const Scalar& value=Scalar() )
  • InputArray src: 输入图像
  • OutputArray dst: 输出图像,与src图像有相同的类型,其尺寸应为Size(src.cols+left+right, src.rows+top+bottom)
  • int类型的top、bottom、left、right: 在图像的四个方向上扩充像素的值
  • int borderType: 边界类型,由borderInterpolate()来定义,常见的取值为BORDER_CONSTANT
  • Scalar:默认值为0,如果边界类型为BORDER_CONSTANT则表示为边界值

(五)计算二维矢量的幅值:magnitude()函数

void magnitude(InputArray x, InputArray y, OutputArray magnitude)

参数解释:

  • InputArray x: 浮点型数组的x坐标矢量,也就是实部
  • InputArray y: 浮点型数组的y坐标矢量,必须和x尺寸相同
  • OutputArray magnitude: 与x类型和尺寸相同的输出数组
    其计算公式如下:
    在这里插入图片描述

(六)计算自然对数:log()函数

void log(InputArray src,OutputArray dst)
  • 第一个参数:输入图像
  • 第二个参数:得到的对数值

原理:
在这里插入图片描述

(七)矩阵归一化:normalize()函数

作用:将矩阵归一化

void normalize(InputArray src, OutputArray dst, double alpha=1, double beta=0, int norm_type=NORM_L2, int dtype=-1, InputArray mask=noArray() )

1、参数1:InputArray src: 输入图像
2、参数2:OutputArray dst: 输出图像,尺寸大小和src相同
3、参数3:double类型的alpha。归一化后的最大值,有默认值1
4、参数4:double类型的beta。归一化后的最小值,有默认值0
5、参数5:int norm_type = NORM_L2: 归一化的类型,主要有

  • NORM_INF: 归一化数组的C-范数(绝对值的最大值)
  • NORM_L1: 归一化数组的L1-范数(绝对值的和)
  • NORM_L2: 归一化数组的L2-范数(欧几里得)
  • NORM_MINMAX: 数组的数值被平移或缩放到一个指定的范围,线性归一化,一般较常用。
    6、参数6:int dtype = -1: 当该参数为负数时,输出数组的类型与输入数组的类型相同,否则输出数组与输入数组只是通道数相同,而depth = CV_MAT_DEPTH(dtype)
    7、参数7:InputArray mask = noArray(): 操作掩膜版,用于指示函数是否仅仅对指定的元素进行操作。

(八)示例程序:离散傅里叶变换

示例代码

#include "opencv2/opencv.hpp"
#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include<iostream>
using namespace std;
using namespace cv;

int main()
{
    //[1]以灰度图读取原始图像并显示
    Mat srcImage = imread("D://lili/Desktop/jpg/opencv/dota.jpg", 0);
    if(!srcImage.data) {
        printf("获取图像失败\n");
        return false;
    }
    imshow("原始图", srcImage);

    //[2]将输入图像扩展到最佳尺寸,边界填0
    int m= getOptimalDFTSize(srcImage.rows);
    int n = getOptimalDFTSize(srcImage.cols);
    //将添加的像素初始化为0
    Mat padded;
    copyMakeBorder(srcImage, padded, 0, m - srcImage.rows, 0, n - srcImage.cols, BORDER_CONSTANT, Scalar::all(0));

    //[3]为傅里叶变换的结果分配存储空间(实部和虚部)
    //将planes数组合并成一个多通道的数组complexI
    Mat planes[] = {Mat_<float>(padded), Mat ::zeros(padded.size(), CV_32F)};
    Mat complexI;
    merge(planes, 2, complexI);

    //[4]进行就地离散傅里叶变换
    dft(complexI, complexI);

    //[5]将复数转换为幅度值
    split(complexI, planes);  //将多通道数组分离成几个单通道数组
    // planes[0] = Re(DFT(I));
    // planes[1] = Im(DFT(I));

    magnitude(planes[0], planes[1], planes[0]);

    Mat magnitudeImage = planes[0];

    //[6]进行对数尺度缩放
    magnitudeImage += Scalar::all(1);
    log(magnitudeImage, magnitudeImage);

    //[7]剪切和重分幅度图象限
    //若有奇数行或列,进行频谱裁剪
    magnitudeImage = magnitudeImage(Rect(0, 0, magnitudeImage.cols & -2, magnitudeImage.rows & -2));
    //重新排列傅里叶变换中的象限,使得原点位于图像中心
    int cx = magnitudeImage.cols/2;
    int cy = magnitudeImage.rows/2;
    Mat q0(magnitudeImage,Rect(0,0,cx,cy));  //ROI左上角
    Mat q1(magnitudeImage, Rect(cx, 0, cx,cy));  // 右下角
    Mat q2(magnitudeImage, Rect(0,cy, cx, cy));
    Mat q3(magnitudeImage, Rect(cx, cy, cx, cy));
    //交换象限(左上角和右下角交换)
    Mat tmp;
    q0.copyTo(tmp);
    q3.copyTo(q0);
    tmp.copyTo(q3);

    //交换象限(右上角和左下交换)
    q1.copyTo(tmp);
    q2.copyTo(q1);
    tmp.copyTo(q2);

    //[8]归一化,用0-1之间的浮点值将矩阵变换到可视的图像格式
    //normalize(magnitudeImage, magnitudeImage, 0 , 1, CV_MINMAX);//opencv2
    normalize(magnitudeImage, magnitudeImage, 0, 1, NORM_MINMAX);//opencv3

    imshow("频谱图", magnitudeImage);
    // waitKey(0);
    while(char (waitKey(1)) != 'q'){}
    return 0;

}

运行效果
在这里插入图片描述

六、输入输出XML和YAML文件

(一)XML和YAML文件简介

XML:可扩展标识语言。是一种语义/结构化语言,它描述了文档的结构和语义。
YAML:一个可读性高,用来表达资料序列的格式。
总之,YAML试图用一种比XML更敏捷的方式,来完成XML所完成的任务。

(二)FileStorage类操作文件的使用引导

写入或读取数据到XML或YAML文件中的过程:
(1)实例化一个FileStorage类的对象,用默认带参数的构造函數完成初始化,或者用FileStorage::open()成员函数辅助初始化。
(2)使用流操作符<<进行文件写入操作,或者>>进行文件读取操作,类似c++中的文件输入输出流。
(3)使用FileStorage::release()函数析构掉FileStorage类对象,同时关闭文件。

下面分别对这三个步骤进行实例讲解。

〖第一步〗XML、YAML文件的打开

(1)准备文件写操作

FileStorege是OpenCV中XML和YAML文件的存储类,封装了所有相关的信息。它是OpenCV从文件中读数据或向文件中写数据时必须要使用的一个类。

此类的构造函数为FileStorage::FileStorage,有两个重载,如下。

FiIeStorage::FileStorage
FiIeStorage::FileStorage(const string & source,int flags,const string& encoding=string())

构造函数在实际使用中,方法一般有两种。

  • 对于第二种带参数的构造函数,进行写操作范例如下。
FileStorage fs;
fs.open("abc.xml",FileStorage::WRITE);
  • 对于第一种不带参数的构造函数,可以使用其成员函数FileStorage::open进行数据的与操作,范例如下。
FileStorage fs;
fs.open("abc.xml",FileStorage::WRITE);
(2)准备文件读操作

上面讲到的都是以FileStorage::WRITE为标识符的写操作,而读操作,采用FileStronge::READ标识符即可,相关示例代码如下。

  • 第一种方式
FileStorage fs("abc.xml",FileStorage::READ);
  • 第二种方式
FileStorage fs;
fs.open("abc.xml",FileStroage::READ);

〖第二步〗进行文件的读取操作

(1)文本和数字的输入和输出

定义好FileStorage类对象之后,写入文件可以使用“<<”运算符,例如:

fs<<"iterationNr"<<100;

而读取文件,使用“>>”运算符,例如:

int itNr;
fs["iterationNr"]>>itNr;
itNr=(int)fs["iterationNr"];
(2)OpenCV数据结构的输入和输出

关于OpenCV数据结构的输入和输出,和基本的C++形式相同,范例如下。

//数据结构的初始化
	Mat R = Mat_<uchar>::eye(3, 3);
	Mat T = Mat_<double>::zeros(3, 1);

	//向Mat中写入数据
	fs << "R" << R;
	fs << "T" << T;

	//从Mat中读出数据
	fs["R"] >> R;
	fs["T"] >> T;

〖第三步〗vector(arrays)和maps的输入和输出

对于vector结构的输入和输出,要注意在第一个元素前加上"[",在最后一个元素前加上“]”。例如:

//开始读入string文本序列
	fs << "strings" << "[";
	fs << "image1.jpg" << "Awesomeness" << "baboon.jpg";
	fs << "]";

而对于map结构的操作,使用的符号是“{”和“}”,例如:

fs << "Mapping";
	fs << "{" << "One" << 1;
	fs << "Two" << 2 << "}";

读取这些结构的时候,会用到FileNode和Fileodelterator数据结构。对Filestorage类的“[”、“]”操作符会返回FileNode数据类型;对于一连串的node,可以使用FileNodeIterator结构,例如:

FileNode n = fs["strings"];//读取字符串序列以得到节点
if (n.type()!=FileNode::SEQ)
{
	cerr << "发生错误!字符串不是一个序列!" << endl;
	return 1;
}

//遍历节点
FileNodeIterator it = n.begin(), it_end = n.end();
for (; it != it_end; it++)
{
	cout << (string)*it << endl;
}

〖第四步〗关闭文件

需要注意的是,文件关闭操作会在FileStroage类销毁的同时自动进行,但我们也可显式调用其析构函数FileStroage::release()实现。FileStroage::release()函数会析构掉FileStroage类对象,同时关闭文件。
调用过程非常简单,如下。

fs.release();

(三)示例程序:XML和YAML文件的写入

示例代码

#include<opencv2\opencv.hpp>
#include<time.h>

using namespace cv;

int main(){
	//初始化
	FileStorage fs("D:/lili/Desktop/test.yaml", FileStorage::WRITE);
	
	//开始文件写入
	fs << "frameCount" << 5;
	time_t rawtime;
	time(&rawtime);
	fs << "calibrationDate" << asctime(localtime(&rawtime));
	Mat cameraMatrix = (Mat_<double>(3, 3) << 100, 0, 320, 0, 1000, 240, 0, 0, 1);
	Mat distCoeffs = (Mat_<double>(5, 1) << 0.1, 0.01, -0.001, 0, 0);
	fs << "cameraMatrix" << cameraMatrix << "distCoeffs" << distCoeffs;
	fs << "features" << "[";
	for (int i = 0; i < 3; i++)
	{
		int x = rand() % 640;
		int y = rand() % 480;
		uchar lbp = rand() % 256;

		fs << "{:" << "x" << x << "y" << y << "lbp" << "[:";
		for (int j = 0; j < 8; j++)
		{
			fs << ((lbp >> j) & 1);
		}
		fs << "]" << "}";
	}
	fs << "]";
	fs.release();

	printf("文件读写完毕!");
	getchar();

	return 0;
}

运行效果
在这里插入图片描述

(四)示例程序:XML和YAML文件的读取

示例代码

#include<opencv2\opencv.hpp>
#include<time.h>

using namespace cv;
using namespace std;

int main(){
	//改变控制台字体颜色
	system("color 6F");

	//初始化
	FileStorage fs2("D:/lili/Desktop/test.yaml", FileStorage::READ);

	//第一种方法,对FileNode进行操作
	int frameCount = (int)fs2["frameCount"];
	std::string date;

	//第二种方法,使用FileNode运算符
	fs2["calibrationDate"] >> date;
	Mat cameraMatrix2, distCoeffs2;
	fs2["cameraMatrix"] >> cameraMatrix2;
	fs2["distCoeffs"] >> distCoeffs2;

	cout << "frameCount:" << frameCount << endl
		<< "calibration date:" << date << endl
		<< "camera matrix:" << cameraMatrix2 << endl
		<< "dist coeffs2" << distCoeffs2 << endl;

	FileNode features = fs2["features"];
	FileNodeIterator it = features.begin(), it_end = features.end();
	int idx = 0;
	std::vector<uchar> lbpval;
	//使用FileNodeIterator遍历序列
	for (; it != it_end; it++)
	{
		cout << "feature #" << idx << ":";
		cout << "x=" << (int)(*it)["x"] << ",y=" << (int)(*it)["y"] << ",lbp:(";
		(*it)["lbp"] >> lbpval;
		for (int i = 0; i < (int)lbpval.size(); i++)
		{
			cout << " " << (int)lbpval[i];
		}
		cout << ")" << endl;
	}
	fs2.release();

	printf("\n文件读取完毕,请输入任意键结束程序!\n");
	getchar();

	return 0;
}

运行效果
在这里插入图片描述

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

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

相关文章

基于FPGA的超声波测距

文章目录 一、HC-SR04超声波测距模块说明1、产品特点2、电气参数3、HC-SR04超声波测距模块4、超声波时序图 二、程序设计 一、HC-SR04超声波测距模块说明 1、产品特点 HC-SR04 超声波测距模块可提供 2cm-400cm 的非接触式距离感测功能&#xff0c;测距精度可达高到 3mm&#…

驱动开发--字符设备驱动

目录 1.驱动模块 hello.c Makefile 2.内核中的打印函数&#xff08;编写第一个驱动程序&#xff09; Source Insight 使用&#xff1a; 3.打印函数编写 分析 4、驱动的多文件编译 5、模块传递参数 6、安装好驱动之后如何传参&#xff1f; 7、字符设备驱动 8、字符设…

云上VPC网络规划实战

新钛云服已累计为您分享750篇技术干货 什么是VPC 虚拟专有网络&#xff08;Virtual Private Cloud&#xff0c;简称VPC&#xff09;是阿里云提供的一种云上私有网络&#xff0c;为用户提供独立且可控的网络环境。用户可以自主定义VPC的IP地址范围、配置路由表和网关等&#xff…

chatgpt赋能python:Python在Win7上的安装教程

Python在Win7上的安装教程 如果你是一名Win7用户&#xff0c;并且打算开始学习或者使用Python编程语言&#xff0c;那么本文将会为你提供一个简单易懂的Python安装教程。 1. 下载Python 在安装Python之前&#xff0c;你需要先去Python的官方网站&#xff08;https://www.pyt…

chatgpt赋能python:Python输出0到9:从基础到高阶

Python 输出 0 到 9&#xff1a;从基础到高阶 在Python中&#xff0c;输出0到9这样的数字非常简单&#xff0c;你可以使用内置的range()函数或循环进行实现。在本篇文章中&#xff0c;我们将介绍几种不同的方法来输出0到9的数字。 使用range()函数输出0到9 range()函数是Pyt…

夜天之书 #84 国产开源社群的运营,为何总是画风奇特?

在过去几年的投入和关注下&#xff0c;国产开源社群如雨后春笋一般冒了出来。今天&#xff0c;以 GPT 为首的 AI 新势力接过话题度的接力棒&#xff0c;我们可以在降温周期里回顾一下过去几年间冒出来的国产开源社群都有什么样的成绩&#xff0c;有些什么样共性的问题可以改进。…

苹果宣布最新操作系统:visionOS

今天凌晨&#xff0c;WWDC23 全球开发者大会正式开幕。 大会上&#xff0c;苹果展示了包括 iOS 17、iPadOS 17、watchOS 10 和 macOS Sonoma 在内的新系统。硬件方面&#xff0c;苹果发布了 15 英寸的 MacBook Air、搭载 M2 Ultra 的 Mac Studio 以及 Mac Pro。 此外&#xff0…

sqlserver练习----涉及多个表的连接查询

等值联接 多表查询语句中的连接条件使用的是等号,例&#xff1a;Student.SnoSC.Sno 例&#xff1a; Student 学号 Sno 姓名 Sname 性别 Ssex 年龄 Sage 所在系 Sdept 202015121李勇男20CS202015122刘晨女10 CS 202015123 王敏女18 MA 202015125张力男19IS SC: 学号 Sn…

秋招面试腹稿

1、自我介绍 你好&#xff0c;我叫熊志君&#xff0c;是就读于电子信息专业的24届研究生。在校期间获得过两次一等奖学金、两次省级竞赛一等奖&#xff0c;英语过了6级&#xff0c;我的研究方向是水下slam多传感器融合方向&#xff0c;用过c/c/python三种编程语言。 2、系统移植…

如何缓解高考前紧张的情绪,ChatGPT这么说......

明天就要高考了&#xff0c;看到家长有各种打气的做法&#xff0c;既有上灵隐寺的&#xff0c;也有穿着旗袍希望旗开得胜的&#xff0c;还有说什么失败了不要紧的......&#xff0c;反正都是焦虑的不行。 面对高考&#xff0c;大多考生都会紧张&#xff0c;但适度的紧张对发挥出…

解码器 | 基于 Transformers 的编码器-解码器模型

基于 transformer 的编码器-解码器模型是 表征学习 和 模型架构 这两个领域多年研究成果的结晶。本文简要介绍了神经编码器-解码器模型的历史&#xff0c;更多背景知识&#xff0c;建议读者阅读由 Sebastion Ruder 撰写的这篇精彩 博文。此外&#xff0c;建议读者对 自注意力 (…

Mocha AE:AdjustTrack 模块

跟踪时由于缺乏细节或有障碍物阻挡&#xff0c;跟踪点会发生漂移&#xff0c;此时可考虑使用 AdjustTrack &#xff08;调整跟踪&#xff09;模块手动设置表面区域 Planar Surface关键帧来获得更可靠的表面跟踪数据。 但是&#xff0c;如果需要设置较多的关键帧时&#xff0c;建…

Linux计划任务

常见的计划任务&#xff1a;进行日志的轮替&#xff08;log rotate&#xff09;&#xff1b;日志文件分析&#xff08;logwatch&#xff09;任务&#xff1b;建立locate数据库&#xff1b;man page查询数据库的建立&#xff1b;RPM软件登录文件的建立&#xff1b;移除暂存档&am…

尺度悖论解析费米悖论:从夜郎自大到揭秘宇宙中智慧生命的谜团

费米悖论是一个引人入胜的问题&#xff0c;它引发了人们对宇宙中是否存在其他智慧生命体的思考。然而&#xff0c;尺度悖论提供了一个可能的解释角度&#xff0c;即我们对宇宙的观测和推断尺度可能太小&#xff0c;无法涵盖整个宇宙范围。下面深入探讨尺度悖论以及费米悖论的具…

Linux系统一般用来干嘛

Linux系统是一种开源的操作系统&#xff0c;广泛应用于服务器、嵌入式设备、超级计算机等领域。它具有高度的稳定性、安全性和灵活性&#xff0c;可以用来进行各种各样的任务&#xff0c;例如&#xff1a; 1、服务器操作系统 Linux系统在服务器领域应用广泛&#xff0c;可以用…

Maven继承

Maven 在设计时&#xff0c;借鉴了 Java 面向对象中的继承思想&#xff0c;提出了 POM 继承思想。 当一个项目包含多个模块时&#xff0c;可以在该项目中再创建一个父模块&#xff0c;并在其 POM 中声明依赖&#xff0c;其他模块的 POM 可通过继承父模块的 POM 来获得对相关依赖…

微信小程序自定义导航栏

微信小程序自定义导航栏 业务需求&#xff1a; 点击小房子进行跳转指定的页面 、更改小房子的样式、或者是自定义导航栏 首先我们需要找到pages.json这个文件 如果是原生的微信小程序文件名字是 app.json其实就是找到配置路由的文件在代码里面添加属性"navigationStyle&qu…

java设计模式(十四)模板方法

目录 定义模式结构角色职责代码实现适用场景优缺点 定义 模板方法模式(Template Method Pattern)&#xff0c;又叫模板模式(Template Pattern)&#xff0c; 指在一个抽象类公开定义了执行它的方法的模板。它的子类可以按需要重写方法实现&#xff0c;但调用将以抽象类中定义的…

1. Hadoop 入门

1. Hadoop 入门 1. 大数据概述 1. 大数据相关说明 大数据由来&#xff1a; 传统数据处理应用软件不足以处理&#xff08;存储和计算&#xff09;它们大而复杂的数据集 大数据面临的两大问题&#xff1a; 针对海量数据的 存储、计算 大数据的特性&#xff1a;容量大、种类多…

VFP使用BLOB字段存取图片到SQL2000,显示出来也EASY

首先来看一下BLOB这个数据类型的介绍&#xff1a; 大二进制对象(Blob)数据类型&#xff0c;若要存储一个任何种类的二进制数据&#xff0c;如 ASCII 码文本、一个可执行文件(.exe) 或一个带有不确定长度的字节字符串&#xff0c;可使用大二进制对象数据类型。对于从 SQL Serve…