見出し画像

画像処理、白黒の粗密濃淡表現

画像処理の勉強の為のソフトウエアの作成。今回は、印刷物等で白黒のみ(白黒の粗密)で灰色等の濃淡表現するハーフトーン処理のソフトウエアを作成する。

①階調を表現するための濃度を確率的に与える。(ランダムディザ)
以下の書籍を参考にさせていただいた。
・はじめてのデジタル画像処理 P85
・インターフェース2017/05 P42
濃淡画像の注目画素ごとに乱数によりしきい値を設定して、それより明るければ白、それより暗ければ黒にする。また、画面全体の明暗が調整出来る様にパラメータkを用意する。

画像の素材はここからダウンロードさせていただいた。
前回使用させていただいた物と同じ。
これをグレースケールに変換した物が以下の画像。

画像1 ダウンロードさせていただいた画像をグレースケールに変換

これをランダムディザ処理をするソフトウエアを作成して加工を行った。
加工したのが以下の画像。画像が小さいのか?ザラザラで不鮮明になってしまった。

画像2 ランダムディザ処理を加えた画像

以下がランダムディザ処理行ったソフトウエアです。
C++で記述、画像を扱う為OpenCVを使用。
ランダムディザ処理部分は資料のインターフェース掲載記事ほぼそのまま。
ファイルのパスの指定は自分の環境のままですので、参考にされる場合は書き換えて下さい。

#include <opencv2/opencv.hpp>

double k = 0.5;  // 濃度調整(0~1)小さいほど黒くなる

int main()
{
  // 加工前の画像読み込み
  cv::Mat image = cv::imread("D:/PROG_WK/VisualStudio2022_WK/Project/100_OpenCV/21_Halftone_01/ORG_GRY.jpg", cv::IMREAD_COLOR);  // グレースケールファイルを用意カラーで読む
  int x_max = image.cols;
  int y_max = image.rows;

  // 白黒の粗密(ランダムディザ/ホワイトノイズ)で濃淡表現
  for (int y = 0; y_max > y; y++) {               // y方向のループ
    cv::Vec3b* src = image.ptr<cv::Vec3b>(y);      // 加工画像y行目の先頭画素のポインタを取得
    for (int x = 0; x_max > x; x++) {               // x方向のループ
      cv::Vec3b pix = src[x];
      double T = (1.0 - k) * (1.0 - double(pix[1]) / 255.0);
      double P = double(rand() / double(RAND_MAX));
      if (P > T)  src[x] = cv::Vec3b(255, 255, 255);  // B, G, R(白)
      else        src[x] = cv::Vec3b(0, 0, 0);        // B, G, R(黒)
    }
  }
  // 表示、保存
  cv::imshow("", image);
  cv::imwrite("D:/PROG_WK/VisualStudio2022_WK/Project/100_OpenCV/21_Halftone_01/Halftone_01.jpg", image);   // JPEGフォーマットで保存

  cv::waitKey(0);  // グラフィック表示ウィンドウ上でキークリック
  cv::destroyAllWindows();
}

②濃度パターンを用いる濃淡表現(ハーフトーン型ハーフトーニング)
以下の書籍を参考にさせていただいた。
・はじめてのデジタル画像処理 P76 P84
・インターフェース2017/05 P43
ハーフトーニングとして、ディザパターンという濃度パターンを用いて局所ごとに2値化していく、ディザ法を使用する。今回はハーフトーン型というパターンを用いる。

ハーフトーン型ハーフトーニング説明

画像1をハーフトーン型ハーフトーニング処理をするソフトウエアを作成して加工を行った。加工したのが以下の画像。

画像3 ハーフトーン型ハーフトーニング加工画像

以下がハーフトーン型ハーフトーニング処理行ったソフトウエアです。
C++で記述、画像を扱う為OpenCVを使用。
ハーフトーン型ハーフトーニング処理部分は資料のインターフェース掲載記事ほぼそのまま。ファイルのパスの指定は自分の環境のままですので、参考にされる場合は書き換えて下さい。

#include <opencv2/opencv.hpp>

int main()
{
  // 加工前の画像読み込み
  cv::Mat image = cv::imread("D:/PROG_WK/VisualStudio2022_WK/Project/100_OpenCV/22_Halftone_02/ORG_GRY.jpg", cv::IMREAD_COLOR);  // グレースケールファイルを用意カラーで読む
  int x_max = image.cols;
  int y_max = image.rows;

  // ハーフトーンのパターン
  int b[4][4] = {{10, 4, 6, 8},
                 {12, 0, 2,14},
                 { 7, 9,11, 5},
                 { 3,15,13, 1}};

  // 濃度パターンを用いる濃淡表現1(ハーフトーン型ハーフトーニング)
  // ブロック単位の座標
  for (int y = 0; y < y_max; y += 4) {               // y方向のループ
    for (int x = 0; x < x_max; x += 4) {               // x方向のループ
      // ブロック内の座標
      for (int y2 = 0; y2 < 4; y2++) {
        if (y + y2 >= y_max)  break;
        cv::Vec3b* src = image.ptr<cv::Vec3b>(y + y2);
        for (int x2 = 0; x2 < 4; x2++) {
          if (x + x2 >= x_max)  break;
          cv::Vec3b pix = src[x + x2];
          if (pix[1] < b[x2][y2] * 16 + 8)  src[x + x2] = cv::Vec3b(0, 0, 0);        // B, G, R(黒)
          else                              src[x + x2] = cv::Vec3b(255, 255, 255);  // B, G, R(白)
        }
      }
    }
  }
  // 表示、保存
  cv::imshow("", image);
  cv::imwrite("D:/PROG_WK/VisualStudio2022_WK/Project/100_OpenCV/22_Halftone_02/Halftone_02.jpg", image);   // JPEGフォーマットで保存

  cv::waitKey(0);  // グラフィック表示ウィンドウ上でキークリック
  cv::destroyAllWindows();
}

③濃度パターンを用いる濃淡表現2(ベイヤー型ハーフトーニング)
以下の書籍を参考にさせていただいた。
・はじめてのデジタル画像処理 P84 P85
・インターフェース2017/05 P44
ハーフトーニングとして、今回はベイヤー型というディザパターンを用いる。

ベイヤー型ハーフトーニング説明

画像1をベイヤー型ハーフトーニング処理をするソフトウエアを作成して加工を行った。加工したのが以下の画像。

画像4 ベイヤー型ハーフトーニング加工画像

以下がベイヤー型ハーフトーニング処理行ったソフトウエアです。
C++で記述、画像を扱う為OpenCVを使用。
ベイヤー型ハーフトーニング処理部分は資料のインターフェース掲載記事ほぼそのまま。ファイルのパスの指定は自分の環境のままですので、参考にされる場合は書き換えて下さい。

#include <opencv2/opencv.hpp>

int main()
{
  // 加工前の画像読み込み
  cv::Mat image = cv::imread("D:/PROG_WK/VisualStudio2022_WK/Project/100_OpenCV/23_Halftone_03/ORG_GRY.jpg", cv::IMREAD_COLOR);  // グレースケールファイルを用意カラーで読む
  int x_max = image.cols;
  int y_max = image.rows;

  // ベイヤー型ハーフトーンのパターン
  int b[4][4] = {{ 0, 8, 2,10},
                 {12, 4,14, 6},
                 { 3,11, 1, 9},
                 {15, 7,13, 5}};

  // 濃度パターンを用いる濃淡表現2(ベイヤー型ハーフトーニング)
  // ブロック単位の座標
  for (int y = 0; y < y_max; y += 4) {               // y方向のループ
    for (int x = 0; x < x_max; x += 4) {               // x方向のループ
      // ブロック内の座標
      for (int y2 = 0; y2 < 4; y2++) {
        if (y + y2 >= y_max)  break;
        cv::Vec3b* src = image.ptr<cv::Vec3b>(y + y2);
        for (int x2 = 0; x2 < 4; x2++) {
          if (x + x2 >= x_max)  break;
          cv::Vec3b pix = src[x + x2];
          if (pix[1] < b[x2][y2] * 16 + 8)  src[x + x2] = cv::Vec3b(0, 0, 0);        // B, G, R(黒)
          else                              src[x + x2] = cv::Vec3b(255, 255, 255);  // B, G, R(白)
        }
      }
    }
  }
  // 表示、保存
  cv::imshow("", image);
  cv::imwrite("D:/PROG_WK/VisualStudio2022_WK/Project/100_OpenCV/23_Halftone_03/Halftone_03.jpg", image);   // JPEGフォーマットで保存

  cv::waitKey(0);  // グラフィック表示ウィンドウ上でキークリック
  cv::destroyAllWindows();
}

④誤差拡散法(フロイド・スタインバーグ・ディザリング)
以下の書籍を参考にさせていただいた。
・はじめてのデジタル画像処理 P85
・インターフェース2017/05 P45
誤差拡散法では、注目画素の濃度が中間値(作例では127)より大きい時は白、小さい時には黒にして、元の濃度値との差を他の画素に分散して加算する。

フロイド・スタインバーグ型誤差拡散法説明

画像1をフロイド・スタインバーグ型誤差拡散処理をするソフトウエアを作成して加工を行った。加工したのが以下の画像。

画像5 フロイド・スタインバーグ型誤差拡散法による加工画像

以下がフロイド・スタインバーグ型誤差拡散法処理行ったソフトウエアです。C++で記述、画像を扱う為OpenCVを使用。
フロイド・スタインバーグ型誤差拡散法処理の部分は資料のインターフェース掲載記事の計算方法そのまま。コンパイラの最適化処理の為、デバックを行った部分がそのまま残っているが、自分の備忘録で残しています。
ファイルのパスの指定は自分の環境のままですので、参考にされる場合は書き換えて下さい。

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

int main()
{
  //int cnt = 0;
  // 加工前の画像読み込み
  cv::Mat image = cv::imread("D:/PROG_WK/VisualStudio2022_WK/Project/100_OpenCV/24_Halftone_04/ORG_GRY.jpg", cv::IMREAD_COLOR);  // グレースケールファイルを用意カラーで読む
  int x_max = image.cols;
  int y_max = image.rows;

  // 誤差拡散法フロイド・スタインバーグ・ディザリング(Floyd?Steinberg dithering)
  for (int y = 0; y < y_max; y++) {               // y方向のループ
    cv::Vec3b* src = image.ptr<cv::Vec3b>(y);
    for (int x = 0; x < x_max; x++) {               // x方向のループ
      cv::Vec3b pix = src[x];
      int temp = pix[1];                              // 誤差計算用
      //char temp = pix[1];                             // 誤差計算用(変わらない)
      int e;                                          // 誤差
      //char e;                                         // 誤差(変わらない)
      if (pix[1] > 127) {
          src[x] = cv::Vec3b(255, 255, 255);  // B, G, R(白)
          e = temp - 255;
      }
      else {
          src[x] = cv::Vec3b(0, 0, 0);        // B, G, R(黒)
          e = temp;
      }
      // 誤差拡散法処理
      if (0 <= (x - 1) && (x + 1) < x_max && (y + 1) < y_max) {
        //int e = temp - pix[1];                          // 誤差(最初この様に記述したら誤差拡散の処理をしなかった。コンパイラが間違えて最適化してpix[1]の内容が変わっていても強制的にeを0にしている。)
        //if (e != 0) cnt++;
        cv::Vec3b pix02 = src[x + 1];    int pix02_dt = pix02[1] + int((7.0 / 16.0) * double(e));  // y,     X + 1
        if (pix02_dt < 0)  pix02_dt = 0;  else if (255 < pix02_dt)  pix02_dt = 255;
        src[x + 1] = cv::Vec3b(pix02_dt, pix02_dt, pix02_dt);
        cv::Vec3b* src02 = image.ptr<cv::Vec3b>(y + 1);
        cv::Vec3b pix03 = src02[x - 1];  int pix03_dt = pix03[1] + int((3.0 / 16.0) * double(e));  // y + 1, x - 1
        if (pix03_dt < 0)  pix03_dt = 0;  else if (255 < pix03_dt)  pix03_dt = 255;
        src02[x - 1] = cv::Vec3b(pix03_dt, pix03_dt, pix03_dt);
        cv::Vec3b pix04 = src02[x];      int pix04_dt = pix04[1] + int((5.0 / 16.0) * double(e));  // y + 1, x
        if (pix04_dt < 0)  pix04_dt = 0;  else if (255 < pix04_dt)  pix04_dt = 255;
        src02[x] = cv::Vec3b(pix04_dt, pix04_dt, pix04_dt);
        cv::Vec3b pix05 = src02[x + 1];  int pix05_dt = pix05[1] + int((1.0 / 16.0) * double(e));  // y + 1, x + 1
        if (pix05_dt < 0)  pix05_dt = 0;  else if (255 < pix05_dt)  pix05_dt = 255;
        src02[x + 1] = cv::Vec3b(pix05_dt, pix05_dt, pix05_dt);
      }
    }
  }
  //std::cout<<"cnt = "<<cnt<<std::endl;  // cnt = 0と表示される
  // 表示、保存
  cv::imshow("", image);
  cv::imwrite("D:/PROG_WK/VisualStudio2022_WK/Project/100_OpenCV/24_Halftone_04/Halftone_09.jpg", image);   // JPEGフォーマットで保存

  cv::waitKey(0);  // グラフィック表示ウィンドウ上でキークリック
  cv::destroyAllWindows();
}

今回は以上です。

この記事が気に入ったらサポートをしてみませんか?