Opencv视觉处理(C++)语法学习(11)深入理解直方图和滤波器

直方图概述

直方图是变量分布的统计图表示,它让我们能够理解数据的密度估计和概率分布。直方图是通过整个变量值范围划分为小的值范围,然后计算每个间隔落入多少个值来创建的

编码实现

	//首先判断图像的类型
	if (src.channels() == 1) {//灰度图像
		// do something
	}
	else if (src.channels() == 3) {//BGR三通道图像
		//1.将BGR三通道图像弄成vector
		std::vector <Mat> bgr;
		cv::split(src, bgr);

		//2.定义数据范围并创建三个矩阵来存储每个直方图
		float range[] = { 0,256 };
		const float* histRange = { range };
		Mat bHist, gHist, rHist;
		int numBins = 256;

		//3.用calcHist来计算直方图
		//api说明(输入图像,用于计算直方图的输入图像数,用于计算直方图的数字通道尺寸,可选的掩码矩阵,用于存储得到的直方图的变量,直方图维度,要计算的区间数(256?),输入变量的范围)
		cv::calcHist(&bgr[0], 1, 0, Mat(), bHist, 1, &numBins, &histRange);
		cv::calcHist(&bgr[1], 1, 0, Mat(), gHist, 1, &numBins, &histRange);
		cv::calcHist(&bgr[2], 1, 0, Mat(), rHist, 1, &numBins, &histRange);

		//4.对直方图进行标准化
		//在最小值0和最大值之间标准化直方图矩阵
		//api说明:(输入图像,输出图像,直方图矩阵最低点,直方图矩阵最高点,前两个参数的枚举类型说明)
		int w = 512, h = 300;
		cv::normalize(bHist, bHist, 0, h, NORM_MINMAX);
		cv::normalize(gHist, gHist, 0, h, NORM_MINMAX);
		cv::normalize(rHist, rHist, 0, h, NORM_MINMAX);

		//5.计算完毕之后在画布上画出来
		Mat histImg(h, w, CV_8UC3, Scalar(20, 20, 20));

		//6.画图线
		//计算每一条直线的步长,计算有多少个像素在每个区间之间,从水平位置i-1到i绘制每条小线,垂直位置是相应i中的直方图值
		int binStep = cvRound((float)w / (float)numBins);
		for (int i = 1; i < numBins; i++) {
			cv::line(histImg, Point(binStep * (i - 1), h - cvRound(bHist.at<float>(i - 1))),
				Point(binStep * (i), h - cvRound(bHist.at<float>(i))), Scalar(255, 0, 0));//注意参数
			cv::line(histImg, Point(binStep * (i - 1), h - cvRound(gHist.at<float>(i - 1))),
				Point(binStep * (i), h - cvRound(gHist.at<float>(i))), Scalar(0, 255, 0));//注意参数
			cv::line(histImg, Point(binStep * (i - 1), h - cvRound(rHist.at<float>(i - 1))),
				Point(binStep * (i), h - cvRound(rHist.at<float>(i))), Scalar(0, 0, 255));//注意参数
		}

		//7.显示图像即可
		imshow("hist", histImg);

代码剖析

对于代码中所使用的api,有些参数可能不是那么好理解,在这里进行解释

calcHist

参数为:
(输入图像,用于计算直方图的输入图像数,用于计算直方图的数字通道尺寸,可选的掩码矩阵,用于存储得到的直方图的变量,直方图维度,要计算的区间数(256?),输入变量的范围)
这个api的作用是计算出直方图的高度,注意,这个高度是相对的,需要画出来之前要先对直方图作一个归一化,
所谓的掩码矩阵就是,在绘制直方图的同时要不要对图像进行增强,增加一个卷积内核.

line

在本编码中,对于线的绘制比较复杂,但实际上非常简单,关键是要搞懂Point里面的参数,首先cvRound是一个四舍五入的函数,然后binstep是步长,设置这个参量可以使得绘制的线在整幅图像上都铺满,接着是 **h-cvRound(rHist.at < float >(i-1) )**这个参数,由于图像的坐标分布是从左上角开始的,因此对于线在图中的坐标应当是最大高度-直方图中所计算得到的高度.

那么在这里可以在直方图绘制的过程中,进行打擂, 计算出频度最高的灰度值,可以作为阈值参考使用

灰度分布直方图

以上是根据opencv的内置api来计算得到直方图,下面对一个灰度图像进行灰度分布直方图的计算,并且求取阈值

#include <stdio.h>
#include <math.h>
#include <vector>
#include <iostream>
#include <algorithm>

using namespace std;

const int srcRow=128;
const int srcCol=128;

void handle(int pic[][128],double &a,double &b){
    //1.十字架不一定水平与垂直
    //2.存在模糊
    //3.明暗度不同

    //阈值化分割:灰度图像阈值化分割
    //1.设置好计算的参数
    double nHistogram[256];//灰度分布直方图
    double dVariance[256];//类间方差
    int sumPic=srcRow*srcCol;
    for(int i=0;i<256;i++){
        nHistogram[i]=0.0;
        dVariance[i]=0.0;
    }

    //2.对每一个像素点进行归类
    for(int i=0;i<srcRow;i++){
        for(int j=0;j<srcCol;j++){
            nHistogram[pic[i][j]]++;//计算出频度
        }
    }

    //3.根据频度计算出概率
    double Pa=0.0;//背景出现的频率
    double Pb=0.0;//十字出现的频率
    double Wa=0.0;//背景平均灰度
    double Wb=0.0;//目标平均灰度
    double W0=0.0;//全局平均灰度
    double dData1=0.0,dData2=0.0;
    for(int i=0;i<256;i++){
        nHistogram[i] /= sumPic;//计算出频率
        W0 += i*nHistogram[i];
    }

    //4.对每个灰度值计算类间方差
    for(int i=0;i<256;i++){
        Pa += nHistogram[i];
		Pb = 1-Pa;
		dData1 += i*nHistogram[i];
		dData2 = W0-dData1;
		Wa = dData1/Pa;
		Wb = dData2/Pb;
		dVariance[i] = (Pa*Pb* pow((Wb-Wa), 2));
    }

    //5.遍历每个方差,打擂求取类间最大方差所对应的灰度值
    double temp=0;
    double nThreshold = 0;
    for(int i=0;i<256;i++){
        if(dVariance[i]>temp){
            temp=dVariance[i];
            nThreshold=i;
        }
    }
    //根据阈值二值化图像,找到十字中心
    //1.图像二值化
    for(int i=0;i<srcRow;i++){
        for(int j=0;j<srcCol;j++){
            if(pic[i][j]>= (nThreshold-19) ){
                pic[i][j]=255;
            }else{
                pic[i][j]=0;
            }
        }
    }

    //2.设置边缘点及其检测标志
    double top=srcRow-1,down=0,left=srcCol-1,right=0;//假设上下左右的那几个端点

    //3.检测最左侧的点和最右侧的点(打擂)
    for(int i=0;i<srcRow;i++){
        for(int j=0;j<srcCol;j++){//找最左侧的点
            if(pic[i][j]==0 && left>j){
                left=j;
            }
        }
        for(int j=srcCol-1;j>=0;j--){//右
            if(pic[i][j]==0 && right<j){
                right=j;
            }
        }
    }

    //4.检测最上面的点和最下面的点
    for(int i=0;i<srcCol;i++){
        for(int j=0;j<srcRow;j++){//上
            if(pic[j][i]==0 && top>j){
                top=j;
            }
        }
        for(int j=srcRow-1;j>=0;j--){//下
            if(pic[j][i]==0 && down<j){
                down=j;
            }
        }
    }

    //5.计算
    b=(top+down)/2;
    a=(right+left)/2;
    //write it
    /*
    FILE *fp;
    fp=fopen("E:/data/firsttest_output.txt","r+");
    for(int i=0;i<srcRow;i++){
        for(int j=0;j<srcCol;j++){
            fprintf(fp,"%d ",pic[i][j]);
        }
        fprintf(fp,"\n");
    }
    */
}

int main(){

    int input[srcRow][srcCol];
    FILE *fp;
    fp=fopen("E:/data/83 80.txt","r+");

    for(int i=0;i<srcRow;i++){
        for(int j=0;j<srcCol;j++){
            int x;
            fscanf(fp,"%d",&x);
            input[i][j]=x;
        }
    }
    double ansx,ansy;
    handle(input,ansx,ansy);
    cout<<(int)ansy<<" "<<(int)ansx;
}

计算灰度分布直方图的方法

OTSU算法:
1.首先建立一定区间的数组,用来存储类间方差和灰度分布频度
2.分别计算前景、背景区域所占像素点的个数及其比例
3.计算前景、背景区域所占像素值均值
4.计算类间方差,公式为:前景区域像素点的频率 * (前景像素值均值-全图像素值均值)的平方 + 背景区域像素点的频率 (背景像素值均值-全图像素值均值)的平方
5.将阈值从0-255进行遍历,求取使得类间方差最大的阈值.

直方图的应用:图像颜色均衡

图像均衡(即直方图均衡化)试图获得具有均匀分布值的直方图。均衡的结果是导致图像的对比度增强。均衡能够使对比度较低的局部区域获得高对比度,从而分散最频繁的强度。当图像非常暗或者非常亮,并且背景和前景之间存在非常小的差异时,通过使用直方图均衡化,可以增加对比度,并提升暴露过度或者暴露不足的细节
主要缺点:背景噪声会增加,有用信号的减少

具体算法步骤

为了均衡彩色图像,只需均衡亮度通道。可以用每个颜色通道执行该操作,但结果不能使用。
可以采用任何其他彩色图像格式(例如HSV或YCrCb)来分离单个通道中的亮度分量,因此,可以选择YCrCb并用Y通道(亮度)进行均衡
算法步骤在此处呈现在代码上

	//1.用cvtcolor函数将BGR图像转换或者输入为YCrCb
	Mat result, ycrcb;
	cv::cvtColor(src, ycrcb, COLOR_BGR2YCrCb);

	//2.将YCrCb图像的各个通道分离出来
	vector <Mat> channels;
	split(ycrcb, channels);

	//3.用equalist函数均衡在Y通道中的直方图,该函数只有两个参数,输入和输出矩阵
	cv::equalizeHist(channels[0], channels[0]);

	//4.合并生成的通道并将其转换为BGR格式
	cv::merge(channels, ycrcb);
	cv::cvtColor(ycrcb, result, COLOR_YCrCb2BGR);

	imshow("dst", result);

Lomography效果

简单地说,LOMO效果是一种渐变光晕的效果,不太好描述,请查阅百度上的相关图片
应用:在图像中产生一种光环的效果,在LOMO效果之后可以使用blur滤镜函数对光影图像应用大模糊,以获得平滑效果。

实现LOMO效果的步骤

1.通过使用查找表(LUTk)将一个曲线应用于(B/G/R)任一通道来实现颜色操作效果
2.通过对图像应用暗晕来实现复古效果

了解LUT技术

LUT是向量或表,它返回给定值的预处理值,以便在存储器中执行计算。LUT是一种常用的技术,用于通过避免重复执行耗时的计算来节省CPU周期。我们不对每个像素调用exponential/divide函数,而是只对每个可能的像素值执行一次(256)次,并将结果存储在表中。
Opencv提供LUT函数(输入图像,查找表的矩阵,输出图像)

代码实现

	//1.计算lut变量
	const double exponential_e = std::exp(1.0);
	Mat lut(1, 256, CV_8UC1);
	Mat res;
	UCHAR* plut = lut.data;
	for (int i = 0; i < 256; i++) {
		double x = (double)(i / 256.0);
		plut[i] = cvRound(256 * (1.0 / 1.0 + pow(exponential_e, -((x - 0.5)/0.1))));
	}//类似于根据新的计算公式,把原像素值映射到新像素值上去

	//2.分离通道
	std::vector<Mat> bgr;
	split(src, bgr);

	//3.调用LUT函数处理红色通道
	cv::LUT(bgr[2], lut, bgr[2]);

	//4.合并通道
	cv::merge(bgr, res);

	//5.创建黑暗光环
	Mat holo(src.size(), CV_32FC3, Scalar(0.3, 0.3, 0.3));
	circle(holo, Point(src.cols / 2, src.rows / 2), src.cols / 3, Scalar(1, 1, 1));

	//6.将光环放到原图像上去,将两个图像相乘
	Mat resultf;
	res.convertTo(resultf, CV_32FC3);
	cv::multiply(resultf, holo, resultf);
	resultf.convertTo(res, CV_8UC3);
	imshow("res", res);

卡通效果

算法步骤

1.边缘检测
2.颜色过滤
这也是我们需要学习的技术

代码演示

	//1.去噪声,根据噪声特点
	cv::medianBlur(src, src, 3);

	//2.消除噪声之后,用Canny检测算子检测强边缘
	Mat imgCanny;
	cv::Canny(src, imgCanny, 50, 150);
	Mat kernel = getStructuringElement(MORPH_RECT, Size(2, 2));
	cv::dilate(imgCanny, imgCanny,kernel);

	//3.将边缘加到图像上去,同样的可以用相乘的方法
	//当然直接相乘会直接溢出,达不到效果的因此要先整到0-1的区间
	imgCanny = imgCanny / 255;
	imgCanny = 1 - imgCanny;

	Mat imgCannyf;
	imgCanny.convertTo(imgCannyf, CV_32FC3);

	//小操作
	blur(imgCannyf, imgCannyf, Size(5, 5));

	//接下来就是颜色过滤了
	//4.为了得到卡通外观,使用bilaternal双边滤波滤镜
	Mat imgBF;
	cv::bilateralFilter(src, imgBF, 9, 150.0, 150.0);
	//当直径大于5的时候,bilateral开始变慢
	//当sigma的值大于150的时候,会出现卡通效果

	//小技巧:为了获得更明显的卡通效果,通过乘和除将可能的颜色值截断为10
	Mat result = imgBF / 25;
	result = result * 25;

	//5.合并边缘结果和颜色效果
	Mat imgCanny3c;
	Mat cannyChannel[] = { imgCannyf,imgCannyf,imgCannyf };
	cv::merge(cannyChannel,3,imgCanny3c);
	//同样的在乘于之前先转化图像类型
	Mat resultf;
	result.convertTo(resultf, CV_32FC3);
	cv::multiply(resultf, imgCanny3c, resultf);
	resultf.convertTo(result, CV_8UC3);
	imshow("res", result);

总结

通过这个学习章节,应当学到求取直方图的方法,通过直方图,求取类间方差,通过遍历类间方差来找到阈值。
通过直方图均衡化来提高图像的对比度
实现LOMO效果的时候,应当学会使用LUT对将要计算像素值进行预处理
实现卡通效果的时候,注意merge函数内部的参数使用,以及实现图像合并的时候,做乘法先要转化数据类型8UC3->32FC3->8UC3这样的步骤来做.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值