opencv学习笔记

opencv学习笔记

标签(空格分隔): 学习笔记


(1)操作像素

1.1对像素值的索引

1.1.1一维矩阵

if(iamge.channels()==1)
{
image.at<uchar>(i,j);
}

初始化矩阵的某一行

    result.row(0).setTo(cv::Scalar(0));//单独设置矩阵中某一行的像素值
    result.row(result.rows - 1).setTo(cv::Scalar(0));
    result.col(0).setTo(cv::Scalar(0));
    result.col(result.cols - 1).setTo(cv::Scalar(0));

1.1.2多维矩阵(以RGB图像为例)

if(image.channels()==3)
{
image.at<cv::Vec3b>(i,j)[0]=255;
image.at<cv::Vec3b>(i,j)[1]=255;
image.at<cv::Vec3b>(i,j)[2]=255;
//对三维矩阵的元素进行索引
}

注:opencv还有二元素向量和四元素向量类型(cv::Vec2b和cv::Vec4b).同样的,也有其他数据类型(如s代表short, i代表int,f代表float,d代表double.)所有的这些类型都是使用模板类cv::Vect

1.1.3扩展

使用CV::Mat的成员函数的返回值类型必须通过在调用时通过模板参数指定。因此,opencv提供了类cv::Mat_,此类的指针或者引用可以直接进项相互类型转换。该类重载了()操作符。例:

cv::Mat_ impointer=image;//impointer指针指向image;
impointer(50,100)=0;//直接索引到图像矩阵的第50行,100列;

1.1.4 程序运用:对RGB图像加入椒盐噪声

 void Salt(cv::Mat &image, int n)
{
    for (int k = 0; k < n; ++k)
    {
        //rand()随机数生成
        int i = rand() % image.rows;
        int j = rand() % image.cols;

        if (image.channels() == 1)//彩色图
        {
            image.at(i, j) = 255;
        }
        if (image.channels() == 3)//灰度图
        {
            image.at(i, j)[0] = 255;
            image.at(i, j)[1] = 255;
            image.at(i, j)[2] = 255;
        }
    }
}

1.2使用指针遍历图像

1.2.1 RGB图片在opencv的存储方式

图像缓冲区的前面三个字节对应图像左上角像素的三个通道值,接下来的三个字节对应第一行的第二个像素,以此类推。

1.2.2使用输入输出参数

在处理图像时,如果不想图片被改变,可以创建图像的“深拷贝”。

iamge=cv::imread("baboon.png");
//克隆图像
cv::Mat imageClone=image.clone();

或者使用create函数创建一个与输入图像的尺寸和类型相同的矩阵:

image.create(image.rows,image.cols,image.type());

注:create函数创建的图像的内存都是连续的,create函数不会对图像进行填补。要遍历图像需要使用两个指针完成:

for(int i=0;i//得到图像的第i行的首地址
    const uchar*data_in=image.ptr<uchar>(i);
    uchar* data_out=result.ptr<uchar>(i);
    for(int j=0;j//进行对每一个像素的处理
    }
}

1.2.3底层指针运算

cv::Mat形式的矩阵在内存中的存储首地址可以通过data成员变量得到,且data是一个unsigned cahr型的指针:

uchar* data =image.data;

从当前行到下一行可以通过对指针加上行宽得到:

data += iamge.step;

可以通过下一行代码调用i行j列像素地址

data = image.data+i*image.step+j*image.elemSize();
//相当于常用的矩阵索引(i*cols+j),先定义到位置:i*image.step,再加上下一个元素所占的内存空间+j*image.elemSize()

1.3 使用迭代器遍历图像

一个图像的迭代器可以这样声明:

cv::MatIterator_::Vec3b>it;

或者

cv::Mat_::Vec3b>::iterator it;

1.3.1使用迭代器遍历图像所有像素

//得到初始位置的迭代器
cv::Mat_::iteraator it = image.begin();
//得到终止位置的迭代器
cv::Mat_::iterator itend = image.end();
//遍历所有像素
for(;it!=itend;++it)
{
(*it)[0]=...;
(*it)[1]=...;
(*it)[2]=...;
}

1.3.2高效的图像遍历循环

void colorReduce(cv::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;
    for (int i = 0; i < nl; i++)
    {
        ucahr* data = image.ptr(j);
        for (int i = 0; i < nc; i++)
        {
            //一次处理处理三个通道的一个像素
            *data++ = *data&mask + dic / 2;
            *data++ = *data&mask + div / 2;
            *data++ = *data&mask + div / 2;
        }
    }

}

1.3.3遍历图像和邻域操作

通过获取相邻像素的指针对图像进行锐化处理
void sharpen(const cv::Mat &image, cv::Mat &result)

{ 
    result.create(image.size(), image.type());
    for (int i = 1; i < image.rows - 1; i++)//除了第一行和最后一行的所有行
    {
        const uchar* previous = image.ptr<const uchar>(i - 1);//获取图像上一行的指针
        const uchar* current = image.ptr<const uchar>(i);//获取当前行的指针
        const uchar* next = image.ptr<const uchar>(i + 1);//获取下一行的指针
        uchar *output = result.ptr<uchar>(i);//输出行
        for (int j = 1; j < image.cols - 1; j++)
        {
            *output = cv::saturate_cast<uchar>(5*current[j]-current[j-1]-current[j+1]-previous[j]-next[j]);
            output++;
        }
    }
    //将未处理的像素全部设置为0
    result.row(0).setTo(cv::Scalar(0));
    //单独设置矩阵中某一行的像素值
    result.row(result.rows - 1).setTo(cv::Scalar(0));
    result.col(0).setTo(cv::Scalar(0));
    result.col(result.cols - 1).setTo(cv::Scalar(0)); 
}

1.3.4把多维矩阵分层拉出

int main()
{
    cv::Mat image;
    image = cv::imread("baboon.png");

    std::vector::Mat>planes;
    cv::split(image, planes);

    cv::namedWindow("channel1");
    cv::imshow("channel1", planes[0]);

    cv::namedWindow("channel2");
    cv::imshow("channel2", planes[1]);

    cv::namedWindow("channel3");
    cv::imshow("channel3", planes[2]);

    cv::waitKey(0);
}

1.3.5 定义感兴趣区域

方法一:使用cv::Rect
cv::Rect须指定矩形左上角的坐标(构造函数的前两个参数)和矩形的长宽(构造函数的后两个参数)。
方法二:使用cv::Range
cv::Range

//270,385是矩形左上角的坐标,logo.rows和logo.cols是矩形的长宽
cv::Mat imageROI = image(cv::Range(270,270+logo.rows),cv::Range(385,385+logo.cols))

方法三:直接在原图上定义

start,end为起始坐标
cv::Mat imageROI = image.rowRange(start,end);
cv::Mat imageROI = image.colRange(start,end);

(2)基于类的图像处理

2.1面向对象的编程思想

2.1.1 示例程序:鉴定图像中含有给定颜色的所有像素,并返回一个二值图像,相同像素赋值255,其他像素赋值0;

//类定义
class ColorDetector
{
private:
    int minDist;
    cv::Vec3b target;
    cv::Mat result;
public:
//类方法申明
    ColorDetector() :minDist(100){
        //初始化默认参数
        target[0] = target[1] = target[2] = 0;
    }
    cv::Mat process(const cv::Mat &image);
    int getDistance(const cv::Vec3b & color)const;
    void setColorDistanceThreshold(int distance);
    int setColorDistanceThreshold()const;
    void setTargetColor(unsigned char red, unsigned char green, unsigned char blue);
    cv::Vec3b getTargetColor()const;
    ~ColorDetector(){};
};

cv::Vec3b ColorDetector::getTargetColor()const
{
    return target;
}
void ColorDetector::setTargetColor(unsigned char red, unsigned char green, unsigned char blue)
{
    //BGR顺序
    target[2] = red;
    target[1] = green;
    target[0] = blue;
}
void ColorDetector::setColorDistanceThreshold(int distance)
{
    if (distance < 0)
    {
        distance = 0;
    }
    minDist = distance;
}

int ColorDetector::setColorDistanceThreshold()const
{
    return minDist;
}
int ColorDetector::getDistance(const cv::Vec3b & color)const
{
    return abs(color[0] - target[0]) + abs(color[1] - target[1]) + abs(color[2] - target[2]);
}

 cv::Mat ColorDetector::process(const cv::Mat &image)
{
    //按需重新分配二值图像
    //与输入图像的尺寸相同,但是只有一个通道
    result.create(image.rows, image.cols, CV_8U);
    //得到迭代器
    cv::Mat_::const_iterator it = image.begin();
    cv::Mat_::const_iterator itend = image.end();
    cv::Mat_::iterator itout = result.begin();

    //处理每个像素
    for (; it != itend; it++, itout++)
    {
        //计算每个像素和目标颜色的距离
        if (getDistance(*it) < minDist)
        {
            *itout = 255;
        }
        else
        {
            *itout = 0;
        }
    }
    return result;
}

测试程序

 int main()
 {
     //创建图像处理的对象
     ColorDetector cdetect;
     //读取输入图像
     cv::Mat image = cv::imread("boldt.jpg");
     if (!image.data)
         return 0;
     //设置输入参数
     cdetect.setTargetColor(130,190,230);
     cv::namedWindow("result");
     //处理并显示结果
     cv::imshow("result", cdetect.process(image));
     cv::waitKey();
     return 0;
 }

处理结果

![输出效果图][2]

2.2 图像空间的转换

//RGB图像空间转换至LAB颜色空间
cv::cvtColor(image,converted,CV_BGR2Lab);
//RGB图像空间转换至YCbCr空间
cv::cvtColor(image,converted,CV_BGR2YCrCb);
//RGB图像空间转换至HSV图形空间
cv::cvtColor(image,converted, CV_BGR2HSV);
//RGB图像转换为灰度图
cv::cvtColor(color,gray,CV_BGR2Gray);

(3)使用直方图统计像素

3.1统计直方图并显示

3.1.1直方图统计类

class Histgram1D
{
private:
    //项的数量、范围、通道数
    int hist_size[1];
    float hist_range[2];
    const float* ranges[1];
    int channels[1];
public:
    //准备1D直方图的参数,创建构造函数
    Histgram1D()
    {
        hist_size[0] = 256;
        hist_range[0] = 0.0;
        hist_range[1] = 255.0;
        ranges[0] = hist_range;
        channels[0] = 0;
    }
    //定义获取直方图的函数
    cv::MatND getHistgram(const cv::Mat & image)
    {
        cv::MatND hist;
        //调用calcHist函数统计图像的直方图
        cv::calcHist(&image,    //输入图像
            1,                  //输入图像的个数
            channels,           //通道数
            cv::Mat(),          //不使用图像作为掩码
            hist,               //返回的直方图
            1,                  //1D直方图
            hist_size,          //项的数量
            ranges              //像素值的范围
            );
            return hist;
    }
    //定义绘制直方图的函数
    cv::Mat getHistgramImage(const cv::Mat & image)
    {
        //首先获得统计好的直方图数据
        cv::MatND hist = getHistgram(image);
        //获取直方图边界
        double max_value = 0;
        double min_value = 0;
        cv::minMaxLoc(hist, &min_value, &max_value, 0, 0);
        //显示直方图的图像
        //创建一个大小为256*256,的矩阵,矩阵中的数值类型uchar,初始化为255
        cv::Mat histImg(hist_size[0], hist_size[0], CV_8U, cv::Scalar(255));
        //设置最高点为nbins的90%
        int high_pointer = static_cast<int>(0.9*hist_size[0]);
        //每个像素值都绘制一条垂直线
        for (int i = 0; i < hist_size[0]; i++)
        {
            float bin_value = hist.at<float>(i);//获取统计好的直方图数据
            //定义垂直线
            int intensity = static_cast<int>(bin_value*high_pointer / max_value);
            //绘制垂直线
            cv::line(histImg, cv::Point(i, hist_size[0]),
                cv::Point(i, hist_size[0] - intensity),
                cv::Scalar::all(0));
        }
        return histImg;
    }
};

3.1.2测试函数

int main()
{
    cv::Mat image = cv::imread("lena.bmp", 0);
    if (!image.data)
    {
        //图像未被成功打开
        std::cout << "can not open this image!" << std::endl;
        exit(0);
    }
    //创建Histgram1D 类对象
    Histgram1D CalcHist;
    //计算直方图
    cv::Mat histo = CalcHist.getHistgramImage(image);

    //遍历每个条目
    /*for (int i = 0; i < 256; i++)
    {
        std::cout << "Count[" << i << "]=" << histo.at(i) << std::endl;
    }*/



    //显示直方图
    cv::namedWindow("Histgram");
    cv::imshow("Histgram", histo);
    cv::waitKey(0);
    }

显示结果如下图:

####3.1.3生成二值图像的阈值函数

cv::Mat thresholded;
cv::threshold(image,thresholded,60,255,cv::THRESH_BINARY);
//(原图,输出图,图像阈值,像素灰度最大值,二值图像格式)

你可能感兴趣的:(C++学习笔记)