opencv 学习
第一章:
- 载入图片,保存,旋转。
第二章:
2.1目标:
一,学会如何遍历一张图像并处理其像素
二,高效的处理方法
灰度图:像素由8位无符号数来表示;
彩色图:由三个这样的8位无符号数来表示三个颜色通道,0黑色;255白色;
opencv允许创建不同像素类型的矩阵或图像:如整形(CV_8U),浮点型(CV_32F)。
它们在一些图像处理过程中,用来保存中间值这样的内容很有用。大多数矩阵可以用于任意类型的矩阵,但有些运算对数据类型或矩阵的通道数有要求。
2.2.存取像素值:高效的遍历数组。椒盐噪点,即随机设置为黑白,黑白1通道,彩色3通道。Mat类,at方法。
需要在代码中指定元素所在的行(row)和列(col),函数会返回相应的元素。单通道返回单个数值;多通道返回一组向量(Vector)。
//2021.6.27 图像椒盐,加噪点。
#include<opencv2/opencv.hpp>
#include<iostream>
using namespace cv;
void salt(Mat& image, int n) {
//第一个参数是一张输入图像,使用传引用的参数传递方式(值,指针,传引用三种方式);第二个参数是要该改变的噪点个数
for (int k = 0; k < n; k++)
{
int i = rand() % image.cols;//随机获取行和列 //col,row;是了类cv::Mat的公有成员变量,能给出图像的宽高;
int j = rand() % image.rows;
if (image.channels() == 1) {
//灰度为1
image.at<uchar>(j, i) = 255;//255白色,0为黑色 //成员函数at(int y,int x)用来存取图像的元素;但是必须要知道图像的数据类型。cv::Mat可以存放任意类型的元素。
}
else if (image.channels() == 3)//彩色为3
{
image.at<Vec3b>(j, i)[0] = 255; //分清类型
image.at<Vec3b>(j, i)[1] = 255; // 确保指定的数据类型和矩阵的数据类型
image.at<Vec3b>(j, i)[2] = 255; //相符,at方法本身不会经行数据类型转换
}
}
}
int main() {
Mat src;
src = imread("D:/opencvcode/img/13.png");
if (!src.data) {
printf("not img!");
return -1;
}
salt(src, 3000);
namedWindow("inputimg");
imshow("inputimg", src);
waitKey(0);
return 0;
}
拓展阅读:cv::Mat_是cv::Mat的一个模板子类。
2.3 用指针遍历:rows,cows。
将要遍历图像的所有像素时,像素个数很多,所以高效的遍历很重要。以下两种方法,第一种是指针算术;
每个像素的每个通道,将其值除以N,整除。再乘以N。
#include<opencv2/opencv.hpp>
#include<iostream>
using namespace cv;
//颜色缩减的其中一种方法,(函数)
void colorreduce(Mat& image, int div = 64) {
int n1 = image.rows;
int nc = image.cols * image.channels(); //获取每行像素值
for (int j = 0; j < n1; j++) {
uchar* data = image.ptr<uchar>(j);//ptr函数得到图像任意首地址,ptr返回第J行的首地址
for (int i = 0; i < nc; i++) {
data[i] = data[i] / div * div + div / 2;//使用指针从一列移到下一列(颜色缩减函数公式1)
//*data++ = *data / div * div + div/2;//颜色缩减函数公式2
//data[i] = data[i] - data[i] % div + div / 2; //颜色缩减函数公式3,这个方法比较慢
////用位运算(非常高效),限制缩减因子为2^n,(运用掩模???)
//uchar mask = OxFF << n;//e.g.for div=16,mask=OxFo;
//data[i] = (data[i] & mask) + div / 2;
}
}
}
////result.creatr(image.rows, image.cows, image.tye()); //创建一个与输入图像的尺寸和类型相同的矩阵,create 函数创建的内存是连续的,不会对图像进行填补。内存大小为total()*elemSize();
// for (int j = 0; j < n1; j++) {
// const uchar* data_in = image.ptr<uchar>(j);
// uchar* data_out = result.ptr<uchar>(j);
// for (int i = 0; i < nc; i++) {
// data_out[i] = data_in[i] / div * div + div / 2;
// }
// }
int main() {
Mat img;
img = imread("D:/opencvcode/img/13.png");
colorreduce(img);//调用颜色缩减函数
namedWindow("input");
imshow("input", img);
// 额外的复制操作,法1
Mat imageClone = img.clone();//clone函数的使用//处理克隆的图像
colorreduce(imageClone);//原始图像不变
//额外的复制操作,法2
void colorReduce(const Mat & image,//输入图像,常量引用传递,不能被函数修改
Mat & resul, //输出图像
int div = 64);
//colorreduce(img,img);//in-place处理方式时,可以将输入输出指定为同一个变量。不能用,mat->int
//////另一个实列,不然就要提供另外一个cv::Mat的实列
//cv::Mat result;
//colorreduce(img,result);//必须检查输入输出图像的大小,元素类型是否一致。cv::Mat的create成员函数有内置检查操作。
void colorreduce(Mat & img, int div = 64) {
//利用图像的连续性,把整个处理过程用一个循环完成,颜色缩减函数重写为:
int n1 = img.rows;
int nc = img.cols * img.channels();
if (img.isContinuous())//函数
{
nc = nc * n1;
n1 = 1;
}
for (int j = 0; j < n1; j++) {
uchar* data = img.ptr<uchar>(j);
data[i] = data[i] / div * div + div / 2;
}
}
//也可以用reshape方法来重写这段函数:
if (img.isContinuous())
{
img.reshape(1,img.cols * img.rows);//reshpape不用内存拷贝或重新分配就能改变矩阵维度。两个参数为新的通道数和新的行数。矩阵的列数可根据通道数和行数来自适应。
}
int n1 = img.rows;
int nc = img.cols * img.channels();
/*namedWindow("output");
imshow("output", imageClone);*/
namedWindow("input");
imshow("input", img);
waitKey(0);
return 0;
}
cols代表宽度,rows代表高度,step代表以字节为单位的图像有效宽度。elemSize函数能得到像素大小,channels方法得到图像通道数,total函数返回矩阵像素个数。
in-place变换:直接在输入的图像上进行操作,不然就是新创建一个图像 ,“深拷贝”的方式是调用clone函数。
3.高效的遍历连续图像
若不对图像行尾经行填补的时候,图像视为一个长WxH的一位数组。通过cv::Mat的一个成员函数isContinuous方法来判断是否经行了填补,真,没有填补。
4.底层指针运算
容易出错,还不能定义感兴趣区域,不好用。
2.4 迭代器遍历图像
面向对象中常用迭代器编历数据集合,迭代器是一种特殊的类。不管数据类型是什么,都可以用相似的方式遍历集合,STL标准模板库为每一个容器类提供了迭代器,cv::Mat提供与STL迭代器兼容的迭代器。
#include<opencv2/opencv.hpp>
#include<iostream>
using namespace cv;
//Mat Iterator_<Vec3b>it;//创建一个迭代器特化版本1
//Mat_<Vec3b>::iterator it;//创建一个迭代器特化版本2
//第二种实现方式:
void colorreduce(Mat& img, int div = 64) {
//迭代器初始化完成(法1),再开始循环遍历
Mat_<Vec3b>::iterator it = img.begin<Vec3b>();//处理彩色图像3b,用begin方法获得初始位置,得到左上角位置的迭代器
//img.begin<Vec3b>() + img.rows;//从图像的第二行开始
Mat_<Vec3b>::iterator itend = img.end<Vec3b>();//终止迭代的运算
//img.end<Vec3b>() - img.rows;//希望在迭代过程的图像最后一行之前停止
//循环方法1
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;
//处理像素完毕
}
//循环方法2
/*
while (it!=item){
//处理每一个像素
……
//处理完成
++it;//移动迭代器。可随意改步长,it+=10;
}
*/
}
int main() {
Mat img = imread("D:/opencvcode/img/13.png");
if (img.empty()) {
printf("not img!");
return -1;
}
colorreduce(img);//函数调用
namedWindow("input");
imshow("input", img);
waitKey(0);
return 0;
}
在循环体内部,使用解引用操作符*来读写当前元素。读,element=*it,写,*it=element。
若操作对象是const cv::Mat或者强调当前循环不会对cv::Mat的实列进行修改,要创建常量迭代器。
创建方法1:cv::Mat ConstIterator_cv::Vec3bit;
方法2:cv::Mat_cv::Vec3b::const_iterator it;
迭代器初始化(法2)
cv::Mat_<cv::Vec3b>cimage=image;
cv::Mat_<cv::Vec3b>::iterator it =cimage.begin();
cv::Mat_<cv::Vec3b>::iteratoe itend=cimage.end();
面向对象的迭代器概念,阅读STL中迭代器相关的入门文章。关键字:STL Iterator
2.5. 高效的图像遍历循环
分析效率,提高运行效率。
opencv中, cv:: getTickCount()函数用来检测一段代码的运行时间,计算从上次开机算起的时钟周期数。我们需要计算某个代码段的运行毫秒数,用cv::getTickFrequency(),此函数返回每秒内的时钟周期数。
//统计函数耗费时间的方法
double duration;
duration =static_cast<double>(cv::getTickCount());
colorreduce(image);//被测试的函数
duration =static_cast<double>(cv::getTickCount())-duration;
duration/=cv::getTickFrequency();//运行时间,以ms为单位
at方法实现:
//at方法实现
for(int j=0;j<nl;j++){
for(int i=0;i<nc;i++){
//process ench pixel------
image.at<cv::Vec3b>(j,i)[0]=image.at<cv::Vec3b>(j,i)[0]/div*div+div/2;
image.at<cv::Vec3b>(j,i)[1]=image.at<cv::Vec3b>(j,i