第二章: 操作像素
2.1引言:
灰度图像像素由8位无符号数来表示,0表示黑色,255表示白色。
彩色图像(RGB)像素由三个8位的无符号数来表示,存储方式为三元数(B,G,R)
2.2存取像素值
Mat类有若干成员和成员函数来获取图像的属性:
成员cols和rows表示 宽和高(列和行)
成员函数 at<像素类型名称>(int i, int j)可以用来存取像素
image.at<uchar>(i,j); //灰度图像的像素,at后的数据类型一定要和image像素数据类型吻合
image.at<Vec3b>(i,j); //RGB图像的像素 Vec3b表示三个8位无符号整形组成的向量
image.at<Vec3b>(i,j)[channel]; //像素向量的第channel个元素
image.at<Vec3b>(i,j)[1]; //第二个
image.at<Vec3b>(i,j)[2]; //第三个
int Mat::channels(); //用来获得像素的通道数
cv::Mat_类可以更方便访问像素
cv::Mat_<uchar> image1=image; //用Mat_类定义带数据类型的图像数据,可以直接访问像素
image1(i,j) = 0;
2.3使用指针来遍历图像
uchar *data = image.ptr<uchar>(j); //获取第i行的指针
data[i] = 0; //表示第i行,第j列元素值置0,而不是第i行第j列像素置0(RGB图像每个像素有三个元素)
颜色缩减函数:
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <iostream>
using namespace cv;
using namespace std;
void colorReduce(Mat &image, int div)
{
int n1 = image.rows;
int nc = image.cols*image.channels();
for (int i = 0;i < n1;i++)
{
uchar *data = image.ptr<uchar>(i);
for (int j = 0;j < nc;j++)
{
data[j] = data[j] / div *div + div / 2;
}
}
}
int main(int argc, char** argv)
{
Mat image = imread("D:/1.jpg", 1);
namedWindow("picture");
imshow("picture", image);
colorReduce(image, 10);
namedWindow("key");
imshow("key", image);
waitKey(0);
return 0;
}
image.step //表示行的字节数image.element//表示像素的字节数颜色缩减:
div = pow(2,n);
uchar mask = 0xFF<<n;
data[i] = (data[i]&mask)+div/2;
上个颜色缩减函数中,改变原图像数据,若不想修改原图像则可以用:image.copyTo(image1);或者:void reducecolor(const Mat &image, Mat &result, int div){result.creat(image.rows, image.cols, image.type());……}//检查result和image大小数据类型是否一致
高效遍历图像:
void colorReduce(Mat &image, int div)
{
int n1 = image.rows;
int nc = image.cols*image.channels();
//image.isContinuous()判断image是否有额外的填补
//返回值为真,则image没有进行额外的填补,即图像在内存中是连续存储的;
//可以用一层循环来实现;这种方式在处理若干个小图像有优势
if (image.isContinuous())
{
nc = image.cols*image.cols;
n1 = 1;
//image.reshape(1, image.cols*image.rows); 也可以用这个
}
for (int i = 0;i < n1;i++)
{
uchar *data = image.ptr<uchar>(i);
for (int j = 0;j < nc;j++)
{
data[j] = data[j] / div *div + div / 2;
}
}
}
底层指针运算:
void colorReduce(Mat &image, int div)
{
int n1 = image.rows;
int nc = image.cols*image.channels();
uchar *data = image.data;//image.data可以用来获取当前内存块的首地址
for (int i = 0;i < n1;i++)
{
for(int j =0;j<nc;j++)
{
data[j] = data[j] / div *div + div / 2;
}
data+=image.step;//image.step表示每一行的宽度(包括补充值)
}
}
2.4使用迭代器遍历图像遍历器是一种特殊的类,专门用于遍历集合中的各个元素图像迭代器的声明:
Mat Iterator_<Vec3b> it ;
Mat_<Vec3b>::iterator;
void colorReduce(Mat &image, int div)
{
//cv::Mat iterator_<cv::Vec3b> it;//不好使
Mat_<Vec3b>::iterator it = image.begin<Vec3b>();
Mat_<Vec3b>::iterator itend = image.end<Vec3b>();
for (;it != itend;++it)//it加一,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类实例进行修改
Mat ConstIterator_<Vec3b> it ;
Mat_<Vec3b>::const_iterator;
2.5 编写高效的图像遍历循环
void colorReduce(Mat &image, int div)
{
int n1 = image.rows;
int nc = image.cols;
if (image.isContinuous())//分类效率高
{
nc *= n1;
n1 = 1;
}
int n = static_cast<int>(log(static_cast<double>(div)) / log(2.0));//位运算效率高
uchar mask = 0xFF << n;
for (int j = 0;j < n1;++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;
}
}
}
2.6 遍历图像和邻域操作锐化滤波器灰度图像:
void sharpen(const Mat &image, Mat &output)
{
output.create(image.size(), image.type());
for (int i = 1;i < image.rows - 1;++i)
{
const uchar *previous = image.ptr<uchar>(i-1);
const uchar *current = image.ptr<uchar>(i);
const uchar *next = image.ptr<uchar>(i+1);
uchar *result = output.ptr<uchar>(i);
for (int j = 1;j < image.cols-1;++j)
{
result[j] = saturate_cast<uchar>// saturate_cast<uchar>用于数据截断到0~255
(5 * current[j] - previous[j] - next[j] - current[j - 1] - current[j + 1]);
//*result++ = 5 * current[j] - previous[j] - next[j] - current[j - 1] - current[j + 1];
}
}
output.row(0).setTo(Scalar(0));//output.row(0)表示输出的第0行,setTo(Scalar(0)),置为0
output.row(output.rows-1).setTo(Scalar(0));
output.col(0).setTo(Scalar(0));
output.col(output.cols-1).setTo(Scalar(0));
}
彩色图像:
void sharpen3d(const Mat &image, Mat &output)
{
output.create(image.size(), image.type());
int nc = image.cols * 3;
for (int i = 1;i < image.rows - 1;++i)
{
const uchar *previous = image.ptr<uchar>(i-1);
const uchar *current = image.ptr<uchar>(i);
const uchar *next = image.ptr<uchar>(i+1);
uchar *result = output.ptr<uchar>(i);
for (int j = 3;j < nc-3;++j)//也可以使用迭代器
{
result[j] = saturate_cast<uchar>// saturate_cast<uchar>用于数据截断到0~255
(5 * current[j] - previous[j] - next[j] - current[j - 3] - current[j + 3]);
//*result++ = 5 * current[j] - previous[j] - next[j] - current[j - 1] - current[j + 1];
}
}
output.row(0).setTo(Scalar(0, 0, 0));//output.row(0)表示输出的第0行,setTo(Scalar(0)),置为0
output.row(output.rows-1).setTo(Scalar(0, 0, 0));
output.col(0).setTo(Scalar(0, 0, 0));
output.col(output.cols-1).setTo(Scalar(0, 0, 0));
}
filter2D滤波器首先定义Mat核矩阵
#include <opencv2/imgproc.hpp> //包含filter2D()函数
void sharpen2D(const Mat &image, Mat &output)
{
Mat kernel(3, 3, CV_32F, Scalar(0));//定义核矩阵
kernel.at<double>(0, 1) = -1;
kernel.at<double>(1, 0) = -1;
kernel.at<double>(1, 1) = 5;
kernel.at<double>(2, 1) = -1;
kernel.at<double>(1, 2) = -1;
filter2D(image, output, image.depth, kernel);
}
2.7进行简单的图像算数
double beta, double gamma, OutputArray dst, int dtype = -1);
dst = src1*alpha + scr*beta + gamma//(按输入的原格式返回)
add(imageA, imageB, imageC); //imageC = imageA + imageB//(A和B的大小应该相同)
add(imageA, scalar(k), imageC); //imageC = imageA + k
scaleAdd(imageA, k, imageB, imageC); //imageC = imageA*k +imageB
m1*m2 //矩阵乘法
m1.inv() //矩阵求逆
m1.t() //矩阵求转置
2.8 定义感兴趣的区域(ROI)添加logo
Mat image = imread("D:/1.jpg", 0);
imshow("picture原图", image);
Mat logo = imread("D:/2.jpg", 0);
Mat imageROI;
logo = logo(Rect(0, 0, 400,200));
imshow("picturelogo", logo);
imageROI = image(Rect(0, 0, logo.cols, logo.rows));
addWeighted(imageROI, 0.5, logo, 0.5, 0.0, imageROI);
imshow("picture叠加", imageROI);
waitKey(0);
也可以通过图像掩膜完成定义ROI的方法:
imageROI = image(Rect(100, 100, 300, 300)); //选择顶点(100,100)宽度和长度分别为300,300
imageROI = image(range(100, 400), range(100,400)); //ROI与原始图像共享数据区
创建原始图像的特定行:
Mat imageROI = image.rowRange(start, end);
Mat imageROI = image.colRange(start, end);