opencv从入门到。。。。

图像既然是沿着空间分布的信号,说明它是一个二维信号。图像中沿着水平方向的任何一行可以看作是像素点随着x轴变化的信号。而沿着竖直方向的任何一列则可以看作是像素点随着y轴变化的信号。单独提取一行或者是一列数据出来,按照像素值的大小随x/y轴的变化画出来。这一刻我们豁然开朗,又看到了以往熟悉的信号波形。没错,就是这么简单嘞。

图像的频率 对应到的是图像细节的多少,比如城市的部分频率高于天空的部分。

图像的幅度 对应到的是图像中像素的大小,比如天空中太阳照亮的区域幅度高于其他区域。

一般来讲,很少遇到对图像信号的相位 做分析的情况。但当你是一个图像算法工程师的时候,这件事却会变成必要和基本的。相位是什么东西?对于一个正弦信号来讲,相位可以看作是第一个波峰or波谷相对于原点的距离。那么,对于图像来讲,可以对应的看作是左上角像素点的起点,也就是画面的开始位置。不同的开始位置对应的是不同的画面偏移量。这里举个例子说明相位对图片的影响。现在如果我们要对下图中的高楼拍摄一张照片,可以按照黑框取景,也可以按照红框取景。这两种取景方式在最终的照片表现的东西都是高楼,不过区别在于高楼在其中一个画面的中间,而在另一个画面的黄金分割点位置。两个取景方式的实质区别在于对画面相位的取舍不同而已。

很多图像处理事实上就是信号处理。比如

图像去噪 (Denoise),一般就是应用数字低通滤波器对图像进行滤波。

图像增强 (Detail Enhancement),一般就是应用数字高通滤波器得到图像的高频信号,并对高频信号进行增强

对比度增强 (Contrast Enhancement),一般就是参考画面的亮暗程度 (图像的幅度),并人为修改亮暗的一种处理。

相位的概念一般会在图像的缩放(Scaling) 中使用到。

图像与信号处理之二:从信号的角度理解图像内容

基础

opencv中用RNG产生随机数

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有两种复制方法

  1. 复制变量的时候只复制矩阵头,而不是每个点的像素值。他们的像素值是共享的,如果改变像素值,会一起变
cpp 复制代码
Mat B(A);   // 使用拷贝构造函数
Mat C = A;      // 赋值运算符
//就借用一下
  1. 下面这样复制过去之后不会相互影响
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

OpenCV在图像中添加文字,画点,画直线

但是这些点、直线要在彩色模式下画。

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 源码分析

相关推荐
lizhihai_991 小时前
股市学习心得-量比的作用
大数据·人工智能·学习
楚灵魈1 小时前
[SKILL]从零开始的Arch Linux安装工作流程
linux·人工智能
薛定猫AI1 小时前
【深度解析】Qwen 3.6 vs Gemma 4:本地大模型时代,如何选对“日常开发模型”
人工智能·状态模式
陈天伟教授1 小时前
人生的力量来源何处?
人工智能·学习
闵孚龙1 小时前
Claude Code 缓存优化模式全解析:AI Agent 上下文工程、Prompt Cache、工具 Schema 缓存、Token 成本优化
人工智能·缓存·prompt
AI周红伟3 小时前
All in Token, 移动,电信,联通,阿里,百度,华为,字节,Token石油战争,Token经济,百度要“重写”AI价值度量
大数据·人工智能·机器学习·百度·copilot·openclaw
AI周红伟3 小时前
Token经济学:AI时代的新货币战争,All in Token, 新时代的石油战争,华为,阿里,百度,字节的石油战争
大数据·人工智能·机器学习·百度·华为·copilot·openclaw
XM_jhxx7 小时前
±0.03mm的精度怎么保证?翌东塑胶用AI赋能质量管控升级
人工智能
阿正的梦工坊7 小时前
深入理解 PyTorch 中的 unsqueeze 操作
人工智能·pytorch·python