VS+openCV 之操作像素(上)

一、访问像素值

        【准备工作】为了说明如何直接访问像素值,将创建一个简单的函数,随机选择一些像素,把它们设置为白色。

        【实现】记得添加头文件 #include<random>

        1. 使用循环,每次把随机选择的像素设置为255,即白色。这里使用了 type 来区分灰度图像和彩色图像。

void salt(cv::Mat image, int n) {
	//C++11 的随机数生成器
	std::default_random_engine generator;
	std::uniform_int_distribution<int>
		randomRow(0, image.rows - 1);
	std::uniform_int_distribution<int>
		randomCol(0, image.cols - 1);
	int i, j;
	for (int k = 0; k < n; k++) {
		//随机生成图形位置
		i = randomCol(generator);
		j = randomRow(generator);
		if (image.type() == CV_8UC1) {//灰度图像
			//单通道8位图像
			image.at<uchar>(j, i) = 255;
		}
		else if (image.type() == CV_8UC3) {//彩色图像
			//3通道图像
			image.at<cv::Vec3b>(j, i)[0] = 255;
			image.at<cv::Vec3b>(j, i)[1] = 255;
			image.at<cv::Vec3b>(j, i)[1] = 255;
		}
	}
}

        2. 传入图像,调用函数        

    cv::Mat image;
	image = cv::imread("1.jpg");
	salt(image, 3000);
	cv::namedWindow("Image");
	cv::imshow("Image", image);
	cv::waitKey(0);

         【实现原理】

        1. 变量 cols 和 rows 可得到图像的列数和行数;cv::Mat 的 at(int y,int x)方法可以访问元素,其中 x 是 列号,y 是行号。在调 用 at 方法时,必须指定图像元素的类型,且保证指定的类型与矩阵内的类型是一致的。

        2. 彩色图像有红色通道、绿色通道和蓝色通道,因此包含彩色图像的 cv::Mat 类会返回一个向量,向量中包含三个 8 位的数值。OpenCV 为这样的短向量定义了一种 类型,即 cv::Vec3b。这个向量包含三个无符号字符(unsigned character)类型的数据。

        3. 类似的向量类型表示二元素向量和四元素向量:cv::Vec2b 和 cv::Vec4b。此外还有针对其他元素类型的向量。例如,表示二元素浮点数类型的向量就是把类型名称的最后一个字母换成 f,即 cv::Vec2f。对于短整型,最后的字母换成 s;对于整型,最后的字母换成 i; 对于双精度浮点数向量,最后的字母换成 d。所有这些类型都用 cv::Vec模板类定义,其 中 T 是类型,N 是向量元素的数量。

        【扩展阅读】

        可以用 operator() 直接访问矩阵的元素。使用操作符 operator()和使用 at 方法产生的结果是完全相同的, 只是前者的代码更简短。

// 用 Mat 模板操作图像
cv::Mat_<uchar> img(image); 
img(50,100)= 0; // 访问第 50 行、第 100 列处那个值

二、用指针扫描图像

        【准备工作】减少图像中颜色的数量。将图像中每个像素的值除以 N(这里假定使用整数除法,不保留余数)。然后将结果乘以 N,得到 N 的倍数,并且刚好不超过原始像素值。加上 N / 2,就得到相邻的 N 倍数之间的中间值。

        【实现】

        1. 处理函数

void colorReduce(cv::Mat image, int div = 64) {
	int n1 = image.rows;//行数
	//每行的元素数量
	int nc = image.cols * image.channels();
	for (int j = 0; j < n1; j++) {
		//取得行j的地址
		uchar* data = image.ptr<uchar>(j);
		for (int i = 0; i < nc; i++) {
			//处理每个像素---------------
			data[i] = data[i] / div * div + div / 2;
			//像素处理结束---------------
		}//一行结束
	}
}

        2. 测试函数

    cv::Mat image;
	image = cv::imread("1.jpg");
	colorReduce(image, 64);
	cv::namedWindow("Image");
	cv::imshow("Image", image);
	cv::waitKey(0);

         【实现原理】

        1. 获得每一行中像素值的个数:int nc= image.cols * image.channels();

        2. 返回第 j 行的地址:uchar* data= image.ptr(j);

        3. 也可以利用指针运算从一列移到下一列:*data++= *data/div*div + div2;

        【我的疑问】

        data[i] / div * div + div / 2 ; 这里除了又乘了后,为什么会变化,而且又加了二分之一的div

        对于下面的扩展阅读就更懵了

        【扩展阅读】

        1. 其他减色算法(位运算效率最高)

//以使用取模运算符,它可以直接得到 div 的倍数
data[i]= data[i] – data[i]%div + div/2;

//位运算符,截取像素值的掩码
uchar mask= 0xFF<<n; // 如 div=16,则 mask= 0xF0

*data &= mask; // 掩码
*data++ += div>>1; // 加上 div/2 
// 这里的+也可以改用“按位或”运算符

        2. 使用输入和输出函数

        若不希望对原始图像进行修改,因此调用函数前要先备份图像

    cv::Mat image;
	image = cv::imread("1.jpg");
	//复制图像,深复制最简单的是使用clone
	cv::Mat imageClone = image.clone();
	//处理图像副本
	colorReduce(imageClone, 64);
	cv::namedWindow("Image");
	cv::imshow("Image", imageClone);
	cv::waitKey(0);

        若允许用户选择是否就地处理

void colorReduce(const cv::Mat &image, // 输入图像
     cv::Mat &result, // 输出图像
     int div=64);

        PS:输入图像是一个引用的 const,表示图像不会在函数中修改。输出图像是一个引用参数,在函数中会被修改,并且返回给调用这个函数的代码。

//就地处理
colorReduce(image,image);
//否则
cv::Mat result; 
colorReduce(image,result); 

        函数中首先要调用 create 方法,构建一个大小和类型都与输入图像相同的矩阵(如果必要)

result.create(image.rows,image.cols,image.type()); 
//分配的内存块的大小表示为 total()*elemSize()
//扫描过程
for (int j=0; j<nl; j++) { 
 // 获得第 j 行的输入和输出的地址
 const uchar* data_in= image.ptr<uchar>(j); 
 uchar* data_out= result.ptr<uchar>(j); 
 for (int i=0; i<nc*nchannels; i++) { 
 // 处理每个像素 --------------------- 
 data_out[i]= data_in[i]/div*div + div/2; 
 // 像素处理结束 ---------------- 
 } // 一行结束
} 

        3. 对连续图像的高效扫描

//---------------测试矩阵的连续性----------------
// 检查行的长度(字节数)与“列的个数×单个像素”的字节数是否相等 
image.step == image.cols*image.elemSize(); 

//-------isContinuous 方法检查矩阵的连续性-------
//处理图像可以改为
void colorReduce(cv::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= staic_cast<int>(
        log(static_cast<double>(div))/log(2.0) + 0.5); 
     // 用来截取像素值的掩码
     uchar mask= 0xFF<<n; // 如果 div=16, 那么 mask= 0xF0 
     uchar div2 = div >> 1; // div2 = div/2 
     // 对于连续图像,这个循环只执行一次
     for (int j=0; j<nl; j++) { 
         uchar* data= image.ptr<uchar>(j); 
         for (int i=0; i<nc; i++) { 
             *data &= mask; 
             *data++ += div2; 
         } // 一行结束
     } 
}

//如果结果表明图像中没有填充像素,就把宽度设为 1,高度设为 W×H,从而去除外层的循环。
//注意,这里还需要用 reshape 方法。
if (image.isContinuous()) 
{ 
     // 没有填充像素
     image.reshape(1, // 新的通道数
                   1); // 新的行数
} 
int nl= image.rows; // 行数
int nc= image.cols * image.channels(); 

        4. 低层次指针算法

        在 cv::Mat 类中,图像数据是存放在无符号字符型的内存块中的。其中 data 属性表示内存块第一个元素的地址,它会返回一个无符号字符型的指针。

//从图像的起点开始循环
uchar *data= image.data;

//利用有效宽度来移动行指针,可以从一行移到下一行
data+= image.step; // 下一行

//用 step 属性可得到一行的总字节数(包括填充像素)
// (j,i)像素的地址,即&image.at(j,i) 
data= image.data+j*image.step+i*image.elemSize();
//不推荐使用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值