图像处理基础算法

图像处理基础算法

三月 19, 2019

Opencv的Mat数据转换

  • Mat -> Vector
  • vector -> Mat
  • uchar[] -> Mat
  • Mat -> uchar[]

Mat -> Vector

要将mat转换为vector可以借助opencv官方API将图像转换为行存入vector中.

1
Mat cv::Mat::reshape(int cn, int rows = 0) cons

功能:
在不复制数据的情况下更改二维矩阵的形状和通道数目.
参数:
cn 新的通道数,如果参数为0,则通道数保持不变.
rows 新的行数,如果参数为0,则通道数保持不变.

1
2
3
4
5
template<typename _Tp>
vector<_Tp> Mat2vector(const Mat &mat)
{
return (vector<_Tp>)(mat.reshape(1, 1));
}

vector -> Mat

Mat构造函数可以将vector转换为一维Mat,之后可以使用上述的Mat::reshape将一维度数组转换成二维图像数组.

1
cv::Mat::Mat(const std::vector< _Tp > &vec,bool copyData = false)

功能:
根据vector向量生成Mat,生成的Mat有一列,行数和元素个数相同.
参数:
vec vector类型的向量
copyData (true)是否将vector中的数据复制,(false)或与vector共享数据.

1
2
3
4
5
6
7
8
template<typename _Tp>
cv::Mat vector2Mat(vector<_Tp> vec, int channels, int rows)
{
cv::Mat mat = cv::Mat(v, ture);
//必须拷贝一份,否则会出错(多行一列)
cv::Mat dst = mat.reshape(channels, rows).clone;
return dst;
}

数组->Mat

可以使用Mat的构造函数将数组转换为Mat类型.

1
2
cv::Mat::Mat(Size size, int type, void * data, size_t step = AUTO_STEP)
cv::Mat::Mat(int rows, int cols, int type, void* data, size_t setp =AUTO_STEP)

功能:
将数组转化为Mat
形参:
rows 图像的行数
cols 图像的列数
size 图像的尺寸
type 图像的类新 CV_8UC\CV_32FC1
*data 图像数据
step 每个矩阵行所占的字节数

1
2
3
4
5
cv::Mat array2Mat(void* array, int rows, int cols, int type)
{
cv::Mat dst(rows, cols, type, (void*)array)
return dst.clone();
}

Mat->数组

对于连续的数组可以使用Mat::data()函数取出图像数据.

1
2
3
4
5
6
7
8
9
template<class _Tp>
_Tp* Mat2array(const Mat &mat)
{
_Tp *array = new _Tp[mat.total()];
if(mat.isContinuous()){
array = mat.data;
}
return array;
}

图像缩放

  图像缩放算法往往基于插值实现,常见的图像插值方法包括最近邻插值(Nearest-neighbor)丶双线性插值(Bilinear)丶双立方插值(bicubic)丶方向插值(Edge-directed interpolation)丶基于深度学习等算法.公式推导
图像插值

最近邻插值

最近邻插值是采取采样点周围四个相邻像素中距离最近的一个点的灰度值作为该点的灰度值的方法.I

最近邻插值

优点:
  简单快速,灰度保真较好.
缺点:
  会产生明显的锯齿和马赛克,视觉特性较差.
公式:

欧氏距离

引入0.5是为了让原图像与目标图像几何中心对齐.

距离度量

如下图所示,P为目标图像中对应到原始图像中的点(亚像素),Q11丶Q12丶Q21丶Q22是P点周围的四个整数点,可以根据距离度量公式(De欧氏距离丶D4城市街区距离丶D8棋盘
距离)
欧氏距离De: 城市距离D4:

  1. 欧几里德距离
    欧几里德距离简称欧氏距离,也就是我们通常所说的亮点之间的直线距离.

    欧氏距离

    1
    2
    3
    4
    5
    double distance_euclid(double x0, double y0, double x1, double y1)
    {
    double distance = sqrt(pow((x0 - x1), 2)+ pow((y0 - y1), 2));
    return distance;
    }
  2. 城市距离(曼哈顿距离)
    出租车几何或曼哈顿距离,用以表明两个点在基准坐标系下绝对轴距之和.

    曼哈顿距离

    1
    2
    3
    4
    5
    double distance_manhattan(double x0, double y0, double x1, double y1)
    {
    double distance = fabs(x0 - x1)+fabs(y0 - y1);
    return distance;
    }
  3. 棋盘距离(切比雪夫距离)
    在国际棋盘上,王从一点到另一点的距离

    棋盘距离

    1
    2
    3
    4
    5
    void distance_checkerboard(double x0, double y0, double x1, double y1)
    {
    double distance = fabs(x0 - x1) > fabs(y0 - y1)?fabs(x0 - x1):fabs(y0 - y1);
    return distance;
    }

代码实现

为了简单直观,这里直接使用但通道图像做演示.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
/**
* 函数功能:使用最近邻插值方法缩放图像
* 参数:
* input_image - 输入图像(单色图像)
* output_image - 输出图像
* size -输出图像大小(像素)
* distance_type - 计算距离方法 0 欧式距离 1 曼哈顿距离 2 切比雪夫距离
**/
void nearestScaler(cv::Mat &input_image, cv::Mat &output_image, cv::Size size, int distance_type)
{
//拷贝输入图像
cv::Mat temp_image = input_image.clone();
output_image = cv::Mat(size, temp_image.type());
//计算缩放比例
double scale_row = static_cast<double>(temp_image.rows)/size.height;
double scale_col = static_cast<double>(temp_image.cols)/size.width;
//遍历图像
for(int rows = 0; rows < size.height; rows++){
for(int cols = 0; cols < size.width; cols++){
//计算缩放后原始图坐标
double x = (rows + 0.5) * scale_row + 0.5;
double y = (cols + 0.5) * scale_col + 0.5;
//找出坐标周围的四个点
int x0 = static_cast<uint>(x);
if(x0 > temp_image.rows - 1) x0 = temp_image.rows - 1;
int x1 = x0 + 1;
int y0 = static_cast<uint>(y);
if(y0 > temp_image.cols - 1) y0 = temp_image.cols - 1;

int y1 = y0 + 1;
//使用距离度量公式找出最小距离
std::vector<double>dist(4);
switch(distance_type){
case 0://欧氏距离
dist[0] = distance_euclid(x, y, x0, y0);
dist[1] = distance_euclid(x, y, x1, y0);
dist[2] = distance_euclid(x, y, x0, y1);
dist[3] = distance_euclid(x, y, x1, y1);
break;
case 1://曼哈顿距离
dist[0] = distance_manhattan(x, y, x0 ,y0);
dist[1] = distance_manhattan(x, y, x1 ,y0);
dist[2] = distance_manhattan(x, y, x0 ,y1);
dist[3] = distance_manhattan(x, y, x1 ,y1);
break;
case 2://棋盘距离
dist[0] = distance_checkerboard(x, y, x0, y0);
dist[1] = distance_checkerboard(x, y, x1, y0);
dist[2] = distance_checkerboard(x, y, x0, y1);
dist[3] = distance_checkerboard(x, y, x1, y1);
break;
default:
break;
}
//找出最小距离对因的下标
auto smallest = min_element(std::begin(dist), std::end(dist));
int min_index = distance(std::begin(dist), smallest);
//图像插值
switch(min_index){
case 0:
output_image.at<uchar>(rows, cols) = temp_image.at<uchar>(x0, y0);
break;
case 1:
output_image.at<uchar>(rows, cols) = temp_image.at<uchar>(x1, y0);
break;
case 2:
output_image.at<uchar>(rows, cols) = temp_image.at<uchar>(x0, y1);
break;
case 3:
output_image.at<uchar>(rows, cols) = temp_image.at<uchar>(x1, y1);
break;
default:
break;
}
}
}
}

额外参考:https://blog.csdn.net/ccblogger/article/details/72918354

双线性插值

双线性插值利用周围四个点的灰度值在两个方向上做线性插值得到采样点的灰度值.

双线性插值

优点:
   1. 计算中较为充分的考虑了相邻各个点的特征,具有灰度平滑过渡的特点.
   2. 一般情况下可以得到满意的结果

缺点:
   1. 具有地通滤波特性,使图像轮廓模糊
   2. 平滑做用使得图像细节退化,尤其在放大的时候尤为明显.

公式:

双线性插值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
* 函数功能: 双线性插值缩放图像
* 参数:
* input_image - 输入图像
* output_image - 输出图像
* size - 缩放尺寸
* 备注:
* 只能进行单通道图片缩放
**/
void bilinearIntertpolatioin(cv::Mat &input_image, cv::Mat &output_image, cv::Size size)
{
cv::Mat temp_image = input_image.clone();
output_image = cv::Mat(size, temp_image.type());

//计算缩放比例
double scalar_rows = static_cast<double>(temp_image.rows) / size.height;
double scalar_cols = static_cast<double>(temp_image.cols) / size.width;

for(int rows = 0; rows < size.height; rows++){
for(int cols = 0; cols < size.width; cols++){
double x = (rows + 0.5) * scalar_rows - 0.5;
double y = (cols + 0.5) * scalar_cols - 0.5;
//防止超出图像边界
double x0 = static_cast<uint>(x);
x0 = x0 < 0? 0:x0;
x0 = x0 > (temp_image.rows - 1)? (temp_image.rows - 1) : x0;
double x1 = x0 + 1;

double y0 = static_cast<uint>(y);
y0 = y0 < 0? 0 : y0;
y0 = y0 > (temp_image.cols -1)? (temp_image.cols - 1) : y0;
double y1 = y0 + 1;
cv::Matx12d matx = {x1 - x, x - x0};
cv::Matx22d matd = {static_cast<double>(temp_image.at<uchar>(x0, y0)), static_cast<double>(temp_image.at<uchar>(x0, y1)),
static_cast<double>(temp_image.at<uchar>(x1, y0)), static_cast<double>(temp_image.at<uchar>(x1,y1))};
cv::Matx21d maty = {y1 - y,
y - y0};

auto val = matx * matd * maty;
output_image.at<uchar>(rows, cols) = val(0, 0);
}
}
}

static_cast()只保留整数部分,小数部分被舍弃:
static_cast<uchar>(4.1)结果为4
static_cast<uchar>(4.8)结果也为4

双三次插值

双三次插值方法,(三次立方插值法或CC插值法),是一种利用待插值点16个邻点像素值经过计算得到,没一个点需要经过5次计算,所以耗费时间长.

双三次插值

优点:
   1. 三次卷积内插法减少高频损失,可以有效的平滑噪声.
   2. 三次卷积内差法可以得到最佳结果(相比于上面两种插值方法).

缺点:
   计算量大,所需时间长.

公式:

二维高斯

Canny边缘检测

介绍

Canny算子是John Canny在1986年提出的一种边缘检测方法.Canny的目地就是找到一个最优的边缘检测算法,优的边缘检测含义是:

  1. 最优检测:算法能够尽可能地多标识出图像中的实际边缘. - 边缘全
  2. 检测到的便晕应精确的定位在真实边缘的中心. - 定位准确
  3. 途中给定的边缘只标记一次, 并且在可能的情况下,图像的噪声不产生假的边缘. - 抗噪声能力强
    步骤:
      高斯平滑滤波->计算梯度和方向->非极大值抑制->双阈值检测和连接边缘

Setp1: 高斯平滑

高斯核

  高斯模糊(Gaussian Blur),也叫做高斯平滑,通常用它来减少噪声以及降低细节测层次.高斯模糊对于原图想来说是一个低通滤波器.
公式:

二维高斯

σ:标准差,其越大图像越模糊

代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* 功能: 求解高斯核
* 参数:
* sigma - σ(标准差)
* size - 高斯矩阵大小(只能是奇数)
**/
//高斯卷积核生成
void getOneGuassionArray(int size, double sigma, double **gauss)
{
double pi = 3.1415927;
int center = size/2;
double sum = 0.0;
for(int i = 0; i < size; i++){
for(int j = 0; j < size; j++){
gauss[i][j] = exp(-(pow((i-center),2) + pow((j-center),2))/(2*pow(sigma,2))) / (2*pi*pow(sigma,2));
sum += gauss[i][j];
}
}

for(int i = 0; i < size; i++){
for(int j = 0; j < size; j++){
gauss[i][j] = gauss[i][j] / sum;
}
}
}

结果:
下面是σ = 0.5,size = 3所得到的高斯核

高斯核

图像卷积

用一个模板和一副图像进行卷积,对于图上的一点,让模板的中点(几何中心点)和该点重合,然后模板上的点和图像对应点相乘,然后将各个点所得的乘积相加,就得到该点的卷积值.简单的来说就中心像素值和相邻像素的加权求和.

高斯核

公式:

高斯核

算法实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* 功能: 对图像进行卷积操作
* 参数:
* input_image - 输入图像
*
**/
void convolution(cv::Mat &input_image, cv::Mat &output_image, double **kernel, int size)
{
//找出卷积核的中心点
int center = size / 2;
cv::Mat temp_image = input_image.clone();
output_image = cv::Mat(temp_image.size(), temp_image.type());
for(int rows = 0; rows < temp_image.rows; rows++){
for(int cols = 0; cols < temp_image.cols; cols++){
double dst_pixel_value = 0.0;
for(int i = -size/2; i <= size/2; i++){
for(int j = -size/2; j <= size/2; j++){
int row = rows + i;
int col = cols + j;
//限幅,防止超出图片范围
row = row >= temp_image.rows - 1 ? row - 1 : row;
row = row < 0 ? 0:row;
col = col >= temp_image.cols - 1? col - 1 : col;
col = col < 0 ? 0 : col;
dst_pixel_value += temp_image.at<uchar>(row,col) * kernel[center+i][center+j];
}
}
output_image.at<uchar>(rows, cols) = static_cast<uchar>(dst_pixel_value);
}
}
}

setp2: 计算梯度和方向

图像的边缘处像素值变化比较剧烈,所以想要得到图像的边缘首先应该求出图像的梯度,图像的梯度就是对二元函数进行”导数”, Canny算子使用Sobel的四个算子来检测图像的水平丶垂直丶对角线边缘:

双三次插值

双三次插值

双三次插值