OpenCV之Core组件进阶

Stella981
• 阅读 507

颜色空间缩减

利用C++类型转换时向下取整操作,实现定义域内颜色缩减。表达式如下

Inew = (Iold/10)*10

简单的颜色空间缩减算法可由以下两步组成:

(1)遍历图像矩阵的每个元素 (2)对像应用上述公式

LUT函数:Look up table操作

上文提到的Look up table操作,OpenCV官方文档中强烈推荐使用一个原型为operationsOnArrays:LUT()的函数来进行。使用方法如下:

//首先我们建立一个mat型用于查表
Mat lookUpTable(1, 256, CV_8U);
uchar* p = lookUpTable.data;
for(int i = 0; i < 256; ++i)
  p[i] = table[i];

//然后我们调用函数(I是输入J是输出):
for(int i = 0; i < times; ++i)
  LUT(I, lookUpTable, J);

计时函数

  • getTickCount()函数返回CPU自时间以来走过的时钟周期数
  • getTickFrequency()函数返回CPU一秒钟所走的时钟周期数。

访问图像中像素的三类方法

  • 指针访问:C语言操作符[]; (最快)
  • 迭代器iterator; (最安全)
  • 动态地址计算; (最直观)

示例程序如下

#include<core.hpp>
#include<highgui.hpp>
#include<iostream>
using namespace std;
using namespace cv;

void colorReduce1(Mat& inputImage, Mat& outputImage, int div);//用指针访问像素(这种方法最快)
void colorReduce2(Mat& inputimage, Mat& outputImage, int div);//用迭代器操作像素
void colorReduce3(Mat& inputImage, Mat& outputImage, int div);//动态地址计算

int main()
{
    //1.创建原始图并显示
    Mat srcImage = imread("..//..//3.jpg");
    imshow("原始图像", srcImage);

    //2.按原始图的参数规格来创建效果图
    Mat dstImage;
    dstImage.create(srcImage.rows, srcImage.cols, srcImage.type());    //效果图的大小、类型与原始图片相同

    //3.记录起始时间
    double time0 = static_cast<double>(getTickCount());

    //4.调用颜色空间缩减函数
    colorReduce2(srcImage, dstImage, 32);

    //5.计算运行时间并输出
    time0 = ((double)getTickCount() - time0) / getTickFrequency();
    cout << "此方法运行时间为:" << time0 << "秒" << endl;    //输出运行时间

    //6.显示效果图
    imshow("效果图", dstImage);
    waitKey(0);
    return 0;
}

//用指针访问像素(这种方法最快)
void colorReduce1(Mat& inputImage, Mat& outputImage, int div)
{
    //参数准备
    outputImage = inputImage.clone();    //复制实参到临时变量
    int rowNumber = outputImage.rows;    //行数
    int colNumber = outputImage.cols * outputImage.channels();    //列数x通道数=每个元素的个数

    //双重循环,遍历所有的像素值
    for (int i = 0; i < rowNumber; i++)    //行循环
    {
        uchar* data = outputImage.ptr<uchar>(i);    //获取第i行的首地址
        for (int j = 0; j < colNumber; j++)    //列循环
        {
            //-----开始处理每个像素------
            data[j] = data[j] / div* div + div / 2;
            //-----处理结束-----
        }    //行处理结束
    }
}

//用迭代器操作像素
void colorReduce2(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;

        //----处理结束----
    }
}


//动态地址计算
void colorReduce3(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; //红色通道
        }
    }
}

感兴趣区域:ROI

  • 使用Rect指定区域

    //定义一个Mat类型并给其设定ROI区域 Mat imageROI; //方法一 imageROI = image(Rect (500, 250, logo.cols, logo.rows)); //image 为已载入的图片

  • 用Range来定义ROI

Range 是指从起始索引到终止索引(不包括终止索引)的一连续序列。

imageROI = image(Range(250, 250+logoImage.rows), Range(200, 200+logoImage.cols));
// image 为已载入的图片

示例如下

#include<core.hpp>
#include<highgui.hpp>
#include<stdio.h>
using namespace cv;

int main()
{
    //1.input image
    Mat srcImage1 = imread("..//..//3.jpg");
    Mat logoImage = imread("..//..//1.jpg");
    if (!srcImage1.data)
    {
        printf("读取srcImage1错误\n");
        return 0;
    }
    if (!logoImage.data)
    {
        printf("读取logoImage错误\n");
        return 0;
    }
    //2.define ROI
    Mat imageROI = srcImage1(Rect(100, 150, logoImage.cols, logoImage.rows));

    //3.make mask  (mast be grey value image)
    Mat mask = imread("..//..//4.img", 0);

    //4.mask to ROI
    logoImage.copyTo(imageROI, mask);

    //show dstimage
    namedWindow("<1>利用ROI实现图像叠加示例窗口");
    imshow("<1>利用ROI实现图像叠加示例窗口", srcImage1);
    waitKey(0);
    return 0;
}

线性混合操作与addWeighted()函数

  • 线性混合理论公式:g(x) = (1-a)fa(x)+ af3(x)

  • addWeighted()函数 函数原型

    void addWeighted(InputArray src1, double alpha, InputArray src2, double beta, double gamma, OutputArray dst, int dtype = -1);

参数

  • InputArray 类型的src1,表示需要加权的第一个数组,常常填一个Mat;
  • double类型的alpha,表示第一个数组的权重
  • InputArray 类型的src2,表示第二个数组,它需要和第一个数组拥有相同的尺寸和通道数
  • double 类型的beta,表示第一个数组的权重值;
  • double 类型的gamma,一个加到权重和上的标量值。
  • OutputArray 类型的dst,输出的数组,它和输入的两个数组拥有相同的尺寸和通道数
  • int 类型的dtype,输出阵列的可选深度,默认值-1。当两个输入数组具有相同的深度时,这个参数设置为-1(默认值),即等同于src1.depth()。

addWeighted 函数作用矩阵的表达式

dst = src1[I]*alpha + src2[I]*beta + gamma;

示例如下

#include<core.hpp>
#include<highgui.hpp>
#include<stdio.h>
using namespace cv;

int main()
{
    //0.定义一些局部变量
    double alphaValue = 0.5;
    double betaValue;
    Mat srcImage2, srcImage3, dstImage;

    //1.读取图像(两幅图像需为同样的类型和尺寸)
    srcImage2 = imread("..//..//3.jpg");
    srcImage3 = imread("..//..//4.jpg");
    if (!srcImage2.data)
    {
        printf("读取srcImage2错误");
    }
    if (!srcImage3.data)
    {
        printf("读取srcImage3错误");
    }

    //2.做图像混合加权操作
    betaValue = (1.0 - alphaValue);
    addWeighted(srcImage2, alphaValue, srcImage3, betaValue, 0.0, dstImage);

    //3.创建并显示原图窗口
    namedWindow("<2>线性混合示例窗口【原图】", 1);
    imshow("<2>线性混合示例窗口【原图】", srcImage2);

    namedWindow("<3>线性混合示例窗口【效果图】");
    imshow("<3>线性混合示例窗口【效果图】", dstImage);
    waitKey(0);

    return 0;
}

颜色通道的分离与混合

通道分离:split()函数

函数原型

void split(const Mat& src, Mat* mvbegin);
void split(InputArray m, OutputArrayOfArrays mv);

参数

  • InputArray 类型的m或者const Mat&类型的src,填我们需要分离的多通道数组。
  • OutputArrayOfArrays类型的mv,填函数的输出数组或者输出的vector容器

示例

vector<Mat> channels;
Mat imageBlueChannel;
Mat imageGreenChannel;
Mat imageRedChannel;
srcImage = imread(filename);
//把一个3通道图像转换成3个单通道图像
split(srcImage, channels);  //分离色彩通道
imageBlueChannel = channels.at(0);
imageGreenChannel = channels.at(1);
imageRedChannel = chan.at(2);

通道合并:merge()函数

函数原型

void merge(const Mat* mv,size_tcount, OutputArray dst);
void merge(InputArrayOfArrays mv, OutputArray dst);

参数

  • 填需要被合并的输入矩阵或vector容器的阵列(数组),这个mv参数中所有的矩阵必须有着一样的尺寸和深度。
  • 当mv为一个空白的C语言数组时,代表输入矩阵的个数,这个参数显然必须大于1。
  • dst即输出矩阵,和mv[0]具有一样的尺寸和深度,并且通道的数量是矩阵阵列中的通道的总数。

示例如下

#include<iostream>
#include<highgui.hpp>
#include<core.hpp>
using namespace cv;
using namespace std;

bool MultiChannelBlending();

int main()
{
    system("color 9F");
    if (MultiChannelBlending())
    {
        cout << endl << "\n运行成功,得出了需要的图像";
    }
    waitKey(0);
    return 0;
}

bool MultiChannelBlending()
{
    //0.定义相关变量
    Mat srcImage;
    Mat logoImage;
    vector<Mat> channels;
    Mat imageBlueChannel;

    //多通道混合蓝色通道部分
    //1.读入图片
    logoImage = imread("..//..//1.jpg", 0);
    srcImage = imread("..//..//3.jpg");
    if (!logoImage.data)
    {
        printf("读取logoImage错误\n");
        return false;
    }
    if (!srcImage.data)
    {
        printf("读取srcImage错误\n");
        return false;
    }
    imshow("srcImage【原图】", srcImage);
    //2.把一个3通道图像转换成3个单通道图像
    split(srcImage, channels);    //分离色彩通道

    //3.将原图的蓝色通道引用返回给imageBlueChannel,注意是引用,相当于两者等价,修改一个另一个跟着变
    imageBlueChannel = channels.at(0);
    //4.将原图的蓝色通道的(500,250)坐标处右下方的一块区域和logo图进行加权操作,将得到的混合结果存到imageBlueChannel中
    addWeighted(imageBlueChannel(Rect(100, 100, logoImage.cols, logoImage.rows)), 1.0, logoImage, 0.5, 0, imageBlueChannel(Rect(100, 100, logoImage.cols, logoImage.rows)));
    //5.将三个单通道重新合并成一个三通道
    merge(channels, srcImage);
    //6.显示效果图
    namedWindow("<1>游戏原画+logo蓝色通道");
    imshow("<1>游戏原画+logo蓝色通道", srcImage);


    //多通道混合绿色通道部分
    //0.定义相关变量
    Mat imageGreenChannel;

    //1.读入图片
    logoImage = imread("..//..//1.jpg", 0);
    srcImage = imread("..//..//3.jpg");
    if (!logoImage.data)
    {
        printf("读取logoImage错误\n");
        return false;
    }
    if (!srcImage.data)
    {
        printf("读取srcImage错误\n");
        return false;
    }
    //2.把一个3通道图像转换成3个单通道图像
    split(srcImage, channels);    //分离色彩通道

//3.将原图的蓝色通道引用返回给imageGreenChannel,注意是引用,相当于两者等价,修改一个另一个跟着变
    imageGreenChannel = channels.at(1);
    //4.将原图的蓝色通道的(500,250)坐标处右下方的一块区域和logo图进行加权操作,将得到的混合结果存到imageGreenChannel中
    addWeighted(imageGreenChannel(Rect(100, 100, logoImage.cols, logoImage.rows)), 1.0, logoImage, 0.5, 0, imageGreenChannel(Rect(100, 100, logoImage.cols, logoImage.rows)));
    //5.将三个单通道重新合并成一个三通道
    merge(channels, srcImage);
    //6.显示效果图
    namedWindow("<2>游戏原画+logo绿色通道");
    imshow("<2>游戏原画+logo绿色通道", srcImage);


    //多通道混合红色通道部分
    //0.定义相关变量
    Mat imageRedChannel;

    //1.读入图片
    logoImage = imread("..//..//1.jpg", 0);
    srcImage = imread("..//..//3.jpg");
    if (!logoImage.data)
    {
        printf("读取logoImage错误\n");
        return false;
    }
    if (!srcImage.data)
    {
        printf("读取srcImage错误\n");
        return false;
    }
    //2.把一个3通道图像转换成3个单通道图像
    split(srcImage, channels);    //分离色彩通道

    //3.将原图的蓝色通道引用返回给imageRedChannel,注意是引用,相当于两者等价,修改一个另一个跟着变
    imageRedChannel = channels.at(2);
    //4.将原图的蓝色通道的(500,250)坐标处右下方的一块区域和logo图进行加权操作,将得到的混合结果存到imageRedChannel中
    addWeighted(imageRedChannel(Rect(100, 100, logoImage.cols, logoImage.rows)), 1.0, logoImage, 0.5, 0, imageRedChannel(Rect(100, 100, logoImage.cols, logoImage.rows)));
    //5.将三个单通道重新合并成一个三通道
    merge(channels, srcImage);
    //6.显示效果图
    namedWindow("<3>游戏原画+logo红色通道");
    imshow("<3>游戏原画+logo红色通道", srcImage);

    return true;
}

图像对比度、亮度值调整

理论公式:g(i,j) = a*f(i,j) + b 其中

  • 参数f(x)表示源图像像素
  • 参数g(x)表示输出图像像素
  • 参数a(需要满足a>0)被称为增益(gain),常常被用来控制图像的对比度。
  • 参数b通常被称为偏置(bias),常常被用来控制图像的亮度。

示例如下

#include<core.hpp>
#include<highgui.hpp>
#include<iostream>
using namespace std;
using namespace cv;

static void on_ContrastAndBright(int, void*);

int g_nContrastValue;    //对比度值
int g_nBrightValue;        //亮度值
Mat g_srcImage, g_dstImage;

int main()
{
    //1.读取输入图像
    g_srcImage = imread("..//..//3.jpg");
    if (!g_srcImage.data)
    {
        printf("读取图片错误,请确定目录下是否有该图片");
        return false;
    }
    g_dstImage = Mat::zeros(g_srcImage.size(), g_srcImage.type());

    //2.设定对比度和亮度的初值
    g_nContrastValue = 80;
    g_nBrightValue = 80;

    //3.创建效果图窗口
    namedWindow("【效果图窗口】", 1);

    //4.创建轨迹条
    createTrackbar("对比度:", "【效果图窗口】", &g_nContrastValue, 300, on_ContrastAndBright);
    createTrackbar("亮度:", "【效果图窗口】", &g_nBrightValue, 200, on_ContrastAndBright);

    //5.进行回调函数初始化
    on_ContrastAndBright(g_nContrastValue, 0);
    on_ContrastAndBright(g_nBrightValue, 0);

    //6.按下"q"键是,程序退出
    while (char(waitKey(1)) != 'q') {

    }
    return 0;
}

static void on_ContrastAndBright(int, void*)
{
    //创建窗口
    namedWindow("【原始图窗口】", 1);
    //三个for循环,执行运算 g_dstImage(i,j) = a*g_srcImage(i,j) + b
    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);
}

其中saturate_cast是对结果进行转换防止溢出,原理大致如下

if (data < 0)
  data = 0;
else if (data > 255)
  data = 255;
点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
2年前
java将前端的json数组字符串转换为列表
记录下在前端通过ajax提交了一个json数组的字符串,在后端如何转换为列表。前端数据转化与请求varcontracts{id:'1',name:'yanggb合同1'},{id:'2',name:'yanggb合同2'},{id:'3',name:'yang
Easter79 Easter79
2年前
swap空间的增减方法
(1)增大swap空间去激活swap交换区:swapoff v /dev/vg00/lvswap扩展交换lv:lvextend L 10G /dev/vg00/lvswap重新生成swap交换区:mkswap /dev/vg00/lvswap激活新生成的交换区:swapon v /dev/vg00/lvswap
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Stella981 Stella981
2年前
Opencv中Mat矩阵相乘——点乘、dot、mul运算详解
Opencv中Mat矩阵相乘——点乘、dot、mul运算详解2016年09月02日00:00:36 \牧野(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fme.csdn.net%2Fdcrmg) 阅读数:59593
Stella981 Stella981
2年前
Nginx + lua +[memcached,redis]
精品案例1、Nginxluamemcached,redis实现网站灰度发布2、分库分表/基于Leaf组件实现的全球唯一ID(非UUID)3、Redis独立数据监控,实现订单超时操作/MQ死信操作SelectPollEpollReactor模型4、分布式任务调试Quartz应用
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
3个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这