OpenCV之Core组件进阶
颜色空间缩减
利用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;
OpenCV之Core组件进阶的更多相关文章
- opencv 3 core组件进阶(3 离散傅里叶变换;输入输出XML和YAML文件)
离散傅里叶变换 #include "opencv2/core/core.hpp" #include "opencv2/imgproc/imgproc.hpp" ...
- opencv 3 core组件进阶(2 ROI区域图像叠加&图像混合;分离颜色通道、多通道图像混合;图像对比度,亮度值调整)
ROI区域图像叠加&图像混合 #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp&g ...
- opencv 3 core组件进阶(1 访问图像中的像素)
访问图像像素的三类方法 ·方法一 指针访问:C操作符[ ]; ·方法二 迭代器iterator; ·方法三 动态地址计算. #include <opencv2/core/core.hpp> ...
- opencv core组件进阶
1.图像在内存中存储方式,图像矩阵的大小取决于颜色模型,取决于所有的通道数:还有重要的颜色空间缩减的概念:因为如果是RGB的话,使用uchar的话,就有256^3的结合方法.所以要用到颜色缩减的方法, ...
- core组件进阶
访问图像像素 存储方式 BGR连续存储有助于提升图像扫描速度. isContinuous()判断是否是连续存储. 颜色空间缩减 仅用这些颜色中具有代表性的很小的部分,就足以达到同样的效果. 将现有颜色 ...
- .Net Core组件化视图(部分视图)
.Net Core组件化视图(部分视图) 1.背景 1.以前我们使用.Net的时候使用部分视图的方式在,.Net Core 中能在单独处理逻辑的部分视图没有了,但是我们还是想使用现在的.Net Cor ...
- Vue基础二之全局API、实例属性和全局配置,以及组件进阶(mixins)的详细教程(案列实现,详细图解,附源码)
本篇文章主要是写Vue.directive().Vue.use()等常用全局API的使用,vm.$props.vm.$options.vm.$slots等实例属性的使用,以及Vue全局配置.组件的mi ...
- Django之forms组件进阶
Django Form表单组件 Form介绍 我们之前在HTML页面中利用form表单向后端提交数据时,都会写一些获取用户输入的标签并且用form标签把它们包起来. 与此同时我们在好多场景下都需要 ...
- opencv:联通组件扫描
#include <opencv2/opencv.hpp> #include <iostream> using namespace cv; using namespace st ...
随机推荐
- Uva1640(统计数字出现的次数)
题意: 统计两个整数a,b之间各个数字(0~9)出现的次数,如1024和1032,他们之间的数字有1024 1025 1026 1027 1028 1029 1030 1031 1032 总共有10个 ...
- 推荐15款Google Chrome 插件
Google Chrome是由Google开发的免费网页浏览器.Chrome是化学元素“铬”的英文名称,过去也用Chrome称呼浏览器的外框.Chrome相应的开放源代码计划名为Chromium,而G ...
- 通过nginx实现多个域名访问同一个服务器
一台服务器通过nginx配置多个域名(80端口) 参考:https://www.cnblogs.com/ruanjianlaowang/p/11182486.html 1. 问题描述 多个域名对应一 ...
- go语言 base58编码解码
package main import ( "bytes" "encoding/hex" "fmt" "math/big" ...
- java编码解码过程
最近做项目的时候,有时会遇到中文乱码的问题,网上查询了很多资料,发现大多都是只讲解决方案,并没有讲到为什么要使用这种方案,这种方案的原理是什么? 最典型的就是连接数据库的URL,我们一般把它放到cla ...
- ASP.NET MVC简单流程解释(传值方式)
在上篇笔记里我们创建了一个简单的MVC程序 在view中,@Model指的就是MyFirstMvc.Models.IndexModel,我们的提交页面是/Test/Index,当我们输入完数字并按下提 ...
- Wannafly Camp 2020 Day 1A 期望逆序对 - 概率期望
分类讨论即可 #include <bits/stdc++.h> using namespace std; #define int long long const int N = 5005; ...
- redis分布式锁在springboot中的实现
理论知识 redis分布式锁的实现方案请参考文章 如何优雅地用redis实现分布式锁 本案例简介 以秒杀活动为例子,在多线程高并发的情况下需要保证秒杀业务的线程安全性,确保秒杀记录与所扣库存数 ...
- C++常用字符串操作和UTF-8和GBK之间的转换以及判定(转)
编码转换原文地址:https://www.cnblogs.com/Toney-01-22/p/9935297.html C++字符串常用操作:C++ 中字符串查找.字符串截取.字符串替换
- android 获取所有SD卡目录
//返回sd卡路径public static List<String> getStorageDirectories(Context context) { StorageManager sm ...