【视觉SLAM】 十四讲ch5习题

news2024/11/14 10:41:41

1.*寻找一个相机(你手机或笔记本的摄像头即可),标定它的内参。你可能会用到标定板,或者自己打印一张标定用的棋盘格。

参考我之前写过的这篇博客:【OpenCV】 相机标定 calibrateCamera

Code来源是《学习OpenCV3》18.01的例程,笔者做了注释补充,Code如下:

// Example 18-1. Reading a chessboard’s width and height, reading and collecting
// the requested number of views, and calibrating the camera
#include <iostream>
#include <opencv2/opencv.hpp>
 
using std::vector;
using std::cout;
using std::cerr;
using std::endl;
 
void help(char **argv) {  // todo rewrite this
    cout << "\n\n"
         << "Example 18-1:\nReading a chessboard’s width and height,\n"
         << "              reading and collecting the requested number of views,\n" 
         << "              and calibrating the camera\n\n" 
         << "Call:\n" << argv[0] << " <board_width> <board_height> <number_of_boards> <if_video,_delay_between_framee_capture> <image_scaling_factor>\n\n"
         << "Example:\n" << argv[0] << " 9 6 15 500 0.5\n"
         << "-- to use the checkerboard9x6.png provided\n\n"
         << " * First it reads in checker boards and calibrates itself\n" 
         << " * Then it saves and reloads the calibration matricies\n"
         << " * Then it creates an undistortion map and finally\n"
         << " * It displays an undistorted image\n"
         << endl;
}
 
int main(int argc, char *argv[]) {
    int n_boards = 0;           // will be set by input list
    float image_sf = 0.5f;      // image scaling factor
    float delay = 1.f;
    int board_w = 0;
    int board_h = 0;
 
    if (argc < 4 || argc > 6) {
        cout << "\nERROR: Wrong number of input parameters\n";
        help(argv);
        return -1;
    }
 
    board_w = atoi(argv[1]);
    board_h = atoi(argv[2]);
    n_boards = atoi(argv[3]);
 
    if (argc > 4) {
        delay = atof(argv[4]);
    }
    if (argc > 5) {
        image_sf = atof(argv[5]);
    }
 
    /*
    image_sf是一个用来缩放图像的尺度因子(scale factor)。            
        角点检测的稳定性: 通过缩放图像,可以在一定程度上增强棋盘格角点检测的稳定性。
            有时候,一些图像处理算法对于图像的尺寸大小比较敏感,通过调整图像的尺寸可以帮助算法更好地检测到棋盘格角点。
        提高匹配精度: 调整图像的尺寸可以使棋盘格角点在缩小或放大后仍然保持一定的像素间距,有助于提高匹配的准确性。这样在后续的相机标定过程中,可以更准确地匹配到对应的图像坐标和三维空间坐标。
        加快算法速度: 在某些情况下,缩小图像的尺寸可以减少计算量,从而加快算法的执行速度。特别是对于较大分辨率的图像,通过缩小尺寸可以降低处理的复杂度,提高算法的效率。
    */
 
    int board_n = board_w * board_h;
    cv::Size board_sz = cv::Size(board_w, board_h);
    cv::VideoCapture capture(0);
    if (!capture.isOpened()) {
        cout << "\nCouldn't open the camera\n";
        help(argv);
        return -1;
    }
 
    // ALLOCATE STORAGE
    //
    vector<vector<cv::Point2f> > image_points;
    vector<vector<cv::Point3f> > object_points;
 
    // Capture corner views: loop until we've got n_boards successful
    // captures (all corners on the board are found).
    //
    double last_captured_timestamp = 0;
    cv::Size image_size;
    while (image_points.size() < (size_t)n_boards) {
    cv::Mat image0, image;
    capture >> image0;
    image_size = image0.size();
    cv::resize(image0, image, cv::Size(), image_sf, image_sf, cv::INTER_LINEAR);
 
    // Find the board
    //
    vector<cv::Point2f> corners;
    bool found = cv::findChessboardCorners(image, board_sz, corners);
 
    // Draw it
    //
    drawChessboardCorners(image, board_sz, corners, found);
 
    // If we got a good board, add it to our data
    //
    double timestamp = static_cast<double>(clock()) / CLOCKS_PER_SEC;
    if (found && timestamp - last_captured_timestamp > 1) {
        last_captured_timestamp = timestamp;
        image ^= cv::Scalar::all(255);
        /*
        代码使用了异或运算符 ^,并且将图像 image 与所有像素值为255的白色 cv::Scalar 进行异或操作。
        换句话说,这行代码将图像中所有像素值与255进行逐位异或操作,实现了反转图像像素的效果。
        具体来说,对于每个像素的每个通道,如果像素值为0,异或255后为255;
        如果像素值为255,异或255后为0。因此,总体上就是将原图像中的亮度值取反,白色变为黑色,黑色变为白色,实现了一种图像反色的效果。
        这种操作常常用于在图像处理中实现一些效果,比如图像的反色展示或者图像的二值化处理。
        */
        cv::Mat mcorners(corners);
        /*
        将存储棋盘格角点的corners向量转换为cv::Mat类型的mcorners矩阵,并对矩阵中的数据进行缩放操作。
        具体解释如下:
        cv::Mat mcorners(corners);: 这一行代码通过将corners向量作为参数,创建了一个新的cv::Mat类型的mcorners矩阵。这将导致mcorners矩阵拥有与corners向量相同的数据内容。
        mcorners *= (1.0 / image_sf);: 这行代码对mcorners矩阵中的所有元素进行缩放操作。具体地,每个元素除以image_sf,这样就实现了对角点坐标的缩放操作。
        这是由于之前将图像缩放了image_sf倍,而角点坐标也需要相应进行缩放处理。
        通过这两行代码,将角点坐标存储在corners向量中的数据转换为了cv::Mat类型的矩阵,
        并对矩阵中的数据进行了缩放操作。这是为了保持角点坐标和图像坐标的一致性。
         */
        // 后续似乎没有用到
        // do not copy the data
        mcorners *= (1.0 / image_sf);
 
        // scale the corner coordinates
        image_points.push_back(corners);
        object_points.push_back(vector<cv::Point3f>());
        vector<cv::Point3f> &opts = object_points.back();
 
        opts.resize(board_n);
        for (int j = 0; j < board_n; j++) {
            opts[j] = cv::Point3f(static_cast<float>(j / board_w),
                                  static_cast<float>(j % board_w), 0.0f);
        }
        cout << "Collected our " << static_cast<uint>(image_points.size())
             << " of " << n_boards << " needed chessboard images\n" << endl;
    }
    cv::imshow("Calibration", image);
 
    // show in color if we did collect the image
    if ((cv::waitKey(30) & 255) == 27)
      return -1;
 
    }
 
    // END COLLECTION WHILE LOOP.
    cv::destroyWindow("Calibration");
    cout << "\n\n*** CALIBRATING THE CAMERA...\n" << endl;
 
    // CALIBRATE THE CAMERA!
    //
    cv::Mat intrinsic_matrix, distortion_coeffs;
    double err = cv::calibrateCamera(
        object_points, image_points, image_size, intrinsic_matrix,
        distortion_coeffs, cv::noArray(), cv::noArray(),
        cv::CALIB_ZERO_TANGENT_DIST | cv::CALIB_FIX_PRINCIPAL_POINT);
 
    /*
    err表示相机标定的平均重投影误差。
    在 OpenCV 中,calibrateCamera 函数会返回相机标定的重投影误差,
    即用标定结果对相机拍摄的图像进行重投影并计算误差的平均值。
    */
 
   /*
   一些常见的重投影误差标准范围:
    一般标准: 对于一般应用,重投影误差在0.1到1像素之间可以被接受。
    精度要求高: 对于需要高精度测量的应用,重投影误差应该控制在0.1像素以下。
    实时应用: 对于实时图像处理或运动跟踪等应用,重投影误差在1到2像素之间可能是可以接受的。
   */
 
    // SAVE THE INTRINSICS AND DISTORTIONS
    cout << " *** DONE!\n\nReprojection error is " << err
         << "\nStoring Intrinsics.xml and Distortions.xml files\n\n";
    cv::FileStorage fs("intrinsics.xml", cv::FileStorage::WRITE);
    fs << "image_width" << image_size.width << "image_height" << image_size.height
       << "camera_matrix" << intrinsic_matrix << "distortion_coefficients"
       << distortion_coeffs;
    fs.release();
 
    // EXAMPLE OF LOADING THESE MATRICES BACK IN:
    fs.open("intrinsics.xml", cv::FileStorage::READ);
    cout << "\nimage width: " << static_cast<int>(fs["image_width"]);
    cout << "\nimage height: " << static_cast<int>(fs["image_height"]);
    cv::Mat intrinsic_matrix_loaded, distortion_coeffs_loaded;
    fs["camera_matrix"] >> intrinsic_matrix_loaded;
    fs["distortion_coefficients"] >> distortion_coeffs_loaded;
    cout << "\nintrinsic matrix:" << intrinsic_matrix_loaded;
    cout << "\ndistortion coefficients: " << distortion_coeffs_loaded << endl;
 
    // Build the undistort map which we will use for all
    // subsequent frames.
    //
    cv::Mat map1, map2;
    cv::initUndistortRectifyMap(intrinsic_matrix_loaded, distortion_coeffs_loaded,
                              cv::Mat(), intrinsic_matrix_loaded, image_size,
                              CV_16SC2, map1, map2);
 
    // Just run the camera to the screen, now showing the raw and
    // the undistorted image.
    //
    for (;;) {
        cv::Mat image, image0;
        capture >> image0;
 
        if (image0.empty()) {
            break;
        }
        cv::remap(image0, image, map1, map2, cv::INTER_LINEAR,
            cv::BORDER_CONSTANT, cv::Scalar());
        cv::imshow("Undistorted", image);
        if ((cv::waitKey(30) & 255) == 27) {
            break;
        }
    }
 
    return 0;
}

2.叙述相机内参的物理意义。如果一个相机的分辨率变成两倍而其他地方不变,它的内参如何变化?


3.搜索特殊的相机(鱼眼或全景)相机的标定方法。它们与普通的针孔模型有何不同?

鱼眼相机模型和普通针孔相机模型的标定流程类似,但是鱼眼相机在结构上比普通相机的透镜更多,引入了更大的径向畸变。

因为,鱼眼相机的投影模型为了将尽可能大的场景投影到有限的图像平面内,允许了相机畸变的存在。并且由于鱼眼相机的径向畸变非常严重,所以鱼眼相机主要的是考虑径向畸变,而忽略其余类型的畸变。

为了将尽可能大的场景投影到有限的图像平面内,鱼眼相机会按照一定的投影函数来设计。根据投影函数的不同,鱼眼相机的设计模型大致能被分为四种:等距投影模型、等立体角投影模型、正交投影模型和体视投影模型。

总的来说,就是在标定时采用的畸变模型不同。以OpenCV为例,OpenCV求解相机参数分为两步:1、求解焦距和偏移,2、求解畸变参数。鱼眼相机与普通相机第一步大致相同,但鱼眼相机标定时畸变模型一般使用,五元素形式(k1,k2,p1,p2和k3)。详细请参考【OpenCV】 相机标定 calibrateCamera和鱼眼相机成像模型以及基于OpenCV标定鱼眼镜头

笔者没有接触过全景相机,请参考【计算机视觉】全景相机标定(MATLAB/opencv)

4.调研全局快门相机(global shutter)和卷帘快门相机(rolling shutter)的异同。它们在SLAM 中有何优缺点?

全局快门相机通过整幅场景在同一时间曝光实现的(CCD是全局快门)。Sensor所有像素点同时收集光线,同时曝光。即在曝光开始的时候,Sensor开始收集光线;在曝光结束的时候,光线收集电路被切断。然后Sensor值读出即为一幅照片。全局快门曝光时间更短,容易产生噪点。由于硬件成本,帧率一般比同价位的卷帘快门低。尤其是对于抓拍高速运动的物体有劣势:由于技术限制,很难将巨大的数据量同时处理。

卷帘快门相机通过Sensor逐行曝光的方式实现的(CMOS大多是卷帘快门)。在曝光开始的时候,Sensor逐行扫描逐行进行曝光,直至所有像素点都被曝光。当然,所有的动作在极短的时间内完成。不同行像元的曝光时间不同。大多数CMOS传感器采用这一快门。当物体或者相机在高速运动时会出现果冻效应。

果冻效应

用卷帘快门方式拍摄,逐行扫描速度不够,拍摄结果就可能出现"倾斜"、"摇摆不定"或"部分曝光"等情况。这种卷帘快门方式拍摄出现的现象,就定义为果冻效应。

总结:对于高速移动的物体来说,卷帘快门容易出现扭曲现象(果冻效应)。用Global shutter方式拍摄,假如曝光时间过长,照片会产生像糊现象。而且由于技术限制,很难将巨大的数据量同时处理。


5.RGB-D 相机是如何标定的?以Kinect 为例,需要标定哪些参数?

(参照GitHub - code-iai/iai_kinect2: Tools for using the Kinect One (Kinect v2) in ROS.)

Kinect2由普通RGB相机、红外相机和红外投射器组成,其中红外相机和红外投射器共同组成深度相机。

根据Kinect2结构,所以需要标定参数比普通RGB相机多出来红外相机的相机内参和畸变参数以及普通RGB相机、红外相机的外参(位姿变换矩阵)和深度图的深度值校准。

故需要标定参数为:

RGB相机:相机内参和畸变参数;

红外相机:相机内参和畸变参数;

RGB相机和红外相机的外参(位姿变换矩阵);

深度图的深度值校准;

Kinect2标定原理详情请参考:RGB-D相机的标定与图像配准


6.除了示例程序演示的遍历图像的方式,你还能举出哪些遍历图像的方法?

《OpenCV3编程入门》配套示例程序提供了14种之多。

Code如下:

//--------------------------------------【程序说明】-------------------------------------------
//		程序说明:《OpenCV3编程入门》OpenCV2版书本配套示例程序24
//		程序描述:来自一本国外OpenCV2书籍的示例-遍历图像像素的14种方法
//		测试所用IDE版本:Visual Studio 2010
//		测试所用OpenCV版本:	2.4.9
//		2014年11月 Revised by @浅墨_毛星云
//------------------------------------------------------------------------------------------------


/*------------------------------------------------------------------------------------------*\
   This file contains material supporting chapter 2 of the cookbook:
   Computer Vision Programming using the OpenCV Library.
   by Robert Laganiere, Packt Publishing, 2011.
   This program is free software; permission is hereby granted to use, copy, modify,
   and distribute this source code, or portions thereof, for any purpose, without fee,
   subject to the restriction that the copyright notice may not be removed
   or altered from any source or altered source distribution.
   The software is released on an as-is basis and without any warranties of any kind.
   In particular, the software is not guaranteed to be fault-tolerant or free from failure.
   The author disclaims all warranties with regard to this software, any use,
   and any consequent failure, is purely the responsibility of the user.

   Copyright (C) 2010-2011 Robert Laganiere, www.laganiere.name
\*------------------------------------------------------------------------------------------*/

//---------------------------------【头文件、命名空间包含部分】-----------------------------
//		描述:包含程序所使用的头文件和命名空间
//-------------------------------------------------------------------------------------------------
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
using namespace cv;
using namespace std;



//---------------------------------【宏定义部分】---------------------------------------------
//		描述:包含程序所使用宏定义
//-------------------------------------------------------------------------------------------------
#define NTESTS 14
#define NITERATIONS 20



//----------------------------------------- 【方法一】-------------------------------------------
//		说明:利用.ptr 和 []
//-------------------------------------------------------------------------------------------------
void colorReduce0(Mat& image, int div = 64) {

	int nl = image.rows; //行数
	int nc = image.cols * image.channels(); //每行元素的总元素数量

	for (int j = 0; j < nl; j++)
	{

		uchar* data = image.ptr<uchar>(j);

		for (int i = 0; i < nc; i++)
		{

			//-------------开始处理每个像素-------------------

			data[i] = data[i] / div * div + div / 2;

			//-------------结束像素处理------------------------

		} //单行处理结束                  
	}
}

//-----------------------------------【方法二】-------------------------------------------------
//		说明:利用 .ptr 和 * ++ 
//-------------------------------------------------------------------------------------------------
void colorReduce1(Mat& image, int div = 64) {

	int nl = image.rows; //行数
	int nc = image.cols * image.channels(); //每行元素的总元素数量

	for (int j = 0; j < nl; j++)
	{

		uchar* data = image.ptr<uchar>(j);

		for (int i = 0; i < nc; i++)
		{

			//-------------开始处理每个像素-------------------

			*data++ = *data / div * div + div / 2;

			//-------------结束像素处理------------------------

		} //单行处理结束              
	}
}

//-----------------------------------------【方法三】-------------------------------------------
//		说明:利用.ptr 和 * ++ 以及模操作
//-------------------------------------------------------------------------------------------------
void colorReduce2(Mat& image, int div = 64) {

	int nl = image.rows; //行数
	int nc = image.cols * image.channels(); //每行元素的总元素数量

	for (int j = 0; j < nl; j++)
	{

		uchar* data = image.ptr<uchar>(j);

		for (int i = 0; i < nc; i++)
		{

			//-------------开始处理每个像素-------------------

			int v = *data;
			*data++ = v - v % div + div / 2;

			//-------------结束像素处理------------------------

		} //单行处理结束                   
	}
}

//----------------------------------------【方法四】---------------------------------------------
//		说明:利用.ptr 和 * ++ 以及位操作
//----------------------------------------------------------------------------------------------------
void colorReduce3(Mat& image, int div = 64) {

	int nl = image.rows; //行数
	int nc = image.cols * image.channels(); //每行元素的总元素数量
	int n = static_cast<int>(log(static_cast<double>(div)) / log(2.0));
	//掩码值
	uchar mask = 0xFF << n; // e.g. 对于 div=16, mask= 0xF0

	for (int j = 0; j < nl; j++) {

		uchar* data = image.ptr<uchar>(j);

		for (int i = 0; i < nc; i++) {

			//------------开始处理每个像素-------------------

			*data++ = *data & mask + div / 2;

			//-------------结束像素处理------------------------
		}  //单行处理结束            
	}
}


//----------------------------------------【方法五】----------------------------------------------
//		说明:利用指针算术运算
//---------------------------------------------------------------------------------------------------
void colorReduce4(Mat& image, int div = 64) {

	int nl = image.rows; //行数
	int nc = image.cols * image.channels(); //每行元素的总元素数量
	int n = static_cast<int>(log(static_cast<double>(div)) / log(2.0));
	int step = image.step; //有效宽度
	//掩码值
	uchar mask = 0xFF << n; // e.g. 对于 div=16, mask= 0xF0

	//获取指向图像缓冲区的指针
	uchar* data = image.data;

	for (int j = 0; j < nl; j++)
	{

		for (int i = 0; i < nc; i++)
		{

			//-------------开始处理每个像素-------------------

			*(data + i) = *data & mask + div / 2;

			//-------------结束像素处理------------------------

		} //单行处理结束              

		data += step;  // next line
	}
}

//---------------------------------------【方法六】----------------------------------------------
//		说明:利用 .ptr 和 * ++以及位运算、image.cols * image.channels()
//-------------------------------------------------------------------------------------------------
void colorReduce5(Mat& image, int div = 64) {

	int nl = image.rows; //行数
	int n = static_cast<int>(log(static_cast<double>(div)) / log(2.0));
	//掩码值
	uchar mask = 0xFF << n; // e.g. 例如div=16, mask= 0xF0

	for (int j = 0; j < nl; j++)
	{

		uchar* data = image.ptr<uchar>(j);

		for (int i = 0; i < image.cols * image.channels(); i++)
		{

			//-------------开始处理每个像素-------------------

			*data++ = *data & mask + div / 2;

			//-------------结束像素处理------------------------

		} //单行处理结束            
	}
}

// -------------------------------------【方法七】----------------------------------------------
//		说明:利用.ptr 和 * ++ 以及位运算(continuous)
//-------------------------------------------------------------------------------------------------
void colorReduce6(Mat& image, int div = 64) {

	int nl = image.rows; //行数
	int nc = image.cols * image.channels(); //每行元素的总元素数量

	if (image.isContinuous())
	{
		//无填充像素
		nc = nc * nl;
		nl = 1;  // 为一维数列
	}

	int n = static_cast<int>(log(static_cast<double>(div)) / log(2.0));
	//掩码值
	uchar mask = 0xFF << n; // e.g. 比如div=16, mask= 0xF0

	for (int j = 0; j < nl; j++) {

		uchar* data = image.ptr<uchar>(j);

		for (int i = 0; i < nc; i++) {

			//-------------开始处理每个像素-------------------

			*data++ = *data & mask + div / 2;

			//-------------结束像素处理------------------------

		} //单行处理结束                   
	}
}

//------------------------------------【方法八】------------------------------------------------
//		说明:利用 .ptr 和 * ++ 以及位运算 (continuous+channels)
//-------------------------------------------------------------------------------------------------
void colorReduce7(Mat& image, int div = 64) {

	int nl = image.rows; //行数
	int nc = image.cols; //列数

	if (image.isContinuous())
	{
		//无填充像素
		nc = nc * nl;
		nl = 1;  // 为一维数组
	}

	int n = static_cast<int>(log(static_cast<double>(div)) / log(2.0));
	//掩码值
	uchar mask = 0xFF << n; // e.g. 比如div=16, mask= 0xF0

	for (int j = 0; j < nl; j++) {

		uchar* data = image.ptr<uchar>(j);

		for (int i = 0; i < nc; i++) {

			//-------------开始处理每个像素-------------------

			*data++ = *data & mask + div / 2;
			*data++ = *data & mask + div / 2;
			*data++ = *data & mask + div / 2;

			//-------------结束像素处理------------------------

		} //单行处理结束                    
	}
}


// -----------------------------------【方法九】 ------------------------------------------------
//		说明:利用Mat_ iterator
//-------------------------------------------------------------------------------------------------
void colorReduce8(Mat& image, int div = 64) {

	//获取迭代器
	Mat_<Vec3b>::iterator it = image.begin<Vec3b>();
	Mat_<Vec3b>::iterator itend = image.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;

		//-------------结束像素处理------------------------
	}//单行处理结束  
}

//-------------------------------------【方法十】-----------------------------------------------
//		说明:利用Mat_ iterator以及位运算
//-------------------------------------------------------------------------------------------------
void colorReduce9(Mat& image, int div = 64) {

	// div必须是2的幂
	int n = static_cast<int>(log(static_cast<double>(div)) / log(2.0));
	//掩码值
	uchar mask = 0xFF << n; // e.g. 比如 div=16, mask= 0xF0

	// 获取迭代器
	Mat_<Vec3b>::iterator it = image.begin<Vec3b>();
	Mat_<Vec3b>::iterator itend = image.end<Vec3b>();

	//扫描所有元素
	for (; it != itend; ++it)
	{

		//-------------开始处理每个像素-------------------

		(*it)[0] = (*it)[0] & mask + div / 2;
		(*it)[1] = (*it)[1] & mask + div / 2;
		(*it)[2] = (*it)[2] & mask + div / 2;

		//-------------结束像素处理------------------------
	}//单行处理结束  
}

//------------------------------------【方法十一】---------------------------------------------
//		说明:利用Mat Iterator_
//-------------------------------------------------------------------------------------------------
void colorReduce10(Mat& image, int div = 64) {

	//获取迭代器
	Mat_<Vec3b> cimage = image;
	Mat_<Vec3b>::iterator it = cimage.begin();
	Mat_<Vec3b>::iterator itend = cimage.end();

	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;

		//-------------结束像素处理------------------------
	}
}

//--------------------------------------【方法十二】--------------------------------------------
//		说明:利用动态地址计算配合at
//-------------------------------------------------------------------------------------------------
void colorReduce11(Mat& image, int div = 64) {

	int nl = image.rows; //行数
	int nc = image.cols; //列数

	for (int j = 0; j < nl; j++)
	{
		for (int i = 0; i < nc; i++)
		{

			//-------------开始处理每个像素-------------------

			image.at<Vec3b>(j, i)[0] = image.at<Vec3b>(j, i)[0] / div * div + div / 2;
			image.at<Vec3b>(j, i)[1] = image.at<Vec3b>(j, i)[1] / div * div + div / 2;
			image.at<Vec3b>(j, i)[2] = image.at<Vec3b>(j, i)[2] / div * div + div / 2;

			//-------------结束像素处理------------------------

		} //单行处理结束                 
	}
}

//----------------------------------【方法十三】----------------------------------------------- 
//		说明:利用图像的输入与输出
//-------------------------------------------------------------------------------------------------
void colorReduce12(const Mat& image, //输入图像
	Mat& result,      // 输出图像
	int div = 64) {

	int nl = image.rows; //行数
	int nc = image.cols; //列数

	//准备好初始化后的Mat给输出图像
	result.create(image.rows, image.cols, image.type());

	//创建无像素填充的图像
	nc = nc * nl;
	nl = 1;  //单维数组

	int n = static_cast<int>(log(static_cast<double>(div)) / log(2.0));
	//掩码值
	uchar mask = 0xFF << n; // e.g.比如div=16, mask= 0xF0

	for (int j = 0; j < nl; j++) {

		uchar* data = result.ptr<uchar>(j);
		const uchar* idata = image.ptr<uchar>(j);

		for (int i = 0; i < nc; i++) {

			//-------------开始处理每个像素-------------------

			*data++ = (*idata++) & mask + div / 2;
			*data++ = (*idata++) & mask + div / 2;
			*data++ = (*idata++) & mask + div / 2;

			//-------------结束像素处理------------------------

		} //单行处理结束                   
	}
}

//--------------------------------------【方法十四】------------------------------------------- 
//		说明:利用操作符重载
//-------------------------------------------------------------------------------------------------
void colorReduce13(Mat& image, int div = 64) {

	int n = static_cast<int>(log(static_cast<double>(div)) / log(2.0));
	//掩码值
	uchar mask = 0xFF << n; // e.g. 比如div=16, mask= 0xF0

	//进行色彩还原
	image = (image & Scalar(mask, mask, mask)) + Scalar(div / 2, div / 2, div / 2);
}




//-----------------------------------【ShowHelpText( )函数】-----------------------------
//		描述:输出一些帮助信息
//----------------------------------------------------------------------------------------------
void ShowHelpText()
{
	//输出欢迎信息和OpenCV版本
	printf("\n\n\t\t\t非常感谢购买《OpenCV3编程入门》一书!\n");
	printf("\n\n\t\t\t此为本书OpenCV2版的第24个配套示例程序\n");
	printf("\n\n\t\t\t   当前使用的OpenCV版本为:" CV_VERSION);
	printf("\n\n  ----------------------------------------------------------------------------\n");

	printf("\n\n正在进行存取操作,请稍等……\n\n");
}




//-----------------------------------【main( )函数】--------------------------------------------
//		描述:控制台应用程序的入口函数,我们的程序从这里开始
//-------------------------------------------------------------------------------------------------
int main()
{
	int64 t[NTESTS], tinit;
	Mat image0;
	Mat image1;
	Mat image2;

	//system("color 4F");

	ShowHelpText();

	image0 = imread("1.png");
	if (!image0.data)
		return 0;

	//时间值设为0
	for (int i = 0; i < NTESTS; i++)
		t[i] = 0;


	// 多次重复测试
	int n = NITERATIONS;
	for (int k = 0; k < n; k++)
	{
		cout << k << " of " << n << endl;

		image1 = imread("1.png");
		//【方法一】利用.ptr 和 []
		tinit = getTickCount();
		colorReduce0(image1);
		t[0] += getTickCount() - tinit;

		//【方法二】利用 .ptr 和 * ++ 
		image1 = imread("1.png");
		tinit = getTickCount();
		colorReduce1(image1);
		t[1] += getTickCount() - tinit;

		//【方法三】利用.ptr 和 * ++ 以及模操作
		image1 = imread("1.png");
		tinit = getTickCount();
		colorReduce2(image1);
		t[2] += getTickCount() - tinit;

		//【方法四】 利用.ptr 和 * ++ 以及位操作
		image1 = imread("1.png");
		tinit = getTickCount();
		colorReduce3(image1);
		t[3] += getTickCount() - tinit;

		//【方法五】 利用指针的算术运算
		image1 = imread("1.png");
		tinit = getTickCount();
		colorReduce4(image1);
		t[4] += getTickCount() - tinit;

		//【方法六】利用 .ptr 和 * ++以及位运算、image.cols * image.channels()
		image1 = imread("1.png");
		tinit = getTickCount();
		colorReduce5(image1);
		t[5] += getTickCount() - tinit;

		//【方法七】利用.ptr 和 * ++ 以及位运算(continuous)
		image1 = imread("1.png");
		tinit = getTickCount();
		colorReduce6(image1);
		t[6] += getTickCount() - tinit;

		//【方法八】利用 .ptr 和 * ++ 以及位运算 (continuous+channels)
		image1 = imread("1.png");
		tinit = getTickCount();
		colorReduce7(image1);
		t[7] += getTickCount() - tinit;

		//【方法九】 利用Mat_ iterator
		image1 = imread("1.png");
		tinit = getTickCount();
		colorReduce8(image1);
		t[8] += getTickCount() - tinit;

		//【方法十】 利用Mat_ iterator以及位运算
		image1 = imread("1.png");
		tinit = getTickCount();
		colorReduce9(image1);
		t[9] += getTickCount() - tinit;

		//【方法十一】利用Mat Iterator_
		image1 = imread("1.png");
		tinit = getTickCount();
		colorReduce10(image1);
		t[10] += getTickCount() - tinit;

		//【方法十二】 利用动态地址计算配合at
		image1 = imread("1.png");
		tinit = getTickCount();
		colorReduce11(image1);
		t[11] += getTickCount() - tinit;

		//【方法十三】 利用图像的输入与输出
		image1 = imread("1.png");
		tinit = getTickCount();
		Mat result;
		colorReduce12(image1, result);
		t[12] += getTickCount() - tinit;
		image2 = result;

		//【方法十四】 利用操作符重载
		image1 = imread("1.png");
		tinit = getTickCount();
		colorReduce13(image1);
		t[13] += getTickCount() - tinit;

		//------------------------------
	}
	//输出图像   
	imshow("原始图像", image0);
	imshow("结果", image2);
	imshow("图像结果", image1);

	// 输出平均执行时间
	cout << endl << "-------------------------------------------" << endl << endl;
	cout << "\n【方法一】利用.ptr 和 []的方法所用时间为 " << 1000. * t[0] / getTickFrequency() / n << "ms" << endl;
	cout << "\n【方法二】利用 .ptr 和 * ++ 的方法所用时间为" << 1000. * t[1] / getTickFrequency() / n << "ms" << endl;
	cout << "\n【方法三】利用.ptr 和 * ++ 以及模操作的方法所用时间为" << 1000. * t[2] / getTickFrequency() / n << "ms" << endl;
	cout << "\n【方法四】利用.ptr 和 * ++ 以及位操作的方法所用时间为" << 1000. * t[3] / getTickFrequency() / n << "ms" << endl;
	cout << "\n【方法五】利用指针算术运算的方法所用时间为" << 1000. * t[4] / getTickFrequency() / n << "ms" << endl;
	cout << "\n【方法六】利用 .ptr 和 * ++以及位运算、channels()的方法所用时间为" << 1000. * t[5] / getTickFrequency() / n << "ms" << endl;
	cout << "\n【方法七】利用.ptr 和 * ++ 以及位运算(continuous)的方法所用时间为" << 1000. * t[6] / getTickFrequency() / n << "ms" << endl;
	cout << "\n【方法八】利用 .ptr 和 * ++ 以及位运算 (continuous+channels)的方法所用时间为" << 1000. * t[7] / getTickFrequency() / n << "ms" << endl;
	cout << "\n【方法九】利用Mat_ iterator 的方法所用时间为" << 1000. * t[8] / getTickFrequency() / n << "ms" << endl;
	cout << "\n【方法十】利用Mat_ iterator以及位运算的方法所用时间为" << 1000. * t[9] / getTickFrequency() / n << "ms" << endl;
	cout << "\n【方法十一】利用Mat Iterator_的方法所用时间为" << 1000. * t[10] / getTickFrequency() / n << "ms" << endl;
	cout << "\n【方法十二】利用动态地址计算配合at 的方法所用时间为" << 1000. * t[11] / getTickFrequency() / n << "ms" << endl;
	cout << "\n【方法十三】利用图像的输入与输出的方法所用时间为" << 1000. * t[12] / getTickFrequency() / n << "ms" << endl;
	cout << "\n【方法十四】利用操作符重载的方法所用时间为" << 1000. * t[13] / getTickFrequency() / n << "ms" << endl;

	waitKey();
	return 0;
}

运行结果

其中:方法5利用指针算术、方法八利用 .ptr 和*++ 以及位运算(continuous+channels)以及方法14利用操作符重载的方法最佳。

7.学习OpenCV基本用法

推荐两本学习OpenCV基本用法参考书:

1、《学习OpenCV3》

2、《OpenCV3编程入门》

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

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

相关文章

喜报!CACTER实力入选《嘶吼2024网络安全产业图谱》多项领域

CACTER实力入选多项细分领域 7月16日&#xff0c;嘶吼安全产业研究院正式发布《嘶吼2024网络安全产业图谱》&#xff0c;旨在全面展示网络安全产业的构成及其重要组成部分&#xff0c;探索网络安全产业的竞争格局和发展前景。 CACTER凭借卓越的技术实力和可靠的产品服务&#…

[论文笔记] Pai-megatron-patch cpu-offload 改到 Qwen2

Add MPI Support for tp-comm-overlap and Cpu-Offload for Mcore Distrib… by jerryli1981 Pull Request #283 alibaba/Pai-Megatron-Patch GitHub 以上是在 llama-70B 上实现的 cpu-offload 方法。 下面是在主分支上&#xff0c;仿照 LLaMA-70B&#xff0c;在 Qwen2 上…

手把手教你搭建Docker私有仓库Harbor

1、什么是Docker私有仓库 Docker私有仓库是用于存储和管理Docker镜像的私有存储库。Docker默认会有一个公共的仓库Docker Hub&#xff0c;而与Docker Hub不同&#xff0c;私有仓库是受限访问的&#xff0c;只有授权用户才能够上传、下载和管理其中的镜像。这种私有仓库可以部署…

HarmonyOS工程目录结构

应用级配置文件app.json5 应用唯一标识、版本号、应用图标、应用名称等信息 模块级配置文件module.json5 oh-package.json5 三方库的管理 其他配置 用于编译构建&#xff0c;包括构建配置文件、编译构建任务脚本、混淆规则文件、依赖的共享包信息等。 build-profile.json…

Java学习Day9之数据库链接java

package aboutdb1; import java.sql.*; import java.util.Scanner; public class newDBsystem {private static Connection getConnection() throws Exception {Class.forName("com.mysql.cj.jdbc.Driver"); // 加载MySQL JDBC驱动Connection con DriverManager.get…

阿尔泰科技工业电脑IPC-8363工控机

概述&#xff1a; IPC-8363是一款支持 LGA 1200 Intel 10th/11th Generation Core™ i9/i7/i5/i3, Celeron and Pentium processor 的工业电脑。配置2组独立 SO-DIMM DDR4 2666/2933MHz内存&#xff0c;最大可扩展至128GB。 主要技术指标&#xff1a; 产品图示&#xff1a; 系…

php 小白新手从入门到精通教程(第3版)

前言 PHP&#xff08;PHP: Hypertext Preprocessor&#xff09;即“超文本预处理器”&#xff0c;是在服务器端执行的脚本语言&#xff0c;尤其适用于Web开发并可嵌入HTML中。PHP语法学习了C语言&#xff0c;吸纳Java和Perl多个语言的特色发展出自己的特色语法&#xff0c;并根…

qt初入门8:下拉框,输入框模糊查询,提示简单了解 (借助QCompleter)

实现一个简单的模糊查询的逻辑&#xff0c;输入框能提示相关项。 主要借助qt的QCompleter 类&#xff08; Qt 框架中提供的一个用于自动补全和模糊搜索的类&#xff09;&#xff0c;结合一些控件&#xff0c;比如QComboBox和QLineEdit&#xff0c;实现模糊查询的功能。 1&…

在线实习项目|泰迪智能科技企业级项目学习,暑期大数据人工智能学习

在线实习介绍 实习时间&#xff1a;每个项目周期七周左右 面向对象&#xff1a;大数据、计算机相关专业学生&#xff1b;大三、大四毕业年度学生 在线实习收获 1、获得项目实战技能&#xff0c;积累项目经验 2、获得在线实习证明 项目特点…

能源化工5G防爆终端能给行业带来什么重要作用?

在能源化工领域&#xff0c;5G防爆终端的引入无疑为行业带来了革命性的变革与重要作用。这些集成了先进5G通信技术和防爆设计的高端设备&#xff0c;不仅提升了生产作业的安全性&#xff0c;还极大地增强了运营效率与智能化水平。 高速、低延迟的5G网络为防爆终端提供了前所未有…

安全防御2

实验要求&#xff1a; 实验过程&#xff1a; 7&#xff0c;办公区设备可以通过电信链路和移动链路上网(多对多的NAT&#xff0c;并且需要保留一个公网IP不能用来转换)&#xff1a; 新建电信区&#xff1a; 新建移动区&#xff1a; 将对应接口划归到各自区域&#xff1a; 新建…

Java(二十二)---队列

文章目录 前言1.队列(Queue)的概念2.Queue的使用3.队列的模拟实现4.循环队列5.双端队列6.面试题[1. 用队列实现栈](https://leetcode.cn/problems/implement-stack-using-queues/description/)[2. 用栈实现队列](https://leetcode.cn/problems/implement-queue-using-stacks/de…

VPN以及GRE和MGRE

VPN VPN — 是虚拟专用网络 通俗地说&#xff0c;就是通过虚拟的手段&#xff0c;将两个独立的网络&#xff0c;穿越一个公共网络进行连接&#xff0c;实现点到点专线的效果&#xff08;可以理解为&#xff1a;一个分公司通过公网和总公司建立点到点的专线连接&#xff09; 现…

innovus:如何获取clock net的route_type和clock name

我正在「拾陆楼」和朋友们讨论有趣的话题,你⼀起来吧? 拾陆楼知识星球入口 clock net的route type分为top trunk和leaf,net_type的设置方式见文章:

【06】LLaMA-Factory微调大模型——微调模型评估

上文【05】LLaMA-Factory微调大模型——初尝微调模型&#xff0c;对LLama-3与Qwen-2进行了指令微调&#xff0c;本文则介绍如何对微调后的模型进行评估分析。 一、部署微调后的LLama-3模型 激活虚拟环境&#xff0c;打开LLaMA-Factory的webui页面 conda activate GLM cd LLa…

汇编实验5

本实验在32位Linux虚拟机中完成&#xff08;点击查看安装教程&#xff09; 实验内容 二进制炸弹实际是由C语言源程序生成的可执行目标代码&#xff0c;主程序可参考bomb.c。运行时&#xff0c;会按次序提示用户输入3个不同的字符串。如果输入的字符串有误&#xff0c;炸弹就会…

结合金融场景的Scipy模块编程

结合金融场景的Scipy模块编程 数据链接&#xff1a;https://pan.baidu.com/s/1VMh8-4IeCUYXB9p3rL45qw 提取码&#xff1a;c6ys import numpy as np import pandas as pd import statsmodels import matplotlib.pyplot as plt from pylab import mpl mpl.rcParams[font.sans-se…

4 C 语言控制流与循环结构的深入解读

目录 1 复杂表达式的计算过程 2 if-else语句 2.1 基本结构及示例 2.2 if-else if 多分支 2.3 嵌套 if-else 2.4 悬空的 else 2.5 注意事项 2.5.1 if 后面不要加分号 2.5.2 省略 else 2.5.3 省略 {} 2.5.4 注意点 3 while 循环 3.1 一般形式 3.2 流程特点 3.3 注…

SaaS的“大模型焦虑”

随着大模型技术的兴起&#xff0c;SaaS行业正面临前所未有的机遇与挑战。本文深入剖析了SaaS厂商在AI化升级过程中的’大模型焦虑’&#xff0c;并探讨了如何通过战略性的AI应用找到自信&#xff0c;实现产品与服务的转型升级&#xff0c;为SaaS行业的AI之路提供了宝贵的思考与…

JVM之运行时数据区(一):程序计数器+本地方法栈

JVM之运行时数据区&#xff08;一&#xff09;&#xff1a;程序计数器本地方法栈 1.运行时数据区概述2.程序计数器作用特点常见问题 3.本地方法接口本地方法本地接口 4.本地方法栈特点 1.运行时数据区概述 Java虚拟机定义了若干种程序运行期间会使用到的运行时数据区其中有一些…