计算机视觉 - 图像增强应用实践 (基础篇)C++ OpenCV

  环境配置我之前是跟着B站的一个UP主弄的:VS2019-Opencv4.5.2安装教程(win11上安装跟win10系统安装没有任何区别)_哔哩哔哩_bilibili

(但是不知道是不是高版本的问题,有一些库是缺失的,比如我做到后期想用face一类的头文件,缺失!还有OpenCv里的一些训练器进行人脸识别也用不了,应该要先用cmake进行编译资源(contribute)这里后续我会重新再弄一下,再配置一下,有需要的我会再出一篇文去讲解

p.s. 写在前面的话:此文章单纯是为了小伙伴有需要的可以参考一下,另一方面我在这里也只是为了存个档。(一些图片是在网上找的,用来实验测试,如有侵权,请联系单删~)

实验目的:

1.熟悉图像的表示及基本元素、通道操作;

2.掌握基本图像增强方法;

3.掌握OpenCV计算机视觉库;

1.图像的基本通道操作:

1-1彩色图分离三通道、三通道并合并

首先分别使用split函数把原图分成三个可视通道,再分别输出;

然后再创建一个矩阵,赋予矩阵大小和元素种类,方便接下来的操作,合并是使用了merge函数。不过首先先分别创建一个可操作可赋值的图像数组,使用Scalar进行赋值操作,最后实现合并通道;

#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;

class Show {
public:
	void spllit();//实现三通道分离
	int combine();//合并三通道
};

void Show::spllit() {
	Mat src_color = imread("HappyFish.jpg");
	std::vector<cv::Mat> channels;
	split(src_color, channels);
	Mat B = channels.at(0);
	Mat G = channels.at(1);
	Mat R = channels.at(2);
	imshow("red", R);
	imshow("blue", B);
	imshow("green", G);
	imshow("原图", src_color);
}

int Show::combine() {
	Mat src = imread("HappyFish.jpg");
	if (src.empty()) {
		printf("fail to show\n");
		return -1;
	}

	std::vector<Mat> pho;//动态分配数组
	Mat dst1, dst2, dst3;
	Mat des1, des2, des3;

	split(src, pho);
	dst1 = Mat(src.size(), src.type()); 
    //创建一个矩阵根据src的大小和类型,赋予了矩阵的大小和矩阵元素的种类

	pho[0] = Scalar(0);
	merge(pho, dst1);//合并
	imshow("合并yellow", dst1);// yellow

	split(src, pho);
	dst2 = Mat(src.size(), src.type());
	pho[1] = Scalar(0);
	merge(pho, dst2);
	imshow("合并red", dst2);// red

	split(src, pho);
	dst3 = Mat(src.size(), src.type());
	pho[2] = Scalar(0);//blue
	merge(pho, dst3);
	imshow("合并blue", dst3);
}


int main(int argc, const char* argv[]){
	float chance[256];
	Show a;
	a.spllit();
	a.combine();
	
	waitKey(0);
}

效果如下: 

2.图像的增强方法

2-1直方图均衡化

直方图均衡化是通过拉伸像素强度分布范围来增强图像对比度的一种方法.

目的:通过均衡化处理使得输入图像的直方图变得范围较广以及分布较均匀。

这里先自己创建一个Equalize函数,里面实现的是:先计算直方图0~254,计算出矩阵大小:

 (这个公式可以分开理解为,先是计算灰度图像的直方图分布,然后得到每个级别灰度的概率并依次求和,再乘以灰度级范围)

然后再计算分布函数f(x),最后实现灰度变换;

#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;


void Equalize(Mat& src, Mat& dst) {
	dst.create(src.size(), src.type());

	int histogram[256] = { 0 };//直方图计算 0~255
	int totalnum = src.rows * src.cols;
	uchar* data = src.data;//方便接下来进行计算
	for (int i = 0; i < totalnum; ++i) {
		++histogram[data[i]];
	}


	int Huidu[256], sum = 0;
	for (int i = 0; i < 256; ++i) {
		sum += histogram[i];
		Huidu[i] = 255.0 * sum / totalnum;
	}

	uchar* dataOfSrc = src.data;
	uchar* dataOfDst = dst.data;
	for (int i = 0; i < totalnum; ++i)
		dataOfDst[i] = Huidu[dataOfSrc[i]];
}


int main(int argc, char** argv){
	Mat src, src1, dst;
	src = imread("高山.jpg");
	src1 = imread("高山.jpg");
	if (src1.empty()) {std::cout << "The Pic is showing fail!" << std::endl;return -1;}

	cvtColor(src1, src1, COLOR_BGR2GRAY);

	equalizeHist(src1, dst);//库函数
	//Equalize(src1, dst);

	imshow("原图", src);
	//imshow("灰度图", src1);
	//imshow("均衡化", dst);
	imshow("库函数灰度图", src1);
	imshow("库函数均衡化", dst);

	waitKey(0);
	return 0;
}

实现效果如下:

比对灰度图,可以看到明亮模糊的地方得到了加强,给出的代码里边可以测试官方给出的库函数equalizeHist和自己根据原理编写的函数Equalize有什么不同

 

左是Equalize 右是equalizeHist

(二者区别不是很大,但是使用了库函数的图像得到锐化处理)

 

库函数: 

 

 

2-2 图像的加减乘除操作

–调用库函数add(), subtract(),multipy(),divide(),实现一些基本操作;

参与运算的图像必须相同的大小和类型(输出图像如果合适不符,那么他会被重新分配)同样,由于运算是逐像素进行的,输入图像之一也可以作为输出图像。

#include<opencv2/opencv.hpp>
#include<iostream>
using namespace cv;


class show {
public:
	void ADD(Mat& src1);
	void SUB(Mat& src1);
	void MUP(Mat& src1);
	void DIV(Mat& src1);
};



void show::ADD(Mat& src1) {
	Mat dst1;
	dst1 = src1 + Scalar(50.50, 50);
	imshow("加法", dst1);
}

void show::SUB(Mat& src1) {
	Mat dst1;
	dst1 = src1 - Scalar(50.50, 50);
	imshow("减法", dst1);
}

void show::MUP(Mat& src1) {
	Mat dst1;
	Mat drawer = Mat::zeros(scr1.size(), scr1.type());
	drawer = Scalar(2, 2, 2);
	multiply(drawer, scr1, dst1);
	imshow("乘法", dst1)
}

void show::DIV(Mat& src1) {
	Mat dst1;
	dst1 = src1 / Scalar(2, 2, 2);
	imshow("除法", dst1);
}

int  main(int argc, char** argv) {

	Mat src1 = imread("操场.jpeg");
	a.ADD(src1);
	a.SUB(src1);
	a.MUP(src1);
	a.DIV(src1);
	waitKey(0);
	destroyAllWindows();
	return 0;
}

 效果如下: 

通过Scaler函数直接在图像上进行点运算,实现加减乘除:加法操作看起来比原图稍微明亮点,像素值得到了提高,减法操作则是使得图像的色调总体偏暗,而除法操作则是图像总体调暗了,最后乘法操作实现了“曝光”,把大体的明亮处提高了几倍;

 

2-3 基于图像的加减乘除做加权操作(简单应用)

opencv给我们重载了操作符,上面的操作可以换成下面:reslut = 0.7 * image + 0.9 * image,效果是一样的。有时候我们想对彩色图像的一个通道进行运算,我们可以使用cv::split函数将彩色图像的三个通道分别拷贝到三个独立的cv::Mat实例中,然后对其中一个通道单独处理:

调用函数实现加权操作;(简单的实现加减乘除图像加权)

#include<opencv2/opencv.hpp>
#include<iostream>
using namespace cv;

class show {
public:
	void ADD(Mat & src1, Mat &src2);
	void SUB(Mat& src1, Mat& src2);
	void MUP(Mat& src1, Mat& src2);
	void DIV(Mat& src1, Mat& src2);

};

void show::ADD(Mat& src1, Mat& src2) {
	Mat dst1;
	add(src1, src2, dst1);
	imshow("加法", dst1);
}

void show::SUB(Mat& src1, Mat& src2) {
	Mat dst2;
	subtract(src1, src2, dst2);
	imshow("减法", dst2);

}

void show::MUP(Mat& src1, Mat& src2) {
	Mat dst3;
	multiply(src1, src2, dst3);
	imshow("乘法", dst3);
}

void show::DIV(Mat& src1, Mat& src2) {
	Mat dst4;
	divide(src1, src2, dst4);
	imshow("除法", dst4);
}

int  main(int argc, char** argv) {
	Mat src1 = imread("操场.jpeg");
	Mat src2 = imread("nike.jpg");
	if (src1.empty() || src2.empty()) {printf("fail to show");return -1;}
	imshow("背景", src1);
	imshow("图标", src2);

	show a;
	a.ADD(src1, src2);
	a.SUB(src1, src2);
	a.MUP(src1, src2);
	a.DIV(src1, src2);

	waitKey(0);
	destroyAllWindows();
	return 0;
}

效果如下:

2-4基于直方图规定化增强(匹配):

  首先创建项的数量,用来统计像素的最大值和最小值,为了实现匹配两张图的效果,这里用像素大小一样的图,明暗区别比较大的。这里只需要计算一个通道,单独的创建直方图的参数和函数,用来统计两张图的像素值分布,之后容易拿来对比数据分布;

  设一个函数,先查找最大值用于归一化,直方图的元素类型为32位浮点数,再进行计算原始直方图和规定直方图的累积概率,然后得到的累积概率差值构建灰度级映射表,这里的映射函数规定为单调递增的,即变换后的直方图可以经过逆变换回到原直方图。那么直方图匹配就可以根据这个性质进行处理

#include <iostream>
#include <vector>
#include <opencv2/opencv.hpp>
#include <opencv2/core.hpp>
using namespace std;
using namespace cv;

class histogram {
private:
    int histSize[1];
    float hranges[2];
    const float* ranges[1];
    int channels[1];
public:
    histogram() {
        histSize[0] = 256;//灰度大小
        hranges[0] = 0.0f;
        hranges[1] = 255.0f;
        ranges[0] = hranges;
        channels[0] = 0;
    }

    Mat getHistogram(const Mat& image) {//统计直方图
        Mat hist;
        calcHist(&image, 1, channels, Mat(), hist, 1, histSize, ranges);
        return hist;
    }

    Mat getHistogramImage(const Mat& image) {
        Mat hist = getHistogram(image);

        double maxVal = 0;
        minMaxLoc(hist, NULL, &maxVal);
        Mat histImg(histSize[0], histSize[0], CV_8U, Scalar(255));

        // 设置最高点为最大值的90%
        double hpt = 0.9 * histSize[0];
        for (int h = 0; h < histSize[0]; h++) {
            float binVal = hist.at<float>(h);
            int intensity = static_cast<int>(binVal * hpt / maxVal);
            line(histImg, Point(h, histSize[0]),
                Point(h, histSize[0] - intensity), Scalar::all(0));
        }
        return histImg;
    }
};


void HistSpecify(const Mat& src, const Mat& ref, Mat& result) {
    histogram hist1D;
    Mat src_hist = hist1D.getHistogram(src);
    Mat dst_hist = hist1D.getHistogram(ref);

    float src_cdf[256] = { 0 };
    float dst_cdf[256] = { 0 };

    src_hist /= (src.rows * src.cols);
    dst_hist /= (ref.rows * ref.cols);


    for (int i = 0; i < 256; i++) {
        if (i == 0) {
            src_cdf[i] = src_hist.at<float>(i);
            dst_cdf[i] = dst_hist.at<float>(i);
        }
        else {
            src_cdf[i] = src_cdf[i - 1] + src_hist.at<float>(i);
            dst_cdf[i] = dst_cdf[i - 1] + dst_hist.at<float>(i);
        }
    }

    float diff_cdf[256][256];
    for (int i = 0; i < 256; i++)
        for (int j = 0; j < 256; j++)
            diff_cdf[i][j] = fabs(src_cdf[i] - dst_cdf[j]);

    Mat lut(1, 256, CV_8U);
    for (int i = 0; i < 256; i++) {

        float min = diff_cdf[i][0];
        int index = 0;
        for (int j = 1; j < 256; j++) {
            if (min > diff_cdf[i][j]) {
                min = diff_cdf[i][j];
                index = j;
            }
        }
        lut.at<uchar>(i) = static_cast<uchar>(index);
    }

    LUT(src, lut, result);
}


int main() {
    Mat img2 = imread("2明亮.jpg");
    Mat img3 = imread("1暗淡.jpg");
    Mat specifyImg = Mat::zeros(img2.rows, img2.cols, img2.type());
    HistSpecify(img2, img3, specifyImg);

    histogram hist3;
    Mat histImg3 = hist3.getHistogramImage(img2);
    histogram hist4;
    Mat histImg4 = hist4.getHistogramImage(img3);
    histogram hist5;
    Mat histImg5 = hist5.getHistogramImage(specifyImg);

    imshow("目标图", img2);
    imshow("原图直方图", histImg3);
    imshow("匹配图", img3);
    imshow("匹配图直方图", histImg4);
    imshow("规定化后", specifyImg);
    imshow("规定化直方图", histImg5);
    waitKey();
    return 0;
}

 

 通过函数的实现,匹配图和目标图进行匹配处理,最后得到的图像有较大明显的明暗规定化实现,虽然最终还有点误差;

最终效果:

 

2-5 灰度反转

将一个像素点的三个颜色变量相等,R=G=B,此时该值称为灰度值

利用变换函数T将原图像素灰度值r映射为像素值s。

s = T ( r )

图像的反色变换,即图像反转,将黑色像素点变白色,白色像素点变黑色。广义的反色变换也可以应用于彩色图像,即对所有像素点取补。

	cvtColor(scr, image1_gray, COLOR_BGR2GRAY);
	imshow("灰度图", image1_gray);

	output_image = image1_gray.clone();
	for (int i = 0; i < image1_gray.rows; i++) {
		for (int j = 0; j < image1_gray.cols; j++) {
			output_image.at<uchar>(i, j) = 255 - image1_gray.at<uchar>(i, j);  //灰度反转
		}
	}

 效果:

原图和灰度反转对比

 第三张图片就是灰度值反转之后的效果

 2-6 对数变换

  因为对数曲线在像素值较低的区域斜率较大,像素值较高的区域斜率比较低,所以图像经过对数变换之后,在较暗的区域对比度将得到提升,因而能增强图像暗部的细节,直接显示频谱的话显示设备的动态范围往往不能满足要求,这个时候就需要使用对数变换,使得傅里叶频谱的动态范围被合理地非线性压缩。

对数变换表达式:

g = c*log(1 + f)  

c是常数,f是浮点数。

代码如下:

#include <opencv2/core/core.hpp>        
#include <opencv2/highgui/highgui.hpp> 
#include <opencv2/imgproc/imgproc.hpp>  
#include <iostream> 
using namespace cv;


Mat log_change(cv::Mat src, int c) {
    if (src.empty())
        std::cout << "fail to show!" << std::endl;
    cv::Mat resultImage =
        cv::Mat::zeros(src.size(), src.type());
    cv::add(src, cv::Scalar(1.0), src);
    src.convertTo(src, CV_32F);
    log_change(src, resultImage);
    resultImage = c * resultImage;
    cv::normalize(resultImage, resultImage,
        0, 255, cv::NORM_MINMAX);
    cv::convertScaleAbs(resultImage, resultImage);
    return resultImage;
}

int main() {
    Mat srcImage = cv::imread("girl.jpg", 0);
    if (!srcImage.data)
        return -1;
    imshow("srcImage", srcImage);
    float c = 1.2;
    cv::Mat resultImage;
    double tTime;
    tTime = (double)getTickCount();
    const int nTimes = 10;
    for (int i = 0; i < nTimes; i++)
    {
        resultImage = log_change(srcImage, c);
    }
    tTime = 1000 * ((double)getTickCount() - tTime) /
        getTickFrequency();
    tTime /= nTimes;
    cv::imshow("resultImage", resultImage);
    cv::waitKey(0);
    return 0;
}

效果:

 

2-7 伽马变换

伽马变换的实质就是对每一个像素进行幂函数操作,通过非线性变换,让图像中较暗的区域的灰度值得到增强,图像中灰度值过大的区域的灰度值得到降低。经过伽马变换,图像整体的细节表现会得到增强。

其中,r为灰度图像的输入值(原来的灰度值),取值范围为[0,1]。s为经过伽马变换后的灰度输出值。c为灰度缩放系数,通常取1。γ为伽马因子大小。 

#include "opencv2/opencv.hpp"
#include "opencv2/imgproc.hpp"
#include <iostream>
#include <string>
using namespace cv;
using namespace std;

void gamma_change(cv::Mat& src, cv::Mat& dst, float value, float fC){//value伽马值,FC增加像素值,类型曝光的效果
	dst = cv::Mat::zeros(src.rows, src.cols, src.type());
	assert(src.elemSize() == 1);//检查是否错误,将会在执行之后报错
	float fNormalFactor = 1.0f / 255.0f;
	for (size_t r = 0; r < src.rows; r++){
		unsigned char* pInput = src.data + r * src.step[0];
		unsigned char* pOutput = dst.data + r * dst.step[0];
		for (size_t c = 0; c < src.cols; c++){//gamma变换
			float out = std::pow(pInput[c] * fNormalFactor, value) * fC;
			out = out > 1.0f ? 1.0f : out;
			pOutput[c] = static_cast<unsigned char>(out * 255.0f);
		}
	}
}

int main() {
    Mat src = imread("0331.jpg",0); //读入灰度图
    if (src.empty()) {printf("failer to show");return -1; }
	Mat dst;
	gamma_change(src, dst, 3.21,2.23f);
    imshow("原图", src);
    imshow("Gamma", dst);
    waitKey(0);
    return 0;
}

效果如下:

 

3.基于滤波的图像增强方法

3-1 局部二值模式

(参考:https://blog.csdn.net/qq_32475739/article/details/78064969)

从图像中提取出局部特征,用LBP这种方式提取出的特征具有较低的维度。会发现图像的表达不收光照变化的影响。局部二值模式的基本思想是通过比较像素和它的邻域归纳出图像的局部构成。

以一个像素为中心,与其邻域进行比较。如果中心像素的灰度值大于它的邻域,则将其赋值为1,否则为0.

Mat localTwoValue(Mat src){
    int countRow = src.rows / 8;
    int rowLeft = src.rows % 8;
    int countCol = src.cols / 8;
    int colLeft = src.cols % 8;
    target3 = Mat::zeros(src.size(), src.type());

    for (int k = 0; k < countRow; k++){
        for (int l = 0; l < countCol; l++){
            int value = 0;
            for (int i = k * 8; i < (k + 1) * 8; i++){
                for (int j = l * 8; j < (l + 1) * 8; j++){
                    value += (int)src.at<uchar>(i, j);
                }
            }
            value = value / 64;
            for (int i = k * 8; i < (k + 1) * 8; i++) {
                for (int j = l * 8; j < (l + 1) * 8; j++){
                    if ((int)src.at<uchar>(i, j) < value)
                        target3.at<uchar>(i, j) = 0;
                    else
                        target3.at<uchar>(i, j) = 255;
                }
            }
        }
    }

 
    if (colLeft != 0){
        for (int k = 0; k < countRow; k++){
            for (int l = countCol; l < countCol + colLeft; l++){
                int value = 0;
                for (int i = k * 8; i < (k + 1) * 8; i++){
                    for (int j = countCol * 8; j < countCol * 8 + colLeft; j++) {
                        value += (int)src.at<uchar>(i, j);
                    }
                }
                value = value / (8 * colLeft);
                for (int i = k * 8; i < (k + 1) * 8; i++){
                    for (int j = countCol * 8; j < countCol * 8 + colLeft; j++) {
                        if ((int)src.at<uchar>(i, j) < value)
                            target3.at<uchar>(i, j) = 0;
                        else
                            target3.at<uchar>(i, j) = 255;
                    }
                }
            }
        }
    }

    if (rowLeft != 0 && colLeft != 0) {
        int value = 0;
        for (int i = 8 * countRow; i < src.rows; i++){
            for (int j = 8 * countCol; j < src.cols; j++){
                value += (int)src.at<uchar>(i, j);
            }
        }
        value = value / (rowLeft * colLeft);
        for (int i = 8 * countRow; i < src.rows; i++){
            for (int j = 8 * countCol; j < src.cols; j++) {
                if ((int)src.at<uchar>(i, j) < value)
                    target3.at<uchar>(i, j) = 0;
                else
                    target3.at<uchar>(i, j) = 255;
            }
        }
    }

}

效果:

 3-2 均值滤波

线性滤波算法,主要方法为邻域平均法,即用一个图像区域的各个像素的平均值来代替原图像的各个像素值。均值滤波的主要作用是减小图像灰度值的“尖锐”变化从而达到减小噪声的目的。(但是也存在边缘模糊的问题)

代码实现如下:

#include "opencv2/opencv.hpp"
#include "opencv2/imgproc.hpp"
#include <iostream>
#include <string>
using namespace cv;
using namespace std;

void Mean_pic(Mat& src, Mat& dst) {
	dst = Mat::zeros(src.size(), src.type());
	for (size_t i = 1; i < src.rows - 1; i++) {//遍历,开始进行领域运算
		for (size_t j = 1; j < src.cols - 1; j++) {
			dst.at<uchar>(i, j) = (src.at<uchar>(i - 1, j - 1) + src.at<uchar>(i - 1, j) + src.at<uchar>(i - 1, j + 1) +
				src.at<uchar>(i, j - 1) + src.at<uchar>(i, j) + src.at<uchar>(i, j + 1) +
				src.at<uchar>(i + 1, j - 1) + src.at<uchar>(i + 1, j) + src.at<uchar>(i + 1, j + 1)) / 9;
		}
	}
}

int main() {
    Mat src = imread("椒盐噪声.bmp",0); //读入灰度图
    if (src.empty()) {printf("failer to show");return -1; }
	Mat dst;
	Mean_pic(src, dst);
    imshow("原图", src);
    imshow("均值滤波", dst);
    waitKey(0);
    return 0;
}

 通过调参数可以得到较好的结果,证明均值滤波对椒盐噪声的优化效果比较好

但是图像也会因此丢失一些精度

效果如下:

      总结:一开始最难的其实是配置环境,原理这部分稍微学一下,知道个大概,自己也可以慢慢写出来,在过程中的最大感触是,opencv和c++的库函数却不完全相同,对于库函数第一次的使用和实现还是很措手不及的,就像学c++的第一句代码是 cout<<“Hello,word!”<<endl; Opencv的第一句输入图片和输出却是imread()和imshow(),带给我的感觉还是很不同的,因为在实际操作中一些细节需要花点时间改变编程习惯。在实际编写中,需要参考大量的数学公式和进行转换用代码实现,期间遇到过很多问题,最主要的是掌握如何把自己的一些想法和公式实现用代码写出来。

  • 10
    点赞
  • 56
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值