感谢分享!
English is here (KuKuXia translates into English)
https://github.com/KuKuXia/Image_Processing_100_Questions
Chinese is here (gzr2017, my ex-colleague, translates into Chinese)
https://github.com/gzr2017/ImageProcessing100Wen
My git:
https://github.com/jamery-huang/ImageProcessing100Wen.git
基于c++版本
原code错误:
- Question_11_20/answers_cpp/answer_11.cpp
缺少越界保护,以及像素边缘计算均值的分母有问题,详见下文均值滤波器章节代码。
文章目录
一、均值滤波器
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>
#include <math.h>
// mean filter
cv::Mat mean_filter(cv::Mat img, int kernel_size){
int height = img.rows;
int width = img.cols;
int channel = img.channels();
// prepare output
cv::Mat out = cv::Mat::zeros(height, width, CV_8UC3);
// prepare kernel
int pad = floor(kernel_size / 2);
// filtering
double v = 0;
int vs[kernel_size * kernel_size];
int count = 0;
for (int y = 0; y < height; y++){
for (int x = 0; x < width; x++){
for (int c = 0; c < channel; c++){
v = 0;
count = 0;
// get pixel sum
for (int dy = -pad; dy < pad + 1; dy++){
for (int dx = -pad; dx < pad + 1; dx++){
if (((y + dy) >= 0) && ((x + dx) >= 0)
&& ((y + dy) < height) && ((x + dx) < width)){
v += (int)img.at<cv::Vec3b>(y + dy, x + dx)[c];
count++;
}
}
}
// assign mean value
if (count > 0) {
v /= count;
out.at<cv::Vec3b>(y, x)[c] = (uchar)v;
}
}
}
}
return out;
}
int main(int argc, const char* argv[]){
// read image
cv::Mat img = cv::imread("imori.jpg", cv::IMREAD_COLOR);
// mean filter
cv::Mat out = mean_filter(img, 3);
cv::imwrite("out1.jpg", out);
cv::imshow("img", img);
cv::imshow("answer", out);
cv::waitKey(0);
cv::destroyAllWindows();
return 0;
}
二、Motion Filter
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>
#include <math.h>
// motion filter
cv::Mat motion_filter(cv::Mat img, int kernel_size){
int height = img.rows;
int width = img.cols;
int channel = img.channels();
// prepare output
cv::Mat out = cv::Mat::zeros(height, width, CV_8UC3);
// prepare kernel
int pad = floor(kernel_size / 2);
double kernel[kernel_size][kernel_size];//{{1./3, 0, 0}, {0, 1./3, 0}, {0, 0, 1./3}};
for(int y = 0; y < kernel_size; y++){
for(int x = 0; x < kernel_size; x++){
if (y == x){
kernel[y][x] = 1. / kernel_size;
} else {
kernel[y][x] = 0;
}
}
}
// filtering
double v = 0;
for (int y = 0; y < height; y++){
for (int x = 0; x < width; x++){
for (int c = 0; c < channel; c++){
v = 0;
for (int dy = -pad; dy < pad + 1; dy++){
for (int dx = -pad; dx < pad + 1; dx++){
if (((y + dy) >= 0) && (( x + dx) >= 0) && ((y + dy) < height) && ((x + dx) < width)){
v += (double)img.at<cv::Vec3b>(y + dy, x + dx)[c] * kernel[dy + pad][dx + pad];
}
}
}
out.at<cv::Vec3b>(y, x)[c] = (uchar)v;
}
}
}
return out;
}
int main(int argc, const char* argv[]){
// read image
cv::Mat img = cv::imread("imori.jpg", cv::IMREAD_COLOR);
// motion filter
cv::Mat out = motion_filter(img, 3);
cv::imwrite("out2.jpg", out);
cv::imshow("img", img);
cv::imshow("answer", out);
cv::waitKey(0);
cv::destroyAllWindows();
return 0;
}
三、MAX-MIN滤波器
MAX-MIN滤波器使用网格内像素的最大值和最小值的差值对网格内像素重新赋值。通常用于边缘检测。
边缘检测用于检测图像中的线。像这样提取图像中的信息的操作被称为特征提取。
边缘检测通常在灰度图像上进行。
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>
#include <math.h>
// BGR -> Gray
cv::Mat BGR2GRAY(cv::Mat img){
// get height and width
int width = img.cols;
int height = img.rows;
// prepare output
cv::Mat out = cv::Mat::zeros(height, width, CV_8UC1);
// each y, x
for (int y = 0; y < height; y++){
for (int x = 0; x < width; x++){
// BGR -> Gray
out.at<uchar>(y, x) = 0.2126 * (float)img.at<cv::Vec3b>(y, x)[2] \
+ 0.7152 * (float)img.at<cv::Vec3b>(y, x)[1] \
+ 0.0722 * (float)img.at<cv::Vec3b>(y, x)[0];
}
}
return out;
}
// max min filter
cv::Mat max_min_filter(cv::Mat img, int kernel_size){
int height = img.rows;
int width = img.cols;
int channel = img.channels();
// prepare output
cv::Mat out = cv::Mat::zeros(height, width, CV_8UC1);
int pad = floor(kernel_size / 2);
double vmax = 0, vmin = 999, v = 0;
// filtering
for (int y = 0; y < height; y++){
for (int x = 0; x < width; x++){
vmax = 0;
vmin = 999;
for (int dy = -pad; dy < pad + 1; dy++){
for (int dx = -pad; dx < pad + 1; dx++){
if (((y + dy) >= 0) && (( x + dx) >= 0) && ((y + dy) < height) && ((x + dx) < width)){
v = (double)img.at<uchar>(y + dy, x + dx);
if (v > vmax){
vmax = v;
}
if (v < vmin){
vmin = v;
}
}
}
}
out.at<uchar>(y, x) = (uchar)(vmax - vmin);
}
}
return out;
}
int main(int argc, const char* argv[]){
// read image
cv::Mat img = cv::imread("imori.jpg", cv::IMREAD_COLOR);
// BGR -> Gray
cv::Mat gray = BGR2GRAY(img);
// max min filter
cv::Mat out = max_min_filter(gray, 3);
cv::imwrite("out3.jpg", out);
cv::imshow("img", img);
cv::imshow("answer", out);
cv::waitKey(0);
cv::destroyAllWindows();
return 0;
}
四、差分滤波器(Differential Filter)
差分滤波器对图像亮度急剧变化的边缘有提取效果,可以获得邻接像素的差值。
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>
#include <math.h>
// BGR -> Gray
cv::Mat BGR2GRAY(cv::Mat img){
// get height and width
int width = img.cols;
int height = img.rows;
// prepare output
cv::Mat out = cv::Mat::zeros(height, width, CV_8UC1);
// each y, x
for (int y = 0; y < height; y++){
for (int x = 0; x < width; x++){
// BGR -> Gray
out.at<uchar>(y, x) = 0.2126 * (float)img.at<cv::Vec3b>(y, x)[2] \
+ 0.7152 * (float)img.at<cv::Vec3b>(y, x)[1] \
+ 0.0722 * (float)img.at<cv::Vec3b>(y, x)[0];
}
}
return out;
}
// max min filter
cv::Mat diff_filter(cv::Mat img, int kernel_size, bool horizontal){
int height = img.rows;
int width = img.cols;
int channel = img.channels();
// prepare output
cv::Mat out = cv::Mat::zeros(height, width, CV_8UC1);
// prepare kernel
double kernel[kernel_size][kernel_size] = {{0, -1, 0}, {0, 1, 0}, {0, 0, 0}};
if (horizontal){
kernel[0][1] = 0;
kernel[1][0] = -1;
}
int pad = floor(kernel_size / 2);
double v = 0;
// filtering
for (int y = 0; y < height; y++){
for (int x = 0; x < width; x++){
v = 0;
for (int dy = -pad; dy < pad + 1; dy++){
for (int dx = -pad; dx < pad + 1; dx++){
if (((y + dy) >= 0) && (( x + dx) >= 0) && ((y + dy) < height) && ((x + dx) < width)){
v += img.at<uchar>(y + dy, x + dx) * kernel[dy + pad][dx + pad];
}
}
}
v = fmax(v, 0);
v = fmin(v, 255);
out.at<uchar>(y, x) = (uchar)v;
}
}
return out;
}
int main(int argc, const char* argv[]){
// read image
cv::Mat img = cv::imread("imori.jpg", cv::IMREAD_COLOR);
// BGR -> Gray
cv::Mat gray = BGR2GRAY(img);
// diff filter (vertical)
cv::Mat out_v = diff_filter(gray, 3, false);
// diff filter (horizontal)
cv::Mat out_h = diff_filter(gray, 3, true);
cv::imwrite("out4-v.jpg", out_v);
cv::imwrite("out4-h.jpg", out_h);
cv::imshow("img", img);
cv::imshow("answer (vertical)", out_v);
cv::imshow("answer (horizontal)", out_h);
cv::waitKey(0);
cv::destroyAllWindows();
return 0;
}
五、Sobel滤波器
Sobel滤波器可以提取特定方向(纵向或横向)的边缘。
Sobel算子是在Prewitt算子的基础上改进的,在中心系数上使用一个权值2,相比较Prewitt算子,Sobel模板能够较好的抑制(平滑)噪声。
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>
#include <math.h>
// BGR -> Gray
cv::Mat BGR2GRAY(cv::Mat img){
// get height and width
int width = img.cols;
int height = img.rows;
// prepare output
cv::Mat out = cv::Mat::zeros(height, width, CV_8UC1);
// each y, x
for (int y = 0; y < height; y++){
for (int x = 0; x < width; x++){
// BGR -> Gray
out.at<uchar>(y, x) = 0.2126 * (float)img.at<cv::Vec3b>(y, x)[2] \
+ 0.7152 * (float)img.at<cv::Vec3b>(y, x)[1] \
+ 0.0722 * (float)img.at<cv::Vec3b>(y, x)[0];
}
}
return out;
}
// Sobel filter
cv::Mat sobel_filter(cv::Mat img, int kernel_size, bool horizontal){
int height = img.rows;
int width = img.cols;
int channel = img.channels();
// prepare output
cv::Mat out = cv::Mat::zeros(height, width, CV_8UC1);
// prepare kernel
double kernel[kernel_size][kernel_size] = {{1, 2, 1}, {0, 0, 0}, {-1, -2, -1}};
if (horizontal){
kernel[0][1] = 0;
kernel[2][1] = 0;
kernel[1][0] = 2;
kernel[1][2] = -2;
}
int pad = floor(kernel_size / 2);
double v = 0;
// filtering
for (int y = 0; y < height; y++){
for (int x = 0; x < width; x++){
v = 0;
for (int dy = -pad; dy < pad + 1; dy++){
for (int dx = -pad; dx < pad + 1; dx++){
if (((y + dy) >= 0) && (( x + dx) >= 0) && ((y + dy) < height) && ((x + dx) < width)){
v += img.at<uchar>(y + dy, x + dx) * kernel[dy + pad][dx + pad];
}
}
}
v = fmax(v, 0);
v = fmin(v, 255);
out.at<uchar>(y, x) = (uchar)v;
}
}
return out;
}
int main(int argc, const char* argv[]){
// read image
cv::Mat img = cv::imread("imori.jpg", cv::IMREAD_COLOR);
// BGR -> Gray
cv::Mat gray = BGR2GRAY(img);
// sobel filter (vertical)
cv::Mat out_v = sobel_filter(gray, 3, false);
// sobel filter (horizontal)
cv::Mat out_h = sobel_filter(gray, 3, true);
cv::imwrite("out5-v.jpg", out_v);
cv::imwrite("out5-h.jpg", out_h);
cv::imshow("img", img);
cv::imshow("answer (vertical)", out_v);
cv::imshow("answer (horizontal)", out_h);
cv::waitKey(0);
cv::destroyAllWindows();
return 0;
}
六、Prewitt滤波器
Prewitt滤波器是用于边缘检测的一种滤波器
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>
#include <math.h>
// BGR -> Gray
cv::Mat BGR2GRAY(cv::Mat img){
// get height and width
int width = img.cols;
int height = img.rows;
// prepare output
cv::Mat out = cv::Mat::zeros(height, width, CV_8UC1);
// each y, x
for (int y = 0; y < height; y++){
for (int x = 0; x < width; x++){
// BGR -> Gray
out.at<uchar>(y, x) = 0.2126 * (float)img.at<cv::Vec3b>(y, x)[2] \
+ 0.7152 * (float)img.at<cv::Vec3b>(y, x)[1] \
+ 0.0722 * (float)img.at<cv::Vec3b>(y, x)[0];
}
}
return out;
}
// prewitt filter
cv::Mat prewitt_filter(cv::Mat img, int kernel_size, bool horizontal){
int height = img.rows;
int width = img.cols;
int channel = img.channels();
// prepare output
cv::Mat out = cv::Mat::zeros(height, width, CV_8UC1);
// prepare kernel
double kernel[kernel_size][kernel_size] = {{-1, -1, -1}, {0, 0, 0}, {1, 1, 1}};
if (horizontal){
kernel[0][1] = 0;
kernel[0][2] = 1;
kernel[1][0] = -1;
kernel[1][2] = 1;
kernel[2][0] = -1;
kernel[2][1] = 0;
}
int pad = floor(kernel_size / 2);
double v = 0;
// filtering
for (int y = 0; y < height; y++){
for (int x = 0; x < width; x++){
v = 0;
for (int dy = -pad; dy < pad + 1; dy++){
for (int dx = -pad; dx < pad + 1; dx++){
if (((y + dy) >= 0) && (( x + dx) >= 0) && ((y + dy) < height) && ((x + dx) < width)){
v += img.at<uchar>(y + dy, x + dx) * kernel[dy + pad][dx + pad];
}
}
}
v = fmax(v, 0);
v = fmin(v, 255);
out.at<uchar>(y, x) = (uchar)v;
}
}
return out;
}
int main(int argc, const char* argv[]){
// read image
cv::Mat img = cv::imread("imori.jpg", cv::IMREAD_COLOR);
// BGR -> Gray
cv::Mat gray = BGR2GRAY(img);
// prewitt filter (vertical)
cv::Mat out_v = prewitt_filter(gray, 3, false);
// prewitt filter (horizontal)
cv::Mat out_h = prewitt_filter(gray, 3, true);
cv::imwrite("out6-v.jpg", out_v);
cv::imwrite("out6-h.jpg", out_h);
cv::imshow("img", img);
cv::imshow("answer (vertical)", out_v);
cv::imshow("answer (horizontal)", out_h);
cv::waitKey(0);
cv::destroyAllWindows();
return 0;
}
七、Laplacian滤波器
Laplacian滤波器是对图像亮度进行二次微分从而检测边缘的滤波器。
- 数字图像是离散的, x方向和y方向的一次微分:
- x方向的二次微分:
同理,y方向:
- Laplacian 表达式:
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>
#include <math.h>
// BGR -> Gray
cv::Mat BGR2GRAY(cv::Mat img){
// get height and width
int width = img.cols;
int height = img.rows;
// prepare output
cv::Mat out = cv::Mat::zeros(height, width, CV_8UC1);
// each y, x
for (int y = 0; y < height; y++){
for (int x = 0; x < width; x++){
// BGR -> Gray
out.at<uchar>(y, x) = 0.2126 * (float)img.at<cv::Vec3b>(y, x)[2] \
+ 0.7152 * (float)img.at<cv::Vec3b>(y, x)[1] \
+ 0.0722 * (float)img.at<cv::Vec3b>(y, x)[0];
}
}
return out;
}
// laplacian filter
cv::Mat laplacian_filter(cv::Mat img, int kernel_size){
int height = img.rows;
int width = img.cols;
int channel = img.channels();
// prepare output
cv::Mat out = cv::Mat::zeros(height, width, CV_8UC1);
// prepare kernel
double kernel[kernel_size][kernel_size] = {{0, 1, 0}, {1, -4, 1}, {0, 1, 0}};
int pad = floor(kernel_size / 2);
double v = 0;
// filtering
for (int y = 0; y < height; y++){
for (int x = 0; x < width; x++){
v = 0;
for (int dy = -pad; dy < pad + 1; dy++){
for (int dx = -pad; dx < pad + 1; dx++){
if (((y + dy) >= 0) && (( x + dx) >= 0) && ((y + dy) < height) && ((x + dx) < width)){
v += img.at<uchar>(y + dy, x + dx) * kernel[dy + pad][dx + pad];
}
}
}
v = fmax(v, 0);
v = fmin(v, 255);
out.at<uchar>(y, x) = (uchar)v;
}
}
return out;
}
int main(int argc, const char* argv[]){
// read image
cv::Mat img = cv::imread("imori.jpg", cv::IMREAD_COLOR);
// BGR -> Gray
cv::Mat gray = BGR2GRAY(img);
// laplacian filter
cv::Mat out = laplacian_filter(gray, 3);
cv::imwrite("out7.jpg", out);
cv::imshow("img", img);
cv::imshow("answer", out);
cv::waitKey(0);
cv::destroyAllWindows();
return 0;
}
八、Emboss滤波器
Emboss滤波器常用于检测图像的边缘和轮廓,能够有效地增强图像的高频信息(边缘和轮廓),并保留图像的低频信息(图像内容)。
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>
#include <math.h>
// BGR -> Gray
cv::Mat BGR2GRAY(cv::Mat img){
// get height and width
int width = img.cols;
int height = img.rows;
// prepare output
cv::Mat out = cv::Mat::zeros(height, width, CV_8UC1);
// each y, x
for (int y = 0; y < height; y++){
for (int x = 0; x < width; x++){
// BGR -> Gray
out.at<uchar>(y, x) = 0.2126 * (float)img.at<cv::Vec3b>(y, x)[2] \
+ 0.7152 * (float)img.at<cv::Vec3b>(y, x)[1] \
+ 0.0722 * (float)img.at<cv::Vec3b>(y, x)[0];
}
}
return out;
}
// emboss filter
cv::Mat emboss_filter(cv::Mat img, int kernel_size){
int height = img.rows;
int width = img.cols;
int channel = img.channels();
// prepare output
cv::Mat out = cv::Mat::zeros(height, width, CV_8UC1);
// prepare kernel
double kernel[kernel_size][kernel_size] = {{-2, -1, 0}, {-1, 1, 1}, {0, 1, 2}};
int pad = floor(kernel_size / 2);
double v = 0;
// filtering
for (int y = 0; y < height; y++){
for (int x = 0; x < width; x++){
v = 0;
for (int dy = -pad; dy < pad + 1; dy++){
for (int dx = -pad; dx < pad + 1; dx++){
if (((y + dy) >= 0) && (( x + dx) >= 0) && ((y + dy) < height) && ((x + dx) < width)){
v += img.at<uchar>(y + dy, x + dx) * kernel[dy + pad][dx + pad];
}
}
}
v = fmax(v, 0);
v = fmin(v, 255);
out.at<uchar>(y, x) = (uchar)v;
}
}
return out;
}
int main(int argc, const char* argv[]){
// read image
cv::Mat img = cv::imread("imori.jpg", cv::IMREAD_COLOR);
// BGR -> Gray
cv::Mat gray = BGR2GRAY(img);
// emboss filter
cv::Mat out = emboss_filter(gray, 3);
cv::imwrite("out8.jpg", out);
cv::imshow("img", img);
cv::imshow("answer", out);
cv::waitKey(0);
cv::destroyAllWindows();
return 0;
}
九、LoG滤波器(待完善)
拉普拉斯算子是图像二阶空间导数的二维各向同性测度。拉普拉斯算子可以突出图像中强度发生快速变化的区域,因此常用在边缘检测任务当中。在进行Laplacian操作之前通常需要先用高斯平滑滤波器对图像进行平滑处理,以降低Laplacian操作对于噪声的敏感性。该操作通常是输入一张灰度图,经过处理之后输出一张灰度图。
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>
#include <math.h>
// BGR -> Gray
cv::Mat BGR2GRAY(cv::Mat img){
// get height and width
int width = img.cols;
int height = img.rows;
// prepare output
cv::Mat out = cv::Mat::zeros(height, width, CV_8UC1);
// each y, x
for (int y = 0; y < height; y++){
for (int x = 0; x < width; x++){
// BGR -> Gray
out.at<uchar>(y, x) = 0.2126 * (float)img.at<cv::Vec3b>(y, x)[2] \
+ 0.7152 * (float)img.at<cv::Vec3b>(y, x)[1] \
+ 0.0722 * (float)img.at<cv::Vec3b>(y, x)[0];
}
}
return out;
}
// LoG filter
cv::Mat LoG_filter(cv::Mat img, int kernel_size, double sigma){
int height = img.rows;
int width = img.cols;
int channel = img.channels();
// prepare output
cv::Mat out = cv::Mat::zeros(height, width, CV_8UC1);
// prepare kernel
int pad = floor(kernel_size / 2);
double kernel[kernel_size][kernel_size];
double kernel_sum = 0;
double _x, _y;
for (int y = 0; y < kernel_size; y++){
for (int x = 0; x < kernel_size; x++){
_y = y - pad;
_x = x - pad;
kernel[y][x] = (_x * _x + _y * _y - sigma * sigma) / (2 * M_PI * pow(sigma, 6)) * exp( - (_x * _x + _y * _y) / (2 * sigma * sigma));
kernel_sum += kernel[y][x];
}
}
for (int y = 0; y < kernel_size; y++){
for (int x = 0; x < kernel_size; x++){
kernel[y][x] /= kernel_sum;
}
}
double v = 0;
// filtering
for (int y = 0; y < height; y++){
for (int x = 0; x < width; x++){
v = 0;
for (int dy = -pad; dy < pad + 1; dy++){
for (int dx = -pad; dx < pad + 1; dx++){
if (((y + dy) >= 0) && (( x + dx) >= 0) && ((y + dy) < height) && ((x + dx) < width)){
v += img.at<uchar>(y + dy, x + dx) * kernel[dy + pad][dx + pad];
}
}
}
v = fmax(v, 0);
v = fmin(v, 255);
out.at<uchar>(y, x) = (uchar)v;
}
}
return out;
}
int main(int argc, const char* argv[]){
// read image
cv::Mat img = cv::imread("imori_noise.jpg", cv::IMREAD_COLOR);
// BGR -> Gray
cv::Mat gray = BGR2GRAY(img);
// LoG filter
cv::Mat out = LoG_filter(gray, 5, 3);
cv::imwrite("out9.jpg", out);
cv::imshow("img", img);
cv::imshow("answer", out);
cv::waitKey(0);
cv::destroyAllWindows();
return 0;
}