常用图像增强算法原理及 OpenCV C++ 实现

一、引言

图像增强是数字图像处理中的一个重要分支,其目的是改善图像的视觉效果,突出图像中的重要信息,或者将图像转换为更适合人或机器分析处理的形式。在实际应用中,图像增强技术广泛应用于医学影像、遥感图像、安防监控等领域。本文将详细介绍常用的图像增强算法原理,并给出基于 OpenCV C++ 库的实现代码。

二、图像增强算法分类

图像增强算法可以分为空间域增强和频域增强两大类。空间域增强是直接对图像的像素值进行操作,而频域增强则是先将图像转换到频域,对频域系数进行处理后再转换回空间域。

2.1 空间域增强算法

2.1.1 灰度变换

灰度变换是最基本的空间域增强方法,它通过对图像的每个像素的灰度值进行某种变换来改善图像的对比度。常见的灰度变换包括线性变换、对数变换、幂律变换等。

2.1.1.1 线性变换

线性变换是最简单的灰度变换方法,其基本思想是将图像的灰度值进行线性拉伸或压缩,以增强图像的对比度。线性变换的公式为:

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

void linearTransformation(cv::Mat& image, double a, double b) {
    cv::Mat result = cv::Mat::zeros(image.size(), image.type());
    for (int i = 0; i < image.rows; i++) {
        for (int j = 0; j < image.cols; j++) {
            result.at<uchar>(i, j) = cv::saturate_cast<uchar>(a * image.at<uchar>(i, j) + b);
        }
    }
    cv::imshow("Original Image", image);
    cv::imshow("Linear Transformed Image", result);
    cv::waitKey(0);
}

int main() {
    cv::Mat image = cv::imread("test.jpg", cv::IMREAD_GRAYSCALE);
    if (image.empty()) {
        std::cout << "Could not open or find the image" << std::endl;
        return -1;
    }
    double a = 2.0;  // 斜率
    double b = 0;    // 截距
    linearTransformation(image, a, b);
    return 0;
}

在上述代码中,linearTransformation 函数实现了线性变换。通过遍历图像的每个像素,将其灰度值乘以斜率 a 并加上截距 b,然后使用 cv::saturate_cast<uchar> 函数确保结果在 [0, 255] 范围内。

2.1.1.2 对数变换

对数变换可以将图像的低灰度值部分进行拉伸,高灰度值部分进行压缩,从而增强图像的暗部细节。对数变换的公式为:

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

void logTransformation(cv::Mat& image) {
    cv::Mat result = cv::Mat::zeros(image.size(), image.type());
    double c = 255 / std::log(1 + 255);
    for (int i = 0; i < image.rows; i++) {
        for (int j = 0; j < image.cols; j++) {
            result.at<uchar>(i, j) = cv::saturate_cast<uchar>(c * std::log(1 + image.at<uchar>(i, j)));
        }
    }
    cv::imshow("Original Image", image);
    cv::imshow("Log Transformed Image", result);
    cv::waitKey(0);
}

int main() {
    cv::Mat image = cv::imread("test.jpg", cv::IMREAD_GRAYSCALE);
    if (image.empty()) {
        std::cout << "Could not open or find the image" << std::endl;
        return -1;
    }
    logTransformation(image);
    return 0;
}

在上述代码中,logTransformation 函数实现了对数变换。首先计算常数 c,然后遍历图像的每个像素,对其灰度值进行对数变换,并将结果乘以 c

2.1.1.3 幂律变换

幂律变换也称为伽马变换,它可以通过调整伽马值来增强或减弱图像的对比度。幂律变换的公式为:

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

void powerLawTransformation(cv::Mat& image, double gamma) {
    cv::Mat result = cv::Mat::zeros(image.size(), image.type());
    double c = 255 / std::pow(255, gamma);
    for (int i = 0; i < image.rows; i++) {
        for (int j = 0; j < image.cols; j++) {
            result.at<uchar>(i, j) = cv::saturate_cast<uchar>(c * std::pow(image.at<uchar>(i, j), gamma));
        }
    }
    cv::imshow("Original Image", image);
    cv::imshow("Power Law Transformed Image", result);
    cv::waitKey(0);
}

int main() {
    cv::Mat image = cv::imread("test.jpg", cv::IMREAD_GRAYSCALE);
    if (image.empty()) {
        std::cout << "Could not open or find the image" << std::endl;
        return -1;
    }
    double gamma = 0.5;
    powerLawTransformation(image, gamma);
    return 0;
}

在上述代码中,powerLawTransformation 函数实现了幂律变换。首先计算常数 c,然后遍历图像的每个像素,对其灰度值进行幂律变换,并将结果乘以 c

2.1.2 直方图均衡化

直方图均衡化是一种常用的图像增强方法,它通过对图像的灰度直方图进行调整,使得图像的灰度分布更加均匀,从而增强图像的对比度。直方图均衡化的基本步骤如下:

  1. 计算图像的灰度直方图。
  2. 计算累积直方图。
  3. 根据累积直方图进行灰度映射。
#include <opencv2/opencv.hpp>
#include <iostream>

void histogramEqualization(cv::Mat& image) {
    cv::Mat equalizedImage;
    cv::equalizeHist(image, equalizedImage);
    cv::imshow("Original Image", image);
    cv::imshow("Equalized Image", equalizedImage);
    cv::waitKey(0);
}

int main() {
    cv::Mat image = cv::imread("test.jpg", cv::IMREAD_GRAYSCALE);
    if (image.empty()) {
        std::cout << "Could not open or find the image" << std::endl;
        return -1;
    }
    histogramEqualization(image);
    return 0;
}

在上述代码中,histogramEqualization 函数使用 cv::equalizeHist 函数实现了直方图均衡化。该函数会自动计算图像的灰度直方图和累积直方图,并进行灰度映射。

2.1.3 局部直方图均衡化

局部直方图均衡化是对直方图均衡化的改进,它将图像分成多个小块,对每个小块分别进行直方图均衡化,从而更好地保留图像的局部细节。OpenCV 中可以使用 cv::createCLAHE 函数实现局部直方图均衡化。

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

void localHistogramEqualization(cv::Mat& image) {
    cv::Ptr<cv::CLAHE> clahe = cv::createCLAHE();
    cv::Mat equalizedImage;
    clahe->apply(image, equalizedImage);
    cv::imshow("Original Image", image);
    cv::imshow("Locally Equalized Image", equalizedImage);
    cv::waitKey(0);
}

int main() {
    cv::Mat image = cv::imread("test.jpg", cv::IMREAD_GRAYSCALE);
    if (image.empty()) {
        std::cout << "Could not open or find the image" << std::endl;
        return -1;
    }
    localHistogramEqualization(image);
    return 0;
}

在上述代码中,localHistogramEqualization 函数使用 cv::createCLAHE 函数创建一个局部直方图均衡化对象,然后调用 apply 方法对图像进行处理。

2.1.4 空域滤波

空域滤波是通过在图像的空间域上对像素及其邻域进行操作来增强图像。常见的空域滤波包括平滑滤波和锐化滤波。

2.1.4.1 平滑滤波

平滑滤波的目的是去除图像中的噪声,常见的平滑滤波方法包括均值滤波、中值滤波和高斯滤波。

2.1.4.1.1 均值滤波

均值滤波是一种简单的平滑滤波方法,它将每个像素的值替换为其邻域内所有像素的平均值。

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

void meanFiltering(cv::Mat& image, int kernelSize) {
    cv::Mat filteredImage;
    cv::blur(image, filteredImage, cv::Size(kernelSize, kernelSize));
    cv::imshow("Original Image", image);
    cv::imshow("Mean Filtered Image", filteredImage);
    cv::waitKey(0);
}

int main() {
    cv::Mat image = cv::imread("test.jpg", cv::IMREAD_GRAYSCALE);
    if (image.empty()) {
        std::cout << "Could not open or find the image" << std::endl;
        return -1;
    }
    int kernelSize = 3;
    meanFiltering(image, kernelSize);
    return 0;
}

在上述代码中,meanFiltering 函数使用 cv::blur 函数实现了均值滤波。cv::Size(kernelSize, kernelSize) 指定了滤波核的大小。

2.1.4.1.2 中值滤波

中值滤波是一种非线性滤波方法,它将每个像素的值替换为其邻域内所有像素的中值。中值滤波对椒盐噪声有很好的去除效果。

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

void medianFiltering(cv::Mat& image, int kernelSize) {
    cv::Mat filteredImage;
    cv::medianBlur(image, filteredImage, kernelSize);
    cv::imshow("Original Image", image);
    cv::imshow("Median Filtered Image", filteredImage);
    cv::waitKey(0);
}

int main() {
    cv::Mat image = cv::imread("test.jpg", cv::IMREAD_GRAYSCALE);
    if (image.empty()) {
        std::cout << "Could not open or find the image" << std::endl;
        return -1;
    }
    int kernelSize = 3;
    medianFiltering(image, kernelSize);
    return 0;
}

在上述代码中,medianFiltering 函数使用 cv::medianBlur 函数实现了中值滤波。kernelSize 指定了滤波核的大小,必须为奇数。

2.1.4.1.3 高斯滤波

高斯滤波是一种线性平滑滤波方法,它根据高斯函数对邻域内的像素进行加权平均。高斯滤波对高斯噪声有很好的去除效果。

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

void gaussianFiltering(cv::Mat& image, int kernelSize, double sigma) {
    cv::Mat filteredImage;
    cv::GaussianBlur(image, filteredImage, cv::Size(kernelSize, kernelSize), sigma);
    cv::imshow("Original Image", image);
    cv::imshow("Gaussian Filtered Image", filteredImage);
    cv::waitKey(0);
}

int main() {
    cv::Mat image = cv::imread("test.jpg", cv::IMREAD_GRAYSCALE);
    if (image.empty()) {
        std::cout << "Could not open or find the image" << std::endl;
        return -1;
    }
    int kernelSize = 3;
    double sigma = 1.0;
    gaussianFiltering(image, kernelSize, sigma);
    return 0;
}

在上述代码中,gaussianFiltering 函数使用 cv::GaussianBlur 函数实现了高斯滤波。cv::Size(kernelSize, kernelSize) 指定了滤波核的大小,sigma 指定了高斯函数的标准差。

2.1.4.2 锐化滤波

锐化滤波的目的是增强图像的边缘和细节,常见的锐化滤波方法包括梯度算子、拉普拉斯算子等。

2.1.4.2.1 梯度算子

梯度算子可以检测图像中的边缘,常见的梯度算子包括 Sobel 算子和 Prewitt 算子。

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

void sobelFiltering(cv::Mat& image) {
    cv::Mat grad_x, grad_y;
    cv::Mat abs_grad_x, abs_grad_y;
    cv::Sobel(image, grad_x, CV_16S, 1, 0, 3);
    cv::Sobel(image, grad_y, CV_16S, 0, 1, 3);
    cv::convertScaleAbs(grad_x, abs_grad_x);
    cv::convertScaleAbs(grad_y, abs_grad_y);
    cv::Mat grad;
    cv::addWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5, 0, grad);
    cv::imshow("Original Image", image);
    cv::imshow("Sobel Filtered Image", grad);
    cv::waitKey(0);
}

int main() {
    cv::Mat image = cv::imread("test.jpg", cv::IMREAD_GRAYSCALE);
    if (image.empty()) {
        std::cout << "Could not open or find the image" << std::endl;
        return -1;
    }
    sobelFiltering(image);
    return 0;
}

在上述代码中,sobelFiltering 函数使用 cv::Sobel 函数计算图像在 x 和 y 方向上的梯度,然后使用 cv::convertScaleAbs 函数将梯度值转换为 uchar 类型,最后使用 cv::addWeighted 函数将两个方向的梯度进行加权求和。

2.1.4.2.2 拉普拉斯算子

拉普拉斯算子是一种二阶导数算子,它可以检测图像中的边缘和细节。

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

void laplacianFiltering(cv::Mat& image) {
    cv::Mat laplacian;
    cv::Laplacian(image, laplacian, CV_16S, 3);
    cv::Mat abs_laplacian;
    cv::convertScaleAbs(laplacian, abs_laplacian);
    cv::imshow("Original Image", image);
    cv::imshow("Laplacian Filtered Image", abs_laplacian);
    cv::waitKey(0);
}

int main() {
    cv::Mat image = cv::imread("test.jpg", cv::IMREAD_GRAYSCALE);
    if (image.empty()) {
        std::cout << "Could not open or find the image" << std::endl;
        return -1;
    }
    laplacianFiltering(image);
    return 0;
}

在上述代码中,laplacianFiltering 函数使用 cv::Laplacian 函数计算图像的拉普拉斯变换,然后使用 cv::convertScaleAbs 函数将结果转换为 uchar 类型。

2.2 频域增强算法

频域增强算法是先将图像转换到频域,对频域系数进行处理后再转换回空间域。常见的频域增强算法包括低通滤波、高通滤波和带通滤波。

2.2.1 傅里叶变换

傅里叶变换是将图像从空间域转换到频域的一种方法。OpenCV 中可以使用 cv::dft 函数实现离散傅里叶变换。

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

void fourierTransform(cv::Mat& image) {
    cv::Mat padded;
    int m = cv::getOptimalDFTSize(image.rows);
    int n = cv::getOptimalDFTSize(image.cols);
    cv::copyMakeBorder(image, padded, 0, m - image.rows, 0, n - image.cols, cv::BORDER_CONSTANT, cv::Scalar::all(0));
    cv::Mat planes[] = { cv::Mat_<float>(padded), cv::Mat::zeros(padded.size(), CV_32F) };
    cv::Mat complexI;
    cv::merge(planes, 2, complexI);
    cv::dft(complexI, complexI);
    cv::split(complexI, planes);
cv::magnitude(planes[0], planes[1], planes[0]);
cv::Mat magI = planes[0];
// 对数变换以增强显示效果
magI += cv::Scalar::all (1);
cv::log (magI, magI);
// 裁剪频谱
magI = magI (cv::Rect (0, 0, magI.cols & -2, magI.rows & -2));
// 归一化显示
cv::normalize (magI, magI, 0, 1, cv::NORM_MINMAX);
cv::imshow("Original Image", image);
cv::imshow("Fourier Transform Magnitude", magI);
cv::waitKey(0);
}
int main() {
cv::Mat image = cv::imread("test.jpg", cv::IMREAD_GRAYSCALE);
if (image.empty()) {
std::cout << "Could not open or find the image" << std::endl;
return -1;
}
fourierTransform(image);
return 0;
}
2.2.2 低通滤波

低通滤波的目的是保留图像的低频部分,去除高频部分,从而达到平滑图像的效果。在频域中,低通滤波可以通过设计一个低通滤波器,将高频部分的系数置为零,然后进行逆傅里叶变换得到平滑后的图像。

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

void lowPassFiltering(cv::Mat& image, int radius) {
    cv::Mat padded;
    int m = cv::getOptimalDFTSize(image.rows);
    int n = cv::getOptimalDFTSize(image.cols);
    cv::copyMakeBorder(image, padded, 0, m - image.rows, 0, n - image.cols, cv::BORDER_CONSTANT, cv::Scalar::all(0));

    cv::Mat planes[] = { cv::Mat_<float>(padded), cv::Mat::zeros(padded.size(), CV_32F) };
    cv::Mat complexI;
    cv::merge(planes, 2, complexI);
    cv::dft(complexI, complexI);

    // 创建低通滤波器
    cv::Mat filter = cv::Mat::zeros(complexI.size(), CV_32F);
    cv::Point center = cv::Point(complexI.cols / 2, complexI.rows / 2);
    for (int i = 0; i < complexI.rows; i++) {
        for (int j = 0; j < complexI.cols; j++) {
            double d = std::sqrt(std::pow(i - center.y, 2) + std::pow(j - center.x, 2));
            if (d <= radius) {
                filter.at<float>(i, j) = 1;
            }
        }
    }

    cv::Mat planesFilter[] = { filter, filter };
    cv::Mat complexFilter;
    cv::merge(planesFilter, 2, complexFilter);

    // 应用滤波器
    cv::mulSpectrums(complexI, complexFilter, complexI, 0);

    // 逆傅里叶变换
    cv::Mat inverseTransform;
    cv::idft(complexI, inverseTransform, cv::DFT_SCALE | cv::DFT_REAL_OUTPUT);

    // 裁剪结果
    cv::Mat result = inverseTransform(cv::Rect(0, 0, image.cols, image.rows));

    cv::imshow("Original Image", image);
    cv::imshow("Low Pass Filtered Image", result);
    cv::waitKey(0);
}

int main() {
    cv::Mat image = cv::imread("test.jpg", cv::IMREAD_GRAYSCALE);
    if (image.empty()) {
        std::cout << "Could not open or find the image" << std::endl;
        return -1;
    }
    int radius = 30;
    lowPassFiltering(image, radius);
    return 0;
}

在上述代码中,lowPassFiltering 函数实现了低通滤波。首先对图像进行傅里叶变换,然后创建一个低通滤波器,将滤波器与频域图像相乘,最后进行逆傅里叶变换得到平滑后的图像。

2.2.3 高通滤波

高通滤波的目的是保留图像的高频部分,去除低频部分,从而增强图像的边缘和细节。与低通滤波类似,高通滤波也是在频域中设计一个高通滤波器,将低频部分的系数置为零,然后进行逆傅里叶变换。

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

void highPassFiltering(cv::Mat& image, int radius) {
    cv::Mat padded;
    int m = cv::getOptimalDFTSize(image.rows);
    int n = cv::getOptimalDFTSize(image.cols);
    cv::copyMakeBorder(image, padded, 0, m - image.rows, 0, n - image.cols, cv::BORDER_CONSTANT, cv::Scalar::all(0));

    cv::Mat planes[] = { cv::Mat_<float>(padded), cv::Mat::zeros(padded.size(), CV_32F) };
    cv::Mat complexI;
    cv::merge(planes, 2, complexI);
    cv::dft(complexI, complexI);

    // 创建高通滤波器
    cv::Mat filter = cv::Mat::ones(complexI.size(), CV_32F);
    cv::Point center = cv::Point(complexI.cols / 2, complexI.rows / 2);
    for (int i = 0; i < complexI.rows; i++) {
        for (int j = 0; j < complexI.cols; j++) {
            double d = std::sqrt(std::pow(i - center.y, 2) + std::pow(j - center.x, 2));
            if (d <= radius) {
                filter.at<float>(i, j) = 0;
            }
        }
    }

    cv::Mat planesFilter[] = { filter, filter };
    cv::Mat complexFilter;
    cv::merge(planesFilter, 2, complexFilter);

    // 应用滤波器
    cv::mulSpectrums(complexI, complexFilter, complexI, 0);

    // 逆傅里叶变换
    cv::Mat inverseTransform;
    cv::idft(complexI, inverseTransform, cv::DFT_SCALE | cv::DFT_REAL_OUTPUT);

    // 裁剪结果
    cv::Mat result = inverseTransform(cv::Rect(0, 0, image.cols, image.rows));

    cv::imshow("Original Image", image);
    cv::imshow("High Pass Filtered Image", result);
    cv::waitKey(0);
}

int main() {
    cv::Mat image = cv::imread("test.jpg", cv::IMREAD_GRAYSCALE);
    if (image.empty()) {
        std::cout << "Could not open or find the image" << std::endl;
        return -1;
    }
    int radius = 30;
    highPassFiltering(image, radius);
    return 0;
}

在上述代码中,highPassFiltering 函数实现了高通滤波。与低通滤波不同的是,高通滤波器将低频部分的系数置为零,保留高频部分的系数。

2.2.4 带通滤波

带通滤波是一种同时保留图像中一定频率范围内的信息,去除低频和高频部分的滤波方法。带通滤波可以通过设计一个带通滤波器,将不在指定频率范围内的系数置为零,然后进行逆傅里叶变换。

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

void bandPassFiltering(cv::Mat& image, int innerRadius, int outerRadius) {
    cv::Mat padded;
    int m = cv::getOptimalDFTSize(image.rows);
    int n = cv::getOptimalDFTSize(image.cols);
    cv::copyMakeBorder(image, padded, 0, m - image.rows, 0, n - image.cols, cv::BORDER_CONSTANT, cv::Scalar::all(0));

    cv::Mat planes[] = { cv::Mat_<float>(padded), cv::Mat::zeros(padded.size(), CV_32F) };
    cv::Mat complexI;
    cv::merge(planes, 2, complexI);
    cv::dft(complexI, complexI);

    // 创建带通滤波器
    cv::Mat filter = cv::Mat::zeros(complexI.size(), CV_32F);
    cv::Point center = cv::Point(complexI.cols / 2, complexI.rows / 2);
    for (int i = 0; i < complexI.rows; i++) {
        for (int j = 0; j < complexI.cols; j++) {
            double d = std::sqrt(std::pow(i - center.y, 2) + std::pow(j - center.x, 2));
            if (d >= innerRadius && d <= outerRadius) {
                filter.at<float>(i, j) = 1;
            }
        }
    }

    cv::Mat planesFilter[] = { filter, filter };
    cv::Mat complexFilter;
    cv::merge(planesFilter, 2, complexFilter);

    // 应用滤波器
    cv::mulSpectrums(complexI, complexFilter, complexI, 0);

    // 逆傅里叶变换
    cv::Mat inverseTransform;
    cv::idft(complexI, inverseTransform, cv::DFT_SCALE | cv::DFT_REAL_OUTPUT);

    // 裁剪结果
    cv::Mat result = inverseTransform(cv::Rect(0, 0, image.cols, image.rows));

    cv::imshow("Original Image", image);
    cv::imshow("Band Pass Filtered Image", result);
    cv::waitKey(0);
}

int main() {
    cv::Mat image = cv::imread("test.jpg", cv::IMREAD_GRAYSCALE);
    if (image.empty()) {
        std::cout << "Could not open or find the image" << std::endl;
        return -1;
    }
    int innerRadius = 20;
    int outerRadius = 50;
    bandPassFiltering(image, innerRadius, outerRadius);
    return 0;
}

在上述代码中,bandPassFiltering 函数实现了带通滤波。通过设计一个带通滤波器,将不在指定频率范围内的系数置为零,然后进行逆傅里叶变换得到带通滤波后的图像。