想快速入门图像处理的小伙伴们!跟着步伐我们一起闯关吧!一定会收获满满哦!
为图像处理初学者设计的 100 个问题。这里感谢@gzr2017的翻译,然后我也将随作者的脚步逐一学习实现100道算法题,大家可以一起学习哦,加油!坚持!持续更新中哦!!!
问题6-10:
- 问题六:减色处理
- 问题七:平均池化(Average Pooling)
- 问题八:最大池化(Max Pooloing)
- 问题九:高斯滤波(Gaussian Filter)
- 问题十:中值滤波(Median filter)
代码实现:
- 问题六:减色处理
我们将图像的值由 25 6 3 256^3 2563压缩至 4 3 4^3 43,即将 RGB \text{RGB} RGB的值只取 32 , 96 , 160 , 224 {32, 96, 160, 224} 32,96,160,224。这被称作色彩量化。色彩的值按照下面的方式定义: val = { 32 ( 0 ≤ var < 64 ) 96 ( 64 ≤ var < 128 ) 160 ( 128 ≤ var < 192 ) 224 ( 192 ≤ var < 256 ) \text{val}= \begin{cases} 32& (0 \leq \text{var} < 64)\\ 96& (64\leq \text{var}<128)\\ 160&(128\leq \text{var}<192)\\ 224&(192\leq \text{var}<256) \end{cases} val=⎩⎪⎪⎪⎨⎪⎪⎪⎧3296160224(0≤var<64)(64≤var<128)(128≤var<192)(192≤var<256)
#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
//#include <math.h>
using namespace cv;
Mat decrease_color(Mat img)
{
int height = img.rows;
int width = img.cols;
Mat out = Mat::zeros(height, width, CV_8UC3);
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
for (size_t i = 0; i < img.channels(); i++)
{
out.at<Vec3b>(y, x)[i] = ((img.at<Vec3b>(y, x)[i] / 64) * 64 + 32);
}
}
}
return out;
}
int main(int argc, const char* argv[])
{
Mat img = imread("D:/文件/lenna.png");
Mat out = decrease_color(img);
imshow("img", img);
imshow("out", out);
waitKey(0);
destroyAllWindows();
return 0;
}
输入:
输出:
- 问题七:平均池化(Average Pooling)
将图片按照固定大小网格分割,网格内的像素值取网格内所有像素的平均值。
我们将这种把图片使用均等大小网格分割,并求网格内代表值的操作称为池化(Pooling)。
池化操作是**卷积神经网络(Convolutional Neural Network)**中重要的图像处理方式。平均池化按照下式定义: v = 1 ∣ R ∣ ∑ i = 1 R v i v=\frac{1}{|R|}\ \sum\limits_{i=1}^R\ v_i v=∣R∣1 i=1∑R vi 请把大小为 256 × 128 256\times128 256×128的图像使用 8 × 8 8\times8 8×8的网格做平均池化。
#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
//#include <math.h>
using namespace cv;
//Average Pooling
//方法一:
Mat average_pooling(Mat img)
{
int height = img.rows;
int width = img.cols;
Mat out = Mat::zeros(height, width, CV_8UC3);
float pixcount, pixave;
pixcount = pixave = 0;
for (int y = 1; y <= height; y++)
{
for (int x = 1; x <= width; x++)
{
for (int c = 0; c < img.channels(); c++)
{
if (y % 8 == 0 && x % 8 == 0)
{
pixcount = 0;//归零
for (int i = 1; i <=8; i++)
{
for (int j = 1; j <=8; j++)
{
pixcount += (float)img.at<Vec3b>(y - j, x - i)[c];
}
}
for (int i = 1; i <= 8; i++)
{
for (int j = 1; j <= 8; j++)
{
out.at<Vec3b>(y - j, x - i)[c] = pixcount / 64;
}
}
}
}
}
}
return out;
}
//方法二:(时间复杂度相对于方法一较低,空间复杂度相同)
/*
Mat average_pooling(Mat img) {
int height = img.rows;
int width = img.cols;
int channel = img.channels();
// prepare output
Mat out = Mat::zeros(height, width, CV_8UC3);
int r = 8;
double v = 0;
for (int y = 0; y < height; y += r) {
for (int x = 0; x < width; x += r) {
for (int c = 0; c < channel; c++) {
v = 0;
for (int dy = 0; dy < r; dy++) {
for (int dx = 0; dx < r; dx++) {
v += (double)img.at<Vec3b>(y + dy, x + dx)[c];
}
}
v /= (r * r);
for (int dy = 0; dy < r; dy++) {
for (int dx = 0; dx < r; dx++) {
out.at<Vec3b>(y + dy, x + dx)[c] = (uchar)v;
}
}
}
}
}
return out;
}
*/
int main(int argc, const char* argv[])
{
Mat img = imread("D:/文件/lenna1.png");//此处图片大小为256*128
Mat out = average_pooling(img);
imshow("img", img);
imshow("out", out);
waitKey(0);
destroyAllWindows();
return 0;
}
输入:
输出:
- 问题八:最大池化(Max Pooloing)
网格内的值不取平均值,而是取网格内的最大值进行池化操作。
#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
//#include <math.h>
using namespace cv;
//Max Pooling
//方法一:
Mat max_pooling(Mat img)
{
int height = img.rows;
int width = img.cols;
Mat out = Mat::zeros(height, width, CV_8UC3);
float pix_val, pix_max;
pix_val = pix_max = 0;
for (int y = 1; y <= height; y++)
{
for (int x = 1; x <= width; x++)
{
for (int c = 0; c < img.channels(); c++)
{
if (y % 8 == 0 && x % 8 == 0)
{
pix_max = 0;//归零
pix_val=0;
for (int i = 1; i <=8; i++)
{
for (int j = 1; j <=8; j++)
{
pix_val = (float)img.at<Vec3b>(y - j, x - i)[c];
pix_max = fmax(pix_max, pix_val);
}
}
for (int i = 1; i <= 8; i++)
{
for (int j = 1; j <= 8; j++)
{
out.at<Vec3b>(y - j, x - i)[c] = pix_max;
}
}
}
}
}
}
return out;
}
//方法二:(时间复杂度相对于方法一较低,空间复杂度相同)
/*
Mat max_pooling(Mat img) {
int height = img.rows;
int width = img.cols;
int channel = img.channels();
// prepare output
Mat out = Mat::zeros(height, width, CV_8UC3);
int r = 8;
double v = 0;
double max = 0;
for (int y = 0; y < height; y += r) {
for (int x = 0; x < width; x += r) {
for (int c = 0; c < channel; c++) {
max = 0;
v = 0;
for (int dy = 0; dy < r; dy++) {
for (int dx = 0; dx < r; dx++) {
v = (double)img.at<Vec3b>(y + dy, x + dx)[c];
max = fmax(max, v);
}
}
for (int dy = 0; dy < r; dy++) {
for (int dx = 0; dx < r; dx++) {
out.at<Vec3b>(y + dy, x + dx)[c] = (uchar)max;
}
}
}
}
}
return out;
}
*/
int main(int argc, const char* argv[])
{
Mat img = imread("D:/文件/lenna1.png");
Mat out = max_pooling(img);
imshow("img", img);
imshow("out", out);
waitKey(0);
destroyAllWindows();
return 0;
}
输入:
输出:
- 问题九:高斯滤波(Gaussian Filter)
使用高斯滤波器进行降噪处理。
高斯滤波器是一种可以使图像平滑的滤波器,用于去除噪声。可用于去除噪声的滤波器还有中值滤波器(参见问题十),平滑滤波器(参见问题十一)、LoG滤波器(参见问题十九)。
高斯滤波器将中心像素周围的像素按照高斯分布加权平均进行平滑化。这样的(二维)权值通常被称为卷积核(kernel)或者滤波器(filter)。
但是,由于图像的长宽可能不是滤波器大小的整数倍,因此我们需要在图像的边缘补 0 0 0。这种方法称作Zero Padding。并且权值 g g g(卷积核)要进行归一化操作( ∑ g = 1 \sum\ g = 1 ∑ g=1)。
按下面的高斯分布公式计算权值: g ( x , y , σ ) = 1 2 π σ 2 e − x 2 + y 2 2 σ 2 g(x,y,\sigma)=\frac{1}{2\ \pi\ \sigma^2}\ e^{-\frac{x^2+y^2}{2\ \sigma^2}} g(x,y,σ)=2 π σ21 e−2 σ2x2+y2
标准差 σ = 1.3 \sigma=1.3 σ=1.3的 8 − 8- 8−近邻高斯滤波器如下: K = 1 16 [ 1 2 1 2 4 2 1 2 1 ] K=\frac{1}{16}\ \left[ \begin{matrix} 1 & 2 & 1 \ 2 & 4 & 2 \ 1 & 2 & 1 \end{matrix} \right] K=161 [121 242 121]
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>
#include <math.h>
using namespace cv;
using namespace std;
// gaussian filter
Mat GaussianFilter(const Mat &src, int ksize, double sigma)
{
CV_Assert(src.channels() || src.channels() == 3); // 只处理单通道或者三通道图像
Mat dst = Mat::zeros(src.rows, src.cols, CV_8UC3);
//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;
return dst;
}
int main(int argc, const char* argv[])
{
// read image
Mat img = cv::imread("D:/文件/lenna.png", cv::IMREAD_COLOR);
// gaussian filter
Mat out = GaussianFilter(img,3, 1.3);
imshow("img", img);
imshow("out", out);
waitKey(0);
destroyAllWindows();
return 0;
}
输入:
输出:
这里对高斯滤波的讲解很详细:https://blog.csdn.net/weixin_40647819/article/details/89742936
- 问题十:中值滤波(Median Filter)
使用中值滤波器进行降噪处理.
中值滤波器是一种可以使图像平滑的滤波器。这种滤波器用滤波器范围内(在这里是 3 × 3 3\times3 3×3)像素点的中值进行滤波,请在这里也采用Zero Padding。
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>
#include <math.h>
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
// median filter
Mat MedianFilter(Mat &src, int k)
{
Mat dst = Mat::zeros(src.rows, src.cols, CV_8UC3);
Mat im, m1, m2, m3, m4, m5, m6, B, G, R;
vector <Mat> channels;
split(src, channels);
B = channels.at(0);
G = channels.at(1);
R = channels.at(2);//通道分离
int a = src.rows;
int b = src.cols; //获得行和列
for (int i = k / 2; i < a - k / 2; i++)//遍历图像,注意要把边框部分去除了
{
for (int j = k / 2; j < b - k / 2; j++)
{
m1 = B(Rect(j - k / 2, i - k / 2, k, k)).clone();//选取k*k窗口中的B通道
m2 = G(Rect(j - k / 2, i - k / 2, k, k)).clone();//选取k*k窗口G通道
m3 = R(Rect(j - k / 2, i - k / 2, k, k)).clone();//选取k*k窗口R通道
m1 = m1.reshape(1, 1);
m2 = m2.reshape(1, 1);
m3 = m3.reshape(1, 1); //像素值将其变为一行
cv::sort(m1, m4, 0);
cv::sort(m2, m5, 0);
cv::sort(m3, m6, 0);//对其排序
int p0 = m4.at<uchar>(0, k*k / 2 + 1);
int p1 = m5.at<uchar>(0, k*k / 2 + 1);
int p2 = m6.at<uchar>(0, k*k / 2 + 1);//选取各通道的像素中值点
dst.at<Vec3b>(i - k / 2, j - k / 2)[0] = p0;
dst.at<Vec3b>(i - k / 2, j - k / 2)[1] = p1;
dst.at<Vec3b>(i - k / 2, j - k / 2)[2] = p2;//对其进行重赋值
}
}
return dst;
}
int main(int argc, const char* argv[])
{
// read image
Mat img = cv::imread("D:/文件/lenna.png", cv::IMREAD_COLOR);
// medain filter
Mat out = MedianFilter(img, 5);
imshow("img", img);
imshow("out", out);
waitKey(0);
destroyAllWindows();
return 0;
}
输入:
输出: