opencv主要使用BGR,与其他显示设备RGB不同需注意,是产生颜色不对的原因所在。
opencv Mat的type()值参照表
- | C1 | C2 | 1C3 | C4 | C(5) | C(6) | C(7) | C(8) |
---|---|---|---|---|---|---|---|---|
CV_8U | 0 | 8 | 16 | 24 | 32 | 40 | 48 | 56 |
CV_8S | 1 | 9 | 17 | 25 | 33 | 41 | 49 | 57 |
CV_16U | 2 | 10 | 18 | 26 | 34 | 42 | 50 | 58 |
CV_16S | 3 | 11 | 19 | 27 | 35 | 43 | 51 | 59 |
CV_32S | 4 | 12 | 20 | 28 | 36 | 44 | 52 | 60 |
CV_32F | 5 | 13 | 21 | 29 | 37 | 45 | 53 | 61 |
CV_64F | 6 | 14 | 22 | 30 | 38 | 46 | 54 | 62 |
HSV:H(Hue)表示色相,S(saturation)表示饱和度,V(value)明度
HSL:H(Hue)表示色相,S(saturation)表示饱和度,L(lightness)亮度
YUV:主要用于视频处理,数据源Y代表灰色图像,U、V代表颜色
YUV4:2:0 表示没4个Y里,要么有2个U,要么有2个V
#include
#include
#include
#include
#include
#include
#include
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;
}
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::polylines(line_img, triangle_array.toStlVector(), true, { 255,255,255 },5,16);
// 填充 fillPoly(img, 点集为std::vector类型的数组, 颜色, 线平滑度,按点集中的点成倍缩小)
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);
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
void flip(InputArray src, OutputArray dst, int flipCode);
//ROTATE_90_CLOCKWISE = 0, //!<顺时针旋转90度
//ROTATE_180 = 1, //!<顺时针旋转180度
//ROTATE_90_COUNTERCLOCKWISE = 2, //!<顺时针旋转270度
void rotate(InputArray src, OutputArray dst, int rotateCode);
// 顺时针旋转90度 (rotateCode = ROTATE_90_CLOCKWISE).
// 顺时针旋转180度 (rotateCode = ROTATE_180).
// 顺时针旋转270度 (rotateCode = ROTATE_90_COUNTERCLOCKWISE).
// 参数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(0, 0) = 1;
//imgtrans.at(0, 2) = 100; //水平平移量
//imgtrans.at(1, 1) = 1;
//imgtrans.at(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 srcPointArray = { {100,100},{300,100},{100,300} };
nc::NdArray 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);
需要四个坐标点,用法和三点旋转差不多
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卷积核):先向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);
卷积核固定为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方向的边缘
优点:不需要对横轴和纵轴分别计算,直接获取处理后的图片。
缺点:噪声敏感,需要手工降噪
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 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::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);