图像处理100问(Question_01_10)

感谢分享!
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错误

一、通道交换

RGB -> BGR

#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <iostream>

// Channel swap
cv::Mat channel_swap(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_8UC3);

  // each y, x
  for (int y = 0; y < height; y++){
    for (int x = 0; x < width; x++){
      // R -> B
      out.at<cv::Vec3b>(y, x)[0] = img.at<cv::Vec3b>(y, x)[2];
      // B -> R
      out.at<cv::Vec3b>(y, x)[2] = img.at<cv::Vec3b>(y, x)[0];
      // G -> G
      out.at<cv::Vec3b>(y, x)[1] = img.at<cv::Vec3b>(y, x)[1];
    }
  }

  return out;
}

int main(int argc, const char* argv[]){
  // read image
  cv::Mat img = cv::imread("imori.jpg", cv::IMREAD_COLOR);

  // channel swap
  cv::Mat out = channel_swap(img);

  //cv::imwrite("out.jpg", out);
  cv::imshow("img", img);
  cv::imshow("out", out);
  cv::waitKey(0);
  cv::destroyAllWindows();

  return 0;
}

在这里插入图片描述

二、灰度化

RGB ->YUV
在这里插入图片描述
来源:https://juejin.im/post/6844903640377884679

// 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;
}

在这里插入图片描述

三、二值化(Thresholding)

将灰度图的亮度值,大于阈值设置为255,小于阈值设置为0。

// Gray -> Binary
cv::Mat Binarize(cv::Mat gray, int th){
  int width = gray.cols;
  int height = gray.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++){
      // Binarize
      if (gray.at<uchar>(y, x) > th){
        out.at<uchar>(y, x) = 255;
      } else {
        out.at<uchar>(y, x) = 0;
      }
    
    }
  }

  return out;
}

在这里插入图片描述

四、大津二值化(Otsu’s Method)

小于阈值为前景(0),大于阈值为背景(255)

m0:前景平均灰度
m1:背景平均灰度
w0:前景像素数量占总像素数量的比例(概率)
w1:背景像素数量占总像素数量的比例(概率)

平均灰度m:m0 * w0 + m1 * w1
类间方差:w0 * (m0 - m) ^ 2 + w1 * (m1 - m) ^ 2
计算得到:w0 * w1 * (m0 - m1) ^ 2
统计其最大值即是阈值

// Gray -> Binary
cv::Mat Binarize_Otsu(cv::Mat gray){
  int width = gray.cols;
  int height = gray.rows;

  // determine threshold
  double w0 = 0, w1 = 0;
  double m0 = 0, m1 = 0;
  double max_sb = 0, sb = 0;
  int th = 0;
  int val;

  // Get threshold
  for (int t = 0; t < 255; t++){
    w0 = 0;
    w1 = 0;
    m0 = 0;
    m1 = 0;
    for (int y = 0; y < height; y++){
      for (int x = 0; x < width; x++){
        val = (int)(gray.at<uchar>(y, x));

        if (val < t){
          w0++;
          m0 += val;
        } else {
          w1++;
          m1 += val;
        }
      }
    }

    m0 /= w0;				//前景平均灰度
    m1 /= w1;				//背景平均灰度
    w0 /= (height * width);	//前景像素数量占总像素数量的比例(概率)
    w1 /= (height * width);	//背景像素数量占总像素数量的比例(概率)
    sb = w0 * w1 * pow((m0 - m1), 2);
    
    if(sb > max_sb){
      max_sb = sb;
      th = t;
    }
  }

  std::cout << "threshold:" << th << std::endl;

  // 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++){
      // Binarize
      if (gray.at<uchar>(y, x) > th){
        out.at<uchar>(y, x) = 255;
      } else {
        out.at<uchar>(y, x) = 0;
      }
    
    }
  }

  return out;
}

在这里插入图片描述

五、RGB与HSV变换
  • RGB色彩空间:是最常见的一种颜色模型,被称为设备相关的色彩空间。RGB色彩系统之所以能够用来表示色彩,归根结底还是因为人眼中的锥状细胞和棒状细胞对红色、蓝色和绿色特别敏感。
    RGB颜色模型对应笛卡尔坐标系中的一个立方体,R、G、B分别代表三个坐标轴。当3个分量的取值范围都是0~255之间的整数时,可以表示256³种颜色。
    rgb
  • HSV颜色空间:
    使用色相(Hue)、饱和度(Saturation)、明度(Value)来表示色彩的一种方式。
    (1). 色相:将颜色使用 到 表示,就是平常所说的颜色名称,如红色、蓝色。色相与数值按下表对应:
    绿品红
    60°120°180°240°300°360°
    (2). 饱和度:是指色彩的纯度,饱和度越低则颜色越黯淡(0 ≤ S<1)
    (3). 明度:即颜色的明暗程度。数值越高越接近白色,数值越低越接近黑色(0 ≤ V<1)

HSV

1、RGB转换HSV
  • RGB的取值范围量化为[0, 1];
  • 计算Max与Min
    在这里插入图片描述
  • 计算HSV
    在这里插入图片描述
// BGR -> HSV
cv::Mat BGR2HSV(cv::Mat img){
  // get height and width
  int width = img.cols;
  int height = img.rows;

  float r, g, b;
  float h, s, v;
  float _max, _min;

  // prepare output
  cv::Mat hsv = cv::Mat::zeros(height, width, CV_32FC3);

  // each y, x
  for (int y = 0; y < height; y++){
    for (int x = 0; x < width; x++){
      // BGR -> HSV
      r = (float)img.at<cv::Vec3b>(y, x)[2] / 255;
      g = (float)img.at<cv::Vec3b>(y, x)[1] / 255;
      b = (float)img.at<cv::Vec3b>(y, x)[0] / 255;

      _max = fmax(r, fmax(g, b));
      _min = fmin(r, fmin(g, b));

      // get Hue
      if(_max == _min){
	      h = 0;
      } else if (_min == b) {
	      h = 60 * (g - r) / (_max - _min) + 60;
      } else if (_min == r) {
	      h = 60 * (b - g) / (_max - _min) + 180;
      } else if (_min == g) {
	      h = 60 * (r - b) / (_max - _min) + 300;
      }

      // get Saturation
      s = _max - _min;

      // get Value
      v = _max;

      hsv.at<cv::Vec3f>(y, x)[0] = h;
      hsv.at<cv::Vec3f>(y, x)[1] = s;
      hsv.at<cv::Vec3f>(y, x)[2] = v;
    }
  }
  return hsv;
}

// inverse Hue 色相反转(色相加180)
cv::Mat inverse_hue(cv::Mat hsv){
  int height = hsv.rows;
  int width = hsv.cols;

  for(int y = 0; y < height; y++){
    for(int x = 0; x < width; x++){
      hsv.at<cv::Vec3f>(y, x)[0] = fmod(hsv.at<cv::Vec3f>(y, x)[0] + 180, 360);
    }
  }

  return hsv;
}
2、HSV转换RGB
  • step1
    在这里插入图片描述
  • step2
    在这里插入图片描述
  • step3
    在这里插入图片描述
// HSV -> BGR
cv::Mat HSV2BGR(cv::Mat hsv){
  // get height and width
  int width = hsv.cols;
  int height = hsv.rows;

  float h, s, v;
  double c, _h, _x;
  double r, g, b;

  // prepare output
  cv::Mat out = cv::Mat::zeros(height, width, CV_8UC3);

  // each y, x
  for (int y = 0; y < height; y++){
    for (int x = 0; x < width; x++){

      h = hsv.at<cv::Vec3f>(y, x)[0];
      s = hsv.at<cv::Vec3f>(y, x)[1];
      v = hsv.at<cv::Vec3f>(y, x)[2];

      c = s;
      _h = h / 60;
      _x = c * (1 - abs(fmod(_h, 2) - 1));

      r = g = b = v - c;
      
      if (_h < 1) {
        r += c;
        g += _x;
      } else if (_h < 2) {
        r += _x;
        g += c;
      } else if (_h < 3) {
	  g += c;
	  b += _x;
      } else if (_h < 4) {
	  g += _x;
	  b += c;
      } else if (_h < 5) {
	  r += _x;
	  b += c;
      } else if (_h < 6) {
	  r += c;
	  b += _x;
      }

      out.at<cv::Vec3b>(y, x)[0] = (uchar)(b * 255);
      out.at<cv::Vec3b>(y, x)[1] = (uchar)(g * 255);
      out.at<cv::Vec3b>(y, x)[2] = (uchar)(r * 255);
    }
  }

  return out;
}

色相反转(色相加180)结果:
在这里插入图片描述

六、减色处理

我们将图像的值由256³压缩至4³,即将RGB的值只取{32, 96, 160, 224}。这被称作色彩量化。(256 / 4 / 2 = 32)

色彩的值按照下面的方式定义:
在这里插入图片描述

// Dedcrease color
cv::Mat decrease_color(cv::Mat img){
  // int height = img.cols;
  // int width = img.rows;
  // error: 原文宽高反了
  int width = img.cols;
  int height = img.rows;
  int channel = img.channels();

  cv::Mat out = cv::Mat::zeros(height, width, CV_8UC3);

  for(int y = 0; y < height; y++){
    for(int x = 0; x < width; x++){
      for(int c = 0; c < channel; c++){
        out.at<cv::Vec3b>(y, x)[c] = (uchar)(floor((double)img.at<cv::Vec3b>(y, x)[c] / 64) * 64 + 32);
      }
    }
  }
  return out;
}

在这里插入图片描述

七、平均池化(Average Pooling)

将图片按照固定大小网格分割,网格内的像素值取网格内所有像素的平均值。
我们将这种把图片使用均等大小网格分割,并求网格内代表值的操作称为池化(Pooling)。
池化操作是卷积神经网络(Convolutional Neural Network)中重要的图像处理方式。

定义:
在这里插入图片描述

// average pooling
cv::Mat average_pooling(cv::Mat img){
  int height = img.rows;
  int width = img.cols;
  int channel = img.channels();

  // prepare output
  cv::Mat out = cv::Mat::zeros(height, width, CV_8UC3);

  int r = 8;		//  使用8x8的网格做平均池化
  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<cv::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<cv::Vec3b>(y + dy, x + dx)[c] = (uchar)v;
          }
        }
      }
	  }
	}
  return out;
}

在这里插入图片描述

八、最大池化(Max Pooling)
// max pooling
cv::Mat max_pooling(cv::Mat img){
  int height = img.rows;
  int width = img.cols;
  int channel = img.channels();

  // prepare output
  cv::Mat out = cv::Mat::zeros(height, width, CV_8UC3);

  int r = 8;		//  使用8x8的网格做最大池化
  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 = fmax(img.at<cv::Vec3b>(y + dy, x + dx)[c], v);
          }
        }

        for (int dy = 0; dy < r; dy++){
          for (int dx = 0; dx < r; dx++){
            out.at<cv::Vec3b>(y + dy, x + dx)[c] = (uchar)v;
          }
        }
      }
	  }
	}
  return out;
}

在这里插入图片描述

九、高斯滤波(Gaussian Filter)

高斯滤波器是一种可以使图像平滑的滤波器,用于去除噪声。
可用于去除噪声的滤波器还有中值滤波器(参见问题十),平滑滤波器(参见问题十一)、LoG滤波器(参见问题十九)。

高斯滤波器将中心像素周围的像素按照高斯分布加权平均进行平滑化。这样的(二维)权值通常被称为卷积核(kernel)或者滤波器(filter)。但是,由于图像的长宽可能不是滤波器大小的整数倍,因此我们需要在图像的边缘补 。这种方法称作Zero Padding。并且权值 (卷积核)要进行归一化操作( Σg=1)。
缺点:对椒盐噪声基本无能为力。

按高斯分布公式计算权值:
在这里插入图片描述
使用高斯滤波器(3x3大小,标准差=1.3)进行滤波,计算近邻高斯滤波器:
在这里插入图片描述

// gaussian filter (sigma = 1.3, kernel_size = 3)
cv::Mat gaussian_filter(cv::Mat img, double sigma, 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);
  int _x = 0, _y = 0;
  double kernel_sum = 0;
  
  // get gaussian kernel
  float kernel[kernel_size][kernel_size];

  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] = 1 / (2 * M_PI * sigma * sigma) * 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;
    }
  }
  

  // 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 (((x + dx) >= 0) && ((y + dy) >= 0)){
            v += (double)img.at<cv::Vec3b>(y + dy, x + dx)[c] * kernel[dy + pad][dx + pad];
          }
        }
      }
      out.at<cv::Vec3b>(y, x)[c] = v;
      }
    }
  }
  return out;
}

在这里插入图片描述

十、中值滤波(Median Filter)

中值滤波算法以某像素的领域图像区域中的像素值的排序为基础,将像素领域内灰度的中值代替该像素的值。中值滤波对处理椒盐噪声非常有效。
缺点:易造成图像的不连续性

// median filter
cv::Mat median_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;
	      
	      for (int i = 0; i < kernel_size * kernel_size; i++){
	        vs[i] = 999;
	      }
	      
	      // get neighbor pixels
	      for (int dy = -pad; dy < pad + 1; dy++){
	        for (int dx = -pad; dx < pad + 1; dx++){
	          if (((y + dy) >= 0) && ((x + dx) >= 0)){
	            vs[count++] = (int)img.at<cv::Vec3b>(y + dy, x + dx)[c];
	          }
	        }
	      }
	
	      // get and assign median
	      std::sort(vs, vs + (kernel_size * kernel_size));
	      out.at<cv::Vec3b>(y, x)[c] = (uchar)vs[int(floor(count / 2)) + 1];
      }
    }
  }
  return out;
}

在这里插入图片描述

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值