Introduction
Sometimes, we need to convert color images to black and white ones. Examples include black and white fax machines and photocopiers, both of which require conversion of color images to black and white ones. For this purpose, there are several approaches such as binarization and dithering, each of which includes several variations. The former includes Japanese invented Otsu’s method for automatic thresholding and the simplest fixed thresholding method.
In this paper, we have to first learn how to process images, so the fixed thresholding method is used. The most important thing is to learn how to read into image files and process pixel data in them. We try to provide as many programming languages as possible so that different designers can understand how to process pixel data. The programming languages provided in this paper include Visual C++ 2010, C++ Builder XE5, Visual C# 2012, Visual Basic .NET 2012, Visual Basic 6.0, JAVA with NetBeans v8.1, DELPHI XE5, Android v4.1.2 with Eclipse, HTML5 + JavaScript and Matlab 2011. This author argues that learning digital image processing has nothing to do with programming languages. The most important thing is to learn the knowledge of digital image processing because programming languages are merely a type of tools that helps you achieve this goal, not a vital key. Therefore, this author has set an example by demonstrating the same algorithm in ten different ways. The purpose is to prove that digital image processing has little to do with programming languages.
To make it possible for readers to understand, all the program examples in this paper are accompanied with English comments so that readers not only can comprehend, but can also read the codes with ease. If you have these development tools, you can open the attached source codes. Whether you choose to compile or interpret them, you can run single-step debugging to check the computational process of the algorithm step by step. Or, you can set up breakpoints in the parts which are important or you do not understand so that the program will stop and wait until you have checked the saved values for all the variables. The most important thing is to become familiar with this binarization algorithm because it is a simpler algorithm among digital image processing techniques and does not contain any abstruse mathematic formulas. It requires only setting up thresholds and then comparing sizes to determine black or white. The key point is how to process pixels and calculate indices of pixels.
Using the Code
Visual C++ 2010
for ( int iY = 0; iY < imageA->DibInfo->bmiHeader.biHeight; iY++ )
{
for ( int iX = 0; iX < imageA->DibInfo->bmiHeader.biWidth; iX++ )
{
lIDXA = ( iX * 3 ) + ( iY * imageA->DibInfo->bmiHeader.biWidth * 3 );
byteRGB_BA = imageA->DibArry[lIDXA+0];
byteRGB_GA = imageA->DibArry[lIDXA+1];
byteRGB_RA = imageA->DibArry[lIDXA+2];
dobYUV_YA = (0.299 * byteRGB_RA + 0.587 * byteRGB_GA + 0.114 * byteRGB_BA);
if ( dobYUV_YA > 60 && dobYUV_YA < 160 )
{
lIDXB = ( iX * 3 ) + ( iY * imageB->DibInfo->bmiHeader.biWidth * 3 );
imageB->DibArry[lIDXB+0] = 255;
imageB->DibArry[lIDXB+1] = 255;
imageB->DibArry[lIDXB+2] = 255;
}
else
{
lIDXB = ( iX * 3 ) + ( iY * imageB->DibInfo->bmiHeader.biWidth * 3 );
imageB->DibArry[lIDXB+0] = 0;
imageB->DibArry[lIDXB+1] = 0;
imageB->DibArry[lIDXB+2] = 0;
}
} }
C++ Builder XE5
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
Graphics::TBitmap *TheBitmap, *TempBitmap, *OriginBitmap;
int Threshold;
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
void __fastcall TForm1::Exit1Click(TObject *Sender)
{
Close();
}
void __fastcall TForm1::OpenFile1Click(TObject *Sender)
{
if ( OpenPictureDialog1->Execute() )
{
Image1->AutoSize=false;
Image1->Stretch=true;
Image1->Picture->LoadFromFile(OpenPictureDialog1->FileName);
TheBitmap=Image1->Picture->Bitmap;
OriginBitmap = new Graphics::TBitmap();
OriginBitmap->Assign(TheBitmap);
Image1->Picture->Bitmap->Assign(TheBitmap);
OpenFile = 1;
Threshold = 100;
ScrollBar1->Position = Threshold;
}
}
void __fastcall TForm1::ScrollBar1Change(TObject *Sender)
{
if (OpenFile == 0) {
return;
}
Byte *ptr, *tptr;
Threshold = (int) ScrollBar1->Position;
Image1->Picture->Bitmap->Assign(OriginBitmap);
TempBitmap = new Graphics::TBitmap();
TempBitmap->Assign(TheBitmap);
for (int y=0; y < TheBitmap->Height; y++) {
ptr = (Byte*) TheBitmap->ScanLine[y];
tptr = (Byte*) TempBitmap->ScanLine[y];
for (int x=0; x < TheBitmap->Width; x++) {
if (tptr[x] > Threshold) {
ptr[x] = (Byte) 255;
}
else
{
ptr[x] = (Byte) 0;
}
} } delete TempBitmap;
Repaint();
Image1->Picture->Bitmap->Assign(TheBitmap);
}
Visual C Sharp 2012
Normal
namespace Binarization_for_C_Sharp
{
public partial class Form1 : Form
{
private string curFileName;
private System.Drawing.Bitmap curBitmap;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
OpenFileDialog openDlg = new OpenFileDialog();
openDlg.Filter = "All format | *.bmp; *.pcx; *.png; *.jpg; *.gif;" +
"*.tif; *.ico; *.dxf; *.cgm; *.cdr; *.wmf; *.eps; *.emf";
openDlg.Title = "Open a picture file.";
if (openDlg.ShowDialog() == DialogResult.OK)
{
curFileName = openDlg.FileName;
try
{
curBitmap = (Bitmap)Image.FromFile(curFileName);
}
catch (Exception exp)
{
MessageBox.Show(exp.Message);
}
}
Invalidate();
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
if (curBitmap != null)
{
g.DrawImage(curBitmap, 140, 10, curBitmap.Width, curBitmap.Height);
}
}
private void button2_Click(object sender, EventArgs e)
{
if (curBitmap != null)
{
Color curColor;
int ret;
for (int iX = 0; iX < curBitmap.Width; iX++)
{
for ( int iY = 0; iY < curBitmap.Height; iY++ )
{
curColor = curBitmap.GetPixel(iX, iY);
ret = (int) (curColor.R * 0.299 + curColor.G * 0.578 + curColor.B * 0.114);
if (ret > 120)
{
ret = 255;
}
else
{
ret = 0;
}
curBitmap.SetPixel( iX, iY, Color.FromArgb ( ret, ret ,ret ) );
}
}
Invalidate();
}
}
}
}
Fast
private void Form1_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
if (curBitmap != null)
{
int iR = 0;
int iG = 0;
int iB = 0;
Rectangle rect = new Rectangle(0, 0, curBitmap.Width, curBitmap.Height);
System.Drawing.Imaging.BitmapData bmpData =
curBitmap.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadWrite,
curBitmap.PixelFormat);
IntPtr ptr = bmpData.Scan0;
int bytes = Math.Abs(bmpData.Stride) * curBitmap.Height;
byte[] rgbValues = new byte[bytes];
System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes);
for (int counter = 0; counter < rgbValues.Length; counter += 3)
{
iR = rgbValues[counter + 2];
iG = rgbValues[counter + 1];
iB = rgbValues[counter + 0];
if ((iR + iG + iB) / 3 > 100)
{
rgbValues[counter + 2] = 255;
rgbValues[counter + 1] = 255;
rgbValues[counter + 0] = 255;
}
else
{
rgbValues[counter + 2] = 0;
rgbValues[counter + 1] = 0;
rgbValues[counter + 0] = 0;
}
}
System.Runtime.InteropServices.Marshal.Copy(rgbValues, 0, ptr, bytes);
curBitmap.UnlockBits(bmpData);
g.DrawImage(curBitmap, 140, 10, curBitmap.Width, curBitmap.Height);
}
}
Visual Basic .NET 2012
Public Class Form1
Private curFileName As String
Private curBitmap As Bitmap
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim openFileDialog1 As New OpenFileDialog()
openFileDialog1.InitialDirectory = ".\"
openFileDialog1.Filter = "All format | *.bmp; *.pcx; *.png; *.jpg; *.gif;" +
"*.tif; *.ico; *.dxf; *.cgm; *.cdr; *.wmf; *.eps; *.emf"
openFileDialog1.FilterIndex = 1
openFileDialog1.RestoreDirectory = True
If openFileDialog1.ShowDialog() = System.Windows.Forms.DialogResult.OK Then
Try
curFileName = openFileDialog1.FileName
If (curFileName IsNot Nothing) Then
Dim b As Bitmap = New Bitmap(curFileName)
curBitmap = New Bitmap(b.Width, b.Height, _
System.Drawing.Imaging.PixelFormat.Format32bppPArgb)
curBitmap = b.Clone()
End If
Catch Ex As Exception
MessageBox.Show("Cannot read file from disk. Original error: " & Ex.Message)
Finally
End Try
Invalidate()
End If
End Sub
Private Sub Form1_Paint(sender As Object, e As PaintEventArgs) Handles MyBase.Paint
If (curBitmap IsNot Nothing) Then
e.Graphics.DrawImage(curBitmap, 140, 10, curBitmap.Width, curBitmap.Height)
End If
End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
For y As Integer = 0 To curBitmap.Height - 1
For x = 0 To curBitmap.Width - 1
Dim curPixColor As Color = curBitmap.GetPixel(x, y)
Dim ret As Integer
ret = (curPixColor.R * 0.299 + curPixColor.G * 0.578 + curPixColor.B * 0.114)
If ret > 120 Then
curBitmap.SetPixel(x, y, Color.White)
Else
curBitmap.SetPixel(x, y, Color.Black)
End If
Next
Next
Invalidate()
End Sub
End Class
Visual Basic 6.0
Private Sub Form_Load()
Picture1.Picture = LoadPicture(App.Path & "\B_01.bmp")
Dim lngX As Long
Dim lngY As Long
Dim intS
Dim intR, intG, intB
Picture1.ScaleMode = 3
Picture1.AutoRedraw = True
Picture2.ScaleMode = 3
Picture2.AutoRedraw = True
For lngY = 0 To Picture1.ScaleHeight
For lngX = 0 To Picture1.ScaleWidth
intR = (Picture1.Point(lngX, lngY) And &HFF)
intG = (Picture1.Point(lngX, lngY) And &HFF00&) \ 256
intB = (Picture1.Point(lngX, lngY) And &HFF0000) \ 65536
intS = (intR + intG + intB) / 3
If intS > 120 Then
intS = 255
Else
intS = 0
End If
Picture2.PSet (lngX, lngY), RGB(intS, intS, intS)
Next lngX
Next lngY
End Sub
JAVA for NetBeans v8.0.2
import java.awt.FileDialog;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import java.io.File;
import java.io.IOException;
import java.awt.image.DataBufferByte;
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
FileDialog f = new FileDialog(this, "Open File", FileDialog.LOAD);
f.setDirectory(".");
f.show();
String directory = f.getDirectory();
String filepath = directory+f.getFile();
BufferedImage img = null;
try {
img = ImageIO.read(new File(filepath));
int[][] result = convertToBinarization(img);
jLabel1.setIcon(new javax.swing.ImageIcon(img));
}
catch (IOException e) {
System.err.println("I could not load the file \'"+directory+"'. Sorry.");
}
}
private static int[][] convertToBinarization(BufferedImage image) {
int width = image.getWidth();
int height = image.getHeight();
int[][] result = new int[height][width];
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
result[row][col] = image.getRGB(row, col);
int iRet = result[row][col];
int iA = 0;
int iR = 0;
int iG = 0;
int iB = 0;
int iGray = 0;
iA = (((int) iRet & 0xff) << 24);
iB = ((int) iRet & 0xff);
iG = (((int) iRet & 0x00ff00) >> 8);
iR = (((int) iRet & 0xff0000) >> 16);
iG = ( iR + iG + iB ) / 3;
if ( iG > 120 )
{
iRet = 0xffffff;
image.setRGB(row, col, iRet);
}
else
{
image.setRGB(row, col, 0);
}
}
}
return result;
}
DELPHI XE5
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtDlgs, Vcl.ExtCtrls;
type
TForm1 = class(TForm)
Image1: TImage;
OpenPictureDialog1: TOpenPictureDialog;
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
// The string variable is a file name.
Str : String;
implementation
{$R *.dfm}
procedure TForm1.Button2Click(Sender: TObject);
var
PByte :PByteArray;
Gray,x,y :Integer;
ImageBmp :TBitmap;
begin
// if it is no file name and pop up a message.
if Str = '' then
showmessage('Can not open this file, because it is an empty.')
// Otherwise, There is a file name.
else
begin
// Establish a TBitmap object.
ImageBmp :=TBitmap.Create;
// Assign the Image object.
ImageBmp.Assign(Image1.Picture.Bitmap);
// Set pixel format is 24 bits.
ImageBmp.PixelFormat :=pf24Bit;
// The height of the image.
for y:=0 to ImageBmp.Height-1 do
begin
// Set the point up to Y-axis.
PByte := ImageBmp.scanline[y];
// The width of the image.
for x:=0 to ImageBmp.Width-1 do
begin
// Transform RGB color space to gray scale.
Gray:=Round(PByte[x*3+2]*0.3+PByte[x*3+1]*0.59+PByte[x*3]*0.11);
// This is our threshold, you can change it. What is a different?
if Gray > 120 then
// Set the pixel is white.
begin
PByte[x*3]:=255;
PByte[x*3+1]:=255;
PByte[x*3+2]:=255;
end
// Otherwise, set the pixel is black.
else
begin
PByte[x*3]:=0;
PByte[x*3+1]:=0;
PByte[x*3+2]:=0;
end
end;
end;
// Assign a processed image.
Image1.Picture.Bitmap.Assign(ImageBmp);
// Release the TBitmap object.
ImageBmp.Free;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
// The string is full path.
Path:string;
begin
// Set the path is in the path of the execute file.
path:=ExtractFilePath(application.ExeName);
// Set up initial directory.
OpenPictureDialog1.InitialDir := Path;
// Execute the OpenPictureDialog object.
if OpenPictureDialog1.Execute then
begin
// Set a file name into str variable.
Str := OpenPictureDialog1.FileName;
// Loading a picture from the file.
Image1.Picture.LoadFromFile(Str);
end;
end;
end.
Android v4.1.2 with Eclipse
package com.example.binarization.camera;
import com.example.binarization.camera.R;
import android.os.Bundle;
import android.app.Activity;
import android.view.*;
import android.widget.*;
import android.annotation.SuppressLint;
import android.content.pm.ActivityInfo;
import android.graphics.PixelFormat;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.hardware.Camera;
import android.hardware.Camera.AutoFocusCallback;
import android.hardware.Camera.CameraInfo;
import android.hardware.Camera.PictureCallback;
@SuppressLint("NewApi")
public class MainActivity extends Activity implements SurfaceHolder.Callback {
SurfaceView mSurfaceView ;
Button btn_Capture;
Camera mCamera;
PictureCallback mPictureCB;
AutoFocusCallback mAutoFocusCB;
ImageView ImgView;
TextView txtView;
Bitmap bitmapClone;
@SuppressWarnings("deprecation")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().setFormat(PixelFormat.TRANSLUCENT);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.activity_main);
this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
ImgView = (ImageView)this.findViewById(R.id.ImgView);
txtView = (TextView)this.findViewById(R.id.txtView);
btn_Capture = (Button)this.findViewById(R.id.btn_Capture);
mSurfaceView = (SurfaceView)this.findViewById(R.id.surView_Camera);
SurfaceHolder mSurfaceHolder = mSurfaceView.getHolder();
mSurfaceHolder.addCallback(this);
mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
mPictureCB = new PictureCallback(){
@Override
public void onPictureTaken(byte[] data, Camera camera){
Bitmap mBitmap = BitmapFactory.decodeByteArray(data, 0 , data.length);
bitmapClone = Bitmap.createBitmap(mBitmap.getWidth(),
mBitmap.getHeight(), mBitmap.getConfig());
bitmapClone.copy(mBitmap.getConfig(), true);
int iY = 0;
int iX = 0;
int iPixel = 0;
int iRed = 0;
int iGreen = 0;
int iBlue = 0;
int iRGBAvg = 0;
for ( iY = 0; iY < bitmapClone.getHeight(); iY++ )
{
for ( iX = 0; iX < bitmapClone.getWidth(); iX++ )
{
iPixel = mBitmap.getPixel(iX, iY);
iRed = Color.red(iPixel);
iGreen = Color.green(iPixel);
iBlue = Color.blue(iPixel);
iRGBAvg = ( iRed + iGreen + iBlue ) / 3;
if ( iRGBAvg > 120 )
{
bitmapClone.setPixel(iX, iY, Color.rgb(255, 255, 255));
}
else
{
bitmapClone.setPixel(iX, iY, Color.rgb(0, 0, 0));
}
}
}
ImgView.setImageBitmap(bitmapClone);
camera.startPreview();
camera.autoFocus(null);
}
};
mAutoFocusCB = new AutoFocusCallback(){
@Override
public void onAutoFocus(boolean success, Camera camera){
if ( success == true )
{
camera.takePicture(null, null, mPictureCB);
}
}
};
btn_Capture.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try{
if(mCamera != null){
new Thread(new Runnable() {
public void run() {
mCamera.autoFocus(mAutoFocusCB);
}
}).start();
}
}catch(Exception e) {
e.printStackTrace();
}
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
Camera.Parameters parameters = mCamera.getParameters();
parameters.setPictureSize(640, 480);
parameters.setPreviewSize(width, height);
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
mCamera.setParameters(parameters);
mCamera.startPreview();
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
if ( mCamera == null )
{
mCamera = Camera.open();
}
try {
mCamera.setPreviewDisplay(holder);
}catch(Exception e) {
e.printStackTrace();
}
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
mCamera.stopPreview();
mCamera.release();
}
}
HTML5 + Javascript
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript">
function imageLoaded(ev) {
element = document.getElementById("cancan");
c = element.getContext("2d");
im = ev.target;
width = element.width;
height = element.height;
c.drawImage(im, 0, 0);
imageData = c.getImageData(0, 0, width, height);
w2 = width / 2;
for (y = 0; y < height; y++) {
inpos = y * width * 4;
outpos = inpos + w2 * 4
for (x = 0; x < w2; x++) {
r = imageData.data[inpos++]
g = imageData.data[inpos++]
b = imageData.data[inpos++]
a = imageData.data[inpos++]
gray = (0.299 * r + 0.587 * g + 0.114 * b)
if ( gray > 120 )
{
imageData.data[outpos++] = 255;
imageData.data[outpos++] = 255;
imageData.data[outpos++] = 255;
imageData.data[outpos++] = a;
}
else
{
imageData.data[outpos++] = 0;
imageData.data[outpos++] = 0;
imageData.data[outpos++] = 0;
imageData.data[outpos++] = a;
}
}
}
c.putImageData(imageData, 0, 0);
}
im = new Image();
im.onload = imageLoaded;
im.src = "B_01.jpg";
</script>
</head>
<body>
<!--
<canvas id="cancan" width="1024", height="512">Canvas</canvas>
</body>
</html>
Matlab
% Load image
x=imread('B_01.bmp');
% Transform RGB color space to gray scale
gray=rgb2gray(x);
% Binarization
BW=im2bw(x);
% Show the image
imshow(BW);
Exception
Visual C++ 2010
- There is a notice, if your bit depth of bitmap file is not 24 bits, you should change your bitmap files to adapt this program, or you could rewrite this source code to fit your bitmap format.
- You have to install Microsoft SDK v7.1, because I include
windowscodes.lib
. #pragma comment(lib, "windowscodecs.lib")
C++ Builder XE5
- You should also uncheck Build with runtime packages under: Project-> Options-> Packages-> Runtime Packages-> Link with runtime packages Make sure to uncheck the three items ("Link with runtime packages", "Link with Dynamic RTL" and "Link with Delphi Runtime Library") under the build configuration you are using ('release' / 'debug').
JAVA for NetBeans v8.0.2
- This is another fast way to acquire RGB values method. It is suitable for huge image.
private static int[][] convertTo2DWithoutUsingGetRGB(BufferedImage image) {
final byte[] pixels = ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
final int width = image.getWidth();
final int height = image.getHeight();
final boolean hasAlphaChannel = image.getAlphaRaster() != null;
int[][] result = new int[height][width];
if (hasAlphaChannel) {
final int pixelLength = 4;
for (int pixel = 0, row = 0, col = 0; pixel < pixels.length; pixel += pixelLength) {
int argb = 0;
argb += (((int) pixels[pixel] & 0xff) << 24);
argb += ((int) pixels[pixel + 1] & 0xff);
argb += (((int) pixels[pixel + 2] & 0xff) << 8);
argb += (((int) pixels[pixel + 3] & 0xff) << 16);
result[row][col] = argb;
col++;
if (col == width) {
col = 0;
row++;
}
}
} else {
final int pixelLength = 3;
for (int pixel = 0, row = 0, col = 0; pixel < pixels.length; pixel += pixelLength) {
int argb = 0;
argb += -16777216;
argb += ((int) pixels[pixel] & 0xff);
argb += (((int) pixels[pixel + 1] & 0xff) << 8);
argb += (((int) pixels[pixel + 2] & 0xff) << 16);
result[row][col] = argb;
col++;
if (col == width) {
col = 0;
row++;
}
}
}
return result;
}