IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 人工智能 -> 2021-07-10暑期专题培训(opencv1-6) -> 正文阅读

[人工智能]2021-07-10暑期专题培训(opencv1-6)

opencv 学习

第一章:

  1. 载入图片,保存,旋转。

第二章:

2.1目标:
一,学会如何遍历一张图像并处理其像素
二,高效的处理方法
灰度图:像素由8位无符号数来表示;
彩色图:由三个这样的8位无符号数来表示三个颜色通道,0黑色;255白色;
opencv允许创建不同像素类型的矩阵或图像:如整形(CV_8U),浮点型(CV_32F)。
它们在一些图像处理过程中,用来保存中间值这样的内容很有用。大多数矩阵可以用于任意类型的矩阵,但有些运算对数据类型或矩阵的通道数有要求。

2.2.存取像素值:高效的遍历数组。椒盐噪点,即随机设置为黑白,黑白1通道,彩色3通道。Mat类,at方法。
需要在代码中指定元素所在的行(row)和列(col),函数会返回相应的元素。单通道返回单个数值;多通道返回一组向量(Vector)。

//2021.6.27 图像椒盐,加噪点。
#include<opencv2/opencv.hpp>
#include<iostream>

using namespace cv;
void salt(Mat& image, int n) {
//第一个参数是一张输入图像,使用传引用的参数传递方式(值,指针,传引用三种方式);第二个参数是要该改变的噪点个数
	for (int k = 0; k < n; k++)
	{
		int i = rand() % image.cols;//随机获取行和列        //col,row;是了类cv::Mat的公有成员变量,能给出图像的宽高;
		int j = rand() % image.rows;
		if (image.channels() == 1) {//灰度为1
			image.at<uchar>(j, i) = 255;//255白色,0为黑色   //成员函数at(int y,int x)用来存取图像的元素;但是必须要知道图像的数据类型。cv::Mat可以存放任意类型的元素。
		}
		else if (image.channels() == 3)//彩色为3
		{
			image.at<Vec3b>(j, i)[0] = 255;               //分清类型
			image.at<Vec3b>(j, i)[1] = 255;               // 确保指定的数据类型和矩阵的数据类型
			image.at<Vec3b>(j, i)[2] = 255;               //相符,at方法本身不会经行数据类型转换
		}
	}
}

int main() {
	Mat src;
	src = imread("D:/opencvcode/img/13.png");
	if (!src.data) {
		printf("not img!");
		return -1;
	}
	salt(src, 3000);
	namedWindow("inputimg");
	imshow("inputimg", src);
	waitKey(0);
	return 0;
}

拓展阅读:cv::Mat_是cv::Mat的一个模板子类。

2.3 用指针遍历:rows,cows。
将要遍历图像的所有像素时,像素个数很多,所以高效的遍历很重要。以下两种方法,第一种是指针算术;
每个像素的每个通道,将其值除以N,整除。再乘以N。

#include<opencv2/opencv.hpp>
#include<iostream>

using namespace cv;
//颜色缩减的其中一种方法,(函数)
void colorreduce(Mat& image, int div = 64) {
	int n1 = image.rows;
	int nc = image.cols * image.channels();        //获取每行像素值
	for (int j = 0; j < n1; j++) {
		uchar* data = image.ptr<uchar>(j);//ptr函数得到图像任意首地址,ptr返回第J行的首地址
		for (int i = 0; i < nc; i++) {
			data[i] = data[i] / div * div + div / 2;//使用指针从一列移到下一列(颜色缩减函数公式1)
		//*data++ = *data / div * div + div/2;//颜色缩减函数公式2
		//data[i] = data[i] - data[i] % div + div / 2; //颜色缩减函数公式3,这个方法比较慢

		用位运算(非常高效),限制缩减因子为2^n,(运用掩模???)
		//uchar mask = OxFF << n;//e.g.for div=16,mask=OxFo;
		//data[i] = (data[i] & mask) + div / 2;

		}
	}
}

result.creatr(image.rows, image.cows, image.tye());    //创建一个与输入图像的尺寸和类型相同的矩阵,create 函数创建的内存是连续的,不会对图像进行填补。内存大小为total()*elemSize();

//	for (int j = 0; j < n1; j++) {
//		const uchar* data_in = image.ptr<uchar>(j);
//		uchar* data_out = result.ptr<uchar>(j);
//		for (int i = 0; i < nc; i++) {
//			data_out[i] = data_in[i] / div * div + div / 2;
//		}
//	}

int main() {
	Mat img;
	img = imread("D:/opencvcode/img/13.png");

	colorreduce(img);//调用颜色缩减函数
	namedWindow("input");
	imshow("input", img);
                                          // 额外的复制操作,法1
	Mat imageClone = img.clone();//clone函数的使用//处理克隆的图像
	colorreduce(imageClone);//原始图像不变
                                         //额外的复制操作,法2
	void colorReduce(const Mat & image,//输入图像,常量引用传递,不能被函数修改
		Mat & resul,                  //输出图像
		int div = 64);
	//colorreduce(img,img);//in-place处理方式时,可以将输入输出指定为同一个变量。不能用,mat->int

	//另一个实列,不然就要提供另外一个cv::Mat的实列
	//cv::Mat result;
	//colorreduce(img,result);//必须检查输入输出图像的大小,元素类型是否一致。cv::Mat的create成员函数有内置检查操作。

	void colorreduce(Mat & img, int div = 64) {    //利用图像的连续性,把整个处理过程用一个循环完成,颜色缩减函数重写为:
		int n1 = img.rows;
		int nc = img.cols * img.channels();
		if (img.isContinuous())//函数
		{
			nc = nc * n1;
			n1 = 1;
		}
		for (int j = 0; j < n1; j++) {
			uchar* data = img.ptr<uchar>(j);
			data[i] = data[i] / div * div + div / 2;
		}
	}
//也可以用reshape方法来重写这段函数:
	if (img.isContinuous())
	{
		img.reshape(1,img.cols * img.rows);//reshpape不用内存拷贝或重新分配就能改变矩阵维度。两个参数为新的通道数和新的行数。矩阵的列数可根据通道数和行数来自适应。
	}
	int n1 = img.rows;
	int nc = img.cols * img.channels();

	/*namedWindow("output");
	imshow("output", imageClone);*/


	namedWindow("input");
	imshow("input", img);
	waitKey(0);
	return 0;
}

cols代表宽度,rows代表高度,step代表以字节为单位的图像有效宽度。elemSize函数能得到像素大小,channels方法得到图像通道数,total函数返回矩阵像素个数。
in-place变换:直接在输入的图像上进行操作,不然就是新创建一个图像 ,“深拷贝”的方式是调用clone函数。

3.高效的遍历连续图像
若不对图像行尾经行填补的时候,图像视为一个长WxH的一位数组。通过cv::Mat的一个成员函数isContinuous方法来判断是否经行了填补,真,没有填补。

4.底层指针运算
容易出错,还不能定义感兴趣区域,不好用。

2.4 迭代器遍历图像
面向对象中常用迭代器编历数据集合,迭代器是一种特殊的类。不管数据类型是什么,都可以用相似的方式遍历集合,STL标准模板库为每一个容器类提供了迭代器,cv::Mat提供与STL迭代器兼容的迭代器。

#include<opencv2/opencv.hpp>
#include<iostream>
using namespace cv;
//Mat Iterator_<Vec3b>it;//创建一个迭代器特化版本1
//Mat_<Vec3b>::iterator it;//创建一个迭代器特化版本2
//第二种实现方式:
void colorreduce(Mat& img, int div = 64) {
//迭代器初始化完成(法1),再开始循环遍历
	Mat_<Vec3b>::iterator it = img.begin<Vec3b>();//处理彩色图像3b,用begin方法获得初始位置,得到左上角位置的迭代器
	//img.begin<Vec3b>() + img.rows;//从图像的第二行开始
	Mat_<Vec3b>::iterator itend = img.end<Vec3b>();//终止迭代的运算
	//img.end<Vec3b>() - img.rows;//希望在迭代过程的图像最后一行之前停止
	//循环方法1
	for (; it != itend; ++it) {
	//处理每一个像素
		(*it)[0] = (*it)[0] / div * div + div / 2;
		(*it)[1] = (*it)[1] / div * div + div / 2;
		(*it)[2] = (*it)[2] / div * div + div / 2;
		//处理像素完毕
	}
	//循环方法2
	/*
	while (it!=item){
	//处理每一个像素
	……
	//处理完成
	++it;//移动迭代器。可随意改步长,it+=10;
    }
   */

}
int main() {
	Mat img = imread("D:/opencvcode/img/13.png");
	if (img.empty()) {
		printf("not img!");
		return -1;
	}
	colorreduce(img);//函数调用
	namedWindow("input");
	imshow("input", img);

	waitKey(0);
	return 0;
}


在循环体内部,使用解引用操作符*来读写当前元素。读,element=*it,写,*it=element。
若操作对象是const cv::Mat或者强调当前循环不会对cv::Mat的实列进行修改,要创建常量迭代器。
创建方法1:cv::Mat ConstIterator_cv::Vec3bit;
方法2:cv::Mat_cv::Vec3b::const_iterator it;

迭代器初始化(法2)

cv::Mat_<cv::Vec3b>cimage=image;
cv::Mat_<cv::Vec3b>::iterator it =cimage.begin();
cv::Mat_<cv::Vec3b>::iteratoe itend=cimage.end();

面向对象的迭代器概念,阅读STL中迭代器相关的入门文章。关键字:STL Iterator

2.5. 高效的图像遍历循环

 分析效率,提高运行效率。

opencv中, cv:: getTickCount()函数用来检测一段代码的运行时间,计算从上次开机算起的时钟周期数。我们需要计算某个代码段的运行毫秒数,用cv::getTickFrequency(),此函数返回每秒内的时钟周期数。

//统计函数耗费时间的方法
double  duration;
duration =static_cast<double>(cv::getTickCount());
colorreduce(image);//被测试的函数
duration =static_cast<double>(cv::getTickCount())-duration;
duration/=cv::getTickFrequency();//运行时间,以ms为单位

at方法实现:

//at方法实现
for(int j=0;j<nl;j++){
	for(int i=0;i<nc;i++){
	//process ench pixel------
	image.at<cv::Vec3b>(j,i)[0]=image.at<cv::Vec3b>(j,i)[0]/div*div+div/2;
	image.at<cv::Vec3b>(j,i)[1]=image.at<cv::Vec3b>(j,i)[1]/div*div+div/2;
	image.at<cv::Vec3b>(j,i)[2]=image.at<cv::Vec3b>(j,i)[2]/div*div+div/2;
	//end of pixel processing------
	}//end of line
}

几种函数时间效率的分析:
得到

3-channel loop实现方法,速度最快的版本:

#include<opencv2/opencv.hpp>
#include<iostream>

using namespace cv;
/*
//颜色缩减的其中一种方法,(函数)
void colorreduce(Mat& image, int div = 64) {
	int n1 = image.rows;
	int nc = image.cols * image.channels();//获取每行像素值
	//for (int j = 0; j < n1; j++) {
	//	uchar* data = image.ptr<uchar>(j);//ptr函数得到图像任意首地址,ptr返回第J行的首地址
	//	//for (int i = 0; i < nc; i++) {//可提前计算的变量,提高运算速度
	//	for (int i = 0; i < image.cols * image.channels(); i++) {
	//		data[i] = data[i] / div * div + div / 2;//使用指针从一列移到下一列(颜色缩减函数公式1)
	//	//*data++ = *data / div * div + div/2;//颜色缩减函数公式2
	//	//data[i] = data[i] - data[i] % div + div / 2; //颜色缩减函数公式3,这个方法比较慢

	//	用位运算(非常高效),限制缩减因子为2^n,(运用掩模???)
	//	//uchar mask = OxFF << n;//e.g.for div=16,mask=OxFo;
	//	//data[i] = (data[i] & mask) + div / 2;

	//	}
	//}
	for (int j = 0; j < n1; j++) {
		for (int i = 0; i < nc; i++) {
			image.at<Vec3b>(j, i)[0] =
				image.at<Vec3b>(j, i)[0] / div * div + div / 2;
			image.at<Vec3b>(j, i)[1] =
				image.at<Vec3b>(j, i)[1] / div * div + div / 2;
			image.at<Vec3b>(j, i)[2] =
				image.at<Vec3b>(j, i)[2] / div * div + div / 2;
		}
	}
}
*/
void colorreduce(Mat& image, int div = 64) {   //3-chnnel-loop
	int n1 = image.rows;//行数
	int nc = image.cols;//列数
			//图像是连续存储的吗?

	if (image.isContinuous()) {
		//没有对行进行填补
		nc = nc * n1;
		n1 = 1;//一维数组
	}
	int n = static_cast<int>(
		log(static_cast<double>(div)) / log(2.0));
		//用来对像素值进行取整的二进制掩膜?????
	uchar mask = 0xFF << n;
	//for all pixels
	for (int j = 0; j < nc; j++) {
		//第j列的地主
		uchar* data = image.ptr<uchar>(j);
		for (int i = 0; i < nc; i++) {
			//处理每一个像素
			*data++ = *data & mask + div / 2;
			*data++ = *data & mask + div / 2;
			*data++ = *data & mask + div / 2;
			//像素处理结束
		}//行处理结束
	}
}
int main() {
	Mat img;
	img = imread("D:/opencvcode/img/13.png");

	//colorreduce(img);//调用颜色缩减函数

	double duration;//count the run time
	duration = static_cast<double>(cv::getTickCount());
	colorreduce(img);
	duration = static_cast<double>(cv::getTickCount()) - duration;
	duration /= cv::getTickFrequency();




	namedWindow("input");
	imshow("input", img);
	waitKey(0);

	return 0;
}

//统计函数耗费时间的方法
double  duration;
duration =static_cast<double>(cv::getTickCount());
colorreduce(image);//被测试的函数
duration =static_cast<double>(cv::getTickCount())-duration;
duration/=cv::getTickFrequency();//运行时间,以ms为单位

//at方法实现
for(int j=0;j<nl;j++){
	for(int i=0;i<nc;i++){
	//process ench pixel------
	image.at<cv::Vec3b>(j,i)[0]=image.at<cv::Vec3b>(j,i)[0]/div*div+div/2;
	image.at<cv::Vec3b>(j,i)[1]=image.at<cv::Vec3b>(j,i)[1]/div*div+div/2;
	image.at<cv::Vec3b>(j,i)[2]=image.at<cv::Vec3b>(j,i)[2]/div*div+div/2;
	//end of pixel processing------
	}//end of line
}

2.6. 遍历图像与邻域操作
由当前位置的相邻像素计算新的像素值。
对图像进行锐化,基于拉普拉斯算子。处理过后图像的边缘部分将得到放大,细节更加锐利。

sharpened_pixel=5*current-left-right-up-down;   left代表在当前像素左边紧挨着它的像素……

实现方式:3个指针,指向当前行,上一行,下一行。因为每一个像素值都要计算上下左右4个相邻像素,所以第一行,最后一行, 第一列,最后一列不能计算到。

//锐化//基于拉普拉斯算子//公式sharpened_pixel=5*current-left-right-up-down;
//三个指针,当前行,上一行,下一行
//用黑白图像效果更明显,彩色:cv::Scalar(a,b,c)
#include<opencv2/opencv.hpp>
#include<iostream>

using namespace cv;//method1
/*
void sharpen(const Mat& image, Mat& result) {//法一
//如有必要则分配图像
	result.create(image.size(), image.type());
	for (int j = 1; j < image.rows - 1; j++) {//处理除了第一行和最后一行之外的所有行
		const uchar* previous =
			image.ptr<const uchar>(j - 1);//上一行
		const uchar* current =
			image.ptr<const uchar>(j);      //当前行
		const uchar* next =
			image.ptr<const uchar>(j + 1);   //下一行
		uchar* output = result.ptr<uchar>(j);//输出行
		for (int i = 0; i < image.cols - 1; i++) {
			*output++ = saturate_cast<uchar>(
				5 * current[i] - current[i - 1]
				- current[i + 1] - previous[i] - next[i]);
		}
	}
	//将未处理的像素设置为0
	result.row(0).setTo(cv::Scalar(0));
	result.row(result.rows - 1).setTo(cv::Scalar(0));
	result.col(0).setTo(cv::Scalar(0));
	result.col(result.cols - 1).setTo(cv::Scalar(0));
}*/
void sharpen2D(const cv::Mat& image, cv::Mat& result) {//法二
//构造核(所有项都是初始化为0)
	cv::Mat kernel(3, 3, CV_32F, cv::Scalar(0));
	//对核元素进行赋值
	kernel.at<float>(1, 1) = 5.0;
	kernel.at<float>(0, 1) = -1.0;
	kernel.at<float>(2, 1) = -1.0;
	kernel.at<float>(1, 0) = -1.0;
	kernel.at<float>(1, 2) = -1.0;
	//对图像进行滤波
	cv::filter2D(image, result, image.depth(), kernel);
}
int main() {
	Mat image = imread("D:/opencvcode/img/13.png");
	namedWindow("input1");
	imshow("input1", image);
	//sharpen(image, image);
	sharpen2D(image, image);

	namedWindow("input2");
	imshow("input2", image);

	waitKey(0);
	return 0;
}

3个指针同时进行,计算输出像素值时,模板函数cv::saturate_cast用来截断计算结果。

边缘处理:
边缘像素不好处理,在程序中用两个函数设为0。方法2是,使用setTo 函数来设置矩阵的值,这个函数会将矩阵的所有元素都设为指定的值。

result.row(0).setTo(cv::Scalar(0));将结果第一行的所有像素设置为0。
三通道的彩色图,cv::Scalar(a,b,c)来指定像素的三个通道目标值。

2.7. 简单的图像计算
图像是矩阵,可进行加减乘除运算。
1.图像相加cv::add,完整的cv::addWeighted
//c[i]=a[i]+b[i];
cv::add(imageA,imageB,resultC);

cv::addWeighted(image1,0.7,image2,0.9,0.,result);==
用c++,重载图像操作符:result=0.7*image1+0.9*image2;

//把一个图像加到蓝色通道上

//创建一个图像向量
std::vectorcv::Matplanes;
//将一个三通道图像分离为三个单通道图像
cv::split(image1,planes);
//将新图层叠加到蓝色通道
planes[0]+=image2;
//将三个单通道图像重新合并为一个三通道图像
cv::merge(planes,result);

//cv::merge是cv::split的对偶运算,它将三个单通道图像合并为一个彩色三通道图像

//图像的运算
#include<opencv2/opencv.hpp>
#include<iostream>
using namespace cv;
void sharpen2D(const cv::Mat& image, cv::Mat& result) {
	cv::Mat kernel(3, 3, CV_32F, cv::Scalar(0));
	kernel.at<float>(1, 1) = 5.0;
	kernel.at<float>(0, 1) = -1.0;
	kernel.at<float>(2, 1) = -1.0;
	kernel.at<float>(1, 0) = -1.0;
	kernel.at<float>(1, 2) = -1.0;
	cv::filter2D(image, result, image.depth(), kernel);
	}

int main() {
	Mat image = imread("D:/opencvcode/img/13.png");
	Mat image2 = imread("D:/opencvcode/img/13-1png");
	cv::addWeighted(image, 0.7, image2, 0.9, 0., result);
	sharpen2D(image, image);
	namedWindow("input");
	imshow("input", image);
	waitKey(0);
	return 0;
}

2.8. 定义感兴趣区域
合并两个不同大小的图像,cv::add合并两个相同尺寸的图像,所以不能用。在使用之前定义感兴趣区域(ROI),感兴趣区域的大小与图像大小相同时,cv::add才能工作。ROI的位置决定了插入图像的位置。

源码中创建ROI方式和原始图像共享数据缓冲区,对ROI的任何操作都会影响到原始图像的对应区域。因为创建ROI时不会拷贝数据。

以下创建包含原始图像特定行的ROI

cv::Mat imageROI=image.rowRange(start,end);//行
cv::Mat imageROI=image.colRange(start,end);//列
 

源代码:


#include<opencv2/opencv.hpp>
#include<iostream>

using namespace cv;
int main() {
	Mat image = imread("D:/opencvcode/img/13.png");
	Mat logo = imread("D:/opencvcode/img/13-2.png");
	
	
	//插入logo的操作
	cv::Mat imageROI;//定义图像ROI
	imageROI = image(cv::Rect(385, 270,logo.cols.logo.rows));//1.矩形定义方法,左上角坐标,长宽。
	/*
	//2.Range方法,从起始索引到中止索引(不包括中止索引)
	cv::Mat imageROI=image(cv::Range(270,270+logo.rows),cv::Range(385,385+logo.cols))
	*/
	
	//插入logo
	cv::addWeighted(imageROI, 1.0, logo, 0.3, 0., imageROI);
	
	/*
	//运用掩膜来完成。视觉效果更好一点。
    //定义ROI
	imageROI-image(cv::Rect(385,270,logo.cols,log.rows));
	//加载掩膜(必须是灰度图)
	cv::Mat mask=cv::imread("logo.bmp",0);
	//通过掩膜拷贝ROI
	logo.copyTo(imageROI,mask);
*/

	namedWindow("input1");
	imshow("input1", image);

	//sharpen(image, image);
	//sharpen2D(image, image);

	waitKey(0);
	return 0;
}

第三章 基于类的图像处理
目标:
在算法设计中使用策略(Strategy)模式
使用控制器(Controller)实现模块间通信
使用单件(Singleton)设计模式
使用模型-视图-控制器(Model-View-Controller)架构设计引用程序
颜色空间转换

  1. 3.1设计模式,软件工程中的概念,程序员要熟悉现有的模式。操作图像中的颜色,检测拥有特定颜色的像素。
  2. 3.2 策略设计模式
    将算法封装在类中,替换一个现有的算法。
#include<opencv2/opencv.hpp>
#include<iostream>
using namespace std;
using namespace cv;

/*
int main() {
	Mat result;
	Mat image = imread("D:/opencvcode/img/13.png");
	if (image.empty()) {
		cout << "not img" << endl;
		return -1;
	}
	*/
	class ColorDetector {
	private:
		//最小可接受距离
		int minDist;
		//目标色
		cv::Vec3b target;
		//结果图像
		cv::Mat result;
	};
	/*
	//空构造函数
	ColorDetector(): minDist(100) {
		//初始化默认参数
		target[0] = target[1] = target[2] = 0;
	}

	cv::Mat ColorDetector::process(const cv::Mat& image) {
		//按需重新分配二值图像
		//与输入图像的尺寸相同,但是只有一个通道
		result.create(image.rows, image.cols, CV_8U);


		//得到迭代器
		Mat_<Vec3b>::const_iterator it = image.begin<Vec3b>();
		Mat_<Vec3b>::const_iterator itend = image.end<Vec3b>();
		Mat_<uchar>::iterator itout = result.begin<uchar>();
		//对于每个像素
		for (; it != itend; ++it, ++itout) {
			//处理每个像素------
			//计算离目标颜色的距离
			if (getDistance(*it) < minDist) {
				*itout = 255;
			}
			else {
				*itout = 0;
			}
			//处理像素结束------
		}

		return result;

	}

	*/
int main()
{
	//1.创建图像处理对象
	ColorDetector cdetect;
	//2.读取输入图像
	Mat image = imread("D:/opencvcode/img/13.png ");
	if (!image.data)
		return 0;
	//3.设置输入参数
	cdetect.setTargetColor(130, 190, 230);//蓝天的颜色
	namedWindow("result", WINDOW_AUTOSIZE);
	//4.处理并显示结果
	imshow("result",cdetect.process(image));

	waitKey(0);
	return 0;
	}
	
	/*
//计算与目标颜色的距离
int getDistance(const Vec3b& color)const {
	return abs(color[0] - target[0]) + abs(color[1] - target[1]) + abs(color[2] - target[2]);
}

//设置色彩距离阈值。阈值必须是正的,否则设为0
void setColorDistanceThreshold(int distance) {
	if (distance < 0)
		distance = 0;
	minDist = distance;
}
//获取色彩距离阈值
int getColorDistanceThreshold() const {
	return minDist;
}
*/

/*

 //设置需检测的颜色
void setTargetColor(unsigned char red, unsigned char green, unsigned char blue) {
	//bgr顺序
	target[2] = red;
	target[1] = green;
	target[0] = blue;
}

//设置需检测的颜色
void setTargetColor(cv::Vec3b color) {
	target = color;
}
//获取需检测的颜色
cv::Vec3b getTargetColor() const {
	return target;
}
*/

3.3使用控制器实现模块间通信

#include<opencv2/opencv.hpp>
#include<iostream>

using namespace std;
using namespace cv;
int main() {
	class ColorDetectController {
	private:
		//算法类
		ColorDetector * cdetect;
		Mat image;//被处理的图像
		Mat result;//结果
	public:
		ColorDetectController() {
			//初始化工作
			cdetect = new ColorDetector();
		}

		//设置色彩距离阈值
		void setColorDistanceThreshold(int distance) {
			cdetect->setColorDistanceThreshold(distance);
		}
		//获取色彩距离阈值
		int getColorDistanceThreshold() const {
			return cdetect->getColorDistanceThreshold();
		}

		//设置要检测的色彩
		void setTargetColor(unsigned char red, unsigned char green, unsigned char blue) {
			cdetect->setTargetColor(red, green, blue);
		}

		//获取需检测的色彩
		void setTargetColor(unsigned char &red, unsigned char &green, unsigned char &blue)const {
			Vec3b color = cdetect->getTargetColor();
			red = color[2];
			green = color[1];
			blue = color[0];
		}
		//设置输入图像,通过文件读取
		bool setInputImage(std::string filename) {
			image = cv::imread(filename);
			if (!image.data)
				return false;
			else
				return true;
		}
		//返回当前的输入图像
		const cv::Mat getInputImage()const {
			return image;
		}

		//开始图像处理
		void process() {
			result = cdetect->process(image);
		}

		//获取最近一次处理的结果
		const cv::Mat getLastResult() const {
			return result;
		}

		//删除当前控制器创建的处理对象
		~ColorDetectController() {
			delete cdetect;
		}

		//Open按钮的回调函数
		void onOpen() {
			//用于选择bmp或jpg文件的MFC控件
			CFileDialog dlg(TRUE, T("*.bmp"), NULL,OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY,_T("image files(*.bmp;*,jpg)| *.bmp; *.jpg | All Files(*.*) | *.* || "),NULL);
			dlg.m_ofn.lpstrTitle = _T("Open Image");
			//如果选中了一个文件
			if (dlg.DoModal() == IDOK) {
				//获取选中文件的路径
				std::string filename = dlg.GetPathName();
				//设置并显示该图像
				colordetect.setInputImage(filename);
				cv::imshow("input Image", colordetect.getInputImage());
			}
		}

		//Process 按钮的回调函数
		void OnProcess() {
			//此处目标色为硬编码
			colordetect.setTargetColor(130, 190, 230);
			//处理输入图像,并显示结果
			colordetect.process();
			cv::imshow("output Result", colordetect.getLastResult());
		}

//imshow("output", src);
	waitKey(0);
	return 0;
}
	};


	

3.4使用单件设计模式
单件(Singleton)模式

3.5使用模式-视图-控制器(Model-View-Controller)构架设计应用程序
MVC,基于Qt的GUI

3.6颜色空间转换
使用cv::cvtColor可以轻易在不同颜色空间经行转换,

第4章 使用直方图统计像素
计算图像的直方图
通过查找表修改图像外观
直方图均衡化
反投影直方图以检测特定的图像内容
使用均值漂移(Mean Shift)计算查找物体
通过比较直方图检索相似图片

4.2计算图像的直方图
归一化
cv::calcHist函数,可计算任意类型的多通道图像

4.3使用查找表修改图像外观

4.4直方图均衡化
均衡,

4.5反投影直方图以检测特定的图像内容
检测特定的内容,首先选取感兴趣区域(ROI),
一个概率映射

4.6使用均值漂移(Mean Shift)计算查找物体
知道物体的近似位置,用迭代移动,找到精确的位置。
HSV颜色空间,

4.7通过比较直方图检索相似图片
cv::compareHist函数

第5章 基于形态学运算的图像变换
使用形态学滤波对图像进行腐蚀、膨胀运算
使用形态学滤波对图像进行开闭运算
使用形态学滤波对图像进行边缘及角点检测
使用分水岭算法对图像进行分割
使用GrabCut算法提取前景物体

5.2使用形态学滤波对图像进行腐蚀、膨胀运算
腐蚀和膨胀函数:cv::erode,cv::dilate

#include<opencv2/opencv.hpp>
#include<iostream>
 
using namespace cv;
using namespace std;
int main() {
	//读取输入图像
	Mat image = imread("D:/opencvcode/img/my1.png");
	imshow("input ", image);
	//腐蚀图像
	Mat eroded;//目标图像

	//方法1	
	//erode(image, eroded, Mat());

	//方法2
	//Mat element(7, 7, CV_8U, Scalar(1));
	//erode(image, eroded, element);

	//方法3,效果和方法2一样
	//腐蚀图像三次
	//erode(image, eroded, Mat(), Point(-1, -1), 3);
	
	//方法4
	erode(image, image, Mat());
	imshow("Eroded Image", image);

	//imshow("Eroded Image", eroded);

	//膨胀图像
	Mat dilated;//目标图像
	dilate(image, dilated,Mat());
	//显示膨胀后的图像
	namedWindow("Dilated Image");
	imshow("Dilated Image", dilated);

	waitKey(0);
	return 0;
}

5.3使用形态学滤波对图像进行开闭运算
cv::morphologyEx函数
闭运算
开运算

#include<opencv2/opencv.hpp>
#include<iostream>

using namespace cv;
using namespace std;
int main() {
	//读取输入图像
	Mat image = imread("D:/opencvcode/img/my1.png");
	imshow("input ", image);
	/*

	//腐蚀图像
	Mat eroded;//目标图像

	//方法1	
	//erode(image, eroded, Mat());

	//方法2
	//Mat element(7, 7, CV_8U, Scalar(1));
	//erode(image, eroded, element);

	//方法3,效果和方法2一样
	//腐蚀图像三次
	//erode(image, eroded, Mat(), Point(-1, -1), 3);

	//方法4
	//erode(image, image, Mat());
	//imshow("Eroded Image", image);

	//5.3闭运算
	
	Mat element5(5, 5,CV_8U, Scalar(1));
	/*
	Mat closed;
	morphologyEx(image, closed, MORPH_CLOSE, element5);
	imshow("closed Image", image);
	*/
/*
	//5.3开运算
	Mat opened;
	morphologyEx(image, opened, MORPH_OPEN, element5);
	imshow("opened Image", image);

	//imshow("Eroded Image", eroded);//方法1-3通用

*/

	//膨胀图像
	Mat dilated;//目标图像
	dilate(image, dilated, Mat());

	//5.3先膨胀后进行原地腐蚀
	Mat result;
	dilate(image, result, Mat());
	erode(result, result, Mat());
	imshow("opened Image", result);

	//显示膨胀后的图像
	namedWindow("Dilated Image");
	imshow("Dilated Image", dilated);

	waitKey(0);
	return 0;
}

5.4使用形态学滤波对图像进行边缘及角点检测
形态学滤波用于检测图像中的特征,检测灰度图中的直线和角点

#include<opencv2/opencv.hpp>
#include<iostream>

using namespace std;
using namespace cv;

int main(){ 

	class MorphoFeatures 
{
	private:
		//用于生成二值图像的阈值
		int threshold;
		//角点检测中用到的结构元素
		Mat cross;
		Mat diamond;
		Mat square;
		Mat x;


		//使用morphologyEx函数配上合适的滤波器可以轻松实现直线的检测
		cv::Mat getEdges(const Mat& image) 
		{
			//得到梯度图
			Mat result;
			morphologyEx(image, result, MORPH_GRADIENT, Mat());
			//阈值化得到二值图像
			applyThreshold(result);
			return result;
	    }

		void applyThreshold(Mat& result) {
			//使用阈值化
			if (threshold > 0)
				cv:: threshold(result, result, threshold, 255, THRESH_BINARY);

		}
	};


	//使用形态学检测角点
	MorphoFeatures() : threshold(-1),
		cross(5, 5, CV_8U, cv::Scalar(0)),
		diamond(5, 5, CV_8U, Scalar(1)),
		square(5, 5, CV_8U, Scalar(1)), x(5, 5, CV_8U, Scalar(0)) {
		//创建十字形元素
		for (int i = 0; i < 5; i++) {
			cross.at<uchar>(2, i) = 1;
			cross.at<uchar>(i, 2) = 1;
		}
		//创建菱形元素
		diamond.at<uchar>(0, 0) = 0;
		diamond.at<uchar>(0, 1) = 0;
		diamond.at<uchar>(1, 0) = 0;
		diamond.at<uchar>(4, 4) = 0;
		diamond.at<uchar>(3, 4) = 0;
		diamond.at<uchar>(4, 3) = 0;

		diamond.at<uchar>(4, 0) = 0;
		diamond.at<uchar>(4, 1) = 0;
		diamond.at<uchar>(3, 0) = 0;
		diamond.at<uchar>(0, 4) = 0;
		diamond.at<uchar>(0, 3) = 0;
		diamond.at<uchar>(1, 4) = 0;
		//创建x形元素
		for (int i = 0; i < 5; i++) {
			x.at<uchar>(i, i) = 1;
			x.at<uchat>(4 - i, i) = 1;
		}

	}

	cv::Mat getCorners(const cv::Mat &image) {
		Mat result;
		//十字形膨胀
		dilate(image, result,cross);
		//菱形腐蚀
		cv::erode(result, result, diamond);
		cv::Mat result2;
		//x形膨胀
		cv::dilate(image, result2, x);
		//方形腐蚀
		cv::erode(result2, result2, square);
		//阈值化以得到二值图像
		applyThreshold(result);
		return result;
	}


	//在每个检测点上绘制一个园
	void drawOnImage(const cv::Mat& binary,
		cv::Mat& image) {
		cv::Mat_<uchar>::const_iterator it = binary.begin<uchar>();
		cv::Mat_<uchar>::const_iterator itend = binary.end<uchar>();
		//遍历每一个像素
		for (int i = 0; it != itend; ++it, ++i) {
			if (!*it)
				cv::circle(image,
					cv::circle(image,
						cv::Point(i % image.step, i / image.step),
						5, cv::Scalar(255, 0, 0));
		}
	}



	Mat image = imread("D:/opencvcode/img/gry.png");

	MorphoFeatures morpho;
	morpho.setThreshold(40);
	//获取边缘
	Mat edges;
	edges = morpho.getEdges(image);



	//得到角点
	Mat corners;
	corners = morpho.getCorners(image);
	//在图像中显示角点
	morpho.drawOnImage(corners, image);
	cv::nameWindow("Corners on Image");
	cv::imshow("Corners on Image", image);



	waitKey(0);
	return 0;
}

5.5使用分水岭算法对图像进行分割
快速分割图像为同类区域
分割结果通过cv::watershed函数获得,

#include<opencv2/opencv.hpp>
#include<iostream>

using namespace cv;
using namespace std;
int main() {
	//读取输入图像
	Mat image = imread("D:/opencvcode/img/my1.png");
	

	class WatershedSegment {
	private:
		cv::Mat markers;
	public:
		void setMarkers(const cv::Mat& markerImage) {
			//转换为整数图像
			markerImage.convertTo(markers, CV_32S);
		}
		cv::Mat process(const Mat & image) {
			//使用算法
			watershed(image, markers);
			return markers;
		}
		//移除噪点与微小物体
		cv::Mat fg;
		cv::erode(binary, fg, Mat(), Point(-1, -1), 6);

		//识别不包括物体的像素
		Mat bg;
		dilate(binary, bg, Mat(), Point(-1, -1), 6);
		threshold(bg, bg, 1, 128, THRESH_BINARY_INV);


		//创建标记图像
		Mat markers(binary.size(), CV_8U, Scalar(0));
		markers = fg + bg;

		//创建分水岭分割对象
		WatershedSegment segmenter;
		//设置标记,并进行处理
		segmenter.setMarkers(markers);
		segmenter.process(image);

		//以图像的形式返回结果
		Mat getSetmentation() {
			Mat tmp;
			//标签高于255的分割一律赋值为255
			markers.convertTo(tmp, CV_8U);
			return tmp;
		}

		//以图像的形式返回分水岭
		Mat getWatersheds() {
			Mat tmp;
			//转换前每一个像素都为255p+255
			markers.convertTo(tmp, CV_8U, 255, 255);
			return tmp;
		}

	};

	namedWindow("Dilated Image");
	imshow("Dilated Image", image);

	waitKey(0);
	return 0;
}

5.6使用GrabCut算法提取前景物体
与分水岭算法有相似之处,但更加复杂,产生的结果精确很多。
从静态图像中提前前景物体的应用,如,将一幅图中的物体剪贴到另一幅图中。是最佳算法。

#include<opencv2/opencv.hpp>
#include<iostream>

using namespace std;
using namespace cv;

int main() {
	Mat image;
	//打开图像
	image = imread("D:/opencvcode/img/13.png");
	//定义前景物体的包围盒
	Rect rectangle(10, 100, 380, 180);
	Mat result;//分割(四种可能的值)
	Mat bgModel, fgmodel;//模型(供内部使用)
	//GrabCut分割
	grabCut(image,//输入图像
		result,//分割结果
		rectangle,//包括前景物体的矩形
		bgModel, fgmodel,//模型
		5,//迭代次数
		GC_INIT_WITH_RECT);//使用矩形经行初始化

	//得到可能为前景的像素
	compare(result, GC_PR_FGD,result, CMP_EQ);
	//生成输出图像

	//使用按位与(bitwise-and)核对第1个位
	result = result & 1;

	Mat foreground(image.size(), CV_8UC3,
		Scalar(255, 255, 255));
	image.copyTo(foreground,//不复制背景像素
		result);
	 	
	//namedWindow("output");
	imshow("output", image);
	waitKey(0);

	return 0;

}

第6章 图像滤波
使用低通滤波器
使用中值滤波器
使用方向滤波器检测边缘
计算图像的拉普拉斯变换

6.1滤波操作,增强 部分频段,限制其他频段。低通滤波器去除了图像中的高频成分,高通滤波器去除了低频成分。

6.2使用低通滤波器
降低图像变化的幅度,将每个像素替换为相邻像素的平均值。使用cv::blur函数

cv::blur(image,result,cv::Size(5,5));

高斯函数,cv::GaussianBlur(image,result,cv::Size(5,5),1.5);

#include<opencv2/opencv.hpp>
#include<iostream>

using namespace std;
using namespace cv;
int main() {
	Mat image = imread("D:/opencvcode/img/13.png");
	imshow("intput", image);

	Mat result;
	//cv::blur(image, result, cv::Size(5, 5));
	cv::GaussianBlur(image, result, cv::Size(5, 5), 1.5);//高斯 函数
	
	Mat reducedImage;//包含缩小后的图像
	pyrDown(image, reducedImage);//将图像尺寸减半

	Mat resizedImage;//包含改变尺寸后的图像
	resize(image, resizedImage,Size(image.cols/3,image.rows/3));//改变为1/3
	
	imshow("output", result);
	waitKey(0);
	return 0;
}

6.3使用中值滤波器
上一节线性滤波器,这一节中值滤波器就是一种非线性滤波器。
中值滤波器对去除噪点很有用,

//2021.6.27 图像椒盐,加噪点。
#include<opencv2/opencv.hpp>
#include<iostream>

using namespace cv;
void salt(Mat& image, int n) {
	for (int k = 0; k < n; k++)
	{
		int i = rand() % image.cols;//随机获取行和列
		int j = rand() % image.rows;
		if (image.channels() == 1) {//灰度为1
			image.at<uchar>(j, i) = 255;//255白色,0为黑色
		}
		else if (image.channels() == 3)//彩色为3
		{
			image.at<Vec3b>(j, i)[0] = 255;
			image.at<Vec3b>(j, i)[1] = 255;
			image.at<Vec3b>(j, i)[2] = 255;
		}
	}
}

int main() {
	Mat src,result;
	src = imread("D:/opencvcode/img/13.png");
	if (!src.data) {
		printf("not img!");
		return -1;
	}
	salt(src, 3000);

	namedWindow("inputimg");
	imshow("inputimg", src);

	medianBlur(src, result,5);//中值滤波器去除噪点
	namedWindow("outputimg");
	imshow("outputimg", result);


	waitKey(0);
	return 0;
}

6.4使用方向滤波器检测边缘
强调图像中的高频分量,Sobel滤波器。具有方向性

#include<opencv2/opencv.hpp>
#include<iostream>

using namespace cv;

int main() {
	Mat src, sobelX, sobelY;
	src = imread("D:/opencvcode/img/13.png");
	if (!src.data) {
		printf("not img!");
		return -1;
	}

	namedWindow("inputimg");
	imshow("inputimg", src);

	cv::Sobel(src,sobelX,CV_8U, 1, 0, 3, 0.4, 128);//水平滤波器
	Sobel(src,sobelY, CV_8U, 0, 1, 3, 0.4, 128);//垂直滤波器

	//计算Sobel范式
	Sobel(src, sobelX, CV_16S, 1, 0);
	Sobel(src,sobelY, CV_16S,0,1);
	Mat sobel;
	//计算L1范式
	sobel = abs(sobelX) + abs(sobelY);

	//搜寻Sobel极大值
	double sobmin, sobmax;
	minMaxLoc(sobel, &sobmin, &sobmax);
	//变换为8位图像
	//sobelImage=-alpha*sobel+255;
	Mat sobelImage;
	sobel.convertTo(sobelImage, CV_8U, -255. / sobmax, 255);
/*
	threshold(sobelImage,sobelThresholded,
		threshold, 255, THRESH_BINARY);
*/
	namedWindow("outputimg");
	imshow("outputimg", src);
	waitKey(0);
	return 0;
}

6.5计算图像的拉普拉斯变换
一种基于图像导数的高通线性滤波器。
函数Laplacion与Sobel函数很类似。

#include<opencv2/opencv.hpp>
#include<iostream>

using namespace std;
using namespace cv;
int main() {
	class LaplacianZC {
	private:
		//原图
		Mat img;
		//包含Laplacion的32位浮点图像
		Mat laplace;
		//laplacian卷积核的大小
		int aperture;
	public:
		LaplacianZC() :aperture(3) {}
		//设置卷积核的大小
		void setAperture(int a) {
			aperture = a;
		}
		//计算浮点数Lapladion
		Mat computeLaplacian(const Mat& image) {
			//计算Laplacian
			Laplacian(image, laplace, CV_32F, aperture);
			//保留图像的局部备份(用于零点交叉)
			img = image.clone();
			return laplace;
		}
	};

	//返回8位图像存储的Laplacian结果
	//零点交叉于灰度值128
	//如果没有指定scale参数,那么最大值将缩至强度255
	//你必须在调用它之前调用computeLaplacian
	Mat getLaplacianImage(double scale = -1.0) {
		if (scale < 0)
		{
			double lapmin, lapmax;
			minMaxLoc(laplace, &lapmin, &lapmax);
			scale = 127 / std::max(-lapmin, lapmax);
		}
		Mat laplaceImage;
		laplace.convertTo(laplaceImage, CVV_8U, scale, 128);
		return laplaceImage;

	}

	//使用LaplacianZC类进行计算
	LaplacianZC laplacian;
	laplacian.setAperture(7);
	Mat flap = laplacian.computeLacian(image);
	laplace = laplacian.getLaplacianImage();

	//得到零点交叉的二值图像
	//如果相邻像素的乘积小于thteshold
	//那么零点交叉将忽略
	Mat getZeroCrossings(float threshold = 1.0)
	{
		//创建迭代器
		Mat_<float>::const_iterator it =
			Laplace.begin<float>() + lapace.stepl();
		Mat_<float>::const_iterator itend =
			laplace.end<float>();
		Mat_<float>::const_iterator itup =
			laplace.begin<float>();
		//初始化为白色的二值图像
		Mat binary(laplace.size(), CV_8U, Scalar(255));
		Mat_<uchar>::iterator itout =
			binary.begin<uchar>() + binary.stepl();
		//对输入阈值取反
		threshold *= -1.0;
		for (; it! = itend; ++it, ++itup, ++itout)
			//如果相邻像素的乘积为负数,那么符号发生改变
			if (*it * *(it - 1) < threshold)
				*itout = 0;//水平方向零点交叉
			else if (*it * *itup < threshold)
				*itout = 0;//垂直方向零点交叉
	}
	return binary;


	Mat image= imread("D:/opencvcode/img/13.png");
	//class LaplacianZC;

	namedWindow("inputimg");
	imshow("inputimg", image);
	waitKey(0);
	return 0;
}
  人工智能 最新文章
2022吴恩达机器学习课程——第二课(神经网
第十五章 规则学习
FixMatch: Simplifying Semi-Supervised Le
数据挖掘Java——Kmeans算法的实现
大脑皮层的分割方法
【翻译】GPT-3是如何工作的
论文笔记:TEACHTEXT: CrossModal Generaliz
python从零学(六)
详解Python 3.x 导入(import)
【答读者问27】backtrader不支持最新版本的
上一篇文章      下一篇文章      查看所有文章
加:2021-08-10 13:25:17  更:2021-08-10 13:25:28 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/27 21:54:19-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码