《OpenCV3编程入门》学习笔记五:core组件进阶

一:内容介绍

本节主要介绍OpenCV的core模块基础部分:
1. 访问像素的一些方法
2. 图像混合的方法
3. 图像对比度、亮度调整
4. 离散傅里叶变换
5. XML和YAML文件的读取写入

二:学习笔记

  1. 本章仍是介绍了一些core模块的一些比较简单的应用。
  2. 在本章的大部分内容中,都透着用数字看待图像的观点。面对一副图像,别人看到的是美丽的画面及展示的内容,你还应该清醒的看到图像背后隐藏的数字,并且看的越深刻越好。本章中,在第三块源码及解析的提示部分大多添加了这些图像变换针对于每个像素的数学公式。都比较简单。
  3. 个人在示例7图像对比度亮度调整中加入了CLAHE算法的使用示例。CLAHE算法(Contrast Limited Adaptive Histogram Equalization,自适应直方图均衡化)在OpenCV中的使用。关于直方图均衡化和规定化这一块可参见:OpenCV2学习笔记(二):图像的直方图http://blog.csdn.net/liyuefeilong/article/details/43709359 ,《数字图像处理与机器视觉》3.7节直方图均衡化 3.8节直方图规定化(匹配) 和《数字图像处理原理与实践》2.5节灰度均衡和2.6节直方图规定化。

三:相关源码及解析

本章示例较多,示例列表:
1.用指针访问像素
2.用迭代器访问像素
3.用动态地址计算配合at访问像素
4.遍历图像像素的14种方法
5.初级图像混合
6.多通道图像混合
7.图像对比度、亮度值调整
8.离散傅里叶变换
9.XML和YAML文件写入
10.XML和YAML文件读取

1. 用指针访问图像中的每个像素,缩小颜色空间(即减少图像中颜色的种类)

源码:

#include<opencv2/opencv.hpp>
#include<iostream>
using namespace cv;
using namespace std;

//颜色空间缩减函数
void colorReduce(Mat& inputImage, Mat& outputImage, int div);

int main()
{
    Mat srcImage = imread("poster_tower.jpg");
    imshow("【原始图像】", srcImage);

    Mat dstImage;
    dstImage.create(srcImage.rows, srcImage.cols, srcImage.type());

    double time0 = static_cast<double>(getTickCount());
    colorReduce(srcImage, dstImage, 64);
    time0 = ((double)getTickCount()-time0)/getTickFrequency();
    cout << "此方法运行时间为:" << time0 << "秒" << endl;
    imshow("【效果图】", dstImage);

    waitKey(6000);
    return 0;
}

void colorReduce(Mat& inputImage, Mat& outputImage, int div)
{
    outputImage = inputImage.clone();
    int rowNumber = outputImage.rows;
    int colNumber = outputImage.cols*outputImage.channels(); //每一行元素的个数

    for (int i = 0; i < rowNumber; i++)
    {
        uchar* data = outputImage.ptr<uchar>(i);
        for (int j = 0; j < colNumber; j++)
        {
            data[j] = data[j] / div * div + div / 2;
        }
    }
}

素材:
无
效果图:
无
提示:
对应数学表达公式:I(x,y)= I(x,y)/div *div + div/2;

2 . 用迭代器访问像素,缩小颜色空间(即减少图像中颜色的种类)

源码:

#include<opencv2/opencv.hpp>
#include<iostream>
using namespace cv;
using namespace std;

void colorReduce(Mat& inputImage, Mat& outputImage, int div);

int main()
{
    Mat srcImage = imread("poster_tower.jpg");
    imshow("【原始图像】", srcImage);

    Mat dstImage;
    dstImage.create(srcImage.rows, srcImage.cols, srcImage.type());

    double time0 = static_cast<double>(getTickCount());
    colorReduce(srcImage, dstImage, 64);
    time0 = ((double)getTickCount() - time0) / getTickFrequency();
    cout << "此方法运行时间为:" << time0 << "秒" << endl;
    imshow("【效果图】", dstImage);

    waitKey(6000);
    return 0;
}
//缩减颜色空间
void colorReduce(Mat& inputImage, Mat& outputImage, int div)
{
    outputImage = inputImage.clone();
    Mat_<Vec3b>::iterator it = outputImage.begin<Vec3b>();  //初始位置迭代器
    Mat_<Vec3b>::iterator itend = outputImage.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;
    }

}

素材:
同1
效果图:
同1
提示:
对应数学表达公式:I(x,y)= I(x,y)/div *div + div/2;

3 . 用动态地址计算配合at访问像素,缩小颜色空间(即减少图像中颜色的种类)/font>

源码:

#include<opencv2/opencv.hpp>
#include<iostream>
using namespace cv;
using namespace std;

void colorReduce(Mat& inputImage, Mat& outputImage, int div);

int main()
{
    Mat srcImage = imread("poster_tower.jpg");
    imshow("【原始图像】", srcImage);

    Mat dstImage;
    dstImage.create(srcImage.rows, srcImage.cols, srcImage.type());

    double time0 = static_cast<double>(getTickCount());
    colorReduce(srcImage, dstImage, 64);
    time0 = ((double)getTickCount() - time0) / getTickFrequency();
    cout << "此方法运行时间为:" << time0 << "秒" << endl;
    imshow("【效果图】", dstImage);

    waitKey(6000);
    return 0;
}
//缩减颜色空间
void colorReduce(Mat& inputImage, Mat& outputImage, int div)
{
    outputImage = inputImage.clone();
    int rowNumber = outputImage.rows;
    int colNumber = outputImage.cols;

    for (int i = 0; i < rowNumber; i++)
    {
        for (int j = 0; j < colNumber; j++)
        {
            outputImage.at<Vec3b>(i, j)[0] = outputImage.at<Vec3b>(i, j)[0] / div*div + div / 2; //蓝色通道
            outputImage.at<Vec3b>(i, j)[1] = outputImage.at<Vec3b>(i, j)[1] / div*div + div / 2; //绿色通道
            outputImage.at<Vec3b>(i, j)[2] = outputImage.at<Vec3b>(i, j)[2] / div*div + div / 2; //红色通道
        }
    }

}

素材:
同1
效果图:
同1
提示:
对应数学表达公式:I(x,y)= I(x,y)/div *div + div/2;

4 . 遍历图像像素的14种方法

源码:
素材:
效果图:
提示:

5 . 初级图像混合

源码:

#include<opencv2/opencv.hpp>
#include<iostream>
using namespace cv;
using namespace std;

int main() {
    Mat srcImage = imread("poster_dota.jpg");
    imshow("【原图】", srcImage);
    Mat logoImage = imread("poster_dota_logo.jpg");
    Mat imageROI = srcImage(Rect(200, 250, logoImage.cols, logoImage.rows));

//  Mat mask = Mat::zeros(logoImage.cols, logoImage.rows, CV_8UC1);  //模板为单通道即可
    Mat mask = Mat::ones(logoImage.cols, logoImage.rows, CV_8UC1);
    logoImage.copyTo(imageROI, mask);
    imshow("【利用ROI实现图像叠加示例窗口】", srcImage);

    waitKey(6000);
    return 0;
}

素材:
这里写图片描述
这里写图片描述
效果图:
这里写图片描述
提示:
copyto()函数,mask中元素值为非0时才copy

6 . 多通道图像混合

源码:

#include<opencv2/opencv.hpp>
#include<iostream>
using namespace cv;
using namespace std;

int main()
{
    Mat srcImage, logoImage;
    vector<Mat> channels;

    //多通道混合——蓝色分量
    Mat imageBlueChannel;
    logoImage = imread("poster_dota_logo.jpg", IMREAD_GRAYSCALE); //载入灰度图
    srcImage = imread("poster_dota.jpg");
    split(srcImage, channels);
    imageBlueChannel = channels.at(0); //注意此处是引用,修改其中一个另一个跟着变
    addWeighted(imageBlueChannel(Rect(500, 250, logoImage.cols, logoImage.rows) ), 1.0, logoImage, 0.5, 0, imageBlueChannel(Rect(500, 250, logoImage.cols, logoImage.rows) ) );
    merge(channels, srcImage);
    imshow("【游戏原画+蓝色通道logo】", srcImage);

    //多通道混合——绿色分量
    Mat imageGreenChannel;
    logoImage = imread("poster_dota_logo.jpg", IMREAD_GRAYSCALE); //载入灰度图
    srcImage = imread("poster_dota.jpg");
    split(srcImage, channels);
    imageGreenChannel = channels.at(1); //注意此处是引用,修改其中一个另一个跟着变
    addWeighted(imageGreenChannel(Rect(500, 250, logoImage.cols, logoImage.rows)), 1.0, logoImage, 0.5, 0, imageGreenChannel(Rect(500, 250, logoImage.cols, logoImage.rows)));
    merge(channels, srcImage);
    imshow("【游戏原画+绿色通道logo】", srcImage);

    //多通道混合——红色分量
    Mat imageRedChannel;
    logoImage = imread("poster_dota_logo.jpg", IMREAD_GRAYSCALE); //载入灰度图
    srcImage = imread("poster_dota.jpg");
    split(srcImage, channels);
    imageRedChannel = channels.at(2); //注意此处是引用,修改其中一个另一个跟着变
    addWeighted(imageRedChannel(Rect(500, 250, logoImage.cols, logoImage.rows)), 1.0, logoImage, 0.5, 0, imageRedChannel(Rect(500, 250, logoImage.cols, logoImage.rows)));
    merge(channels, srcImage);
    imshow("【游戏原画+红色通道logo】", srcImage);

    waitKey(16000);
    return 0;
}

素材:
同5
效果图:
这里写图片描述
这里写图片描述
这里写图片描述
提示:
5中时直接将像素叠加上去,此例程中先用split()分离通道,然后在单通道上使用addWeighted()叠加,最后再使用merge()将各通道合并。

7 . 图像对比度、亮度值调整

源码:

#include<opencv2/opencv.hpp>
#include<iostream>
using namespace cv;
using namespace std;

static void on_ContrastAndBright(int, void* );
void useCLAHE();
int g_nContrastValue=80;    //对比度值
int g_nBrightValue=80;    //亮度值
Mat g_srcImage, g_dstImage;

int main()
{
    g_srcImage = imread("poster_girl_1.jpg");
    useCLAHE();
    g_dstImage = Mat::zeros(g_srcImage.size(), g_srcImage.type());
    namedWindow("【效果图窗口】");
    createTrackbar("对比度:", "【效果图窗口】", &g_nContrastValue, 300, on_ContrastAndBright);
    createTrackbar("亮度:", "【效果图窗口】", &g_nBrightValue, 200, on_ContrastAndBright);
    on_ContrastAndBright(0, 0); //进入中断一次

    while (char(waitKey(1)) != 'q');
    return 0;
}

//此函数传入的参数k即为g_nContrastValue或g_nBrightValue
static void on_ContrastAndBright(int k, void*) {
    for (int y = 0; y < g_srcImage.rows; y++) {
        for (int x = 0; x < g_srcImage.cols; x++) {
            for (int c = 0; c < 3; c++) {
                g_dstImage.at<Vec3b>(y, x)[c] = saturate_cast<uchar>((g_nContrastValue*0.01)*(g_srcImage.at<Vec3b>(y,x)[c])+g_nBrightValue);
            }
        }
    }
    imshow("【原始窗口】", g_srcImage);
    imshow("【效果图窗口】", g_dstImage);
}

//Contrast Limited Adaptive histgram equalization/CLAHE
void useCLAHE() {
    Mat grayImage;
    cvtColor(g_srcImage, grayImage, COLOR_RGB2GRAY);
    imshow("【灰度图】", grayImage);
    Ptr<CLAHE> clahe = createCLAHE();
    clahe->setClipLimit(4);
    Mat dst;
    clahe->apply(grayImage, dst);
    imshow("【CLAHE效果图】", dst);
}

素材:
这里写图片描述
效果图:
这里写图片描述
这里写图片描述
这里写图片描述
提示:
改变图像亮度和对比度的数学表达式:I(x, y) = ContrastValue * I(x, y) + BrightValue;
本示例中在书本例程的基础上添加了CLAHE算法(Contrast Limited Adaptive Histogram Equalization,自适应直方图均衡化)在OpenCV中的使用。关于直方图均衡化和规定化这一块可参见:《数字图像处理与机器视觉》3.7节直方图均衡化 3.8节直方图规定化(匹配) 和《数字图像处理原理与实践》2.5节灰度均衡和2.6节直方图规定化。

8 . 散傅里叶变换

源码:

#include<opencv2/opencv.hpp>
#include<iostream>
using namespace std;
using namespace cv;

int main()
{
    //读取灰度图
    Mat srcImage = imread("poster_meal.jpg", IMREAD_GRAYSCALE);
    imshow("【原始图像】", srcImage);
    //将输入图像延扩到最佳尺寸,边界用0补充
    int m = getOptimalDFTSize(srcImage.rows);
    int n = getOptimalDFTSize(srcImage.cols);
    Mat padded;
    copyMakeBorder(srcImage, padded, 0, m - srcImage.rows, 0, n - srcImage.cols, BORDER_CONSTANT, Scalar::all(0));
    //为傅里叶变换的结果(实部和虚部)分配存储空间, 将planes数组组合合并成一个多通道的数组complesI
    Mat planes[] = { Mat_<float>(padded), Mat::zeros(padded.size(), CV_32F) };
    Mat complexI;
    merge(planes, 2, complexI);
    //进行就地离散傅里叶变换
    dft(complexI, complexI);
    //将复数转换为幅值,即=>log(1+sqrt(Re(DFT(I))^2+Im(DFT(I))^2))
    split(complexI, planes);//planes[0]=Re(DFT(I)), planes[1]=Im(DFT(I))
    magnitude(planes[0], planes[1], planes[0]); //planes[0]=magnitude
    Mat magnitudeImage = planes[0];
    //进行对数尺度(logarithm scale)缩放
    magnitudeImage += Scalar::all(1);
    log(magnitudeImage, magnitudeImage); //求自然对数
    //剪切(若有奇数行或奇数列,进行频谱裁剪)和重分布幅度图像限
    magnitudeImage = magnitudeImage(Rect(0, 0, magnitudeImage.cols&-2, magnitudeImage.rows&-2));
    int cx = magnitudeImage.cols / 2;//重新排列傅里叶图像中的象限,使得原点位于图像中心
    int cy = magnitudeImage.rows / 2;
    Mat q0(magnitudeImage, Rect(0, 0, cx, cy)); //ROI区域的左上
    Mat q1(magnitudeImage, Rect(cx, 0, cx, cy)); //右上
    Mat q2(magnitudeImage, Rect(0, cy, cx, cy)); //左下
    Mat q3(magnitudeImage, Rect(cx, cy, cx, cy)); //右下
    Mat tmp; //交换象限
    q0.copyTo(tmp);
    q3.copyTo(q0);
    tmp.copyTo(q3);
    q1.copyTo(tmp);
    q2.copyTo(q1);
    tmp.copyTo(q2);
    //归一化,用0和1之间的浮点值将矩阵变换为可视的图像格式
    normalize(magnitudeImage, magnitudeImage, 0, 1, NORM_MINMAX);
    //显示效果图
    imshow("【频谱幅值】", magnitudeImage);

    waitKey();
    return 0;
}

素材:
这里写图片描述
效果图:
这里写图片描述
提示:
傅里叶变换,难啃的一块骨头。

9 . XML和YAML文件写入

源码:

#include<opencv2/opencv.hpp>
#include<iostream>
#include<time.h>
using namespace std;
using namespace cv;

int main()
{
    FileStorage fs("yaml_test.yaml", FileStorage::WRITE); //初始化
    //开始写入文件
    fs << "frameCount" << 5;
    time_t rawtime; 
    time(&rawtime);
    fs << "CalibrationDate" << asctime(localtime(&rawtime));
    Mat cameraMatrix = (Mat_<double>(3, 3) << 1000, 0, 320, 0, 1000, 240, 0, 0, 1);
    Mat distCoeffs = (Mat_<double>(5, 1)<<0.1, 0.01, -0.001, 0, 0);
    fs << "cameraMatrix" << cameraMatrix << "distCoeffs" << distCoeffs;
    fs << "features" << "[";
    for (int i = 0; i < 3; i++)
    {
        int x = rand() % 640;
        int y = rand() % 480;
        uchar lbp = rand() % 256;
        fs << "{:" << "x" << x << "y" << y << "lbp" << "[:";
        for (int j = 0; j < 8; j++)
            fs << ((lbp>>j)&1);
        fs << "]" << "}";
    }
    fs << "]";
    fs.release();
    cout << "文件读写完毕,请在工程目录下查看生成的文件" << endl;

    waitKey();
    return 0;
}

素材:

效果图:

提示:
YAML用的还是挺广泛的。

10 . XML和YAML文件读取
源码:

#include<opencv2/opencv.hpp>
#include<iostream>
using namespace std;
using namespace cv;

int main()
{
    FileStorage fs2("yaml_test.yaml", FileStorage::READ);
    int frameCount = (int)fs2["frameCount"]; //第一种方法,对FileNode操作
    string date;
    fs2["calibrationDate"] >> date;  //第二种方法,使用FileNode运算符>>
    Mat cameraMatrix2, distCoeffs2;
    fs2["cameraMatrix"] >> cameraMatrix2;
    fs2["distCoeffs"] >> distCoeffs2;
    cout << "frameCount: " << frameCount << endl
        << "calibration date: " << date << endl
        << "camera matrix: " << cameraMatrix2 << endl
        << "distortion coeffs: " << distCoeffs2 << endl;
    FileNode features = fs2["features"];
    FileNodeIterator it = features.begin(), it_end = features.end();
    int idx = 0;
    vector<uchar> lbpval;
    //使用FileNodeIterator遍历序列
    for (; it != it_end; ++it, idx++)
    {
        cout << "feature #" << idx << ": ";
        cout << "x=" << (int)(*it)["x"] << ", y=" << (int)(*it)["y"] << ", lbp: (";
        (*it)["lbp"] >> lbpval;
        for (int i = 0; i < (int)lbpval.size(); i++)
            cout << " " << (int)lbpval[i];
        cout << ")" << endl;
    }
    fs2.release();

    cout << "文件读取完毕,请输入任意键结束程序" << endl;
    return 0;
}

素材:

效果图:

提示:
YAML用的还是挺广泛的。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值