C++ Opencv 图片运算和处理

色彩空间

RGB 和 BGR

opencv主要使用BGR,与其他显示设备RGB不同需注意,是产生颜色不对的原因所在。
opencv Mat的type()值参照表

-C1C21C3C4C(5)C(6)C(7)C(8)
CV_8U08162432404856
CV_8S19172533414957
CV_16U210182634425058
CV_16S311192735435159
CV_32S412202836445260
CV_32F513212937455361
CV_64F614223038465462

HSV 和 HSL

HSV:H(Hue)表示色相,S(saturation)表示饱和度,V(value)明度
在这里插入图片描述
HSL:H(Hue)表示色相,S(saturation)表示饱和度,L(lightness)亮度
在这里插入图片描述

YUV

YUV:主要用于视频处理,数据源Y代表灰色图像,U、V代表颜色

YUV4:2:0 表示没4个Y里,要么有2个U,要么有2个V
在这里插入图片描述

划线

#include <iostream>
#include <vector>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/video.hpp>
#include <NumCpp.hpp>

cv::Mat array2Img(nc::NdArray<uchar> array);

void main(){
	nc::NdArray<uchar> img_array = nc::NdArray<uchar>(40, 60);
   img_array.zeros();
    cv::Mat line_img;
    int count = 0;
    while (count < 30) {
        img_array(count ,9) = 255;      // blue
        img_array(count, 10) = 255;     // green
        img_array(count, 11) = 255;     // red
        count += 1;
    }
    for (size_t i = 0; i < img_array.numRows(); i++)
    {
        std::cout << "第" << i << ": ";
        for (size_t j = 0; j < img_array.numCols(); j++)
        {
            std::cout << (size_t)img_array(i, j);
        }
        std::cout << "\n";
    }
    line_img = array2Img(img_array);
    cv::namedWindow("line_img", cv::WINDOW_NORMAL);
    cv::imshow("line_img",line_img);
    cv::waitKey(0);
 }
 //数组转图片
cv::Mat array2Img(nc::NdArray<uchar> array) {
    
    size_t h = array.numRows();
    size_t w = array.numCols();

    cv::Mat img(h,(size_t)(w/3), CV_8UC3);     //保存为RGB
    for (size_t i = 0; i < h; i++)
    {
        uchar* tmp = img.ptr<uchar>(i);
        for (size_t j = 0; j < w; j++)
        {
            tmp[j] = array(i, j);
        }
    }
    return img;
}

在这里插入图片描述

3通道分割和合并

方法一

	std::vector<cv::Mat> channelsSplistArray, channelsMergeArray;
    cv::Mat buleChannel, redChannel, greenChannel,newAddchannel,imgMerge;
    //分割, 相应通道颜色的会转为灰色
    cv::split(line_img, channelsSplistArray);    // &
    buleChannel = channelsSplistArray.at(0);     // 0   255 255
    greenChannel = channelsSplistArray.at(1);    // 255 0   255 
    redChannel = channelsSplistArray.at(2);      // 255 255 0
    cv::imshow("buleChannel", buleChannel);
    cv::imshow("greenChannel", greenChannel);
    cv::imshow("redChannel", redChannel);
    // 合并三通道 设置blue为0
    newAddchannel = channelsSplistArray.at(0).clone();
    newAddchannel.setTo(0);
    channelsMergeArray.push_back(newAddchannel);
    channelsMergeArray.push_back(greenChannel);
    channelsMergeArray.push_back(redChannel);
    cv::merge(channelsMergeArray, imgMerge);
    // 当通道设置为0 : 0为blue 1为green 2为red
    /*channelsSplistArray[0] = cv::Scalar(0);      
    cv::merge(channelsSplistArray, imgMerge);*/
    cv::imshow("imgMerge", imgMerge);

方法二


    cv::Mat newAddchannel, imgMerge, imgChannelSplitArray[3];
    // 分割
    cv::split(line_img, imgChannelSplitArray);
    cv::imshow("buleChannel", imgChannelSplitArray[0]);
    cv::imshow("greenChannel", imgChannelSplitArray[1]);
    cv::imshow("redChannel", imgChannelSplitArray[2]);
    // 合并
    newAddchannel = imgChannelSplitArray[0].clone();
    newAddchannel.setTo(0);
    cv::Mat imgChannelMergeArray[3] = { newAddchannel,imgChannelSplitArray[1],imgChannelSplitArray[2]};
    cv::merge(imgChannelMergeArray, 3, imgMerge);
    cv::imshow("imgMerge", imgMerge);

画图

线

//画线 line(img,起点, 终点, 线颜色, 线粗(-1为实心), 线平滑度, 起始点和终点成倍缩小)
    cv::line(line_img, { 10,20 }, { 500,500 }, { 0,255,255 },1,16);

// 画圆 circle(img, 圆点, 半径, 线颜色, 线粗(-1为填充), 线平滑度, 按圆心和半径成倍缩小)
cv::circle(line_img, { 30,30 }, 5, { 255,255,0 }, 1, 16,0);

椭圆

// 画椭圆 ellipse(img, 圆点, 椭圆的长宽的一半, 呈现的角度, 画椭圆的起点, 终点, 线颜色, 线粗(-1为填充), 线平滑度, 按圆心和椭圆成倍缩小)
cv::ellipse(line_img, { 200,200 }, { 40,20 }, 0.0, 0.0, 360.0, { 0,255,255 }, 2, 16);

多边形

// 画多边形
    nc::NdArray<cv::Point> triangle_array = { {200,200},{250,200},{250,250} };      // 点集获取方式1
    std::vector<cv::Point> triangle_array2({ {200,200},{250,200},{250,250} });   // 点集获取方式2
    // 画边 polylines(img, 点集为std::vector<cv::Point>类型的数组, 是否连接点集中的点, 颜色, 线粗, 线平滑度,按点集中的点成倍缩小)
    cv::polylines(line_img, triangle_array.toStlVector(), true, { 255,255,255 },5,16);
    // 填充 fillPoly(img, 点集为std::vector<cv::Point>类型的数组, 颜色, 线平滑度,按点集中的点成倍缩小)
    cv::fillPoly(line_img, triangle_array2, { 255,0,255 });

文本

	// 画文本  putText(img, 文字内容, 坐标点, 文字类型, 文字大小, 文字颜色, 线粗, 线平滑度, 是否倒转)
	cv::putText(line_img, "Hello world!", cv::Point(10, 400), cv::FONT_HERSHEY_DUPLEX, 1.0, { 255,255,255 }, 3, 16, true);
    cv::putText(line_img, "Hello world!", cv::Point(10, 450), cv::FONT_HERSHEY_DUPLEX, 1.0, { 255,255,255 }, 3, 16);

在这里插入图片描述

图片运算

加减乘除

	cv::Mat cartoon = cv::imread("C:\\Users\\10358\\Pictures\\img\\3.jpg");
	// std::cout << cartoon.size();		// 打印图片大小
	// cv::Mat ones_img = cv::Mat::ones(cv::Size(394, 700), CV_8UC3) * 20;		// cv自带的ones类只能将blue通道赋值1
	nc::NdArray<uchar> img_array = nc::NdArray<uchar>(700, 394*3).ones();
	cv::Mat test_img= array2Img(img_array)*20;
	cv::Mat add_img, sub_img, mul_img, div_img;
	cv::add(test_img, cartoon, add_img);
	cv::subtract(cartoon, test_img, sub_img);
	cv::multiply(cartoon, test_img, mul_img);
	cv::divide( cartoon, test_img, div_img);

	cv::imshow("orig", cartoon);
	cv::imshow("add_img", add_img);
	cv::imshow("sub_img", sub_img);
	cv::imshow("mul_img", mul_img);
	cv::imshow("div_img", div_img);
	cv::waitKey(0);

非、与、或、异或

cv::Mat cartoon = cv::imread("C:\\Users\\10358\\Pictures\\img\\3.jpg");
	// std::cout << cartoon.size();		// 打印图片大小
	// cv::Mat ones_img = cv::Mat::ones(cv::Size(394, 700), CV_8UC3) * 20;		// cv自带的ones类只能将blue通道赋值1
	nc::NdArray<uchar> img_array = nc::NdArray<uchar>(700, 394*3).ones();
	cv::Mat test_img= array2Img(img_array)*20;
	cv::Weight_img,not_img,and_img,or_img,xor_img;
	cv::addWeighted(add_img, 0.8, mul_img, 0.2, 0, Weight_img);		// 图片的权重融合
	cv::bitwise_not(cartoon, not_img);								// 图片非运算
	cv::bitwise_and(test_img, cartoon,and_img);						// 图片与运算:只有1&&1为真,其余为假(0&&1,0&&0)
	cv::bitwise_or(test_img, cartoon, or_img);						// 图片或运算:只有0||0为假,其余为真(0||1,1||1)
	cv::bitwise_xor(test_img, cartoon, xor_img);					// 图片异或运算:相同为假,不同为真

	cv::imshow("Weight_img", Weight_img);
	cv::imshow("not_img", not_img);
	cv::imshow("and_img", and_img);
	cv::imshow("or_img", or_img);
	cv::imshow("xor_img", xor_img);

	cv::waitKey(0);

在这里插入图片描述
在这里插入图片描述

图像更改

缩小放大 cv::resize

void resize( InputArray src, OutputArray dst, Size dsize, double fx = 0, double fy = 0,int interpolation = INTER_LINEAR );
	/** 最近邻插值 */
    INTER_NEAREST        = 0,
    /** 双线性插值 */
    INTER_LINEAR         = 1,
    /** 双三次插值 */
    INTER_CUBIC          = 2,
    /** 利用像素面积关系重采样。它可能是图像抽取的首选方法,如它提供无云纹的结果。但是当图像被放大时,它类似于INTER_NEAREST方法 */
    INTER_AREA           = 3,
    /** 8x8邻域上的Lanczos插值 */
    INTER_LANCZOS4       = 4,
    /** 位精确双线性插值 */
    INTER_LINEAR_EXACT = 5,
    /** 位精确最近邻插值。这将产生相同的结果在PIL, scikit-image或Matlab中的最近邻方法 */
    INTER_NEAREST_EXACT  = 6,
    /** 插值码掩码 */
    INTER_MAX            = 7,
    /** 标志,填充所有目标图像像素。如果它们中的一些对应于异常值源图像,它们被设为零 */
    WARP_FILL_OUTLIERS   = 8,
    /** 标志, 逆变换

    For example, #linearPolar or #logPolar transforms:
    - flag is __not__ set: \f$dst( \rho , \phi ) = src(x,y)\f$
    - flag is set: \f$dst(x,y) = src( \rho , \phi )\f$
    */
    WARP_INVERSE_MAP     = 16

翻转 cv::flip

void flip(InputArray src, OutputArray dst, int flipCode);
	//ROTATE_90_CLOCKWISE = 0, //!<顺时针旋转90度
    //ROTATE_180 = 1, //!<顺时针旋转180度
    //ROTATE_90_COUNTERCLOCKWISE = 2, //!<顺时针旋转270度

旋转 cv::rotate

	void rotate(InputArray src, OutputArray dst, int rotateCode);
	// 顺时针旋转90度 (rotateCode = ROTATE_90_CLOCKWISE).
	// 顺时针旋转180度 (rotateCode = ROTATE_180).
	// 顺时针旋转270度 (rotateCode = ROTATE_90_COUNTERCLOCKWISE).

仿射变换 cv::warpAffine

// 参数2x3的M为变换矩阵{{1,0,x移动的量},{1,0,y移动的量}}
void warpAffine( InputArray src, OutputArray dst,
                 InputArray M, Size dsize,
                 int flags = INTER_LINEAR,
                 int borderMode = BORDER_CONSTANT,
                 const Scalar& borderValue = Scalar());

平移

	// 方式1 使用Numpcc定义变换矩阵在转换成Mat变换矩阵
	nc::NdArray<float> transMat = { {1,0,100},{0,1,100}};
	cv::Mat imgtrans = cv::Mat(transMat.toStlVector(), CV_32FC1).reshape(0, 2);		// reshape(通道数, 行数)
	// 方式2 使用cv::Mat直接定义变换矩阵
	//cv::Mat imgtrans = cv::Mat::zeros(2, 3, CV_32FC1);
	//imgtrans.at<float>(0, 0) = 1;
	//imgtrans.at<float>(0, 2) = 100; //水平平移量
	//imgtrans.at<float>(1, 1) = 1;
	//imgtrans.at<float>(1, 2) = 100; //竖直平移量
	cv::warpAffine(cartoon, cartoon, imgtrans, cv::Size(cartoon.cols,cartoon.rows));
	cv::imshow("orig", cartoon);
	cv::waitKey(0);

在这里插入图片描述

旋转

中心点旋转

	// getRotationMatrix2D(中心点,角度,缩放比例)
	cv::Mat imgtrans = cv::getRotationMatrix2D(cv::Point(cartoon.cols / 2, cartoon.rows / 2), 20, 0.7);
	cv::warpAffine(cartoon, cartoon, imgtrans, cv::Size(cartoon.cols, cartoon.rows));
	cv::imshow("orig", cartoon);
	cv::waitKey(0);

在这里插入图片描述

三点旋转

在这里插入图片描述

	// 方式1 使用Numpcc定义点集数组,再转换成Mat点集,再通过cv::getAffineTransform转换成变换矩阵
	/*nc::NdArray<float> srcPointArray = { {100,100},{300,100},{100,300} };
	nc::NdArray<float> dstPointArray = { {100,100},{300,300},{100,300} };
	cv::Mat stcPointSet = cv::Mat(srcPointArray.toStlVector(), CV_32FC1).reshape(0, 3);		// reshape(通道数, 行数)
	cv::Mat dstPointSet = cv::Mat(dstPointArray.toStlVector(), CV_32FC1).reshape(0, 3);*/
	// 方式2 使用cv::Point2f直接定义点集,再通过cv::getAffineTransform转换成变换矩阵
	cv::Point2f stcPointSet[] = { {100,100},{300,100},{100,300} };
	cv::Point2f dstPointSet[] = { {100,100},{300,300},{100,300} };
	cv::Mat imgtrans = cv::getAffineTransform(stcPointSet, dstPointSet);
	cv::warpAffine(cartoon, cartoon, imgtrans, cv::Size(cartoon.cols, cartoon.rows));
	cv::imshow("orig", cartoon);
	cv::waitKey(0);

在这里插入图片描述

透视变换 cv::getPerspectiveTransform

需要四个坐标点,用法和三点旋转差不多

Mat getPerspectiveTransform(InputArray src, InputArray dst, int solveMethod = DECOMP_LU);

图像卷积

卷积核:卷积核一般为奇数,如3x3,5x5,7x7等;一方面是增加padding的原因,另一方面是保证锚点在中间,防止位置发生偏移的原因。在深度学习中,卷积核越大看到的信息(感受野)越多提取的特征越好同时计算量也就越大。
锚:主要作用是防止信息偏差
边界扩充(padding):当卷积核大于1且不进行边界扩充,输出尺寸将相应缩小。当卷积核以标准方式进行边界扩充,则输出数据的空间尺寸将与输入相等。
在这里插入图片描述

滤波:对目标图像的噪声进行抑制
输出图像大小的计算公式:

dst = (src-kernel+2P)/S+1
src:输入图像的大小
dst :输出图像的大小
kernel:卷积核的大小
P:扩充边界的数量
S:步长
例子:输入一个5x5的图像,卷积核为3x3,没有扩充边界,步长为2。 (5-3+2*0)/ 2 + 1 = 2 ,所以输出图像大小为2x2

滤波(卷积核)

低通滤波

低通滤波可以去除噪声或平滑图像

方盒滤波和均值滤波

方盒滤波和均值滤波得到的效果相同,其原理是将卷积核均化,即卷积核除以(卷积核的长x宽)得到一个新的卷积核

//方盒滤波
void boxFilter( InputArray src, OutputArray dst, int ddepth,
                             Size ksize, Point anchor = Point(-1,-1),
                             bool normalize = true,
                             int borderType = BORDER_DEFAULT );
//均值滤波
void blur( InputArray src, OutputArray dst,
                        Size ksize, Point anchor = Point(-1,-1),
                        int borderType = BORDER_DEFAULT );
	cv::Mat dst_boxFilter, dst_blur;
	// 自定义卷积核
	//cv::filter2D(cartoon, dst, -1, kernel);
	// 方盒滤波
	cv::boxFilter(cartoon, dst_boxFilter, -1, cv::Size(5, 5));
	// 均值滤波
	cv::blur(cartoon, dst_blur, cv::Size(5, 5));

	cv::imshow("dst_boxFilter", dst_boxFilter);
	cv::imshow("dst_blur", dst_blur);
	cv::imshow("cartoon", cartoon);

	cv::waitKey(0);

在这里插入图片描述

高斯滤波

原理是越往卷积核中心的比重越大
在这里插入图片描述

GaussianBlur( InputArray src, OutputArray dst, Size ksize,
                                double sigmaX, double sigmaY = 0,
                                int borderType = BORDER_DEFAULT );
cv::Mat dst_GaussianBlur_x3, dst_GaussianBlur_x1, dst_GaussianBlur_x01;
	// 高斯滤波 GaussianBlur(原图,输出图,核大小,x方向的标准差,y方向的标准差)
	cv::GaussianBlur(cartoon, dst_GaussianBlur_x3, cv::Size(5, 5), 3);
	cv::GaussianBlur(cartoon, dst_GaussianBlur_x01, cv::Size(5, 5), 0.1);
	cv::GaussianBlur(cartoon, dst_GaussianBlur_x1, cv::Size(5, 5), 1);
	
	cv::imshow("cartoon", cartoon);
	cv::imshow("dst_GaussianBlur_x3", dst_GaussianBlur_x3);
	cv::imshow("dst_GaussianBlur_x01", dst_GaussianBlur_x01);
	cv::imshow("dst_GaussianBlur_x1", dst_GaussianBlur_x1);
	cv::waitKey(0);

在这里插入图片描述

中值滤波

原理是将核排序后取中值作为核的新元素
优点:对胡椒噪效果明显

void medianBlur( InputArray src, OutputArray dst, int ksize );
cv::Mat dst_medianBlur
	// 中值滤波
	cv::medianBlur(cartoon, dst_medianBlur, 15);
	cv::imshow("cartoon", cartoon);
	cv::imshow("dst_medianBlur", dst_medianBlur);
	cv::waitKey(0);

在这里插入图片描述

双边滤波

原理:
在这里插入图片描述

优点:可以保留边缘,同时对边缘内的图像进行平滑处理。如:美颜


CV_EXPORTS_W void bilateralFilter( InputArray src, OutputArray dst, int d,
                                   double sigmaColor, double sigmaSpace,
                                   int borderType = BORDER_DEFAULT );
/*	@param src 源8位或浮点,1通道或3通道图像.
	@param dst 目标图像的大小和类型与src相同.
	@param d 在滤波过程中使用的每个像素邻域的直径。如果它是非正的,它是从sigmaSpace计算的。
	@param sigmaColor 在颜色空间中过滤。该参数的较大值意味着更远的颜色与像素附近的(参见sigmasspace)将混合在一起,形成大面积的半对等色。
	@param sigmaSpace 在坐标空间中过滤。该参数的较大值意味着更远的像素会相互影响,只要它们的颜色足够接近(参见igmaColor)
)。当d>0时,它指定邻域大小,而不考虑sigmasspace。否则,d是
与sigmaSpace成正比。
	@param borderType 用于推断图像外部像素的边界模式
 */
	// 双边滤波
	cv::bilateralFilter(person, dst_bilateralFilter, -1, 50, 40);
	cv::imshow("person", person);
	cv::imshow("dst_bilateralFilter", dst_bilateralFilter);
	cv::waitKey(0);

在这里插入图片描述

高通滤波

高通滤波可以帮助查找图像的边缘

Sobel

原理:在内部通过高斯滤波对噪音进行过滤,再通过一阶导求得图像边缘。
优点:抗噪声强
缺点:需要对横轴和纵轴分别计算,再合并结果
Sobel算子(Sobel卷积核):先向X方向求导,再向y方向求导,将两个结果相加

void Sobel( InputArray src, OutputArray dst, int ddepth,
                         int dx, int dy, int ksize = 3,
                         double scale = 1, double delta = 0,
                         int borderType = BORDER_DEFAULT );
/*@param ddepth 输出图像深度,见@ref filter_depth " composites ";在8位输入图像的情况下,它将导致截断导数。
@param dx x的导数的阶。
@param dy y的导数的阶。
@param ksize 扩展Sobel核的大小;它一定是1 3 5 7, 设为-1时变成Scharr算子
@param scale 计算出的导数值的可选比例因子;默认情况下,不应用缩放(详见# getderikernels)。
@param delta 可选的增量值,在将结果存储到dst之前添加到结果中。
@param borderType 像素外推方法,参见#BorderTypes。#BORDER_WRAP不支持。@sa  Scharr, Laplacian, sepFilter2D, filter2D, GaussianBlur, cartToPolar
*/
	// sobel算子
	cv::Mat dst_sobel_x, dst_sobel_y, dst_sobel;
	cv::Sobel(cartoon, dst_sobel_y, -1, 1, 0);		// 获取y方向的边缘
	cv::Sobel(cartoon, dst_sobel_x, -1, 0, 1);		// 获取x方向的边缘
	cv::add(dst_sobel_x, dst_sobel_y, dst_sobel);
	
	cv::imshow("cartoon", cartoon);
	cv::imshow("dst_sobel", dst_sobel);
	cv::waitKey(0);

在这里插入图片描述

Scharr

卷积核固定为3x3
优点:线条清晰
缺点:需要对横轴和纵轴分别计算,再合并结果。

	// 将Sobel算子的核大小设为-1时,就形成了scharr算子
	cv::Sobel(cartoon, dst_sobel_y, -1, 1, 0,-1);		// 获取y方向的边缘
	cv::Sobel(cartoon, dst_sobel_x, -1, 0, 1,-1);		// 获取x方向的边缘

在这里插入图片描述

Laplacian

优点:不需要对横轴和纵轴分别计算,直接获取处理后的图片。
缺点:噪声敏感,需要手工降噪

void Laplacian( InputArray src, OutputArray dst, int ddepth,
                             int ksize = 1, double scale = 1, double delta = 0,
                             int borderType = BORDER_DEFAULT );
	// Laplacian 算子
	cv::Mat dst_Laplacian;
	cv::Laplacian(cartoon, dst_Laplacian, -1, 3);

在这里插入图片描述

快捷小技巧

指定位置更改颜色 cv::Mat().setTo

	cv::Mat background = cv::Mat(cv::Size(200, 200), CV_8UC3);
	background.setTo(cv::Scalar(255,255,255));
	background({ 10,110 }, { 20,130 }).setTo(cv::Scalar(0, 255, 255));
	background({ 80,190 }, { 70,180 }).setTo(cv::Scalar(0, 255, 255));
	cv::imshow("background", background);
	cv::waitKey(0);

在这里插入图片描述

指定位置加入图片 cv::Mat().copyTo

	cv::resize(cartoon_gry, logo_img, { 65,150 });	//重新设置图片大小
	// 1.ROI方法
	//cv::Rect temp = cv::Rect(10, 20,   logo_img.size().width, logo_img.size().height);
	//logo_img.copyTo(cartoon_gry(temp));
	//2.直接指定具体范围
	logo_img.copyTo(cartoon_gry({ 10,10+ logo_img.size().height }, { 20,20+ logo_img.size().width }));
	cv::imshow("cartoon_gry", cartoon_gry);
	cv::waitKey(0);

在这里插入图片描述

  • 0
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值