ディザ法(Dithering)

 ディザによる二値化処理について、

画像を二値化するとき、元の画像を再現するのに、閾値の設定により、黒と白のパターンにより、中間色を再現しようとしたものです。
誤差拡散法も、同じですが、パターンの作成方法が違います。
カラーの三色で行えば、カラーも同様に再現することもできます。
 基本的には、明と暗の二種類のドットで、ドットのパターンにより、目の錯覚を利用して中間色を再現しようとしたものです。
印刷や、プリンターに利用されています。

ディザ用フィルターの種類

 1.ランダムフィルター
        乱数を発生させ、乱数を閾値として、その閾値より大きければ、白、小さければ黒とする方法です。
 2.マトリックスフィルター
        N×Nのフィルターを用意して、二値化する方法です。
        マトリックスへの値の与え方として、Bayer型、渦巻型、網点型があります。
 ディザパ゛ターン
    Bayert型は、列の合計が同じ値になるようにし、更に隣同士の値が大小関係になるようにしたものです。
  渦巻型は、その名のとおり、中心から外側に向かって渦巻き側に並べたものです。
  網点型は、四個の組として、大と小に組み分けして、組として隣同士が大小となるようにしたものです。(点の集まりが網のようにみえます。)
 3.平均誤差最小法
  平均化は、閾値より大きかったら$FF、小さかったら$0とし、元の値から、決定した値 $FF 又は $0 を差し引いて、次の値に加算して、閾値により判別を繰り返して二値化する方法です。
  この場合は、二バイトの符号付演算となります。
 4.誤差拡散法
    平均誤差最小法では、誤差を次の値にだけ与えていましたが、次の値だけでなく、左下、下、右下にも与えて、誤差を分散して滑らかさを再現したものです。
    近いところには、大きく分配し、遠いところには小さく分配します。分配の合計値は差分と同じ値になるようにします。
 23分配53分配

サンプル画像

サンプル
上サンプルには平均誤差最小法はありません。
実行結果は縦に筋が現れ、見た目には一番悪い結果となります。

 二値化に関しては、ヒストグラム均一化、2値化 にもあります。

電光掲示板とか、印刷とかの特殊な場合を除いて、実際にディザが使用されることは無いでしょう。
最近では、電光掲示板も、輝度コントロールを採用しているので、利用しているのは印刷(一般プリンター含む)位かと思います。

サンプルプログラム

サンプルプログラムには、画像の保存はありません。
二値化後の画像を保存する場合は、TBitmatのフォーマットを モノクロ時 pf1bit カラー時は pf4bit にして、ビットマップとして保存したほうが良いでしょう。

unit DitheringMain;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls, Vcl.ExtDlgs, system.Types,
  GDIPAPI, GDIPOBJ;

type
  TForm1 = class(TForm)
    FileOpen: TButton;
    OpenPictureDialog1: TOpenPictureDialog;
    GrayScaleBtn: TButton;
    DitheringBtn: TButton;
    ColorDitheringBtn: TButton;
    RadioGroup1: TRadioGroup;
    VarianceBtn: TButton;
    ScrollBox1: TScrollBox;
    Image2: TImage;
    ScrollBox2: TScrollBox;
    Image1: TImage;
    RadioGroup2: TRadioGroup;
    MeanBtn: TButton;
    RadioGroup3: TRadioGroup;
    procedure FormCreate(Sender: TObject);
    procedure FileOpenClick(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure GrayScaleBtnClick(Sender: TObject);
    procedure DitheringBtnClick(Sender: TObject);
    procedure ColorDitheringBtnClick(Sender: TObject);
    procedure RadioGroup1Click(Sender: TObject);
    procedure VarianceBtnClick(Sender: TObject);
    procedure RadioGroup2Click(Sender: TObject);
    procedure MeanBtnClick(Sender: TObject);
    procedure RadioGroup3Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

type
  TPrgbarry = array[0..0] of Trgbtriple;      // 24ビットカラーレコード 32ビット用はTRGBQuadArray
  Prgbarray = ^TPrgbarry;                     // ポインター
                                              // 配列のポインターが必要なだけなので、長さは1 [0..0]で問題ありません。

  TPSmallrgb = record                         // 三色計算用 16ビット
    red   : smallint;
    green : smallint;
    blue  : smallint;
  end;

implementation

{$R *.dfm}

const
  OpenFileFilter =
    '画像ファイル|*.png;*.jpg;*.gif;*.bmp;*.tif;*.ico;*.wdp'+
    '|*.png|*.png' +
    '|*.jpg|*.jpg' +
    '|*.gif|*.gif' +
    '|*.bmp|*.bmp' +
    '|*.tif|*.tif' +
    '|*.ico|*.ico' +
    '|*.wdp|*.wdp';

  SaveFileFilter =
    '画像ファイル|*.png;*.jpg;*.gif;*.bmp;*.tif;*.wdp' +
    '|*.png|*.png' +
    '|*.jpg|*.jpg' +
    '|*.gif|*.gif' +
    '|*.bmp|*.bmp' +
    '|*.tif|*.tif' +
    '|*.wdp|*.wdp';

  RK    = 0.298912 * 4096;                     // グレースケール輝度係数 赤
  GK    = 0.586611 * 4096;                     // グレースケール輝度係数 緑
  BK    = 0.114478 * 4096;                     // グレースケール輝度係数 青

// ディザパターン
// bayer型
  bayerpattern : array[0..3, 0..3] of integer = (( 0,  8,  2, 10),  // 20  小 大 小 大
                                                 (12,  4, 14,  6),  // 36  大 小 大 小
                                                 ( 3, 11,  1,  9),  // 24  小 大 小 大
                                                 (15,  7, 13,  5)); // 40  大 小 大 小
                                               // 30  30  30  30
// 渦巻き型
  spiralpattern: array[0..3, 0..3] of integer = (( 6,  7,  8,  9),
                                                 ( 5,  0,  1, 10),
                                                 ( 4,  3,  2, 11),
                                                 (15, 14, 13, 12));
// 編点型
  halftone_dot : array[0..3, 0..3] of integer = ((11,  4,  6,  9),  // 大 小 小 大
                                                 (12,  0,  2, 14),  // 大 小 小 大
                                                 ( 7,  8, 10,  5),  // 小 大 大 小
                                                 ( 3, 15, 13,  1)); // 小 大 大 小


var
  InBitmap        : TBitmap;                  // ビットマップ
  OutBitmap       : TBitmap;
  GHeight, GWidth : integer;                  // ソース画像サイズ
  RGBOut          : array of Prgbarray;       // imagedata用配列
  pattern: array[0..3, 0..3] of integer;      // マスクパターン
  BtnF            : byte;                     // ディザモード

// ディザフィルターのセット  閾に変換
procedure pattern_Sub(a : integer);
var
  II, JJ : Integer;
begin
  for II := 0 to 3 do
    for JJ := 0 to 3 do
      case a of
        0: pattern[II, JJ] :=  bayerpattern[II, JJ] * 16 + 8; // 閾値セット
        1: pattern[II, JJ] := spiralpattern[II, JJ] * 16 + 8;
        2: pattern[II, JJ] :=  halftone_dot[II, JJ] * 16 + 8;
      end;
end;

// NTSCグレースケール
procedure GrayScale;
var
  II, JJ  : Integer;
  ARGB    : Integer;
  IRGB    : Byte;
  RGBT    : Prgbarray;
begin
  setlength(RGBOut , Gheight);                        // ポインター配列確保
  for II := 0 to GHeight - 1 do begin
    RGBOut[II]  := OutBitmap.ScanLine[II];            // ポインタの取得
    RGBT        := Inbitmap.ScanLine[II];             // ポインタの取得
    for JJ := 0 to GWidth - 1 do begin
      ARGB := round(  RK * RGBT[JJ].rgbtRed           // グレースケール化
                    + GK * RGBT[JJ].rgbtGreen
                    + BK * RGBT[JJ].rgbtBlue);
      IRGB := ARGB shr 12;                            // 12ビット右シフト
      RGBOut[II, JJ].rgbtBlue  := IRGB;               // 三色同じ値セット
      RGBOut[II, JJ].rgbtGreen := IRGB;               // 三色同じ値セット
      RGBOut[II, JJ].rgbtRed   := IRGB;               // 三色同じ値セット
    end;
  end;
end;

// グレースケール表示
procedure TForm1.GrayScaleBtnClick(Sender: TObject);
begin
  GrayScale;                                          // グレースケール化
  Image2.Picture.Bitmap := OutBitmap;                 // 出力枠 出力
end;

// ディザパターン選択
procedure TForm1.RadioGroup1Click(Sender: TObject);
begin
  case BtnF of
    0 : ColorDitheringBtnClick(nil);                  // カラーディザ
    1 : DitheringBtnClick(nil);                       // モノクロディザ
  end;
end;

// 誤差拡散カラー選択
procedure TForm1.RadioGroup2Click(Sender: TObject);
begin
  if RadioGroup2.ItemIndex = -1 then RadioGroup2.ItemIndex := 0;
  VarianceBtnClick(nil);
end;

// 誤差平均化カラー
procedure TForm1.RadioGroup3Click(Sender: TObject);
begin
  if RadioGroup3.ItemIndex = -1 then RadioGroup3.ItemIndex := 0;
  MeanBtnClick(nil);
end;

// 誤差拡散カラー
procedure TForm1.VarianceBtnClick(Sender: TObject);
const
  threshold = 128;                                                // 閾値
var
  HH, WW    : Integer;
  DFR, DMR  : Smallint;
  DFG, DMG  : Smallint;
  DFB, DMB  : Smallint;
  RGBT      : Prgbarray;
  RGBTMP    : array of array of TPSmallrgb;                       // Smallint x 3 配列
begin
  if RadioGroup2.ItemIndex = -1 then RadioGroup2.ItemIndex := 0;
  setlength(RGBTMP, Gheight, GWidth);                             // 処理用配列確保
  // Gray 処理 データー用配列に グレー画像コピー
  if RadioGroup2.ItemIndex = 0 then begin
    GrayScale;                                                    // グレイスケール適用
    for HH := 0 to GHeight - 1 do                                 // 作業用配列に画像コピー
      for WW := 0 to GWidth - 1 do begin
        RGBTMP[HH, WW].red   := RGBOut[HH,WW].rgbtRed;            // ByteからSmallintに変換
        RGBTMP[HH, WW].green := RGBOut[HH,WW].rgbtGreen;
        RGBTMP[HH, WW].blue  := RGBOut[HH,WW].rgbtBlue;
      end;
  end;
  // color 処理 データー用配列に カラー画像コピー
  if RadioGroup2.ItemIndex = 1 then begin
    setlength(RGBOut, Gheight);                                   // ポインター用配列確保
    for HH := 0 to GHeight - 1 do begin                           // 作業用配列に画像コピー
      RGBT := Inbitmap.ScanLine[HH];
      for WW := 0 to GWidth - 1 do begin
        RGBTMP[HH, WW].red   := RGBT[WW].rgbtRed;                 // ByteからSmallintに変換
        RGBTMP[HH, WW].green := RGBT[WW].rgbtGreen;
        RGBTMP[HH, WW].blue  := RGBT[WW].rgbtBlue;
      end;
    end;
  end;
  // 誤差拡散処理
  for HH := 0 to GHeight - 1 do begin
    for WW := 0 to GWidth - 1 do begin
      DMR := RGBTMP[HH, WW].red;                                                   // 画素値
      DMG := RGBTMP[HH, WW].green;
      DMB := RGBTMP[HH, WW].blue;
      if DMR >= threshold then RGBTMP[HH, WW].red   := $FF                         // 二値化
                          else RGBTMP[HH, WW].red   := $00;
      if DMG >= threshold then RGBTMP[HH, WW].green := $FF
                          else RGBTMP[HH, WW].green := $00;
      if DMB >= threshold then RGBTMP[HH, WW].blue  := $FF
                          else RGBTMP[HH, WW].blue  := $00;
      DFR := DMR - RGBTMP[HH, WW].red;                                             // 差分計算
      DFG := DMG - RGBTMP[HH, WW].green;
      DFB := DMB - RGBTMP[HH, WW].blue;
      if WW < GWidth - 1 then begin                                                // 右位置補正値 + 7/16
        RGBTMP[HH, WW + 1].red   := RGBTMP[HH, WW + 1].red   + (DFR * 7) div 16;
        RGBTMP[HH, WW + 1].green := RGBTMP[HH, WW + 1].green + (DFG * 7) div 16;
        RGBTMP[HH, WW + 1].blue  := RGBTMP[HH, WW + 1].blue  + (DFB * 7) div 16;
      end;
      if (WW > 0) and (HH < GHeight - 1) then begin                                // 左下補正値   + 3/16
        RGBTMP[HH + 1, WW - 1].red   := RGBTMP[HH + 1, WW - 1].red   + (DFR * 3) div 16;
        RGBTMP[HH + 1, WW - 1].green := RGBTMP[HH + 1, WW - 1].green + (DFG * 3) div 16;
        RGBTMP[HH + 1, WW - 1].blue  := RGBTMP[HH + 1, WW - 1].blue  + (DFB * 3) div 16;
      end;
      if HH < GHeight - 1 then begin                                               // 下位置補正値 + 5/16
        RGBTMP[HH + 1, WW].red   := RGBTMP[HH + 1, WW].red   + (DFR * 5) div 16;
        RGBTMP[HH + 1, WW].green := RGBTMP[HH + 1, WW].green + (DFG * 5) div 16;
        RGBTMP[HH + 1, WW].blue  := RGBTMP[HH + 1, WW].blue  + (DFB * 5) div 16;
      end;
      if (HH < GHeight - 1) and (WW < GWidth - 1) then begin                       // 右下補正値   + 1/16
        RGBTMP[HH + 1, WW + 1].red   := RGBTMP[HH + 1, WW + 1].red   + DFR div 16;
        RGBTMP[HH + 1, WW + 1].green := RGBTMP[HH + 1, WW + 1].green + DFG div 16;
        RGBTMP[HH + 1, WW + 1].blue  := RGBTMP[HH + 1, WW + 1].blue  + DFB div 16;
      end;                                                                                    // 計 16/16
    end;
  end;
  // 処理用配列から出力用ビットマップにコピー
  for HH := 0 to GHeight - 1 do begin
    RGBT := OutBitmap.ScanLine[HH];
    for WW := 0 to GWidth - 1 do begin
      RGBT[WW].rgbtRed    := RGBTMP[HH, WW].red;     // Smallint からByteに変換
      RGBT[WW].rgbtGreen  := RGBTMP[HH, WW].green;
      RGBT[WW].rgbtBlue   := RGBTMP[HH, WW].blue;
    end;
  end;
  Image2.Picture.Bitmap := OutBitmap;                // 出力枠に出力
end;

// グレー デザ法の適用
procedure TForm1.DitheringBtnClick(Sender: TObject);
var
  II, JJ : Integer;
begin
  if RadioGroup1.ItemIndex = -1 then RadioGroup1.ItemIndex := 0;
  GrayScale;
  pattern_Sub(RadioGroup1.ItemIndex);                         // ディザパターンの設定
  for II := 0 to GHeight - 1 do                               // ディザの実行
    for JJ := 0 to GWidth - 1 do begin
      if pattern[II mod 4, JJ mod 4] <= RGBOut[II, JJ].rgbtRed then begin   // 三色同じ値なので赤を使用
        RGBOut[II, JJ].rgbtBlue   := 255;
        RGBOut[II, JJ].rgbtGreen  := 255;
        RGBOut[II, JJ].rgbtRed    := 255;
      end
      else begin
        RGBOut[II, JJ].rgbtBlue   := 0;
        RGBOut[II, JJ].rgbtGreen  := 0;
        RGBOut[II, JJ].rgbtRed    := 0;
      end;
    end;
  Image2.Picture.Bitmap := OutBitmap;                // 出力枠に出力
  BtnF  := 1;                                        // ディザグレイに設定
end;

// 平均値化(誤差拡散)
procedure TForm1.MeanBtnClick(Sender: TObject);
const
  threshold = 128;                                                // 閾値
var
  HH, WW    : Integer;
  DFR, DMR  : Smallint;
  DFG, DMG  : Smallint;
  DFB, DMB  : Smallint;
  RGBT      : Prgbarray;
  RGBTMP    : array of array of TPSmallrgb;                       // Smallint x 3 配列
begin
  if RadioGroup3.ItemIndex = -1 then RadioGroup3.ItemIndex := 0;
  setlength(RGBTMP, Gheight, GWidth);                             // 処理用配列確保
  // Gray 処理 データー用配列に グレー画像コピー
  if RadioGroup3.ItemIndex = 0 then begin
    GrayScale;                                                    // グレイスケール適用
    for HH := 0 to GHeight - 1 do                                 // 作業用配列に画像コピー
      for WW := 0 to GWidth - 1 do begin
        RGBTMP[HH, WW].red   := RGBOut[HH,WW].rgbtRed;            // ByteからSmallintに変換
        RGBTMP[HH, WW].green := RGBOut[HH,WW].rgbtGreen;
        RGBTMP[HH, WW].blue  := RGBOut[HH,WW].rgbtBlue;
      end;
  end;
  // color 処理 データー用配列に カラー画像コピー
  if RadioGroup3.ItemIndex = 1 then begin
    setlength(RGBOut, Gheight);                                   // ポインター用配列確保
    for HH := 0 to GHeight - 1 do begin                           // 作業用配列に画像コピー
      RGBT := Inbitmap.ScanLine[HH];
      for WW := 0 to GWidth - 1 do begin
        RGBTMP[HH, WW].red   := RGBT[WW].rgbtRed;                 // ByteからSmallintに変換
        RGBTMP[HH, WW].green := RGBT[WW].rgbtGreen;
        RGBTMP[HH, WW].blue  := RGBT[WW].rgbtBlue;
      end;
    end;
  end;
  // 平均化処理
  for HH := 0 to GHeight - 1 do begin
    for WW := 0 to GWidth - 1 do begin
      DMR := RGBTMP[HH, WW].red;                                                   // 画素値
      DMG := RGBTMP[HH, WW].green;
      DMB := RGBTMP[HH, WW].blue;
      if DMR >= threshold then RGBTMP[HH, WW].red   := $FF                         // 二値化
                          else RGBTMP[HH, WW].red   := $00;
      if DMG >= threshold then RGBTMP[HH, WW].green := $FF
                          else RGBTMP[HH, WW].green := $00;
      if DMB >= threshold then RGBTMP[HH, WW].blue  := $FF
                          else RGBTMP[HH, WW].blue  := $00;
      DFR := DMR - RGBTMP[HH, WW].red;                                             // 差分計算
      DFG := DMG - RGBTMP[HH, WW].green;
      DFB := DMB - RGBTMP[HH, WW].blue;
      if WW < GWidth - 1 then begin                                                // 右位置補正値 + 7/16
        RGBTMP[HH, WW + 1].red   := RGBTMP[HH, WW + 1].red   + DFR;
        RGBTMP[HH, WW + 1].green := RGBTMP[HH, WW + 1].green + DFG;
        RGBTMP[HH, WW + 1].blue  := RGBTMP[HH, WW + 1].blue  + DFB;
      end;
    end;
  end;
  // 処理用配列から出力用ビットマップにコピー
  for HH := 0 to GHeight - 1 do begin
    RGBT := OutBitmap.ScanLine[HH];
    for WW := 0 to GWidth - 1 do begin
      RGBT[WW].rgbtRed    := RGBTMP[HH, WW].red;     // Smallint からByteに変換
      RGBT[WW].rgbtGreen  := RGBTMP[HH, WW].green;
      RGBT[WW].rgbtBlue   := RGBTMP[HH, WW].blue;
    end;
  end;
  Image2.Picture.Bitmap := OutBitmap;                // 出力枠に出力
end;

// カラーディザ法の適用と表示
procedure TForm1.ColorDitheringBtnClick(Sender: TObject);
var
  II, JJ : Integer;
  CompD  : Integer;
  INPRGB, OUTRGB : Prgbarray;
begin
  if RadioGroup1.ItemIndex = -1 then RadioGroup1.ItemIndex := 0;
  pattern_Sub(RadioGroup1.ItemIndex);                       // ディザパターンの設定
  for II := 0 to GHeight - 1 do begin                       // ディザの実行
    INPRGB := Inbitmap.ScanLine[II];                        // 入力ピッマップラインポインタの取得
    OUTRGB := OutBitmap.ScanLine[II];                       // 出力ピッマップラインポインタの取得
    for JJ := 0 to GWidth - 1 do begin
      CompD := pattern[II mod 4, JJ mod 4];                 // ディザパターンデーター
      if CompD <= INPRGB[JJ].rgbtRed then                   // 赤の処理
        OUTRGB[JJ].rgbtred    := 255
      else
        OUTRGB[JJ].rgbtred := 0;
      if CompD <= INPRGB[JJ].rgbtGreen then                 // 緑の処理
        OUTRGB[JJ].rgbtGreen  := 255
      else
        OUTRGB[JJ].rgbtGreen  := 0;
      if CompD <= INPRGB[JJ].rgbtBlue then                  // 青の処理
        OUTRGB[JJ].rgbtBlue   := 255
      else
        OUTRGB[JJ].rgbtBlue   := 0;
    end;
  end;
  Image2.Picture.Bitmap := OutBitmap;                       // 出力枠に出力
  BtnF  := 0;                                               // ディザカラーに設定
end;

// ファイルのオープン WIC がファイルの種類が多いので使用
procedure TForm1.FileOpenClick(Sender: TObject);
var
  WIC         : TWICImage;
  InFilename  : String;
begin
  OpenPictureDialog1.Filter := OpenFileFilter;                // ファイルオープンフィルターの設定
  if OpenPictureDialog1.Execute then                          // ファイルが指定されたら
    begin
      WIC := TWICImage.Create;                                // TWICImageの生成
      try
        InFilename := OpenPictureDialog1.FileName;            // ファイル名の取得
        WIC.LoadFromFile(InFilename);                         // 画像の読み込み
        GHeight := WIC.Height;                                // 画像高さ取得
        GWidth  := WIC.Width;                                 // 画像幅
        InBitmap.Width    := GWidth;                          // 画像高さ
        InBitmap.Height   := GHeight;
        InBitmap.Canvas.Draw(0, 0, WIC);                      // DrawでInBitmapに入力画像設定フォーマット24ビットに変換されます
        OutBitmap.Width   := GWidth;                          // 出力画像幅
        OutBitmap.Height  := GHeight;                         // 出力画像高さ
        image1.Picture.Bitmap := InBitmap;                    // 画像表示
      finally
        WIC.Free;                                             // TWICImage 解放
      end;
    end
    else Exit;
  GrayScaleBtn.Enabled  := True;
  DitheringBtn.Enabled  := True;
  ColorDitheringBtn.Enabled := True;
  VarianceBtn.Enabled := True;
  MeanBtn.Enabled     := True;
  RadioGroup1.ItemIndex := -1;                                // ディザラジオボタン選択なしにセット
  RadioGroup2.ItemIndex := -1;                                // 誤差拡散ラジオボタン選択なしにセット
  RadioGroup3.ItemIndex := -1;                                // 平均化ラジオボタン選択なしにセット
end;

// 初期設定
procedure TForm1.FormCreate(Sender: TObject);
begin
  Top := (Screen.Height - Height) div 2;                      // 表示位置設定
  Left := (Screen.Width - Width) div 2;
  InBitmap      := TBitmap.Create;                            // 入力画像用
  OutBitmap     := TBitmap.Create;                            // 出力画像用
  InBitmap.PixelFormat    := pf24bit;                         // 24ビットカラーに設定
  OutBitmap.PixelFormat   := pf24bit;                         // 24ビットカラーに設定
  GrayScaleBtn.Enabled  := False;
  DitheringBtn.Enabled  := False;
  ColorDitheringBtn.Enabled := False;
  VarianceBtn.Enabled := False;
  MeanBtn.Enabled     := False;
  BtnF  := 0;                                                 // ディザボタンフラグ カラーボタンにセット
end;

// ビットマップの解法
procedure TForm1.FormDestroy(Sender: TObject);
begin
  InBitmap.Free;
  OutBitmap.Free;
end;

end.

    download Dithering.zip

画像処理一覧へ戻る

      最初に戻る