实验二 图像的空间域增强
一、实验目的
- 进一步理解图像平滑和图像锐化等空间域增强方法的原理。
- 了解图像平滑和图像锐化的效果和作用。
- 掌握图像模板运算的流程。
二、实验内容
编程实现车牌图像的去噪和锐化处理。首先对图像去噪,分别采用均值滤波器和中值滤波器进行处理,比较两种滤波器效果;然后对去噪后的图像进行锐化,分别采用一阶导数和二阶导数,比较锐化效果。
三、实验报告要求
1.给出简要的设计思路(原理)
2.给出主要代码
3.给出实验结果的屏幕截图
- 实验过程中遇到的主要问题、心得体会或建议
四、报告内容
1.均值滤波:需要构建一个全为1的卷积核,对原图像每个像素进行卷积核计算求和后平均化。注意求和过程中可能会超出类型的最大范围,因此要扩大类型,采用Vec3d sum来存储,vec3f也可以
void averageFilter(const Mat& img, int kernelSize) {
Mat averageFilter;
averageFilter.create(img.size(), img.type());
if (kernelSize % 2 == 0) kernelSize += 1; // 仅当kernelSize为偶数时增加1
int init = kernelSize / 2;
for (int y = init; y < img.rows - init; y++) {
for (int x = init; x < img.cols - init; x++) {
Vec3d sum = Vec3d(0, 0, 0);
for (int i = -init; i <= init; i++) {
for (int j = -init; j <= init; j++) {
Vec3b pixel = img.at<Vec3b>(y + i, x + j);
sum[0] += pixel[0];
sum[1] += pixel[1];
sum[2] += pixel[2];
}
}
averageFilter.at<Vec3b>(y, x)[0] = static_cast<uchar>(sum[0] / (kernelSize * kernelSize));
averageFilter.at<Vec3b>(y, x)[1] = static_cast<uchar>(sum[1] / (kernelSize * kernelSize));
averageFilter.at<Vec3b>(y, x)[2] = static_cast<uchar>(sum[2] / (kernelSize * kernelSize));
}
}
namedWindow("img");
imshow("img", img);
namedWindow("均值滤波");
imshow("均值滤波", averageFilter);
}
2.中值滤波:对这个核内的每个像素排序,取中值作为最终原图像某像素的值,需注意的是由于排序只能对三通道某一个通道的具体值进行排序,因此要对每个通道都进行一遍取中值才能保持彩色图像
Mat mediumFilter(const Mat& img, int kernelSize) {
Mat mediumfilter;
mediumfilter.create(img.size(), img.type());
if (kernelSize % 2 == 0) kernelSize += 1; // 仅当kernelSize为偶数时增加1
int init = kernelSize / 2;
for (int y = init; y < img.rows - init; y++) {
for (int x = init; x < img.cols - init; x++) {
vector<uchar> pixels0;
vector<uchar> pixels1;
vector<uchar> pixels2;
for(int i=-init;i<=init;i++)
for (int j = -init; j <= init; j++) {
pixels0.push_back(img.at<Vec3b>(y + i, x + j)[0]);
pixels1.push_back(img.at<Vec3b>(y + i, x + j)[1]);
pixels2.push_back(img.at<Vec3b>(y + i, x + j)[2]);
}
sort(pixels0.begin(), pixels0.end());
sort(pixels1.begin(), pixels1.end());
sort(pixels2.begin(), pixels2.end());
uchar mediumpixel0 = pixels0[pixels0.size() / 2];
uchar mediumpixel1 = pixels1[pixels1.size() / 2];
uchar mediumpixel2 = pixels2[pixels2.size() / 2];
mediumfilter.at<Vec3b>(y, x)[0] = mediumpixel0;
mediumfilter.at<Vec3b>(y, x)[1] = mediumpixel1;
mediumfilter.at<Vec3b>(y, x)[2] = mediumpixel2;
}
}
namedWindow("中值滤波");
imshow("中值滤波", mediumfilter);
return mediumfilter;
}
3.sobel算子锐化:利用sobel算子核计算原图像的灰度图的x,y方向的梯度值最后求出最终梯度值,通过这两个方向的梯度,可以计算出边缘的方向和强度。对该梯度值要进行格式转换,然后与原图像加和,此处加和也要注意数值范围超限,做好类型转换
void sobelOperatorSharpening(const Mat& img) {
Mat gray_img;
cvtColor(img, gray_img, COLOR_BGR2GRAY); // 转换为灰度图
Mat grad_img = Mat::zeros(gray_img.size(), CV_32F); // 创建一个用于存储梯度幅值的图像
//sobel算子核
int sobel_x[3][3] = { {-1, 0, 1}, {-2, 0, 2}, {-1, 0, 1} };
int sobel_y[3][3] = { {-1, -2, -1}, {0, 0, 0}, {1, 2, 1} };
for (int y = 1; y < img.rows - 1; y++) {
for (int x = 1; x < img.cols - 1; x++) {
float grad_x = 0.0, grad_y = 0.0;
for (int i = -1; i <= 1; i++) {
for (int j = -1; j <= 1; j++) {
uchar pixel = gray_img.at<uchar>(y + i, x + j);
grad_x += pixel * sobel_x[i + 1][j + 1];
grad_y += pixel * sobel_y[i + 1][j + 1];
}
}
grad_img.at<float>(y, x) = sqrt(grad_x * grad_x + grad_y * grad_y);
}
}
// 转换梯度图格式
Mat grad_img_uchar;
normalize(grad_img, grad_img_uchar, 0, 255, NORM_MINMAX);
grad_img_uchar.convertTo(grad_img_uchar, CV_8U);
// 与原图片相加实现边缘增强
Mat output = img.clone();
for (int y = 0; y < img.rows; y++) {
for (int x = 0; x < img.cols; x++) {
for (int i = 0; i < 3; i++) {
int val = img.at<Vec3b>(y, x)[i] + grad_img_uchar.at<uchar>(y, x);
//防止加和后uchar数值超过255做的类型转换
output.at<Vec3b>(y, x)[i] = saturate_cast<uchar>(val);
}
}
}
namedWindow("Sobel算子锐化");
imshow("Sobel算子锐化", output);
}
4. 拉普拉斯算子锐化:和上面的sobel算子锐化本质上的区别就在于它不需要关注梯度值,是二阶导数的计算,只需要一个核
void LaplacianSharpening(const Mat& img) {
Mat gray_img,output0,output1,output2;
cvtColor(img, gray_img, COLOR_BGR2GRAY); // 转换为灰度图
output0= Mat::zeros(gray_img.size(), CV_32F);
// 拉普拉斯核
int kernel0[3][3] = {
{1, 1, 1},
{1, -8, 1},
{1, 1, 1}
};
int kernel1[3][3] = {
{0, 1, 0},
{1, -4, 1},
{0, 1, 0}
};
for (int y = 1; y < img.rows - 1; y++) {
for (int x = 1; x < img.cols - 1; x++) {
float sum = 0.0;
for (int k = -1; k <= 1; k++) {
for (int l = -1; l <= 1; l++) {
sum += gray_img.at<uchar>(y + k, x + l) * kernel1[k + 1][l + 1];
}
}
output0.at<float>(y, x) = sum;
}
}
convertScaleAbs(output0, output1);// 转换为8位图像
cvtColor(output1, output2, COLOR_GRAY2BGR); // 将灰度图转换为三通道图像以匹配原图
output2 = img + output2;
namedWindow("拉普拉斯算子锐化");
imshow("拉普拉斯算子锐化", output2);
}
实验结果:
可以看出,对于原图像有很多噪点的椒盐噪声利用中值滤波处理的最好,画面尤为干净。两种算子的锐化结果对比看,拉普拉斯锐化的噪点更加明显
#include <opencv2/opencv.hpp>
#include <algorithm>
using namespace cv;
using namespace std;
void averageFilter(const Mat& img, int kernelSize) {
Mat averageFilter;
averageFilter.create(img.size(), img.type());
if (kernelSize % 2 == 0) kernelSize += 1; // 仅当kernelSize为偶数时增加1
int init = kernelSize / 2;
for (int y = init; y < img.rows - init; y++) {
for (int x = init; x < img.cols - init; x++) {
Vec3d sum = Vec3d(0, 0, 0);
for (int i = -init; i <= init; i++) {
for (int j = -init; j <= init; j++) {
Vec3b pixel = img.at<Vec3b>(y + i, x + j);
sum[0] += pixel[0];
sum[1] += pixel[1];
sum[2] += pixel[2];
}
}
averageFilter.at<Vec3b>(y, x)[0] = static_cast<uchar>(sum[0] / (kernelSize * kernelSize));
averageFilter.at<Vec3b>(y, x)[1] = static_cast<uchar>(sum[1] / (kernelSize * kernelSize));
averageFilter.at<Vec3b>(y, x)[2] = static_cast<uchar>(sum[2] / (kernelSize * kernelSize));
}
}
namedWindow("img");
imshow("img", img);
namedWindow("均值滤波");
imshow("均值滤波", averageFilter);
}
Mat mediumFilter(const Mat& img, int kernelSize) {
Mat mediumfilter;
mediumfilter.create(img.size(), img.type());
if (kernelSize % 2 == 0) kernelSize += 1; // 仅当kernelSize为偶数时增加1
int init = kernelSize / 2;
for (int y = init; y < img.rows - init; y++) {
for (int x = init; x < img.cols - init; x++) {
vector<uchar> pixels0;
vector<uchar> pixels1;
vector<uchar> pixels2;
for(int i=-init;i<=init;i++)
for (int j = -init; j <= init; j++) {
pixels0.push_back(img.at<Vec3b>(y + i, x + j)[0]);
pixels1.push_back(img.at<Vec3b>(y + i, x + j)[1]);
pixels2.push_back(img.at<Vec3b>(y + i, x + j)[2]);
}
sort(pixels0.begin(), pixels0.end());
sort(pixels1.begin(), pixels1.end());
sort(pixels2.begin(), pixels2.end());
uchar mediumpixel0 = pixels0[pixels0.size() / 2];
uchar mediumpixel1 = pixels1[pixels1.size() / 2];
uchar mediumpixel2 = pixels2[pixels2.size() / 2];
mediumfilter.at<Vec3b>(y, x)[0] = mediumpixel0;
mediumfilter.at<Vec3b>(y, x)[1] = mediumpixel1;
mediumfilter.at<Vec3b>(y, x)[2] = mediumpixel2;
}
}
namedWindow("中值滤波");
imshow("中值滤波", mediumfilter);
return mediumfilter;
}
void sobelOperatorSharpening(const Mat& img) {
Mat gray_img;
cvtColor(img, gray_img, COLOR_BGR2GRAY); // 转换为灰度图
Mat grad_img = Mat::zeros(gray_img.size(), CV_32F); // 创建一个用于存储梯度幅值的图像
//sobel算子核
int sobel_x[3][3] = { {-1, 0, 1}, {-2, 0, 2}, {-1, 0, 1} };
int sobel_y[3][3] = { {-1, -2, -1}, {0, 0, 0}, {1, 2, 1} };
for (int y = 1; y < img.rows - 1; y++) {
for (int x = 1; x < img.cols - 1; x++) {
float grad_x = 0.0, grad_y = 0.0;
for (int i = -1; i <= 1; i++) {
for (int j = -1; j <= 1; j++) {
uchar pixel = gray_img.at<uchar>(y + i, x + j);
grad_x += pixel * sobel_x[i + 1][j + 1];
grad_y += pixel * sobel_y[i + 1][j + 1];
}
}
grad_img.at<float>(y, x) = sqrt(grad_x * grad_x + grad_y * grad_y);
}
}
// 转换梯度图格式
Mat grad_img_uchar;
normalize(grad_img, grad_img_uchar, 0, 255, NORM_MINMAX);
grad_img_uchar.convertTo(grad_img_uchar, CV_8U);
// 与原图片相加实现边缘增强
Mat output = img.clone();
for (int y = 0; y < img.rows; y++) {
for (int x = 0; x < img.cols; x++) {
for (int i = 0; i < 3; i++) {
int val = img.at<Vec3b>(y, x)[i] + grad_img_uchar.at<uchar>(y, x);
//防止加和后uchar数值超过255做的类型转换
output.at<Vec3b>(y, x)[i] = saturate_cast<uchar>(val);
}
}
}
namedWindow("Sobel算子锐化");
imshow("Sobel算子锐化", output);
}
void LaplacianSharpening(const Mat& img) {
Mat gray_img,output0,output1,output2;
cvtColor(img, gray_img, COLOR_BGR2GRAY); // 转换为灰度图
output0= Mat::zeros(gray_img.size(), CV_32F);
// 拉普拉斯核
int kernel0[3][3] = {
{1, 1, 1},
{1, -8, 1},
{1, 1, 1}
};
int kernel1[3][3] = {
{0, 1, 0},
{1, -4, 1},
{0, 1, 0}
};
for (int y = 1; y < img.rows - 1; y++) {
for (int x = 1; x < img.cols - 1; x++) {
float sum = 0.0;
for (int k = -1; k <= 1; k++) {
for (int l = -1; l <= 1; l++) {
sum += gray_img.at<uchar>(y + k, x + l) * kernel1[k + 1][l + 1];
}
}
output0.at<float>(y, x) = sum;
}
}
convertScaleAbs(output0, output1);// 转换为8位图像
cvtColor(output1, output2, COLOR_GRAY2BGR); // 将灰度图转换为三通道图像以匹配原图
output2 = img + output2;
namedWindow("拉普拉斯算子锐化");
imshow("拉普拉斯算子锐化", output2);
}
int main() {
Mat img = imread("H://文档//Tencent Files//ASE//噪声图像.bmp");
Mat img1;
averageFilter(img, 3);
img1=mediumFilter(img, 3);
sobelOperatorSharpening(img1);
LaplacianSharpening(img1);
waitKey(0);
return 0;
}