图像既然是沿着空间分布的信号,说明它是一个二维信号。图像中沿着水平方向的任何一行可以看作是像素点随着x轴变化的信号。而沿着竖直方向的任何一列则可以看作是像素点随着y轴变化的信号。单独提取一行或者是一列数据出来,按照像素值的大小随x/y轴的变化画出来。这一刻我们豁然开朗,又看到了以往熟悉的信号波形。没错,就是这么简单嘞。
图像的频率 对应到的是图像细节的多少,比如城市的部分频率高于天空的部分。
图像的幅度 对应到的是图像中像素的大小,比如天空中太阳照亮的区域幅度高于其他区域。
一般来讲,很少遇到对图像信号的相位 做分析的情况。但当你是一个图像算法工程师的时候,这件事却会变成必要和基本的。相位是什么东西?对于一个正弦信号来讲,相位可以看作是第一个波峰or波谷相对于原点的距离。那么,对于图像来讲,可以对应的看作是左上角像素点的起点,也就是画面的开始位置。不同的开始位置对应的是不同的画面偏移量。这里举个例子说明相位对图片的影响。现在如果我们要对下图中的高楼拍摄一张照片,可以按照黑框取景,也可以按照红框取景。这两种取景方式在最终的照片表现的东西都是高楼,不过区别在于高楼在其中一个画面的中间,而在另一个画面的黄金分割点位置。两个取景方式的实质区别在于对画面相位的取舍不同而已。
很多图像处理事实上就是信号处理。比如
图像去噪 (Denoise),一般就是应用数字低通滤波器对图像进行滤波。
图像增强 (Detail Enhancement),一般就是应用数字高通滤波器得到图像的高频信号,并对高频信号进行增强 。
对比度增强 (Contrast Enhancement),一般就是参考画面的亮暗程度 (图像的幅度),并人为修改亮暗的一种处理。
相位的概念一般会在图像的缩放(Scaling) 中使用到。
基础
MAT数据类型
当你用
cpp
Mat srcimage=imread("1.jpg");
Mat goalimg=imread("C:/Users/WQuiet/Desktop/goal.png",CV_RGB2GRAY);
读入图片的时候,要操作这个图片,就需要知道mat这个类是什么结构,怎么操作图片像素。
右击CV_RGB2GRAY追踪源码可以看到各种类型转换的枚举【其中除了RGB之外还有个A 指透明度】
cpp
Mat A, C; // 只创建信息头部分
A = imread(argv[1], CV_LOAD_IMAGE_COLOR); // 这里为矩阵开辟内存
这个数据类型有俩结构:
矩阵头(包含矩阵尺寸,存储方法,存储地址等信息)
一个指向存储所有像素值的矩阵(根据所选存储方法的不同矩阵可以是不同的维数)的指针。
题外话
QImage 类型的读入方法是
QImage pool;
pool.load(":/new/prefix1/0.bmp");
所以Mat有两种复制方法
- 复制变量的时候只复制矩阵头,而不是每个点的像素值。他们的像素值是共享的,如果改变像素值,会一起变
cpp
Mat B(A); // 使用拷贝构造函数
Mat C = A; // 赋值运算符
//就借用一下
- 下面这样复制过去之后不会相互影响
cpp
Mat F = A.clone();
Mat G;
A.copyTo(G);
//拿来吧你,全都告诉我。我复制一份出来给自己
roi区域可以这么取
cpp
Mat D (A, Rect(10, 10, 100, 100) );
Mat E = A(Range:all(), Range(1,3));
输出图像到文件
bool imwrite( const String& filename, InputArray img,
const std::vector& params = std::vector());
图片名称要写上后缀,Mat类型变量,可调节图像质量
存储方式

很多情况下,因为内存足够大,可实现连续存储,因此,图像中的各行就能一行一行地连接起来,形成一个长行。连续存储有助于提升图像扫描速度,我们可以使用 isContinuous() 来去判断矩阵是否是连续存储的.
创建窗口
namedWindow("hello_window",WINDOW_NORMAL);
默认窗口不可调,宏可以把窗口改成可调的
严谨的话后续可调用
destroyAllWindows();
不过小程序没必要
遍历像素
LUT(img,lookUpTable,result);
上面这句就是把img里的像素按照lookUpTable数组把灰度值替换掉,然后输出result图片,都是Mat类型的
cpp
Mat lookUpTable(1, 256, CV_8U);
uchar* p = lookUpTable.data;
for( int i = 0; i < 256; ++i)
p[i] = i/50*50; //i代表着img的灰度值,p[i]存的数值是i将被替换的数值。我这里利用取整丢小数 做成了跨度50灰度的图片
这不就是我想达成的多阈值分割嘛。。哪需要嵌套地用Otsu分割。

OpenCV中查找表修改像素与LUT用法
LUT函数【遍历图片,改数值】
卷积
我知道图像卷积所做的数学计算过程 但是并不是很懂卷积之后的效果,我改了卷积核,但是效果跟我预想的偏差很大。
我学到的是很方便的建二维数组的方法,是mat类型的。。。
cpp
Mat kern = (Mat_<char>(3,3) << 0, -1, 0,
-1, 5, -1,
0, -1, 0);
还有显示矩阵的方法,说是只能显示二维数组
cout << "M = " << endl << " " << kern << endl << endl;
blur(img,dst,Size(11,11));//均值滤波
等效于:
dst = cv.filter2D(img, -1, np.ones((11, 11))/11**2)
cv.filter2D() 函数详解
计时
OpenCV提供了两个简便的可用于计时的函数 getTickCount() 和 getTickFrequency() 。
第一个函数返回你的CPU自某个事件(如启动电脑)以来走过的时钟周期数,
第二个函数返回你的CPU一秒钟所走的时钟周期数。这样,我们就能轻松地以秒为单位对某运算计时:
cpp
double t = (double)getTickCount();
// 做点什么 ...
t = ((double)getTickCount() - t)/getTickFrequency();
cout << "Times passed in seconds: " << t << endl;
淘宝16元安装opencv
然后按照这里的教程挑着学
打开摄像头
cpp
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
VideoCapture cap;
cap.open(0);//打开不意味着有图像
cap>>frame;//输出图像不意味着能看到
imshow("hello",frame);//开个窗口才能看到
图像的赋值与显示
Mat srcImage1 = imread("D:\data\1.bmp", 1);
读取现成的图片,存在srcImage1
Mat C(srcImage1);
复制srcImage1的图像的指针给C ,
Mat C=srcImage1;
我试了试这样,也能复制图像。
Mat D(C, Rect(20, 20, 200, 200));
从C里截取出一块图像
imshow("原始图", C);
显示图像
cout << "D=" << endl << "" << D << endl;
显示图像的RGB数据//实际打印出来的数据的顺序是BGR
Mat M(20, 200, CV_8UC3, Scalar(0, 255, 0));
这个是自己设定RGB,创造图像存到M里。//CV_8UC3的定义没去研究
cvtColor(C, M, COLOR_BGR2GRAY);
就算M在定义时是20*200的大小,我之后把大图像赋值给他也没问题
imwrite("save.jpg", srcImage);//把srcImage保存成jpg格式,第三个参数一般不用填
保存图像到代码工程文件夹的Project1文件夹里 OpenCV:imwrite函数保存图片
opencv的Mat类型有一个ptr方法。使用格式是
cpp
uchar *Threshold = medImage.ptr<uchar>(j);
//指针的类型 指针名 Mat结构体变量名.方法名 以后挨个取出时的类型大小 第几行
medImage.ptr(j)是一个指针,可以看作一个数组名,这个是第j行像素的第一个像素的地址
如果要找第3行第6个元素就是
image.ptr(2)[5]
也是
Threshold[5]
然而 在面向对象编程中常用迭代器
cpp
void colorReduce4(cv::Mat& image, cv::Mat& result, int div){
cv::Mat_<cv::Vec3b>::iterator it = image.begin<cv::Vec3b>();//注意尖括号中为迭代器返回值,必须在编译时确定。
cv::Mat_<cv::Vec3b>::iterator itend = image.end<cv::Vec3b>();
cv::Mat_<cv::Vec3b>::iterator itout = result.begin<cv::Vec3b>();
for(; it!=itend; ++it,++itout){
(*itout)[0] = (*it)[0]/div*div + div/2;
(*itout)[1] = (*it)[1]/div*div + div/2;
(*itout)[2] = (*it)[2]/div*div + div/2;
}
}
OpenCV Mat数据类型指针ptr的使用 这篇博文的举例有问题
【OpenCV】访问Mat图像中每个像素的值 这篇博文table[]的作用在文章后半段有解释
OpenCV中Mat数据结构 这个看着靠谱,就是可读性差了点
图像的简单变化
cvtColor(srcImage1, Q, COLOR_BGR2GRAY);
把srcImage1变成灰度图存进Q
但是这些点、直线要在彩色模式下画。
circle(dst1, Point(temp1.x, temp1.y), 2, Scalar(0, 0, 255), 2);
转成灰度图之后只能用Scalar(255, 0, 0)才能显示得出图案,Scalar的后两个参数没有用
circle(grayImage, point, 2, Scalar(255, 0, 0),-1);//point是Point-1指实心圆
/*注意
第二句里的point是一个结构体变量 是
cpp
Point point;//特征点,把找到的点画在图像里
point.x = temp1.x;//特征点在图像中横坐标
point.y = temp1.y;//特征点在图像中纵坐标
circle(medImage, point, 0.5, cv::Scalar(255, 0, 0),-1);
第一句里的是一个类?
这个Point(temp1.x, temp1.y) 和 point 等价,可以直接用,不必有前三句。
转到定义里是
cpp
template<typename _Tp> inline
Point_<_Tp>::Point_(_Tp _x, _Tp _y)
: x(_x), y(_y) {}
看不懂。为什么用的时候是Point而不是Point_ 是这个吗?
*/
画个矩形
cpp
Rect2i rect(_x, _y,_width, _height);//左上角定位
rectangle(dst, rect, Scalar(0, 255, 0));
1. 线性滤波
要对图像做线性滤波的话【让图像变模糊】,就要用卷积核做卷积

这两行代码就创建了矩阵k
cpp
kernel_size = 3 ;
kernel = Mat::ones( kernel_size, kernel_size, CV_32F )/ (float)(kernel_size*kernel_size);
将核设置好之后,使用函数 filter2D 就可以生成滤波器:
cpp
filter2D(src, dst, ddepth , kernel, anchor, delta, BORDER_DEFAULT );
其中各参数含义如下:
src: 源图像
dst: 目标图像
ddepth: dst 的深度。若为负值(如 -1 ),则表示其深度与源图像相等。
kernel: 用来遍历图像的核
anchor: 核的锚点的相对位置,其中心点默认为 (-1, -1) 。
delta: 在卷积过程中,该值会加到每个像素上。默认情况下,这个值为 0 。
BORDER_DEFAULT: 这里我们保持其默认值,更多细节将在其他教程中详解
高级一点的平滑滤波用的是双边滤波【但是算起来复杂又耗时】这个教程也贴参考文献链接
各种滤波
cpp
//grayImage是原始灰度图
medianBlur(grayImage, medImage, 5);//中值滤波
Mat gauImage;
GaussianBlur(grayImage, gauImage, Size(5, 5), 3, 3); //高斯滤波
Mat meanImage;
blur(grayImage, meanImage,Size(5,5),Point(-1,-1));//均值滤波

源图经过滤波之后会变模糊,师兄选择了不怎么模糊的中值滤波。我有点纳闷。如果说图像越清晰越好,为什么不用源图要滤波,要去除噪点就无可避免的会变模糊,但既然是为了去除噪点,那也就没必要纠结图像不清晰这个问题了,图像越模糊 噪点也越少【大概吧】,反正为下一步做的前处理,在这个阶段就挑选不那么模糊的图片进入下一步不好吧?

上面是canny图像边缘算法的结果。
2.Sobel 导数

我把x和y求导的图像结果输出看了看,内核3阶的时候一片灰,改成5就能看出轮廓了,改成7就很明显,但是它们合成之后的图像就一团糟了

这个是7阶的时候的,合成的就不放了,晚上会做恶梦。。。
另外程序里
cpp
addWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5, 0, grad);
这个是合成的语句,两个0.5是比重,我试了grad_x用三阶,占比0.9 grad_y用七阶,占比0.1 合成图像正常。
话说下面这个图像可能更能明白点那个梯度图的黑白的意思


拆台
Canny算法仍然是图像边缘检测算法中最经典、先进的算法之一。相比Sobel、Prewitt等算子,Canny算法更为优异
各种用过的函数
rectangle函数与Rect函数的用法
opencv人脸检测--detectMultiScale函数
视频接口相关
各种算法
【图像配准】基于灰度的模板匹配算法(一):模板匹配时的MAD、SAD、SSD、MSD、NCC、SSDA、SATD统计方法
Opencv学习笔记(九)光流法
光流Optical Flow介绍与OpenCV实现
calcOpticalFlowFarneback详解
Farneback 光流算法详解与 calcOpticalFlowFarneback 源码分析

