颜色模型转换
RGB颜色模型 | OpenCV中顺序是相反的,为 BGR ,分别对应 blue(蓝), green (绿), red(红)。此为三通道图像时,且每个通道有256个等级,对应0~255。四通道时再加一个透明度,此时为BRGA,没有透明度需求时就变为BGR。 |
YUV颜色模型 | YUV模型是电视信号系统采用的颜色编码方式。分别代表像素的 亮度(Y) 、红色分量与亮度的信号差值(U) 、蓝色与亮度的差值(V) 。 Y = 0.229R + 0.587G + 0.114B U = -0.147R - 0.289G + 0.436B V = 0.615R - 0.515G - 0.100B R = Y + 1.14V G = Y - 0.39U - 0.58V B = Y + 2.03U |
HSV颜色模型 | HSV是 色度 (Hue) 、饱和度 (Saturation) 和 亮度 (Value) 的简写.色度是色彩的基本属性,就是平常说的颜色; 饱和度是指颜色的纯度, 取值0%~100%; 亮度是颜色的明暗程度,其取值由0到计算机中允许的最大值. 相比于RGB模型3个颜色分量与最终颜色联系不直观的缺点, HSV模型更加符合人类感知颜色的方式: 颜色、深浅及亮暗. |
Lab颜色模型 | Lab颜色模型弥补了RGB模型的不足,是一种设备无关和基于生理特征的颜色模型. 在模型中, L 表示亮度 (Luminosity) , a 和 b 是两个颜色通道, 两者的取值区间都是 -128~127, 其中 a 通道数值由小到大对应的颜色是从绿色变成红色 , b 通道数值由小到达对应的颜色是由蓝色变成黄色 . |
GRAY颜色模型 | 此模型非彩色,是一个灰度图像模型,分为256个等级,取值0~255,与RGB对应关系的常用转换式为: GRAY = 0.3R + 0.59G + 0.11B |
/**
* @author IYATT-yx
* @date 2021/2/3
* @brief 颜色模型转换 (通过摄像头获取图像)
*/
#include "opencv2/opencv.hpp"
#include <iostream>
int main()
{
cv::VideoCapture camera(-1);
if (!camera.isOpened())
{
std::cout << "摄像头打开失败" << std::endl;
exit(EXIT_FAILURE);
}
cv::Mat src, gray, HSV, YUV, Lab;
while (camera.read(src))
{
// 默认CV_8U,转为CV_32F. 缩放到 0~1
src.convertTo(src, CV_32F, 1.0 / 255);
cv::imshow("src", src);
// gray
cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY);
cv::imshow("gray", gray);
// HSV
cv::cvtColor(src, HSV, cv::COLOR_BGR2HSV);
cv::imshow("HSV", HSV);
// YUV
cv::cvtColor(src, YUV, cv::COLOR_BGR2YUV);
cv::imshow("YUV", YUV);
// Lab
cv::cvtColor(src, Lab, cv::COLOR_BGR2Lab);
cv::imshow("Lab", Lab);
if (cv::waitKey(40) == 27)
{
break;
}
}
camera.release();
}
多通道分离与合并
/**
* @author IYATT-yx
* @date 2021/2/3
* @brief 多通道分离与合并
*/
#include "opencv2/opencv.hpp"
#include <iostream>
#include <vector>
int main()
{
// RGB三基色
cv::Mat B(200, 200, CV_8UC1, cv::Scalar(200));
cv::Mat G(200, 200, CV_8UC1, cv::Scalar(100));
cv::Mat R(200, 200, CV_8UC1, cv::Scalar(255));
// 将三基色的矩阵添加到一个vector容器中
std::vector<cv::Mat> mergeVector;
mergeVector.push_back(B);
mergeVector.push_back(G);
mergeVector.push_back(R);
// 合并
cv::Mat mergeMat;
cv::merge(mergeVector, mergeMat);
cv::imshow("mergeMat", mergeMat);
// 分离
std::vector<cv::Mat> splitVector;
cv::split(mergeMat, splitVector);
cv::imshow("B", splitVector[0]);
cv::imshow("G", splitVector[1]);
cv::imshow("R", splitVector[2]);
cv::waitKey(0);
}
图像拼接
/**
* @author IYATT-yx
* @date 2021/2/3
* @brief 图像拼接
*/
#include "opencv2/opencv.hpp"
int main()
{
cv::Mat img1(200, 200, CV_8UC3, cv::Scalar(0, 0, 255));
cv::imshow("img1", img1);
cv::Mat img2(200, 200, CV_8UC3, cv::Scalar(255, 0, 0));
cv::imshow("img2", img2);
// 先水平方向拼接
cv::Mat img3, img4;
cv::hconcat(img1, img2, img3);
cv::imshow("img3", img3);
cv::hconcat(img2, img1, img4);
cv::imshow("img4", img4);
// 竖直拼接
cv::Mat img5;
cv::vconcat(img3, img4, img5);
cv::imshow("img5", img5);
cv::waitKey(0);
}
图像翻转
/**
* @author IYATT-yx
* @date 2021/2/3
* @brief 图像翻转
*/
#include "opencv2/opencv.hpp"
int main()
{
cv::VideoCapture camera(-1);
if (!camera.isOpened())
{
std::cout << "打开摄像头失败" << std::endl;
exit(EXIT_FAILURE);
}
cv::Mat src;
camera >> src;
cv::imshow("camera", src);
int leftRight = false;
int upDown = false;
cv::createTrackbar("左右翻转", "camera", &leftRight, 1);
cv::createTrackbar("上下翻转", "camera", &upDown, 1);
while (camera.read(src))
{
// 左右翻转
if (leftRight == true && upDown == false)
{
cv::flip(src, src, 1);
}
// 上下翻转
else if (leftRight == false && upDown == true)
{
cv::flip(src, src, 0);
}
// 上下左右翻转
else if (leftRight == true && upDown == true)
{
cv::flip(src, src, -1);
}
cv::imshow("camera", src);
if (cv::waitKey(40) == 27)
{
break;
}
}
camera.release();
}
图像仿射变换
图像的旋转,平移,缩放的统称
/**
* @author IYATT-yx
* @date 2021/2/3
* @brief 图像仿射变换
*/
#include "opencv2/opencv.hpp"
#include <iostream>
int main()
{
cv::VideoCapture camera(-1);
if (!camera.isOpened())
{
std::cout << "摄像头打开失败" << std::endl;
exit(EXIT_FAILURE);
}
cv::Mat src;
camera >> src;
cv::imshow("camera", src);
// 旋转角度
int angle = 0;
// 缩放 (scale的值除以10为缩放比例)
int scale = 10;
// 三点仿射
int flag = false;
cv::createTrackbar("旋转", "camera", &angle, 360);
cv::createTrackbar("缩放", "camera", &scale, 20);
cv::createTrackbar("点仿射", "camera", &flag, 1);
// 中心点
cv::Point2f center((float)src.cols / 2, (float)src.rows / 2);
std::cout << center.x << std::endl;
std::cout << center.y << std::endl;
// 尺寸
cv::Size dsize(src.rows, src.cols);
// 原始三点
cv::Point2f srcPoints[3];
srcPoints[0] = cv::Point2f(0, 0);
srcPoints[1] = cv::Point2f(0, (float)(src.rows - 1));
srcPoints[2] = cv::Point2f((float)(src.cols - 1), (float)(src.rows - 1));
// 变换后
cv::Point2f dstPoints[3];
dstPoints[0] = cv::Point2f((float)src.cols * 11 / 100, (float)src.rows * 20 / 100);
dstPoints[1] = cv::Point2f((float)src.cols * 15 / 100, (float)src.rows * 70 / 100);
dstPoints[2] = cv::Point2f((float)src.cols * 81 / 100, (float)src.rows * 85 / 100);
cv::Mat rotation1 = cv::getAffineTransform(srcPoints, dstPoints);
while (camera.read(src))
{
cv::Mat rotation2 = cv::getRotationMatrix2D(center, angle, static_cast<double>(scale) / 10.0);
cv::warpAffine(src, src, rotation2, dsize);
if (flag == true)
{
cv::warpAffine(src, src, rotation1, dsize);
}
cv::imshow("camera", src);
if (cv::waitKey(40) == 27)
{
break;
}
}
camera.release();
}
透视变换
按照物体成像规律进行变换,将物体重新投影到新的成像平面
素材图片 (可在图片上右键 "图片另存为")
/**
* @author IYATT-yx
* @date 2021/2/3
* @brief 图像透视变换
*/
#include "opencv2/opencv.hpp"
#include <iostream>
int main(int argc, char *argv[])
{
if (argc != 2)
{
std::cout << "参数: 请输入所给的二维码素材图片路径" << std::endl;
exit(EXIT_FAILURE);
}
cv::Mat src = cv::imread(argv[1]);
if (src.empty())
{
std::cout << "图片读取失败,请确认是否存在" << std::endl;
exit(EXIT_FAILURE);
}
// 二维码初始四角点坐标
cv::Point2f srcPoints[4];
srcPoints[0] = cv::Point2f(94, 374);
srcPoints[1] = cv::Point2f(507, 380);
srcPoints[2] = cv::Point2f(1, 623);
srcPoints[3] = cv::Point2f(627, 627);
// 透视变换后希望的四角点坐标
cv::Point2f dstPoints[4];
dstPoints[0] = cv::Point2f(0,0);
dstPoints[1] = cv::Point2f(627, 0);
dstPoints[2] = cv::Point2f(0, 627);
dstPoints[3] = cv::Point2f(627, 627);
// 计算透视变换矩阵
cv::Mat rotation = cv::getPerspectiveTransform(srcPoints, dstPoints);
// 透视变换投影
cv::Mat dst;
cv::warpPerspective(src, dst, rotation, src.size());
cv::imshow("src", src);
cv::imshow("dst", dst);
cv::waitKey(0);
}
极坐标变换
将图像在直角坐标系与极坐标系中相互变换
素材 在图片上右键 "图片另存为"
/**
* @author IYATT-yx
* @date 2021/2/4
* @brief 图像极坐标变换
*/
#include "opencv2/opencv.hpp"
#include <iostream>
int main(int argc, char *argv[])
{
if (argc != 2)
{
std::cout << "参数: 请输入所给的素材图片的路径" << std::endl;
exit(EXIT_FAILURE);
}
cv::Mat src = cv::imread(argv[1]);
cv::imshow("src", src);
cv::Mat dst;
if (src.empty())
{
std::cout << "图片读取失败,请确认是否存在" << std::endl;
exit(EXIT_FAILURE);
}
// 极坐标图像原点
cv::Point2f center = cv::Point2f(static_cast<float>(src.cols) / 2, static_cast<float>(src.rows) / 2);
// 正极坐标变换
cv::warpPolar(src, dst, cv::Size(300, 600), center, center.x, cv::INTER_LINEAR + cv::WARP_POLAR_LINEAR);
cv::imshow("dst1", dst);
// 逆极坐标变换
cv::warpPolar(dst, dst, cv::Size(src.cols, src.rows), center, center.x, cv::INTER_LINEAR + cv::WARP_POLAR_LINEAR + cv::WARP_INVERSE_MAP);
cv::imshow("dst2", dst);
cv::waitKey(0);
}
感兴趣的区域 (Region of Interest, ROI)
当我们只对一幅图像中的部分区域感兴趣,而原图又比较大,则带着非感兴趣的区域处理会消耗额外不必要的资源,因此希望从原图中截取部分图像后再处理,该区域则为 ROI
/**
* @author IYATT-yx
* @date 2021/2/4
* @brief ROI
*/
#include "opencv2/opencv.hpp"
#include <iostream>
int main(int argc, char *argv[])
{
if (argc != 3)
{
std::cout << "参数: 请输入两张图片的路径" << std::endl;
exit(EXIT_FAILURE);
}
cv::Mat img1 = cv::imread(argv[1]);
cv::resize(img1, img1, cv::Size(500, 500));
cv::imshow("img1", img1);
cv::Mat img2 = cv::imread(argv[2]);
cv::resize(img2, img2, cv::Size(150, 150));
cv::imshow("img2", img2);
// 截图1
// 左上角(x, y) 宽度 高度
cv::Rect rect(38, 30, 68, 75);
cv::Mat ROI1 = img2(rect);
cv::imshow("ROI1", ROI1);
// 截图2
// (行取值范围, 列取值范围)
cv::Mat ROI2 = img2(cv::Range(30, 105), cv::Range(38, 106));
// 用"="是浅拷贝 - 创建一个变量去访问数据,通过原变量和现变量都可以对同一数据做读写
cv::Mat ROI2_copy = ROI2;
cv::putText(ROI2_copy, "@@", cv::Point(5, 70), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(0, 0, 255));
cv::imshow("ROI2", ROI2);
// 插入图片
// 浅拷贝img1中的ROI,准备将img2插入进去
cv::Mat ROI3 = img1(cv::Rect(200, 200, img2.size().width, img2.size().height));
// 将要插入的内容拷贝给ROI (深拷贝, 数据复制)
img2.copyTo(ROI3);
cv::imshow("插入图片", img1);
cv::waitKey(0);
}
图像金字塔
-
高斯金字塔
构建图像的高斯金字塔是解决尺度不确定性的一种常用方法. 高斯金字塔是指通过下采样不断地将图像的尺寸缩小,进而在图像金字塔中包含多个尺寸的图像.一般情况下,高斯金字塔的底层为图像的原图,每往上一层就会通过下采样缩小一次图像的尺寸,通常尺寸会缩小为原来的一半. -
拉普拉斯金字塔
和高斯金字塔相反,是通过上层小尺寸构建下层大尺寸的图像. 拉普拉斯金字塔有预测残差的作用.
/**
* @author IYATT-yx
* @date 2021/2/4
* @brief 图像金字塔
*/
#include "opencv2/opencv.hpp"
#include <iostream>
#include <vector>
#include <string>
// 层数
#define LEVEL 3
int main(int argc, char *argv[])
{
if (argc != 2)
{
std::cout << "参数: 请输入图片的路径" << std::endl;
exit(EXIT_FAILURE);
}
cv::Mat src = cv::imread(argv[1]);
if(src.empty())
{
std::cout << "图片读取错误,请检查是否存在" << std::endl;
exit(EXIT_FAILURE);
}
// 构建高斯金字塔
std::vector<cv::Mat> gauss;
gauss.push_back(src);
for (int i = 0; i < LEVEL; ++i)
{
cv::Mat temp;
cv::pyrDown(gauss[i], temp);
gauss.push_back(temp);
}
// 构建拉普拉斯金字塔
std::vector<cv::Mat> lap;
lap.push_back(gauss[LEVEL]);
for (int i = LEVEL; i > 0; --i)
{
cv::Mat temp;
cv::pyrUp(gauss[i], temp);
lap.push_back(temp);
}
// 显示
for (int i = 0; i <= LEVEL; ++i)
{
std::string number = std::to_string(i);
cv::imshow("G" + number, gauss[i]);
cv::imshow("L" + number, lap[i]);
}
cv::waitKey(0);
}