opencv入门基础(c++)
十三、图像上采样和降采样
13.1图像金字塔概念
- 我们在图像处理中常常会调整图像大小,最常见的就是放大(zoom in)和缩小(zoom out),尽管几何变换也可以实现图像放大和缩小,但是这里我们介绍图像金字塔
- 一个图像金字塔式一系列的图像组成,最底下一张是图像尺寸最大,最上方的图像尺寸最小,从空间上从上向下看就想一个古代的金字塔。
高斯金子塔 – 用来对图像进行降采样
拉普拉斯金字塔 – 用来重建一张图片根据它的上层降采样图片
图像金字塔概念 – 高斯金字塔(变大变小)
高斯金子塔是从底向上,逐层降采样得到。
降采样之后图像大小是原图像MxN的M/2 x N/2 ,就是对原图像删除偶数行与列,即得到降采样之后上一层的图片。
高斯金子塔的生成过程分为两步:
- 对当前层进行高斯模糊
- 删除当前层的偶数行与列
即可得到上一层的图像,这样上一层跟下一层相比,都只有它的1/4大小。
高斯不同(Difference of Gaussian-DOG)(变暗提取轮廓)
定义:就是把同一张图像在不同的参数下做高斯模糊之后的结果相减,得到的输出图像。称为高斯不同(DOG)
高斯不同是图像的内在特征,在灰度图像增强、角点检测中经常用到。
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc.hpp>
#include<iostream>
#include<math.h>
using namespace std;
using namespace cv;
Mat src1,dst;
char output_win[] = "output image";
int main(int argc, char** argv)
{
src1 = imread("C:/Users/Admin/Desktop/1.jpg"); //读取图片(使用图片的绝对路径)
if (src1.empty()) {
printf("could not find the image!\n");
return -1;
}
char input_win[] = "input image";
namedWindow(input_win, WINDOW_AUTOSIZE);
imshow(input_win,src1);
// DOG
Mat gray_src, g1, g2, dogImg;
cvtColor(src1, gray_src, COLOR_BGR2GRAY);
GaussianBlur(gray_src, g1, Size(5, 5), 0, 0);
GaussianBlur(g1, g2, Size(5, 5), 0, 0);
subtract(g1, g2, dogImg, Mat());
// 归一化显示
normalize(dogImg, dogImg, 255, 0, NORM_MINMAX);
imshow("DOG Image", dogImg);
//imshow(output_win, dst);
waitKey(0);//不加此语句图片会一闪而过
system("path");
getchar();
return 0;
}
采样相关API
上采样(cv::pyrUp) – zoom in 放大
降采样 (cv::pyrDown) – zoom out 缩小
pyrUp(Mat src, Mat dst, Size(src.cols2, src.rows2))
生成的图像是原图在宽与高各放大两倍
pyrDown(Mat src, Mat dst, Size(src.cols/2, src.rows/2))
生成的图像是原图在宽与高各缩小1/2
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc.hpp>
#include<iostream>
#include<math.h>
using namespace std;
using namespace cv;
Mat src1,dst;
char output_win[] = "output image";
int main(int argc, char** argv)
{
src1 = imread("C:/Users/Admin/Desktop/1.jpg"); //读取图片(使用图片的绝对路径)
if (src1.empty()) {
printf("could not find the image!\n");
return -1;
}
// 上采样
pyrUp(src1, dst, Size(src1.cols * 2, src1.rows * 2));
// 降采样
Mat s_down;
pyrDown(src1, s_down, Size(src1.cols / 2, src1.rows / 2));
char input_win[] = "input image";
namedWindow(input_win, WINDOW_AUTOSIZE);
imshow(input_win,src1);
imshow(output_win, dst);
namedWindow("sample down", WINDOW_AUTOSIZE);
imshow("sample down", s_down);
//imshow(output_win, dst);
waitKey(0);//不加此语句图片会一闪而过
system("path");
getchar();
return 0;
}
十四、基本阈值操作
图像阈值(threshold)
阈值 是什么?简单点说是把图像分割的标尺,这个标尺是根据什么产生的,阈值产生算法?阈值类型。(Binary segmentation)
阈值类型
阈值二值化(threshold binary)
左下方的图表示图像像素点Src(x,y)值分布情况,蓝色水平线表示阈值
阈值反二值化(threshold binary Inverted)
左下方的图表示图像像素点Src(x,y)值分布情况,蓝色水平线表示阈值
截断 (truncate)
左下方的图表示图像像素点Src(x,y)值分布情况,蓝色水平线表示阈值
阈值取零 (threshold to zero)
阈值反取零 (threshold to zero inverted)
threshold函数的使用
https://blog.csdn.net/u012566751/article/details/77046445
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace cv;
Mat src, gray_src, dst;
int threshold_value = 127;
int threshold_max = 255;
int type_value = 2;
int type_max = 4;
const char* output_title = "binary image";
void Threshold_Demo(int, void*);
int main(int argc, char** argv) {
src = imread("C:/Users/Admin/Desktop/1.jpg");
if (!src.data) {
printf("could not load image...\n");
return -1;
}
namedWindow("input image", WINDOW_AUTOSIZE);
namedWindow(output_title, WINDOW_AUTOSIZE);
imshow("input image", src);
createTrackbar("Threshold Value:", output_title, &threshold_value, threshold_max, Threshold_Demo);
createTrackbar("Type Value:", output_title, &type_value, type_max, Threshold_Demo);
Threshold_Demo(0, 0);
waitKey(0);
return 0;
}
void Threshold_Demo(int, void*) {
cvtColor(src, gray_src, COLOR_BGR2GRAY);
threshold(src, dst, 0, 255, THRESH_TRIANGLE | type_value);
imshow(output_title, dst);
}
十四、自定义线性滤波
14.1卷积概念
卷积是图像处理中一个操作,是kernel在图像的每个像素上的操作。
Kernel本质上一个固定大小的矩阵数组,其中心点称为锚点(anchor point)
14.2卷积如何工作
把kernel放到像素数组之上,求锚点周围覆盖的像素乘积之和(包括锚点),用来替换锚点覆盖下像素点值称为卷积处理。数学表达如下:
Sum = 8x1+6x1+6x1+2x1+8x1+6x1+2x1+2x1+8x1
New pixel = sum / (m*n)
14.3常见算子
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace cv;
int main(int argc, char** argv) {
Mat src, dst;
int ksize = 0;
src = imread("C:/Users/Admin/Desktop/1.jpg");
if (!src.data) {
printf("could not load image...\n");
return -1;
}
char INPUT_WIN[] = "input image";
char OUTPUT_WIN[] = "Custom Blur Filter Result";
namedWindow(INPUT_WIN, WINDOW_AUTOSIZE);
namedWindow(OUTPUT_WIN, WINDOW_AUTOSIZE);
imshow(INPUT_WIN, src);
// Sobel X 方向
Mat kernel_x = (Mat_<int>(3, 3) << -1, 0, 1, -2,0,2,-1,0,1);
filter2D(src, dst, -1, kernel_x, Point(-1, -1), 0.0);
// Sobel Y 方向
// Mat yimg;
// Mat kernel_y = (Mat_<int>(3, 3) << -1, -2, -1, 0,0,0, 1,2,1);
// filter2D(src, yimg, -1, kernel_y, Point(-1, -1), 0.0);
// 拉普拉斯算子
//Mat kernel_y = (Mat_<int>(3, 3) << 0, -1, 0, -1, 4, -1, 0, -1, 0);
//filter2D(src, dst, -1, kernel_y, Point(-1, -1), 0.0);
imshow("OUTPUT_WIN", dst);
waitKey(0);
return 0;
}
14.4自定义卷积模糊
filter2D方法filter2D(
Mat src, //输入图像
Mat dst, // 模糊图像
int depth, // 图像深度32/8
Mat kernel, // 卷积核/模板
Point anchor, // 锚点位置
double delta // 计算出来的像素+delta
)
其中 kernel是可以自定义的卷积核
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace cv;
int main(int argc, char** argv) {
Mat src, dst;
int ksize = 0;
src = imread("D:/vcprojects/images/test1.png");
if (!src.data) {
printf("could not load image...\n");
return -1;
}
char INPUT_WIN[] = "input image";
char OUTPUT_WIN[] = "Custom Blur Filter Result";
namedWindow(INPUT_WIN, CV_WINDOW_AUTOSIZE);
namedWindow(OUTPUT_WIN, CV_WINDOW_AUTOSIZE);
imshow(INPUT_WIN, src);
int c = 0;
int index = 0;
while (true) {
c = waitKey(500);
if ((char)c == 27) {// ESC
break;
}
ksize = 5 + (index % 8) * 2;
Mat kernel = Mat::ones(Size(ksize, ksize), CV_32F) / (float)(ksize * ksize);
filter2D(src, dst, -1, kernel, Point(-1, -1));
index++;
imshow(OUTPUT_WIN, dst);
}
return 0;
}
十五、处理边缘
15.1卷积边界问题
图像卷积的时候边界像素,不能被卷积操作,原因在于边界像素没有完全跟kernel重叠,所以当3x3滤波时候有1个像素的边缘没有被处理,5x5滤波的时候有2个像素的边缘没有被处理。
15.2处理边缘
在卷积开始之前增加边缘像素,填充的像素值为0或者RGB黑色,比如3x3在
四周各填充1个像素的边缘,这样就确保图像的边缘被处理,在卷积处理之
后再去掉这些边缘。openCV中默认的处理方法是: BORDER_DEFAULT,此外
常用的还有如下几种:
- BORDER_CONSTANT – 填充边缘用指定像素值
- BORDER_REPLICATE – 填充边缘像素用已知的边缘像素值。
- BORDER_WRAP – 用另外一边的像素来补偿填充
15.3API说明 – 给图像添加边缘API
copyMakeBorder(
- Mat src, // 输入图像
- Mat dst, // 添加边缘图像
- int top, // 边缘长度,一般上下左右都取相同值,
- int bottom,
- int left,
- int right,
- int borderType // 边缘类型
- Scalar value
)
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc.hpp>
#include<iostream>
#include<math.h>
using namespace std;
using namespace cv;
Mat src,dst;
char input_win[] = "input image";
char output_win[] = "output image";
int main(int argc, char** argv)
{
src = imread("C:/Users/Admin/Desktop/1.jpg"); //读取图片(使用图片的绝对路径)
if (src.empty()) {
printf("could not find the image!\n");
return -1;
}
imshow(input_win, src);
int top = (int)(0.05*src.rows);
int bottom = (int)(0.05*src.rows);
int left = (int)(0.05*src.cols);
int right = (int)(0.05*src.cols);
RNG rng(12345);//用于构造随机值
int borderType = BORDER_DEFAULT;
int c = 0;
while (true) {
c = waitKey(500);
// ESC
if ((char)c == 27) {
break;
}
if ((char)c == 'r') {
borderType = BORDER_REPLICATE;
} else if((char)c == 'w') {
borderType = BORDER_WRAP;
} else if((char)c == 'c') {
borderType = BORDER_CONSTANT;
}
Scalar color = Scalar(rng.uniform(0, 255), rng.uniform(0, 255), rng.uniform(0, 255));
copyMakeBorder(src, dst, top, bottom, left, right, borderType, color);
imshow(output_win, dst);
}
/*namedWindow(input_win, WINDOW_AUTOSIZE);
);*/
waitKey(0);//不加此语句图片会一闪而过
system("path");
getchar();
return 0;
}
十六、Sobel算子
16.1卷积应用-图像边缘提取
边缘是什么 – 是像素值发生跃迁的地方,是图像的显著特征之一,在图像特征提取、对象检测、模式识别等方面都有重要的作用。
如何捕捉/提取边缘 – 对图像求它的一阶导数
delta = f(x) – f(x-1), delta越大,说明像素在X方向变化越大,边缘信号越强,
16.2Sobel算子
是离散微分算子(discrete differentiation operator),用来计算图像灰度的近似梯度
Soble算子功能集合高斯平滑和微分求导
又被称为一阶微分算子,求导算子,在水平和垂直两个方向上求导,得到图像X方法与Y方向梯度图像
求取导数的近似值,kernel=3时不是很准确,OpenCV使用改进版本Scharr函数,算子如下:
16.3API说明cv::Sobel
cv::Sobel (
InputArray Src // 输入图像
OutputArray dst// 输出图像,大小与输入图像一致
int depth // 输出图像深度.
Int dx. // X方向,几阶导数
int dy // Y方向,几阶导数.
int ksize, SOBEL算子kernel大小,必须是1、3、5、7、
double scale = 1
double delta = 0
int borderType = BORDER_DEFAULT
)
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc.hpp>
#include<iostream>
#include<math.h>
using namespace std;
using namespace cv;
Mat src, dst;
char input_win[] = "input image";
char output_win[] = "output image";
int main(int argc, char** argv)
{
src = imread("C:/Users/Admin/Desktop/1.jpg"); //读取图片(使用图片的绝对路径)
if (src.empty()) {
printf("could not find the image!\n");
return -1;
}
imshow(input_win, src);
/*第一步:高斯模糊*/
GaussianBlur(src, dst, Size(3, 3), 0, 0);
//imshow(output_win, dst);
/*第二步:转化为灰度图像*/
Mat gray_src;
cvtColor(dst, gray_src, COLOR_BGR2GRAY);
//imshow("gray image", gray_src);
/*第三步:求梯度x和y*/
Mat xgrad, ygrad, xygrad;
Sobel(gray_src, xgrad, CV_16S, 1, 0, 3);
Sobel(gray_src, ygrad, CV_16S, 0, 1, 3);
convertScaleAbs(xgrad, xgrad);//增强
convertScaleAbs(ygrad, ygrad);
addWeighted(xgrad, 0.5, ygrad, 0.5, 0, xygrad);
imshow("xgrad image", xgrad);
imshow("ygrad image", ygrad);
imshow("xygrad image", xygrad);
waitKey(0);//不加此语句图片会一闪而过
system("path");
getchar();
return 0;
}
16.4API说明cv::Scharr
cv::Scharr (
InputArray Src // 输入图像
OutputArray dst// 输出图像,大小与输入图像一致
int depth // 输出图像深度.
Int dx. // X方向,几阶导数
int dy // Y方向,几阶导数.
double scale = 1
double delta = 0
int borderType = BORDER_DEFAULT
)
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc.hpp>
#include<iostream>
#include<math.h>
using namespace std;
using namespace cv;
Mat src,dst;
char input_win[] = "input image";
char output_win[] = "output image";
int main(int argc, char** argv)
{
src = imread("C:/Users/Admin/Desktop/1.jpg"); //读取图片(使用图片的绝对路径)
if (src.empty()) {
printf("could not find the image!\n");
return -1;
}
//imshow(input_win, src);
/*第一步:高斯模糊*/
GaussianBlur(src, dst, Size(3, 3), 0, 0);
//imshow(output_win, dst);
/*第二步:转化为灰度图像*/
Mat gray_src;
cvtColor(dst, gray_src, COLOR_BGR2GRAY);
//imshow("gray image", gray_src);
/*第三步:求梯度x和y*/
Mat xgrad, ygrad;
Scharr(gray_src, xgrad, CV_16S, 1, 0);//比Sobel更多细节
Scharr(gray_src, ygrad, CV_16S, 0, 1);
convertScaleAbs(xgrad, xgrad);
convertScaleAbs(ygrad, ygrad);
imshow("xgrad image", xgrad);
imshow("ygrad image", ygrad);
//增强
Mat xygrad = Mat(xgrad.size(), xgrad.type());
printf("type : %d\n", xgrad.type());
int width = xgrad.cols;
int height = ygrad.rows;
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
int xg = xgrad.at<uchar>(row, col);
int yg = ygrad.at<uchar>(row, col);
int xy = xg + yg;
xygrad.at<uchar>(row, col) = saturate_cast<uchar>(xy);
}
}
imshow(output_win, xygrad);
waitKey(0);//不加此语句图片会一闪而过
system("path");
getchar();
return 0;
}
十七、Laplance算子
17.1Laplance算子
解释:在二阶导数的时候,最大变化处的值为零即边缘是零值。通过二阶
导数计算,依据此理论我们可以计算图像二阶导数,提取边缘。
17.2处理流程
高斯模糊 – 去噪声GaussianBlur()
转换为灰度图像cvtColor()
拉普拉斯 – 二阶导数计算Laplacian()
取绝对值convertScaleAbs()
显示结果
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc.hpp>
#include<iostream>
#include<math.h>
using namespace std;
using namespace cv;
Mat src,dst;
char input_win[] = "input image";
char output_win[] = "output image";
int main(int argc, char** argv)
{
src = imread("C:/Users/Admin/Desktop/1.jpg"); //读取图片(使用图片的绝对路径)
if (src.empty()) {
printf("could not find the image!\n");
return -1;
}
//imshow(input_win, src);
/*第一步:高斯模糊*/
GaussianBlur(src, dst, Size(3, 3), 0, 0);
//imshow(output_win, dst);
/*第二步:转化为灰度图像*/
Mat gray_src;
cvtColor(dst, gray_src, COLOR_BGR2GRAY);
//imshow("gray image", gray_src);
/*第三步:拉普拉斯 – 二阶导数计算Laplacian()*/
Mat edge_image;
Laplacian(gray_src, edge_image, CV_16S, 3);
convertScaleAbs(edge_image, edge_image);//增强
threshold(edge_image, edge_image, 0, 255, THRESH_OTSU | THRESH_BINARY);
imshow("ygrad image", edge_image);
waitKey(0);//不加此语句图片会一闪而过
system("path");
getchar();
return 0;
}
17.3API使用cv::Laplacian
Laplacian(
InputArray src,
OutputArray dst,
int depth, //深度CV_16S
int kisze, // 3
double scale = 1,
double delta =0.0,
int borderType = 4
)
十八、Canny边缘检测
18.1Canny算法介绍
Canny是边缘检测算法,在1986年提出的。
是一个很好的边缘检测器
很常用也很实用的图像处理方法
Canny算法介绍 – 五步 in cv::Canny
高斯模糊 - GaussianBlur
灰度转换 - cvtColor
计算梯度 – Sobel/Scharr
非最大信号抑制
高低阈值输出二值图像
Canny算法介绍 - 非最大信号抑制
Canny算法介绍-高低阈值输出二值图像
T1, T2为阈值,凡是高于T2的都保留,凡是小于T1都丢弃,从高于T2的像素出发,凡是大于T1而且相互连接的,都保留。最终得到一个输出二值图像。
推荐的高低阈值比值为 T2: T1 = 3:1/2:1其中T2为高阈值,T1为低阈值
18.2API – cv::Canny
Canny(
InputArray src, // 8-bit的输入图像
OutputArray edges,// 输出边缘图像, 一般都是二值图像,背景是黑色
double threshold1,// 低阈值,常取高阈值的1/2或者1/3
double threshold2,// 高阈值
int aptertureSize,// Soble算子的size,通常3x3,取值3
bool L2gradient // 选择 true表示是L2来归一化,否则用L1归一化
)
默认情况一般选择是L1,参数设置为false
#include <opencv2/opencv.hpp>
#include <iostream>
#include <math.h>
using namespace cv;
Mat src, gray_src, dst;
int t1_value = 50;
int max_value = 255;
const char* OUTPUT_TITLE = "Canny Result";
void Canny_Demo(int, void*);
int main(int argc, char** argv) {
src = imread("C:/Users/Admin/Desktop/1.jpg");
if (!src.data) {
printf("could not load image...\n");
return -1;
}
char INPUT_TITLE[] = "input image";
namedWindow(INPUT_TITLE,WINDOW_AUTOSIZE);
namedWindow(OUTPUT_TITLE,WINDOW_AUTOSIZE);
imshow(INPUT_TITLE, src);
cvtColor(src, gray_src, COLOR_BGR2GRAY);
createTrackbar("Threshold Value:", OUTPUT_TITLE, &t1_value, max_value, Canny_Demo);
Canny_Demo(0, 0);
waitKey(0);
return 0;
}
void Canny_Demo(int, void*) {
Mat edge_output;
blur(gray_src, gray_src, Size(3, 3), Point(-1, -1), BORDER_DEFAULT);
Canny(gray_src, edge_output, t1_value, t1_value * 2, 3, false);
//dst.create(src.size(), src.type());
//src.copyTo(dst, edge_output);
// (edge_output, edge_output);
imshow(OUTPUT_TITLE, ~edge_output);
}