文章目录
相关链接
C++&Python&Csharp in OpenCV 专栏
【2022B站最好的OpenCV课程推荐】OpenCV从入门到实战 全套课程(附带课程课件资料+课件笔记)
前言
这次来了解一下图像平滑处理。还是老套路,先写Python,再C++,再Csharp。本篇文章难的不是代码,难的是图像学的知识
图像资源
图像平滑处理
图像平滑处理就是PS中常用的模糊工具,涂抹工具。算法怎么计算的可以看这个文章
图像学知识补充(重点)
本章的难点就是图形学的知识了。这里推荐一本书【数字图像处理(中译第三版)[冈萨雷斯]】
目录简单展示
什么是卷积
卷积是一种叠加态的问题的解法。叠加态的问题有:
- 水池有两个水龙头,一个放水一个抽水。
- 人一天的体重变化,早餐还没彻底消化完,就吃午餐了
- 冰箱里面的食物整体的新鲜度。不新鲜的会被拿走,新鲜的食材会被放进来
- 湖泊的水位,下雨天水位上升,不下雨水位慢慢下降
这个就是卷积的特点:叠加状态。
那么图像的卷积是什么意思?就是考虑到每个像素对应周围像素的影响,卷积后的图像是卷积前的图像像素叠加卷积的结果。通俗点来说,就是黑色像素点周围黑一点,自己白一点。白色像素点周围白一点,自己黑一点。
就像你用墨水写字,然后用水浇上去,字就糊了。这个过程就是卷积。
什么是图像滤波
图标滤波就是对图像的噪点进行抑制,尽量不影响图像的信息量。如果不了解这两个概念,可以近似的看成。
- 卷积是方法论,是公式
- 图像滤波是具体实现。
什么是方框滤波和均值滤波
我感觉两个差不多。
代码
Python
python
# %%
import cv2
import matplotlib.pyplot as plt
import numpy as np
image_src = cv2.imread('d:\workSpace\OpenCV\HellOpenCV\Resources\images\lena.png')
image_config = (7,7)
# 均值滤波
# 最简单的卷积操作
image_blur = cv2.blur(image_src,image_config)
# 方框滤波,基本和均值一样
image_box_t = cv2.boxFilter(image_src,-1,image_config,normalize=True)
# 方框滤波,反方向
image_box_f = cv2.boxFilter(image_src,-1,image_config,normalize=False)
# 高斯滤波,更重视中间值
image_gaussian = cv2.GaussianBlur(image_src,image_config,1)
image_median = cv2.medianBlur(image_src,7)
# 因为openCV默认BGR,Plt默认RGB,所以要转化一下
# 因为有Csharp的习惯,我习惯默认大写驼峰
def PltImshowRgb(row:int,col:int,index:int,image:np.mat,title:str):
plt.subplot(row,col,index)
plt.imshow(cv2.cvtColor(image,cv2.COLOR_BGR2RGB))
plt.title(title)
PltImshowRgb(2,3,1,image_src,'image_src')
PltImshowRgb(2,3,2,image_blur,'image_blur')
PltImshowRgb(2,3,3,image_box_t,'image_box_t')
PltImshowRgb(2,3,4,image_box_f,'image_box_f')
PltImshowRgb(2,3,5,image_gaussian,'image_gaussian')
PltImshowRgb(2,3,6,image_median,'image_median')
plt.show()
用下来的感觉,滤波的效果确实差不多。
想详细了解其中的区别,可以看这篇文章。我就不展开说明了。
C++
C++我自己写了一个图像合并显示的代码
c
#include <opencv2/opencv.hpp>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include<iostream>
#include <vector>
using namespace std;
using namespace cv;
vector<Mat> imageVector;
/// <summary>
/// 将图案并排显示
/// </summary>
/// <param name="imgVector"></param>
/// <param name="dst"></param>
/// <param name="imgCols"></param>
void multipleImage(vector<Mat> imgVector, Mat& dst, int imgCols)
{
const int MAX_PIXEL = 300;
int imgNum = imgVector.size();
//选择图片最大的一边 将最大的边按比例变为300像素
Size imgOriSize = imgVector[0].size();
int imgMaxPixel = max(imgOriSize.height, imgOriSize.width);
//获取最大像素变为MAX_PIXEL的比例因子
double prop = imgMaxPixel < MAX_PIXEL ? (double)imgMaxPixel / MAX_PIXEL : MAX_PIXEL / (double)imgMaxPixel;
Size imgStdSize(imgOriSize.width * prop, imgOriSize.height * prop); //窗口显示的标准图像的Size
Mat imgStd; //标准图片
Point2i location(0, 0); //坐标点(从0,0开始)
//构建窗口大小 通道与imageVector[0]的通道一样
Mat imgWindow(imgStdSize.height * ((imgNum - 1) / imgCols + 1), imgStdSize.width * imgCols, imgVector[0].type());
for (int i = 0; i < imgNum; i++)
{
location.x = (i % imgCols) * imgStdSize.width;
location.y = (i / imgCols) * imgStdSize.height;
resize(imgVector[i], imgStd, imgStdSize, prop, prop, INTER_LINEAR); //设置为标准大小
//imgWindow()
imgStd.copyTo(imgWindow(Rect(location, imgStdSize)));
}
dst = imgWindow;
}
int main()
{
auto image = imread("D:/workSpace/OpenCV/HellOpenCV/Resources/images/lena.png");
Mat showImage;
Mat image_blur;
Mat image_box_t;
Mat image_box_f;
Mat image_gaussian;
Mat image_median;
Size image_config = Size(7, 7);
blur(image, image_blur, image_config);
boxFilter(image, image_box_t, -1, image_config,Point(-1,-1),true);
boxFilter(image, image_box_f,-1, image_config, Point(-1, -1), false);
GaussianBlur(image, image_gaussian, image_config, 1);
medianBlur(image, image_median, 7);
vector<Mat> images{image,image_blur,image_box_t,image_box_f,image_gaussian,image_median};
multipleImage(images, showImage, 3);
imshow("C++", showImage);
waitKey(0);
destroyAllWindows();
return 0;
}
Csharp
还是那句话。C++写好了,Csharp也能写。
csharp
internal class Program
{
static void Main(string[] args)
{
Mat image = Cv2.ImRead("D:/workSpace/OpenCV/HellOpenCV/Resources/images/lena.png");
Mat image_blur = new Mat();
Mat image_box_t = new Mat();
Mat image_box_f = new Mat();
Mat image_gaussian = new Mat();
Mat image_median = new Mat();
Size image_config = new Size(7,7);
Cv2.Blur(image, image_blur, image_config);
Cv2.BoxFilter(image, image_box_t, -1, image_config, new Point(-1, -1), true);
Cv2.BoxFilter(image, image_box_f, -1, image_config, new Point(-1, -1), false);
Cv2.GaussianBlur(image, image_gaussian, image_config, 1);
Cv2.MedianBlur(image, image_median, 3);
var res = MyOpenCV_Extensions.MultipleImage(new List<Mat>() {
image,image_blur,image_box_t,image_box_f,image_gaussian,image_median
}, 3);
Cv2.ImShow("Csharp", res);
Cv2.WaitKey(0);
Cv2.DestroyAllWindows();
}
}
MyOpenCV_Extensions.cs
csharp
public static class MyOpenCV_Extensions
{
/// <summary>
/// 3通道遍历
/// </summary>
/// <param name="mat"></param>
/// <returns></returns>
public static int[,,] MatToArray(Mat mat)
{
var res = new int[mat.Rows, mat.Cols, mat.Channels()];
for (var i = 0; i < mat.Rows; i++)
{
for (var j = 0; j < mat.Cols; j++)
{
var temp = mat.At<Vec3b>(i, j);
res[i, j, 0] = temp[0];
res[i, j, 1] = temp[1];
res[i, j, 2] = temp[2];
}
}
return res;
}
public static Mat MultipleImage(List<Mat> lists, int imgCols)
{
const int MAX_PIXEL = 300;
int imgNum = lists.Count;
//选择图片最大的一边 将最大的边按比例变为300像素
Size imgOriSize = lists[0].Size();
int imgMaxPixel = Math.Max(imgOriSize.Height, imgOriSize.Width);
//获取最大像素变为MAX_PIXEL的比例因子
double prop = imgMaxPixel < MAX_PIXEL ? (double)imgMaxPixel / MAX_PIXEL : MAX_PIXEL / (double)imgMaxPixel;
Size imgStdSize= new Size(imgOriSize.Width* prop, imgOriSize.Height* prop); //窗口显示的标准图像的Size
Mat imgStd = new Mat(); //标准图片
Point location = new Point(0, 0); //坐标点(从0,0开始)
//构建窗口大小 通道与imageVector[0]的通道一样
Mat imgWindow = new Mat(imgStdSize.Height* ((imgNum -1) / imgCols + 1), imgStdSize.Width* imgCols, lists[0].Type());
for (int i = 0; i < imgNum; i++)
{
location.X = (i % imgCols) * imgStdSize.Width;
location.Y = (i / imgCols) * imgStdSize.Height;
Cv2.Resize(lists[i], imgStd, imgStdSize, prop, prop, InterpolationFlags.Linear); //设置为标准大小
imgStd.CopyTo(new Mat(imgWindow, new Rect(location, imgStdSize)) );
}
return imgWindow;
}
}
总结
学这个还是挺无聊的,因为刚开始就是学一些算子的使用。而且用于OpenCV没有Halcon那种进一步的封装,所以代码写起来还是挺痛苦的。最近的学习速度也有点慢下来了。最近在尽力坚持学习。