参考:
https://www.cnblogs.com/wangguchangqing/category/740760.html
1. 拉普拉斯算子锐化
算法实现:
#include <iostream>
#include <opencv2/highgui.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>
using namespace std;
using namespace cv;
int main() {
Mat src = imread("D:\\学习资料\\C++\\C++ code\\图像处理算法opencv实现\\图像处理算法opencv实现\\2.jpg",CAP_MODE_GRAY);
//opencv的sample中有图片,可以用samples::findFile找到图片
//src = imread(samples::findFile(imageName), IMREAD_COLOR);
namedWindow("OriginalImage");
imshow("OriginalImage", src);
//1、拉普拉斯锐化
Mat lp;
Mat kernel = (Mat_<short>(3, 3) << 1, 1, 1, 1, -8, 1, 1, 1, 1);//Laplace算子
filter2D(src, lp, CV_16S, kernel, Point(-1, -1), 0, BORDER_DEFAULT);
//上面两句可以用下面的opencv的Laplacian函数代替
//Laplacian(src, lp, CV_16S, 3);
//显示拉普拉斯锐化图像
Mat show_lp;
lp.convertTo(show_lp, CV_8U);
namedWindow("Laplacian-filtered image");
imshow("Laplacian-filtered image", show_lp);
//2、对锐化的图像进行scale,使其更好显示
int rows = lp.rows;
int cols = lp.cols;
Mat scaled_lp(rows, cols, CV_16S);
//下面这个是可以直接把lp取绝对值,然后再变成CV_8U,但效果跟原来的lp没什么区别
//convertScaleAbs(lp, scaled_lp);
short max = lp.at<short>(0, 0);
short min = lp.at<short>(0, 0);
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
short temp = lp.at<short>(i, j);
if (temp > max) {
max = temp;
}
if (temp < min) {
min = temp;
}
}
}
//cout << "max: " << max << endl;
//cout << "min: " << min << endl;
//归一化为0 - 255
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
short& val = scaled_lp.at<short>(i, j);
short val1 = lp.at<short>(i, j);
val = (val1 - min) * 255 / (max - min);
}
}
scaled_lp.convertTo(scaled_lp, CV_8U);
//显示
namedWindow("scaled_lp");
imshow("scaled_lp", scaled_lp);
//3、将scale后的拉普拉斯锐化图像和原图像进行混合
src.convertTo(src, CV_16S);
//lp.convertTo(lp, CV_16S);
Mat enhancedImg(rows, cols, CV_16S);
enhancedImg = src - lp;
//这段其实可以不要,因为convertTo到CV_8U的时候会自动将范围之外的值自动变成0和255
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
short& temp = enhancedImg.at<short>(i, j);
if (temp > 255) {
temp = 255;
}
else if (temp < 0) {
temp = 0;
}
}
}
//将范围scale到0-255,但效果不好
/*for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
short temp = enhancedImg.at<short>(i, j);
if (temp > max) {
max = temp;
}
if (temp < min) {
min = temp;
}
}
}
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
short& val = enhancedImg.at<short>(i, j);
val = (val - min) * 255 / (max - min);
}
}*/
enhancedImg.convertTo(enhancedImg, CV_8U);
//显示
namedWindow("enhancedImg");
imshow("enhancedImg", enhancedImg);
waitKey(0);
return 0;
}
算法结果:
2. 直方图均衡图像增强对比度
算法实现:
#include <iostream>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>
using namespace std;
using namespace cv;
int main()
{
Mat image = imread("D:\\学习资料\\C++\\C++ code\\图像处理算法opencv实现\\图像处理算法opencv实现\\lena.jpg",1);
if (image.empty())
{
std::cout << "打开图片失败,请检查" << std::endl;
return -1;
}
namedWindow("original image");
imshow("original image", image);
Mat imageRGB[3];
//下面split是将image的三通道分开,并存储在Mat[3]数组imageRGB中
split(image, imageRGB);
for (int i = 0; i < 3; i++)
{
//equalizeHist只能处理单通道8位的Mat
equalizeHist(imageRGB[i], imageRGB[i]);
}
//merge是将Mat[3]数组imageRGB的三个通道进行合并到image中
merge(imageRGB, 3, image);
namedWindow("equalized image");
imshow("equalized image", image);
waitKey(0);
return 0;
}
算法结果:
3. 基于对数Log变换的图像增强
算法讲解:
对数变换可以将图像的低灰度值部分扩展,显示出低灰度部分更多的细节,将其高灰度值部分压缩,减少高灰度值部分的细节,从而达到强调图像低灰度部分的目的。变换方法:
算法实现:
#include <iostream>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
using namespace std;
using namespace cv;
int main()
{
Mat image = imread("D:\\学习资料\\C++\\C++ code\\图像处理算法opencv实现\\图像处理算法opencv实现\\3.jpg");
Mat imageLog(image.size(), CV_32FC3);
for (int i = 0; i < image.rows; i++)
{
for (int j = 0; j < image.cols; j++)
{
//log的系数、底数以及log(1+?)都可以修改。对于不同的底数,底数越大,对低灰度部分的扩展就越强,对高灰度部分的压缩也就越强
imageLog.at<Vec3f>(i, j)[0] = log(1 + image.at<Vec3b>(i, j)[0]);
imageLog.at<Vec3f>(i, j)[1] = log(1 + image.at<Vec3b>(i, j)[1]);
imageLog.at<Vec3f>(i, j)[2] = log(1 + image.at<Vec3b>(i, j)[2]);
}
}
//归一化到0~255
normalize(imageLog, imageLog, 0, 255, CV_MINMAX);
//转换成8bit图像显示
convertScaleAbs(imageLog, imageLog);
imshow("Soure", image);
imshow("after", imageLog);
waitKey();
return 0;
}
算法结果:
4. 基于伽玛变换的图像增强
算法讲解:
伽马变换主要用于图像的校正,将灰度过高或者灰度过低的图片进行修正,增强对比度。变换公式就是对原图像上每一个像素值做乘积运算:
γ值以1为分界,值越小,对图像低灰度部分的扩展作用就越强,值越大,对图像高灰度部分的扩展作用就越强,通过不同的γ值,就可以达到增强低灰度或高灰度部分细节的作用。
伽马变换对于图像对比度偏低,并且整体亮度值偏高(对于于相机过曝)情况下的图像增强效果明显。
算法实现:
#include <iostream>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
using namespace std;
using namespace cv;
int main()
{
Mat image = imread("D:\\学习资料\\C++\\C++ code\\图像处理算法opencv实现\\图像处理算法opencv实现\\4.jpg");
Mat imageGamma(image.size(), CV_32FC3);
for (int i = 0; i < image.rows; i++)
{
for (int j = 0; j < image.cols; j++)
{
imageGamma.at<Vec3f>(i, j)[0] = (image.at<Vec3b>(i, j)[0]) * (image.at<Vec3b>(i, j)[0]) * (image.at<Vec3b>(i, j)[0]);
imageGamma.at<Vec3f>(i, j)[1] = (image.at<Vec3b>(i, j)[1]) * (image.at<Vec3b>(i, j)[1]) * (image.at<Vec3b>(i, j)[1]);
imageGamma.at<Vec3f>(i, j)[2] = (image.at<Vec3b>(i, j)[2]) * (image.at<Vec3b>(i, j)[2]) * (image.at<Vec3b>(i, j)[2]);
}
}
//归一化到0~255
normalize(imageGamma, imageGamma, 0, 255, CV_MINMAX);
//转换成8bit图像显示
convertScaleAbs(imageGamma, imageGamma);
imshow("原图", image);
imshow("伽马变换图像增强效果", imageGamma);
waitKey();
return 0;
}
算法结果:
5. 各种滤波
opencv调包算法实现:
#include<iostream>
#include<opencv2\opencv.hpp>
#include<opencv2\imgproc\imgproc.hpp>
using namespace std;
using namespace cv;
int main() {
Mat src, dst, dst1, dst2, dst3, dst31;
Mat img = imread("D:\\学习资料\\C++\\C++ code\\图像处理算法opencv实现\\图像处理算法opencv实现\\orange.jpg", 1);
img.copyTo(src);//深拷贝
double sigma1 = 10.0;
double sigma2 = 20.0;
//方框滤波,
//-1是指输出的深度跟原图一样,这里其实就是均值滤波
boxFilter(src, dst, -1, Size(5, 5));
//均值滤波,内部调用了方框 滤波
blur(src, dst1, Size(5, 5));
//高斯滤波
GaussianBlur(src, dst2, Size(9, 9), sigma1, sigma2);
//中值滤波
medianBlur(src, dst3, 7);//第三个参数表示孔径的线性尺寸,它的值必须是大于1的奇数
//双边滤波,在平滑的同时能够保留边缘
bilateralFilter(src, dst31, 25, 25 * 2, 25 / 2);
imshow("src", src);
imshow("方框", dst);
imshow("均值", dst1);
imshow("高斯", dst2);
imshow("中值", dst3);
imshow("双边", dst31);
waitKey(0);
system("pause");
return 0;
}
算法结果:
高斯滤波
高斯滤波是可分离滤波器,可以将二维的高斯滤波器分解成两个一维(水平和垂直)的高斯滤波器的叠加,分解的目的在于降低时间复杂度,分解前后的效果近似相等。
手写函数算法实现:
#include <iostream>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui.hpp>
using namespace std;
using namespace cv;
void GaussianFilter(const Mat& src, Mat& dst, int ksize, double sigma)
{
CV_Assert(src.channels()==2 || src.channels() == 3); // 只处理单通道或者三通道图像
const static double pi = 3.1415926;
// 根据窗口大小和sigma生成高斯滤波器模板
// 申请一个二维数组,存放生成的高斯模板矩阵
double** templateMatrix = new double* [ksize];
for (int i = 0; i < ksize; i++)
templateMatrix[i] = new double[ksize];
int origin = ksize / 2; // 以模板的中心为原点
double x2, y2;
double sum = 0;
for (int i = 0; i < ksize; i++)
{
x2 = pow(i - origin, 2);
for (int j = 0; j < ksize; j++)
{
y2 = pow(j - origin, 2);
// 高斯函数前的常数可以不用计算,会在归一化的过程中给消去
double g = exp(-(x2 + y2) / (2 * sigma * sigma));
sum += g;
templateMatrix[i][j] = g;
}
}
for (int i = 0; i < ksize; i++)
{
for (int j = 0; j < ksize; j++)
{
templateMatrix[i][j] /= sum;
cout << templateMatrix[i][j] << " ";
}
cout << endl;
}
// 将模板应用到图像中
int border = ksize / 2;
//边界扩展的函数
copyMakeBorder(src, dst, border, border, border, border, BorderTypes::BORDER_REFLECT);
int channels = dst.channels();
int rows = dst.rows - border;
int cols = dst.cols - border;
for (int i = border; i < rows; i++)
{
for (int j = border; j < cols; j++)
{
double sum[3] = { 0 };
for (int a = -border; a <= border; a++)
{
for (int b = -border; b <= border; b++)
{
if (channels == 1)
{
sum[0] += templateMatrix[border + a][border + b] * dst.at<uchar>(i + a, j + b);
}
else if (channels == 3)
{
Vec3b rgb = dst.at<Vec3b>(i + a, j + b);
auto k = templateMatrix[border + a][border + b];
sum[0] += k * rgb[0];
sum[1] += k * rgb[1];
sum[2] += k * rgb[2];
}
}
}
for (int k = 0; k < channels; k++)
{
if (sum[k] < 0)
sum[k] = 0;
else if (sum[k] > 255)
sum[k] = 255;
}
if (channels == 1)
dst.at<uchar>(i, j) = static_cast<uchar>(sum[0]);
else if (channels == 3)
{
Vec3b rgb = { static_cast<uchar>(sum[0]), static_cast<uchar>(sum[1]), static_cast<uchar>(sum[2]) };
dst.at<Vec3b>(i, j) = rgb;
}
}
}
// 释放模板数组
for (int i = 0; i < ksize; i++)
delete[] templateMatrix[i];
delete[] templateMatrix;
templateMatrix = NULL;
}
// 分离的计算
void separateGaussianFilter(const Mat& src, Mat& dst, int ksize, double sigma)
{
CV_Assert(src.channels() == 1 || src.channels() == 3); // 只处理单通道或者三通道图像
// 生成一维的高斯滤波模板
double* matrix = new double[ksize];
double sum = 0;
int origin = ksize / 2;
for (int i = 0; i < ksize; i++)
{
// 高斯函数前的常数可以不用计算,会在归一化的过程中给消去
double g = exp(-(i - origin) * (i - origin) / (2 * sigma * sigma));
sum += g;
matrix[i] = g;
}
// 归一化
for (int i = 0; i < ksize; i++)
matrix[i] /= sum;
// 将模板应用到图像中
int border = ksize / 2;
copyMakeBorder(src, dst, border, border, border, border, BorderTypes::BORDER_REFLECT);
int channels = dst.channels();
int rows = dst.rows - border;
int cols = dst.cols - border;
// 水平方向
for (int i = border; i < rows; i++)
{
for (int j = border; j < cols; j++)
{
double sum[3] = { 0 };
for (int k = -border; k <= border; k++)
{
if (channels == 1)
{
sum[0] += matrix[border + k] * dst.at<uchar>(i, j + k); // 行不变,列变化;先做水平方向的卷积
}
else if (channels == 3)
{
Vec3b rgb = dst.at<Vec3b>(i, j + k);
sum[0] += matrix[border + k] * rgb[0];
sum[1] += matrix[border + k] * rgb[1];
sum[2] += matrix[border + k] * rgb[2];
}
}
for (int k = 0; k < channels; k++)
{
if (sum[k] < 0)
sum[k] = 0;
else if (sum[k] > 255)
sum[k] = 255;
}
if (channels == 1)
dst.at<uchar>(i, j) = static_cast<uchar>(sum[0]);
else if (channels == 3)
{
Vec3b rgb = { static_cast<uchar>(sum[0]), static_cast<uchar>(sum[1]), static_cast<uchar>(sum[2]) };
dst.at<Vec3b>(i, j) = rgb;
}
}
}
// 竖直方向
for (int i = border; i < rows; i++)
{
for (int j = border; j < cols; j++)
{
double sum[3] = { 0 };
for (int k = -border; k <= border; k++)
{
if (channels == 1)
{
sum[0] += matrix[border + k] * dst.at<uchar>(i + k, j); // 列不变,行变化;竖直方向的卷积
}
else if (channels == 3)
{
Vec3b rgb = dst.at<Vec3b>(i + k, j);
sum[0] += matrix[border + k] * rgb[0];
sum[1] += matrix[border + k] * rgb[1];
sum[2] += matrix[border + k] * rgb[2];
}
}
for (int k = 0; k < channels; k++)
{
if (sum[k] < 0)
sum[k] = 0;
else if (sum[k] > 255)
sum[k] = 255;
}
if (channels == 1)
dst.at<uchar>(i, j) = static_cast<uchar>(sum[0]);
else if (channels == 3)
{
Vec3b rgb = { static_cast<uchar>(sum[0]), static_cast<uchar>(sum[1]), static_cast<uchar>(sum[2]) };
dst.at<Vec3b>(i, j) = rgb;
}
}
}
delete[] matrix;
}
void test01() {
Mat src = imread("D:\\学习资料\\C++\\C++ code\\图像处理算法opencv实现\\图像处理算法opencv实现\\2.jpg");
Mat dst;
int ksize = 5;
double sigma = 0.8;
GaussianFilter(src, dst, ksize, sigma);
namedWindow("src");
imshow("src", src);
namedWindow("dst");
imshow("dst", dst);
//waitKey(0);
}
void test02() {
Mat src = imread("D:\\学习资料\\C++\\C++ code\\图像处理算法opencv实现\\图像处理算法opencv实现\\2.jpg");
Mat dst;
int ksize = 5;
double sigma = 0.8;
separateGaussianFilter(src, dst, ksize, sigma);
namedWindow("dst1");
imshow("dst1", dst);
waitKey(0);
}
int main() {
test01();
test02();
system("pause");
return 0;
}
算法结果:
双边滤波器
双边滤波(Bilateral filter)是一种非线性的滤波方法,是结合图像的空间邻近度和像素值相似度的一种折衷处理,同时考虑空域信息和灰度相似性,达到保边去噪的目的。
双边滤波器之所以能够做到在平滑去噪的同时还能够很好的保存边缘(Edge Preserve),是由于其滤波器的核由两个函数生成:
- 一个函数由像素欧式距离决定滤波器模板的系数
- 另一个函数由像素的灰度差值决定滤波器的系数
其综合了高斯滤波器(Gaussian Filter)和αα-截尾均值滤波器(Alpha-Trimmed mean Filter)的特点。高斯滤波器只考虑像素间的欧式距离,其使用的模板系数随着和窗口中心的距离增大而减小;Alpha截尾均值滤波器则只考虑了像素灰度值之间的差值,去掉α%α%的最小值和最大值后再计算均值。
双边滤波器使用二维高斯函数生成距离模板,使用一维高斯函数生成值域模板。
距离模板系数的生成公式如下:
其中,(k,l)为模板窗口的中心坐标;(i,j)为模板窗口的其他系数的坐标;σd为高斯函数的标准差。 使用该公式生成的滤波器模板和高斯滤波器使用的模板是没有区别的。
值域模板系数的生成公式如下:
其中,函数f(x,y)表示要处理的图像,f(x,y)表示图像在点(x,y)处的像素值;(k,l)为模板窗口的中心坐标;(i,j)为模板窗口的其他系数的坐标;σr为高斯函数的标准差。
将上述两个模板相乘就得到了双边滤波器的模板
OpenCV中的bilateralFilter
实现,其实现主要有两个优化:
- 使用查表的方式计算灰度值模板系数
- 将二维的模板转换为一维,降低算法复杂度。
可以直接用opencv的bilateralFilter函数。