Site Overlay

图像处理小练习合集

图像处理一百题 Q.1. チャネル入れ替え - 通道替换

题目

画像[Gazou]を読み込み、RGBをBGRの順[jun]に入れ替え[i re ka e]よ。

载入图片,交换 RGB 为 BGR 顺序。

画像の赤成分[sei bun]を取り出す[toridasu]には、以下[ika]のコード[code]で可能[kanou]。 cv2.imread()関数[kansu]ではチャネル[channel]がBGRの順になることに注意[chui]! これで変数[hensuu]redにimori.jpgの赤成分のみが入る[hairu]。

下列代码可能对抽取红色有用。注意用cv2.imread()函数读取后通道为BGR顺序。

import cv2
img = cv2.imread("imori.jpg")
red = img[:, :, 2].copy()

答案

#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_8UC1,CV_8UC2,CV_8UC3。
    1, 2, 3 representates the count of channel.
    8U indicates 8bit Unsigned.
  */
  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("img.jpg", cv::IMREAD_COLOR);

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

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

  return 0;
}

效果

输入:
img.jpg
输出:
out.jpg

总结

思路十分简单,imread 读入->遍历替换像素-> imwrite 写入。

图像处理一百题 Q.2. グレースケール化(Grayscale 化,灰度化)

题目

Q.2. グレースケール化
画像をグレースケールにせよ。 グレースケールとは、画像の輝度表現方法[Kido hyōgen hōhō]の一種[Isshu]であり下式[Kashiki]で計算[Keisan]される[FINISH]。

Y = 0.2126 R + 0.7152 G + 0.0722 B

答案

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

cv::Mat channel_swap(cv::Mat img){
  int width = img.cols;
  int height = img.rows;

  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++){
      uchar kido = (
              img.at<cv::Vec3b>(y, x)[0] * 0.2126
            + img.at<cv::Vec3b>(y, x)[2] * 0.7152 
            + img.at<cv::Vec3b>(y, x)[1] * 0.0722
      );
      out.at<cv::Vec3b>(y, x)={kido,kido,kido};
    }
  }

  return out;
}

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

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

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

  return 0;
}

效果

输入:
img.jpg
输出:
out.jpg

总结

加权平均即可。
标准答案的做法:

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

只用了一个通道 CV_8UC1,更简洁一点。

图像处理一百题 Q.3. 二値化 二值化,二进制化

题目

画像を二値化せよ。 二値化とは、画像を黒と白の二値で表現する方法である。 ここでは、グレースケールにおいて閾値を128に設定し、下式で二値化する。

y = { 0 (if y < 128)
     255 (else) 

答案

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

cv::Mat channel_swap(cv::Mat img){
  int width = img.cols;
  int height = img.rows;

  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++){
      uchar kido = (
              img.at<cv::Vec3b>(y, x)[0] * 0.3
            + img.at<cv::Vec3b>(y, x)[2] * 0.3 
            + img.at<cv::Vec3b>(y, x)[1] * 0.3
      );
      out.at<uchar>(y, x)=kido>128?255:0;
    }
  }

  return out;
}

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

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

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

  return 0;
}

效果

输入:
img.jpg
输出:
out.jpg

总结

先用灰度公式计算灰度,然后大于 128 的取 255,小于 128 的取 0.

图像处理一百题 Q.4. 大津の二値化

题目

大津の二値化を実装[Jissō]せよ。 大津の二値化[Ni atai-ka]とは判別分析法[Hanbetsu bunseki-hō]と呼ばれ[yobare]、二値化における[おける,可以]分離[ri]の閾値[ikichi]を自動決定[kettei ]する手法[shuhō]である。 これはクラス内分散[nai bunsan](类内方差)とクラス間分散[kan bunsan](类间方差)の比から計算される。

请实现大津的二值化方法。大津的二值化方法又称为判别分析法。用这种方法,二值化的分离阈值可以自动决定。这通过类内方差和类间方差的比值计算而来。

  • 閾値t未満をクラス0, t以上をクラス1とする。

  • w0, w1 ... 閾値tにより分離された各クラスの画素数の割合 (w0 + w1 = 1を満たす)

  • S0^2, S1^2 ... 各クラスの画素値の分散

  • M0, M1 ... 各クラスの画素値の平均値

  • 小于阈值 t,归为 class 0,大于 t 归为 class 1

  • w0,w1:各类别的像素数的占比,由阈值 t 分割,且满足 w0+w1= 1。

  • $S_0^2$$ S_1^2$:各类像素值的方差

  • $M_0$ $M_1$:各类别像素值的平均值

とすると、如果

img

となり、分離度Xは次式で定義される。

然后,分离度 X 由下式定义。

img

となるので、因为

img

となる。すなわち、Sb^2 = w0 w1 (M0 - M1) ^2 が最大となる、閾値tを二値化の閾値とすれば良い。

然后 $Sb^2 = w0 w1 (M0 - M1) ^2 $函数值最大时的阈值就是最优阈值。

分析

看完题的我是懵 B 的。于是去看代码。我不习惯超简短命名,所以为了便于理解,我就稍微改了改(详见答案部分)。然后我瞬间就懂它的原理了。

首先介绍两个概念:组内方差和组间方差。

组内方差就是一组数据内部的各个数据的方差,和我们初高中理解的是一样的。

组间方差,就是相当于把各个组看成各个数(也就这个组的平均数),求这几个数的方差。

记$Sw$为总组内方差和,$w_0,w_1$分别为白像素和黑像素的占比,$S_0^2$$ S_1^2$为像素值的组内方差,则有:

$S_w = w_0S_0^2 + w_1S_1^2$

通过上式我们可以计算得到组内总方差,接下来计算组间方差:把两组数据(白像素和黑像素)看作两个数 $M_0,M_1$,那么这两个数其实就是两组数据各自的平均数。再记$M_t$为$M_0,M_1$的平均数,于是得到组间方差:

$S_b^2 = w_0(M_0-M_t)^2+w_1(M_1-M_t)^2$

带入 $M_t={M_0+M_1\over{2}}$ 得:

$S_b^2 = w_0w_1(M_0-M_1)^2$

总方差记作$S_t$,则有:

$S_t^2 = S_w^2 + S_b^2$

然后定义分离度为组间方差比组内方差的二次方,这是因为显然组间方差越大,组内方差越小,则黑白分离越成功,也就是分离度最高。分离度记作 $X$,则:

$X = (S_b / S_w)^2 = {S_b^2\over{S_t^2-S_b^2}}$

上下同除以分子,可以发现,由于$S_t$是定值(总方差不变嘛),$S_b$ 增大,分离度也增大。因此组间方差$S_b$最大的时候,就是分离得最好的时候。我们看下面的代码,由于分离阈值 $t\in[0,255]$,因此依次遍历代入,找到$S_b$最大时对应的$t$,进行分离即可。这种方法就是大津的二值化法。

答案

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

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

  // determine threshold
  double counterWhenGrayLessThanThreshold = 0, counterWhenGrayMoreThanThreshold = 0;
  double accumulationOfGrayValThatLessThanCurrentThreshold = 0, accumulationOfGrayValThatMoreThanCurrentThreshold = 0;
  double MaxValOfVarianceBetweenTwoClasses = 0, varianceBetweenTwoClasses = 0;
  int threshold = 0;
  int grayOfCurrentPixel;

  // Get threshold
  for (int t = 0; t < 255; t++){
    counterWhenGrayLessThanThreshold = 0;// increase when gray is less than t, and t is changing
    counterWhenGrayMoreThanThreshold = 0;
    accumulationOfGrayValThatLessThanCurrentThreshold = 0;// increase when gray is less than t, and t is changing
    accumulationOfGrayValThatMoreThanCurrentThreshold = 0;// increase when gray is more than t
    for (int y = 0; y < height; y++){
      for (int x = 0; x < width; x++){
          // traverse each pixel
        grayOfCurrentPixel = (int)(gray.at<uchar>(y, x));// gray, ie. kido, ie.huidu

        if (grayOfCurrentPixel < t){
          counterWhenGrayLessThanThreshold++;
          accumulationOfGrayValThatLessThanCurrentThreshold += grayOfCurrentPixel;
        } else {
          counterWhenGrayMoreThanThreshold++;
          accumulationOfGrayValThatMoreThanCurrentThreshold += grayOfCurrentPixel;
        }
      }
    }

    double avgAccumulationOfGrayValThatLessThanCurrentThreshold = accumulationOfGrayValThatLessThanCurrentThreshold/ counterWhenGrayLessThanThreshold;
    double avgAaccumulationOfGrayValThatMoreThanCurrentThreshold = accumulationOfGrayValThatMoreThanCurrentThreshold /= counterWhenGrayMoreThanThreshold;
    counterWhenGrayLessThanThreshold /= (height * width);
    counterWhenGrayMoreThanThreshold /= (height * width);
    // 组间方差
    varianceBetweenTwoClasses = counterWhenGrayLessThanThreshold * counterWhenGrayMoreThanThreshold * pow((avgAccumulationOfGrayValThatLessThanCurrentThreshold - avgAaccumulationOfGrayValThatMoreThanCurrentThreshold), 2);
    // 方差最大时的阈值就是最优解
    if(varianceBetweenTwoClasses > MaxValOfVarianceBetweenTwoClasses){
      MaxValOfVarianceBetweenTwoClasses = varianceBetweenTwoClasses;
      threshold = t;
    }
  }

  std::cout << "threshold:" << threshold << 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) > threshold){
        out.at<uchar>(y, x) = 255;
      } else {
        out.at<uchar>(y, x) = 0;
      }

    }
  }

  return out;
}

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

  // BGR -> Gray
  cv::Mat gray = BGR2GRAY(img);

  // Gray -> Binary
  cv::Mat out = Binarize_Otsu(gray);

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

  return 0;
}

效果

图像处理一百题 Q.5. HSV変換

Q.5. HSV変換

做这道题的过程中遇到了一些玄学错误(主要是数据类型和结构体的问题),今天终于弄好了。不过还是出现了蜜汁色差,最后发现是取模的精度问题。使用 fmod 代替强转 int 后取模的老方法就解决了。

题目

HSV変換(henkan)を実装(jisso)して、色相(shikisou)Hを反転(hanten)せよ。

请实现 HSV 变换,并反转色相 H。

HSV変換とは、Hue(色相)Saturation(彩度)Value(明度) で色を表現する手法である。

所谓 HSV 变换,是指用 Hue(色相)Saturation(彩度)Value(明度) 来表现颜色的手段。

  • Hue ... 色合いを0~360度で表現し、赤や青など色の種類を示す。 ( 0 <= H < 360) 色相は次の色に対応する。
  • Hue:色相,用 0~360 度表示,从而表示出颜色的类型,比如红色或蓝色。0~360 度对应颜色如下:
赤 黄色  緑  水色  青  紫   赤
0  60  120  180 240 300 360
  • Saturation ... 色の鮮やかさ。Saturationが低いと灰色さが顕著になり、くすんだ色となる。 ( 0<= S < 1)
  • Saturation:颜色的饱和度。值越低,越灰暗。
  • Value ... 色の明るさ。Valueが高いほど白に近く、Valueが低いほど黒に近くなる。 ( 0 <= V < 1)
  • Value:颜色的亮度。只越高,越接近白色,值越低,越接近黑色。

RGB -> HSV変換は以下の式で定義される。

RGB -> HSV 的变换由下式定义。

R,G,Bが[0, 1]の範囲にあるとする。

把 RGB 的范围看作是 [0,1]

Max = max(R,G,B)
Min = min(R,G,B)

H =  { 0                            (if Min=Max)
       60 x (G-R) / (Max-Min) + 60  (if Min=B)
       60 x (B-G) / (Max-Min) + 180 (if Min=R)
       60 x (R-B) / (Max-Min) + 300 (if Min=G)

V = Max

S = Max - Min

HSV -> RGB変換は以下の式で定義される。

HSV -> RGB 的变换由下式定义。

C = S

H' = H / 60

X = C (1 - |H' mod 2 - 1|)

(R,G,B) = (V - C) (1,1,1) + { (0, 0, 0)  (if H is undefined)
                              (C, X, 0)  (if 0 <= H' < 1)
                              (X, C, 0)  (if 1 <= H' < 2)
                              (0, C, X)  (if 2 <= H' < 3)
                              (0, X, C)  (if 3 <= H' < 4)
                              (X, 0, C)  (if 4 <= H' < 5)
                              (C, 0, X)  (if 5 <= H' < 6)

ここでは色相Hを反転(180を加算)し、RGBに直し画像を表示せよ。

在此,反转色相H(加上180),将图像转换为RGB后显示。

我的答案

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

using namespace std;

// find the maximal
float rgbMax(float r, float g, float b)
{
  return r > g ? (r > b ? r : b) : (g > b ? g : b);
}

// find the minimal
float rgbMin(float r, float g, float b)
{
  return r < g ? (r < b ? r : b) : (g < b ? g : b);
}
cv::Mat rgbToHsv(cv::Mat img)
{

  // init
  cv::Mat ret = cv::Mat::zeros(img.rows, img.cols, CV_32FC3);

  // for each pixel
  for (int i = 0; i < img.cols; i++)
  {
    for (int j = 0; j < img.rows; j++)
    {
      float r, g, b;
      // cout << "pixel: " << img.at<cv::Vec3b>(j, i) << endl;
      // the color is stored by rbg order.
      r = img.at<cv::Vec3b>(j, i)[0] / 255.0;
      b = img.at<cv::Vec3b>(j, i)[1] / 255.0;
      g = img.at<cv::Vec3b>(j, i)[2] / 255.0;

      // get the lightest and the dimest
      float max = rgbMax(r, g, b);
      float min = rgbMin(r, g, b);
      // cout << "Max: " << max << endl;
      float h, s = max - min, v = max;
      float diff = 60.0 / s;
      // calculate h
      if (max == min)
      {
        h = 0;
      }
      else if (min == b)
      {
        h = diff * (g - r) + 60.0;
      }
      else if (min == r)
      {
        h = diff * (b - g) + 180.0;
      }
      else if (min == g)
      {
        h = diff * (r - b) + 300.0;
      }
      cout << cv::Vec3f(h, s, v) << endl;
      ret.at<cv::Vec3f>(j, i) = cv::Vec3f(h, s, v);
    }
  }
  return ret;
}

cv::Mat hsvToRgb(cv::Mat hsv)
{
  cv::Mat ret = cv::Mat::zeros(hsv.rows, hsv.cols, CV_8UC3);
  for (int i = 0; i < hsv.cols; i++)
  {
    for (int j = 0; j < hsv.rows; j++)
    {
      float h = hsv.at<cv::Vec3f>(j, i)[0];
      float s = hsv.at<cv::Vec3f>(j, i)[1];
      float v = hsv.at<cv::Vec3f>(j, i)[2];
      float h_d = h / 60.0;
      float x = s * (1.0 - abs(int(h_d) % 2 - 1.0)); // 此处取模应为 fmod(h_d, 2)

      cv::Vec3f rgb = (v - s) * cv::Vec3f(1, 1, 1);
      if (h_d >= 0 && h_d < 1)
      {
        rgb += cv::Vec3f(s, x, 0);
      }
      else if (h_d >= 1 && h_d < 2)
      {
        rgb += cv::Vec3f(x, s, 0);
      }
      else if (h_d >= 2 && h_d < 3)
      {
        rgb += cv::Vec3f(0, s, x);
      }
      else if (h_d >= 3 && h_d < 4)
      {
        rgb += cv::Vec3f(0, x, s);
      }
      else if (h_d >= 4 && h_d < 5)
      {
        rgb += cv::Vec3f(x, 0, s);
      }
      else if (h_d >= 5 && h_d < 6)
      {
        rgb += cv::Vec3f(s, 0, x);
      }
      ret.at<cv::Vec3b>(j, i) = cv::Vec3f(rgb[0], rgb[2], rgb[1]) * 255;
      //cout << "pixel: " << ret.at<cv::Vec3b>(j, i) << " at: " << j << ", " << i << endl;
    }
  }
  return ret;
}
int main(int argc, const char *argv[])
{
  // read image
  cv::Mat img = cv::imread("img.jpg", cv::IMREAD_COLOR);

  // BGR -> Gray
  cv::Mat hsv = rgbToHsv(img);
  cv::Mat out = hsvToRgb(hsv);
  cv::imwrite("out.jpg", out);
  cv::imshow("sample", out);
  cv::waitKey(0);
  cv::destroyAllWindows();

  return 0;
}

参考答案

include <opencv2/core.hpp>

include <opencv2/highgui.hpp>

include

include

// 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(y, x)2 / 255;
g = (float)img.at(y, x)1 / 255;
b = (float)img.at(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(y, x)[0] = h;
  hsv.at(y, x)[1] = s;
  hsv.at(y, x)[2] = v;
}

}
return hsv;
}

// 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(y, x)[0];
  s = hsv.at(y, x)[1];
  v = hsv.at(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(y, x)[0] = (uchar)(b * 255);
  out.at(y, x)[1] = (uchar)(g * 255);
  out.at(y, x)[2] = (uchar)(r * 255);
}

}

return out;
}

// inverse Hue
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(y, x)[0] = fmod(hsv.at(y, x)[0] + 180, 360);
}
}

return hsv;
}

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

// BGR -> HSV
cv::Mat hsv = BGR2HSV(img);

// Inverse Hue
hsv = inverse_hue(hsv);

// Gray -> Binary
cv::Mat out = HSV2BGR(hsv);

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

return 0;
}

分析

就是带公式啦。不过我对转换的公式背后的原理还是不太明白……到底是怎么推导的呢?留待以后解决吧。

效果

img.jpg

out.jpg

我这里产生了奇怪的色差,解决方法上面说了。

图像处理一百题 Q.6 減色処理

減色処理

题目

ここでは画像の値を256^3から4^3、すなわちR,G,B in {32, 96, 160, 224}の各4値に減色せよ。 これは量子化操作である。 各値に関して、以下の様に定義する。

val = {  32  (  0 <= val <  64)
         96  ( 64 <= val < 128)
        160  (128 <= val < 192)
        224  (192 <= val < 256)

解析

这道题十分简单。算是一种彩色的阈值化吧。

我的解法

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

cv::Mat ColorReduction(cv::Mat in)
{
  cv::Mat out = cv::Mat::zeros(in.rows, in.cols, CV_8UC3);
  for (int x = 0; x < in.cols; x++)
    for (int y = 0; y < in.rows; y++)
      for (int i = 0; i < 3; i++)
        out.at<cv::Vec3b>(y, x)[i] =
            in.at<cv::Vec3b>(y, x)[i] >= 192 ? 224 : 
            in.at<cv::Vec3b>(y, x)[i] >= 128 ? 160 : 
            in.at<cv::Vec3b>(y, x)[i] >=  64 ? 96  : 32;
  return out;
}

int main(int argc, const char *argv[])
{
  cv::Mat img = cv::imread("img.jpg", cv::IMREAD_COLOR);
  cv::Mat out = ColorReduction(img);
  cv::imwrite("out.jpg", out);
  cv::imshow("sample", out);
  cv::waitKey(0);
  cv::destroyAllWindows();
  return 0;
}

答案解法

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

// Dedcrease color
cv::Mat decrease_color(cv::Mat img){
  int height = img.cols;
  int width = 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;
}

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

  // decrease color
  cv::Mat out = decrease_color(img);

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

  return 0;
}

图像处理一百题 Q.7. 平均プーリング(平均值池化 / 合并)

平均プーリング

题目

ここでは画像をグリッド分割(ある固定長の領域に分ける)し、かく領域内(セル)の平均値でその領域内の値を埋める。 このようにグリッド分割し、その領域内の代表値を求める操作はPooling(プーリング) と呼ばれる。 これらプーリング操作はCNN(Convolutional Neural Network) において重要な役割を持つ。

在此,将图像划分为网格(划分为某些固定长度的区域),并用该区域(单元)中的平均值填充该区域中的值。 以这种方式划分网格并获得区域代表值的操作称为合并(也称池化)。 这些合并操作在CNN(卷积神经网络中起着重要作用。

これは次式で定義される。

定义如下:
$$
v = 1/|R| * Sum_{i\ in\ R} v_i
$$

ここではimori.jpgは128x128なので、8x8にグリッド分割し、平均プーリングせよ。

这里 imori.jpg 是 128x128 大小,因此将其网格化为8x8并平均池化。

分析

很简单,就是把 128x128 的图片看成一系列 8x8 的方块的组合,而这 8x8 的方块被浓缩成一个像素。如果这个像素取最大值,就是最大值合并,取平均值就是均值合并。

我参考了这篇文章:

http://pynote.hatenablog.com/entry/dl-pooling

最大值池化:

dl-pooling_01.gif

均值池化:

dl-pooling_02.gif

我的答案

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

// 均值池化
cv::Mat AveragePooling(cv::Mat in)
{
  cv::Mat out = cv::Mat::zeros(in.rows , in.cols, CV_8UC3);
  for (int x = 0; x < in.cols; x+=8)
    for (int y = 0; y < in.rows; y+=8)
    {
      // get avg of 8x8 matrix
      cv::Vec3b avg = cv::Vec3b(0,0,0);
      for (int i = 0; i < 8; i++) // offset for x
      {
        for (int j  = 0; j < 8; j++)  // offset for y
        {
          avg += 1.0 / 64.0 * in.at<cv::Vec3b>(x + i, y + j);
        }        
      }
      for (int i = 0; i < 8; i++) // offset for x
      {
        for (int j  = 0; j < 8; j++)  // offset for y
        {
          out.at<cv::Vec3b>(x + i, y + j) = avg;
        }        
      }      
      std::cout << "current: " << avg << std::endl;
    }   

  return out;
}

int main(int argc, const char *argv[])
{
  cv::Mat img = cv::imread("imori.jpg", cv::IMREAD_COLOR);
  cv::Mat out = AveragePooling(img);
  cv::imwrite("out.jpg", out);
  cv::imshow("sample", out);
  cv::waitKey(0);
  cv::destroyAllWindows();
  return 0;
}

参考答案

参考答案的思路跟我一模一样。不过我用的是加权平均,而他是最后算均值。

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

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

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

  // average pooling
  cv::Mat out = average_pooling(img);

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

  return 0;
}

效果

输入:
imori.jpg

输出:
out.jpg

图像处理一百题 Q.8. Maxプーリング(最大值池化)

Maxプーリング

题目

ここでは画像をグリッド分割(ある固定長の領域に分ける)し、かく領域内(セル)の平均値でその領域内の値を埋める。 このようにグリッド分割し、その領域内の代表値を求める操作はPooling(プーリング) と呼ばれる。 これらプーリング操作はCNN(Convolutional Neural Network) において重要な役割を持つ。

これは次式で定義される。

v = 1/|R| * Sum_{i in R} v_i

ここではimori.jpgは128x128なので、8x8にグリッド分割し、平均プーリングせよ。

分析

上一文说得很清楚了,此略。

我的答案

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

cv::Mat MaxPooling(cv::Mat in)
{
  cv::Mat out = cv::Mat::zeros(in.rows, in.cols, CV_8UC3);
  for (int x = 0; x < in.cols; x += 8)
    for (int y = 0; y < in.rows; y += 8)
    {
      // get max of 8x8 matrix
      cv::Vec3b max = cv::Vec3b(0, 0, 0);
      uchar maxsum = 0;
      for (int i = 0; i < 8; i++) // offset for x
      {
        for (int j = 0; j < 8; j++) // offset for y
        {
          auto cur = in.at<cv::Vec3b>(x + i, y + j);
          auto sum = cur[0] + cur[1] + cur[2];
          if (maxsum < sum)
          {
            maxsum = sum;
            max = in.at<cv::Vec3b>(x + i, y + j);
          }
        }
      }
      for (int i = 0; i < 8; i++) // offset for x
      {
        for (int j = 0; j < 8; j++) // offset for y
        {
          out.at<cv::Vec3b>(x + i, y + j) = max;
        }
      }
      std::cout << "current: " << max << std::endl;
    }

  return out;
}

int main(int argc, const char *argv[])
{
  cv::Mat img = cv::imread("imori.jpg", cv::IMREAD_COLOR);
  cv::Mat out = MaxPooling(img);
  cv::imwrite("out.jpg", out);
  cv::imshow("sample", out);
  cv::waitKey(0);
  cv::destroyAllWindows();
  return 0;
}

参考答案

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

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

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

  // average pooling
  cv::Mat out = average_pooling(img);

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

  return 0;
}

效果

输入:

输入

输出:

out.jpg

图像处理一百题 Q.9. ガウシアンフィルタ(高斯滤波器)

题目

ガウシアンフィルタ(3x3、標準偏差1.3)を実装し、imori_noise.jpgのノイズを除去せよ。

ガウシアンフィルタとは画像の平滑化(滑らかにする)を行うフィルタの一種であり、ノイズ除去にも使われる。

ノイズ除去には他にも、メディアンフィルタ(Q.10)、平滑化フィルタ(Q.11)、LoGフィルタ(Q.19)などがある。

ガウシアンフィルタは注目画素の周辺画素を、ガウス分布による重み付けで平滑化し、次式で定義される。 このような重みはカーネルフィルタと呼ばれる。

ただし、画像の端はこのままではフィルタリングできないため、画素が足りない部分は0で埋める。これを0パディングと呼ぶ。 かつ、重みは正規化する。(sum g = 1)

重みはガウス分布から次式になる。

img

重み g(x,y,s) = 1/ (2 * pi * sigma * sigma) * exp( - (x^2 + y^2) / (2*s^2))
標準偏差s = 1.3による8近傍ガウシアンフィルタは
            1 2 1
K =  1/16 [ 2 4 2 ]
            1 2 1

实现高斯滤波器(3x3,标准偏差 1.3),并消除 imori_noise.jpg 的噪声。

高斯滤波器是一种使图像平滑(平滑)的滤波器,也可用于去除噪声。

除此之外,还有中值滤波器(Q.10),平滑滤波器(Q.11),Lo G滤波器(Q.19)等用于去除噪声。

高斯滤波器通过加权高斯分布来平滑目标像素的周围像素,并由以下等式定义。这样的权重称为内核和过滤器。

但是,由于无法按原样过滤图像的边缘,因此像素不足的部分将填充为0。这称为零填充。重量归一化。 (总和g = 1)

权重由高斯分布的以下公式给出。

权重
$$
g(x,y,s) = {1\over (2 \pi\sigma^2)}e^{ - {x^2 + y^2 \over2s^2}}
$$
标准差 $s = 1.3$

8邻高斯滤波器:

$$
K = {1\over16}
\left[ \begin{matrix}
1& 2& 1\
2& 4& 2 \
1&2& 1\
\end{matrix}
\right]
$$

分析

实际上这里已经给出一个滤波器模板,原理的话,我在这篇文章写了。我们开干!

我的解法

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

cv::Mat Gaussian(cv::Mat in)
{
  cv::Mat out = cv::Mat::zeros(in.rows, in.cols, CV_8UC3);
  float tmpl[9] = {0.0625, 0.1250, 0.0625,
                   0.1250, 0.2500, 0.1250,
                   0.0625, 0.1250, 0.0625};
  cv::Mat matTmpl = cv::Mat(3, 3, CV_32FC1, tmpl);
  for (int x = 0; x < in.cols; x += 1)
    for (int y = 0; y < in.rows; y += 1)
    {
      for (int c = 0; c < in.channels(); c++)
      {
        float data[9] = {0, 0, 0, 0, 0, 0, 0, 0, 0};
        cv::Mat mat = cv::Mat(3, 3, CV_32FC1, data);
        for (int i = -1; i <= 1; i++)
        {
          for (int j = -1; j <= 1; j++)
          {
            int p_x = x + i;
            int p_y = y + j;
            if (p_x < 0 || p_y < 0 || p_x >= in.cols || p_y >= in.rows)
            {
              mat.at<float>(i + 1, j + 1) = 0.0f;
            }
            else
            {
              mat.at<float>(i + 1, j + 1) = (float)in.at<cv::Vec3b>(p_x, p_y)[c];
            }
          }
        }
        out.at<cv::Vec3b>(x,y)[c] = (uchar)mat.dot(matTmpl);
      }
    }

  return out;
}

int main(int argc, const char *argv[])
{
  cv::Mat img = cv::imread("imori.jpg", cv::IMREAD_COLOR);
  cv::Mat out = Gaussian(img);
  cv::imwrite("out.jpg", out);
  cv::imshow("sample", out);
  cv::waitKey(0);
  cv::destroyAllWindows();
  return 0;
}

参考答案的做法


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

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

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

  // gaussian filter
  cv::Mat out = gaussian_filter(img, 1.3, 3);

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

  return 0;
}

参考答案的核是现算的,可以,很强。

效果

输入:
in
输出:

out.jpg

发表评论

电子邮件地址不会被公开。 必填项已用*标注