Tải bản đầy đủ (.pdf) (60 trang)

Tìm hiểu và khảo sát ứng dụng của kỹ thuật làm mảnh đối tượng ảnh nhị phân

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (672.92 KB, 60 trang )


1
Bài tập lớn xử lý ảnh

( Nhóm tin 2-K49 : Hoàng Tuấn Khánh & Hoàng Thanh Tùng )
Giới thiệu qua đề tài

Phần I : Kiến thức cơ bản

Phần II : Áp dụng một số thuật toán

Phần III : Ứng dụng trong hệ thống nhận dạng mẫu

Giới thiệu qua đề tài
Đề tài
Đề 23: Tìm hiểu và khảo sát ứng dụng của kỹ thuật làm mảnh đối tượng ảnh nhị
phân
Giáo viên hướng dẫn: Nguyễn Thị Hoàng Lan
Nhóm sinh viên thực hiện : Hoàng
Tuấn Khánh
Hoàng
Thanh Tùng
Lời nói đầu

Xử lý ảnh là một môn học có rất nhiều ứng dụng trong thực tế. Để có thể vận dụng các
kiến thức được học trên lớp, chúng em phải hoàn thành một project nhất định. Chúng em
đã chọn đề tài "Tìm hiểu và khảo sát ứng dụng của kỹ thuật làm mảnh đối tượng
ảnh nhị phân" vì thấy đây là một đề tài rất hay, đặc biệt có liên quan đến vấn đề phục
hồi và nhận dạng, một kỹ thuật rất được quan tâm bây giờ.
Chính nhờ quá trình nỗ lực làm bài tập lớn, chúng em đã xây dựng được những mô đun
và tích luỹ được những kinh nghiệm hết sức quý báu. Và vì vậy nên do nhận thấy sự liên


quan, hỗ trợ lẫn nhau giữa các đề tài nên chúng em xin được phát triển thêm một số tính
năng của chương trình bằng cách tìm hiểu và giải quyết thêm một số đề tài khác. Nhờ vậy
mà khả năng làm việc theo nhóm của chúng em đã tiến bộ rõ rệt.
Do thời gian gấp rút vì sắp đến thời kỳ ôn thi, và cũng chưa có được kinh nghiệm cũng
như tài liệu đầy đủ, nên chúng em chưa thể hoàn thành mục đích đề ra, tuy nhiên cũng đã
thu được rất nhiều thắng lợi ban đầu.
Chúng em xin cám ơn cô đã tận tình hướng dẫn và dành thời gian đọc bản báo cáo này.
Qua việc làm bài tập lớn, chúng em cảm thấy yêu môn học này hơn và quyết định sẽ đi
sâu tìm hiểu về lĩnh vực này một ngày không xa, khi có điều kiện.
Hà Nội, ngày 15 tháng 4 năm 2008

2
Nhóm sinh viên
Hoàng Tuấn Khánh
Hoàng Thanh Tùng

Phân công công việc:

Vì mục đích là trau dồi kiến thức, chúng em luôn hỗ trợ và phân công công việc theo tiêu
trí cả 2 cùng phát triển. Tức là em và bạn Tùng cùng tìm hiểu và cài đặt thuật toán bằng
C, C++ hoặc Java, sau đó tìm hiểu thêm việc áp dụng vào thực tế qua kết quả của chương
trình. Nhưng nếu phân công chi tiết thì:
Hoàng Tuấn Khánh: Tìm hiểu kỹ thuật làm mảnh ảnh nhị phân, phân tích và tìm hiểu giải
thuật làm mảnh Lu-Wang được phát triển từ thuật toán Zhang-Suen, ứng dụng của kỹ
thuật làm mảnh.
Hoàng Thanh Tùng: Tìm hiểu kỹ thuật làm mảnh ảnh nhị phân, giải thuật làm mảnh song
song bằng giải thuật Lu-Wang. Cụ thể hoá bằng chương trình.
Ngoài ra chúng em còn tìm hiểu thêm một số vấn đề khác được nêu trong bài viết này.

Cụ thể hoá chương trình:


Chương trình cuối cùng được quyết định viết bằng Java, giao diện đơn giản dễ dùng, và
đã chạy thành công một số giải thuật cốt yếu. Chương trình có các tính năng như dò biên,
làm mảnh ảnh nhị phân, biểu đồ histogram, biến đổi ảnh màu và xám, biến đổi furier và
cosin rời rạc, ngoài ra đang phát triển thêm bộ công cụ cho phép vẽ và thao tác trực tiếp
lên ảnh nhờ các phép toán vị trí trên ma trận, nhưng hiện tại chúng em chưa có thời gian
để thực hiện.
Chúng em xin giới thiệu qua giao diện của chương trình :



3
Kiến thức cơ bản
Xử lý ảnh là môn khoa học còn mới mẻ nhưng ứng dụng khá nhiều trong thực tế, đặc biệt
là hệ thống nhận dạng. Một hệ thống xử lý ảnh cơ bản thực hiện việc thu nhận ảnh + phân
tích ảnh + trích chọn đặc tính + lưu trữ để phục vụ cho các mục đích khác nhau. Vì ảnh
trong tự nhiên có lượng thông tin rất lớn, do đó ta phải biết lưu trữ sao cho tốn ít dung
lượng mà vẫn đảm bảo ảnh lưu trữ được những đặc tính cơ bản nhất. Muốn đọc được
ảnh, trước tiên đòi hỏi bạn phải có kiến thức căn bản về C, và cách tổ chức file. Bạn cần
phải biết đọc các header để biết dạng ảnh, đọc các pixel ảnh và chuyển đổi chúng thành
dữ liệu thích hợp để thực hiện việc xử lý. Chẳng hạn việc đọc ảnh màu bạn phải biết đổi
màu ra mức xám và sau khi thực hiện xử lý bạn đổi ngược lại, điều này cần một sự phân
ngưỡng chẳng hạn như ma trận dither. Hay việc phóng to thu nhỏ ảnh chỉ đơn thuần là ta
chèn thêm hàng và cột hoặc bỏ đi theo cách nội suy, vì thế khi phóng to hết cỡ ảnh bạn
thấy những ô vuông có duy nhất một mầu, là vì các điểm ảnh giống nhau tập hợp lại do
phép chèn. Ví dụ một ảnh :


1 2 3
4 5 6

7 8 9

phóng to thành

1 1 2 2 3 3
1 1 2 2 3 3
4 4 5 5 6 6
4 4 5 5 6 6
7 7 8 8 9 9
7 7 8 8 9 9

Để có thể hiểu được bài viết này đòi hỏi bạn phải có một kiến thức cơ bản về xử lý ảnh
trong môn học này, nếu biết thêm về đồ hoạ thì càng tốt. Phần đầu tiên này sẽ giới thiệu
qua về các kiến thức cơ bản nhất, vì thời gian có hạn nên chỉ mang tính giới thiệu, bạn
xem lại sách để biết thêm.

Biến đổi ảnh

Đó là cách đưa ảnh từ miền A sang miền B, sao cho có thể đưa ngược lại từ B sang A,
đưa ngược lại là khôi phục ảnh. Các biến đổi hay dùng đó là biến đổi furier chuyển sang
miền tần số hoặc là kl làm giảm không gian nhờ trích chọn đặc tính qua việc làm cực tiểu
bình phương (giảm thứ nguyên). Thông thường, để đơn giản ta chỉ cần hiểu 2 ảnh là u và
v, phép biến đổi là A thì :
u = A*t v A*
v= A u At

với At là ma trận chuyển vị, còn A* là ma trận đổi dấu phần phức của A


4

Trích chọn đặc tính

Lấy biên hoặc vùng ảnh phục vụ cho các mục đích nhận dạng, trước khi trích chọn đặc
tính cần cải thiện ảnh, đó là các phép xử lý như lọc, tăng độ tương phản


Lưu trữ

Ảnh được lưu trữ dưới các mảng, khi hiển thị thì dùng thuật toán cắt tỉa để hiện vùng ảnh
xuất hiện, giải thuật cohen dùng 4 bít từ phải sang trái để mã hoá cho vị trí trái phải dưới
trên. Để giảm dung lượng thường dùng cách lưu trữ dưới dạng nén, chẳng hạn định dạng
gif cho việc mã hoá khối lượng màu lớn, jpeg cho đồ hoạ kỹ xảo, và nén không mất
thông tin như huffman
Áp dụng một số thuật toán


Biến đổi từ ảnh đa mức xám sang ảnh màu

Biến đổi Cosin rời rạc

Biểu đồ xám Histogram và co giãn biểu đồ làm tăng độ tương phản ảnh:

Phép lọc số để cải thiện ảnh

Phát hiện và tách biên

Ứng dụng của việc phát hiện biên ảnh

Kỹ thuật lọc Wiener


Làm mảnh biên cho ảnh nhị phân

Nhận dạng ảnh

Nén ảnh

Trước tiên giả sử đã xây dựng được các lớp thao tác vào ra cho chương trình như đọc file
ảnh, dựa vào phần header cùng bảng giải mã kèm theo để lấy nội dung ảnh, các phương
thức vẽ ảnh , vì mục đích là để nêu cách giải quyết vấn đề, mô phỏng thuật toán nên
chúng em không nêu các lớp phụ này, tập trung để giới thiệu những lớp chính.
Biến đổi từ ảnh đa mức xám sang ảnh màu

5
Kỹ thuật này còn gọi là kỹ thuật LUT (look up table)

Để hiện một ảnh màu từ ảnh đa mức xám bạn phải có một ngưỡng quy ước trong bảng
nào đó, ví dụ với ảnh 2 màu bạn có thể dùng ngưỡng như sau:
nếu mức xám lớn hơn 127 cho hiện thành màu trắng, nếu nhỏ hơn thì hiện màu đen
Nếu bạn muốn có 4 màu thì làm như sau :
chia làm 4 ngưỡng 0-64-128-192-255
ta có các màu trong bảng tra như sau:

ngưỡng mức xám màu
1 đen đen
2 xám đậm đỏ
3 xám nhạt xanh
4 trắng vàng

Muốn có nhiều màu hơn bạn phải phân chi tiết hơn. Để hiểu thêm về màu bạn hãy nghiên
cứu phần bù và tính bão hoà. Chẳng hạn phần bù của RGB là CYM, cho thêm màu trắng

vào một màu nào đó thì nó sẽ nhanh chóng bão hoà

Còn nếu muốn biến ảnh màu thành ảnh đa mức xám để phục vụ cho việc xử lý ảnh bạn
làm như sau: một màu 32 bit chia làm 8 cụm hexa, bạn dùng các mặt nạ và phép dịch bit
để tính giá trị 2 cụm một tương ứng với các màu, sau đó lấy giá trị trung bình chuyển
sang mức xám. Lớp này có thể thực hiện như sau:

//lấy màu đỏ từ pixel

public class RGB {
public static int getRed(int pix){
return (pix&0x00ff0000)>>16;
}

//lấy màu lục từ pixel

public static int getGreen(int pix){
return (pix&0x0000ff00)>>8;
}

//lấy màu lam từ pixel

public static int getBlue(int pix){
return pix&0x000000ff;
}

//tính trung bình

public static int getColorAverage(int pixel) {
int red = pixel & 0x00ff0000;

int green = pixel & 0x0000ff00;
int blue = pixel & 0x000000ff;
return ((red >> 16) + (green >> 8) + blue) / 3;
}

6

//lấy cả 3 màu

public static int[] getColorRGB(int pixel[]) {
int red;
int green;
for (int i=0;i<pixel.length;i++){
pixel[i]=pixel[i]&0x000000ff;
red=pixel[i];
green=pixel[i];
pixel[i]=pixel[i]|(red<<16)|(green<<8)|0xff000000;
}
return pixel;
}
}

Biến đổi Cosin rời rạc

Mục đích của biến đổi là ta chuyển sang một không gian trực giao khác, và ảnh có thể
được biểu diễn qua các ảnh cơ sở của không gian này, giống như là các vector đơn vị
biểu diễn một vector trong hệ trục toạ độ trực giao. Việc tính nhanh biến đổi thực chất là
tính biến đổi kích cỡ n bằng một biến đổi kích cỡ 2n do tính đối xứng của nó. Chẳng hạn
như cách tìm ma trận biến đổi furier unitar trong sách đã dậy, đây là một lớp cho phép
bạn nén ảnh nhờ biến đổi Cosin rời rạc:

//lớp DCT decrete cosin transform

public class CompressDCT extends Component {
public CompressDCT(){
int [][]F=new int [DrawImage.width][DrawImage.height];
//DrawImage là đối tượng ma trận ảnh
for (int i=0;i<DrawImage.height;i++){
for (int k=0;k<DrawImage.width;k++){

F[k][i]=RGB.getColorAverage(DrawImage.pixels[i*DrawImage.width+k]);
}
}

F=compute(F); //phương thức tính ra biến đổi

for (int i=0;i<DrawImage.height;i++){
for (int k=0;k<DrawImage.width;k++){
DrawImage.pixels[i*DrawImage.width+k]=F[k][i];
}
}

ProcessImageView.image=createImage
(new
MemoryImageSource(DrawImage.width,DrawImage.height,DrawImage.pixels,0,D
rawImage.width));
}

public int[][] compute(int f[][]){

7

double pi=Math.PI;
double Cu=0;
double Cv=0;
int Nx=DrawImage.width;
int Ny=DrawImage.height;
int [][]F=new int[Nx][Ny];
double tg = 0;
for (int u=0;u<Nx;u++){
for (int v=0;v<Ny;v++){
for (int x=0;x<Nx;x++){
for (int y=0;y<Ny;y++) {
tg= tg + f[x][y]*Math.cos((2*x+1)*u*pi/2/Nx) *
Math.cos((2*y+1)*v*pi/2/Ny);
}
}
if (u==0)Cu=1/Math.sqrt(Nx);
else Cu=1;
if (v==0)Cv=1/Math.sqrt(Ny);
else Cv=1;
F[u][v]=(int)(4/(Nx+Ny)*Cu*Cv*tg);
System.out.println(F[u][v]);
}
}
return F;
}
public int[] formular(int [] x){
int N=x.length;
float [] X=new float[N];
float tg=0;
for (int k=0;k<N;k++){

for (int n=0;n<N;n++){
if (k==0) tg=(float) (tg +1/Math.sqrt(2)* x[n] *
Math.cos((2 * n + 1) / (2 * N)));
else
tg=(float) (tg + x[n] * Math.cos((2 * n + 1) / (2 *
N)));
}
X[k]=(float) (Math.sqrt(2 / N) * tg);
}
tg=0;
for (int n=0;n<N;n++ ){
for (int k=0;k<N;k++){
if (k==0) tg=(float) (tg +1/Math.sqrt(2)* X[k] *
Math.cos((2*n+1)/2/N));
else
tg=(float) (tg + X[k] * Math.cos((2*n+1)/2/N));
}
x[n]=(int) (Math.sqrt(2 / N) + tg);
}
return x;
}
}

Biểu đồ xám Histogram và co giãn biểu đồ làm tăng độ tương phản ảnh:

8


Việc tính ra biểu đồ histogram thực chất là tính tần xuất xuất hiện các điểm có mức xám
từ 0 đến một ngưỡng nào đó, tối đa là 255. Lớp tính Histogram như sau:




public class Histogram extends JPanel {
float [] pix;
float scale;
float max=0;
Label lab;
public Histogram(float [] pix,Label lab){ //contructor
this.lab=lab;
setBackground(Color.white);
this.pix=pix;
for (int i=0 ;i<256;i++){
if (max<pix[i])max=pix[i];
}
scale=100/max;
paintImmediately(0,0,150,260);
addMouseMotionListener(new MouseMoved());
}
public float getMax(){
return max;
}
//phương thức để vẽ ra
public void paintComponent(Graphics g){
g.drawLine(19,40+(int)(max*scale),19,40);
Graphics2D g2D=(Graphics2D) g;
g2D.setPaint(Color.red);
for (int i=0;i<256;i++){

g2D.drawLine(20+i,40+(int)(max*scale),20+i,40+(int)(max*scale-

pix[i]*scale));
if (pix[i]!=0) System.out.println("i="+i+" "+pix[i]);


}

}
// sự kiện khi di chuột

9

class MouseMoved extends java.awt.event.MouseMotionAdapter{
public void mouseMoved(MouseEvent event){
if (event.getX()>19 && event.getX()<256+20){
lab.setText("Point:"+(event.getX()-20)+"
Value:"+pix[event.getX()-20]);
}
}
}
bạn muốn hiệu ứng xảy ra khi co giãn biểu đồ thì có thể dùng một thanh slider để gán sự
kiện cho nó
Lớp co giãn biểu đồ xám

public class ExpandHistogram {
public int[] transform(int [] pixel,float lmax,float lmin){
int max=0;
int min=255;
for (int i=0 ;i<pixel.length;i++){
pixel[i]=RGB.getColorAverage(pixel[i]);
if (pixel[i]>max)max=pixel[i];

if (pixel[i]<min)min=pixel[i];
}
float tg=(lmax-lmin)/(max-min);
float tg1=(lmin*max-lmax*min)/(max-min);
for (int i=0 ;i<pixel.length;i++){
pixel[i]=(int)(tg*pixel[i]+tg1);
}
pixel=RGB.getColorRGB(pixel);
return pixel;
}
}

Phép lọc số để cải thiện ảnh
Một bức ảnh được lọc thông thấp :


10


Phương pháp lọc rất đơn giản, đó là bạn xếp chồng ma trận lọc vào ảnh, chỉ xếp trồng
trung tâm, các ma trận lọc có thể là thông cao, thông thấp, hoặc trung vị.
Giả sử ma trận lọc là h nhận được từ việc đọc một file ngoài txt.

short result[][]=new short [DrawImage.height][DrawImage.width];
short min=Short.MAX_VALUE;
short max=Short.MIN_VALUE;
int size=Convolution.size;

for (int i=1;i<DrawImage.height-1;i++){
for (int j=1;j<DrawImage.width-1;j++){

result[i][j]=0;
for(int I=-size; I<=size; I++)
for(int J=-size; J<=size; J++)
result[i][j]+=(short)((F[i+I][j+J]*h[I+1][J+1]));
result[i][j]=(short)(result[i][j]/fact);
if (result[i][j]<min) min=result[i][j];
if (result[i][j]>max) max=result[i][j];

}
}
if (min<0) {
if (max-min<256){//khi gia tri chua vuot khoang 255
for (int i=1;i<DrawImage.height-1;i++){
for (int k=1;k<DrawImage.width-1;k++){
DrawImage.pixels[i*DrawImage.width+k]=result[i][k]
- min;
}
}
}else {//ca max va min khong chap nhan duoc
short tg=(short) (max - min);
for (int i=1;i<DrawImage.height-1;i++){
for (int k=1;k<DrawImage.width-1;k++){

11
DrawImage.pixels[i*DrawImage.width+k]=
(result[i][k] - min) * 255 / tg;
// if (
DrawImage.pixels[i*DrawImage.width+k]>=255) va++;
}
}

}
}else if (max-min>255)
{
for (int i=1;i<DrawImage.height-1;i++){
for (int k=1;k<DrawImage.width-1;k++){

DrawImage.pixels[i*DrawImage.width+k]=result[i][k]*255/max;
}
}
}else if (max>225)
{
short tg=(short) (max - 255);
for (int i=1;i<DrawImage.height-1;i++){
for (int k=1;k<DrawImage.width-1;k++){
DrawImage.pixels[i*DrawImage.width+k]=result[i][k]-
tg;
}
}
}


Cuối cùng sau khi lọc xong bạn hiển thị lại ảnh bằng cách lấy màu và
gọi phương thức vẽ lại ảnh :

DrawImage.pixels=RGB.getColorRGB(DrawImage.pixels);

ProcessImageView.image=createImage(new

MemoryImageSource(DrawImage.width,DrawImage.height,DrawImage.pixels,0,D
rawImage.width));

ProcessImageView.drawImage.paintImage();
Phát hiện và tách biên
Sobel Method

Laplacian Method

Giải thuật viết bằng Java:

*********************************************************

Ảnh gốc :


12


Ảnh tách biên theo phương pháp Sobel:



Ảnh tách biên theo phương pháp Laplacian:



Đặc tính biên ảnh là một vấn đề cơ bản và rất quan trọng trong vấn đề xử lý ảnh. Cạnh
của ảnh là những vùng ảnh với độ tương phản rất rõ - một đột biến về cường độ sáng từ
điểm ảnh này đến điểm ảnh khác. Dò biên ảnh có một ý nghĩa quan trọng trong việc làm
giảm số lượng dữ liệu và lọc đi những thông tin dư thừa, trong khi đó giữ lại những đặc
tính cấu trúc quan trọng của bức ảnh. Có nhiều cách để thực hiện vấn đề này. Tuy nhiên
các phương pháp khác nhau có thể nhóm vào 2 mục chính, gradient và Laplacian.

Phương pháp dò biên theo gradient thực hiện bằng cách dò tìm điểm cực đại và cực tiểu
từ bức ảnh nguồn. Phương pháp Laplacian tìm kiếm các điểm 0 thông qua bức ảnh thứ 2
biến đổi từ ảnh gốc để tìm ra cạnh. Một cạnh là đường một chiều dạng bờ dốc và tính
toán từ bức ảnh gốc có thể làm nổi rõ vị trí của nó. Giả sử chúng ta có tín hiệu ảnh sau,
với một cạnh được chỉ ra do sự biến thiên đột ngột của cường độ sáng như hình dưới đây:


13


Nếu chúng ta lấy gradient của tín hiệu này, chúng ta có đồ thị hình vẽ như sau:



Rõ ràng đồ thị cho ta thấy một điểm cực đại ở giữa cạnh trong tín hiệu nguồn. Phương
pháp định vị một cạnh gọi là đặc tính lọc gradient rất thông dụng trong việc dò cạnh và
trong đó có phương pháp lọc Sobel. Một vị trí điểm ảnh được hiện rõ trong một vị trí
cạnh nếu giá trị gradient của nó vượt một ngưỡng nào đó. Như vậy, điểm nằm trên cạnh
sẽ có giá trị cường độ lớn hơn các điểm khác xung quanh nó. Vì thế nếu một ngưỡng
được tìm ra, bạn có thể so sánh gradient với ngưỡng này và dò ra cạnh bất cứ điểm nào
vượt ngưỡng. Hơn thế, khi mức độ sáng của nguồn là cực đại, thì mức độ của nó trong
bức ảnh dẫn xuất là cực tiểu. Và kết quả là, việc tìm ra cạnh của ảnh tức là định vị những
điểm có cường độ là 0 trong bức ảnh thứ 2 được dẫn xuất. Phương pháp này gọi là
Laplacian và tín hiệu của nó có dạng :



Sobel :

Dựa vào lý thuyết đạo hàm một chiều, lý thuyết này có thể được mở rộng ra cho 2 chiều,

cũng như việc tính xấp xỉ nó áp dụng cho một bức ảnh 2 chiều. Toán tử Sobel thực hiện
một gradient không gian 2 chiều để tính toán một bức ảnh. Điển hình là nó sử dụng một

14
giá trị tuyệt đối xấp xỉ cho mỗi điểm ảnh từ một bức ảnh đa mức xám đầu vào. Phương
pháp dò sử dụng một mặt nạ tích chập kích thước 3x3, một cái là tính theo hướng x và
một cái tính theo hướng y. Mặt nạ trượt trên ma trận ảnh và tính ra giá trị tương ứng mỗi
điểm trung tâm. Mặt nạ Sobel cho dưới đây:



Công thức tính gradient là :




Và độ lớn của gradient có thể được tính xấp xỉ :

|G| = |Gx| + |Gy|

Sau đây là toàn bộ chương trình giải thuật được viết bằng C hoặc có thể dùng C++ cũng
ổn.

#include (stdio.h)
#include (stdlib.h)
#include (math.h)
#include (alloc.h)

/* STRUCTURES */
typedef struct {int rows; int cols; unsigned char* data;} sImage;


/* PROTOTYPES */
long getImageInfo(FILE*, long, int);
void copyImageInfo(FILE* inputFile, FILE* outputFile);
void copyColorTable(FILE* inputFile, FILE* outputFile, int nColors);

int main(int argc, char* argv[])
{
FILE *bmpInput, *bmpOutput;
sImage originalImage;
sImage edgeImage;

15
unsigned int X, Y;
int I, J;
long sumX, sumY;
int nColors, SUM;
unsigned long vectorSize;
unsigned long fileSize;
int GX[3][3];
int GY[3][3];
unsigned char *pChar, someChar;
unsigned int row, col;

someChar = '0'; pChar = &someChar;

/* 3x3 GX Sobel mask. Ref: www.cee.hw.ac.uk/hipr/html/Sobel.html */
GX[0][0] = -1; GX[0][1] = 0; GX[0][2] = 1;
GX[1][0] = -2; GX[1][1] = 0; GX[1][2] = 2;
GX[2][0] = -1; GX[2][1] = 0; GX[2][2] = 1;


/* 3x3 GY Sobel mask. Ref: www.cee.hw.ac.uk/hipr/html/Sobel.html */
GY[0][0] = 1; GY[0][1] = 2; GY[0][2] = 1;
GY[1][0] = 0; GY[1][1] = 0; GY[1][2] = 0;
GY[2][0] = -1; GY[2][1] = -2; GY[2][2] = -1;

if(argc < 2) {
printf("Usage: %s bmpInput.bmp\n", argv[0]);
exit(0);
};
printf("Reading filename %s\n", argv[1]);

/* DECLARE INPUT & OUTPUT FILES */
bmpInput = fopen(argv[1], "rb");
bmpOutput = fopen("edgeSob.bmp", "wb");

/* SET POINTER TO BEGINNING OF FILE */
fseek(bmpInput, 0L, SEEK_END);

/* GET INPUT BMP DATA */
fileSize = getImageInfo(bmpInput, 2, 4);
originalImage.cols = (int)getImageInfo(bmpInput, 18, 4);
originalImage.rows = (int)getImageInfo(bmpInput, 22, 4);
edgeImage.rows = originalImage.rows;
edgeImage.cols = originalImage.cols;

/* PRINT DATA TO SCREEN */
printf("Width: %d\n", originalImage.cols);
printf("Height: %d\n", originalImage.rows);
printf("File size: %lu\n", fileSize);


nColors = (int)getImageInfo(bmpInput, 46, 4);
printf("nColors: %d\n", nColors);

/* ALLOCATE MEMORY FOR FILES */
vectorSize = fileSize - (14+40+4*nColors);
printf("vectorSize: %lu\n", vectorSize);
edgeImage.data = farmalloc(vectorSize*sizeof(unsigned char));
if(edgeImage.data == NULL) {
printf("Failed to malloc edgeImage.data\n");

16
exit(0);
}
printf("%lu bytes malloc'ed for edgeImage.data\n", vectorSize);

originalImage.data = farmalloc(vectorSize*sizeof(unsigned char));
if(originalImage.data == NULL) {
printf("Failed to malloc originalImage.data\n");
exit(0);
}
printf("%lu bytes malloc'ed for originalImage.datt\n", vectorSize);

/* COPY HEADER AND COLOR TABLE */
copyImageInfo(bmpInput, bmpOutput);
copyColorTable(bmpInput, bmpOutput, nColors);
fseek(bmpInput, (14+40+4*nColors), SEEK_SET);
fseek(bmpOutput, (14+40+4*nColors), SEEK_SET);

/* Read input.bmp and store it's raster data into originalImage.data

*/
for(row=0; row<=originalImage.rows-1; row++) {
for(col=0; col<=originalImage.cols-1; col++) {
fread(pChar, sizeof(char), 1, bmpInput);
*(originalImage.data + row*originalImage.cols + col) =
*pChar;
}
}

/*
Sobel ALGORITHM STARTS HERE
*/
for(Y=0; Y<=(originalImage.rows-1); Y++) {
for(X=0; X<=(originalImage.cols-1); X++) {
sumX = 0;
sumY = 0;

/* image boundaries */
if(Y==0 || Y==originalImage.rows-1)
SUM = 0;
else if(X==0 || X==originalImage.cols-1)
SUM = 0;

/* Convolution starts here */
else {

/* X GRADIENT APPROXIMATION */
for(I=-1; I<=1; I++) {
for(J=-1; J<=1; J++) {
sumX = sumX + (int)( (*(originalImage.data + X + I

+
(Y + J)*originalImage.cols)) *
GX[I+1][J+1]);
}
}

/* Y GRADIENT APPROXIMATION */
for(I=-1; I<=1; I++) {
for(J=-1; J<=1; J++) {

17
sumY = sumY + (int)( (*(originalImage.data + X +
I +
(Y + J)*originalImage.cols)) *
GY[I+1][J+1]);
}
}

/* GRADIENT MAGNITUDE APPROXIMATION (Myler p.218) */
SUM = abs(sumX) + abs(sumY);
}

if(SUM>255) SUM=255;
if(SUM<0) SUM=0;

*(edgeImage.data + X + Y*originalImage.cols) = 255 -
(unsigned char)(SUM);
fwrite((edgeImage.data + X +
Y*originalImage.cols),sizeof(char),1,bmpOutput);
}

}

printf("See edgeSob.bmp for results\n");
fclose(bmpInput);
fclose(bmpOutput);
farfree(edgeImage.data); /* Finished with edgeImage.data */
farfree(originalImage.data); /* Finished with originalImage.data */
return 0;
}

/* GET IMAGE INFO SUBPROGRAM */
long getImageInfo(FILE* inputFile, long offset, int numberOfChars)
{
unsigned char *ptrC;
long value = 0L;
unsigned char dummy;
int i;

dummy = '0';
ptrC = &dummy;

fseek(inputFile, offset, SEEK_SET);

for(i=1; i<=numberOfChars; i++)
{
fread(ptrC, sizeof(char), 1, inputFile);
/* calculate value based on adding bytes */
value = (long)(value + (*ptrC)*(pow(256, (i-1))));
}
return(value);


} /* end of getImageInfo */

/* COPIES HEADER AND INFO HEADER */
void copyImageInfo(FILE* inputFile, FILE* outputFile)
{
unsigned char *ptrC;
unsigned char dummy;

18
int i;

dummy = '0';
ptrC = &dummy;

fseek(inputFile, 0L, SEEK_SET);
fseek(outputFile, 0L, SEEK_SET);

for(i=0; i<=50; i++)
{
fread(ptrC, sizeof(char), 1, inputFile);
fwrite(ptrC, sizeof(char), 1, outputFile);
}

}

/* COPIES COLOR TABLE */
void copyColorTable(FILE* inputFile, FILE* outputFile, int nColors)
{
unsigned char *ptrC;

unsigned char dummy;
int i;

dummy = '0';
ptrC = &dummy;

fseek(inputFile, 54L, SEEK_SET);
fseek(outputFile, 54L, SEEK_SET);

for(i=0; i<=(4*nColors); i++) /* there are (4*nColors) bytesin color
table */
{
fread(ptrC, sizeof(char), 1, inputFile);
fwrite(ptrC, sizeof(char), 1, outputFile);
}

}

Mặt nạ được trượt trên mỗi vùng của bức ảnh, thay đổi giá trị của điểm ảnh rồi trượt sang
phải cho đến khi hết hàng. Rồi nó lại thực hiện với hàng tiếp theo. Chẳng hạn:


19


Laplacian:


Phương pháp này sử dụng một mặt nạ kích thước 5x5 để tính xấp xỉ ra bức ảnh dẫn xuất,
khác với Sobel nó tính trực tiếp luôn với hướng x và y. Tuy nhiên nó rất nhậy cảm với

nhiễu vì tính xấp xỉ, và có vẻ không tốt bằng Sobel, nhưng lại cho biên mảnh và nét hơn
nếu ảnh ít nhiễu.
Ma trận Laplacian cho dưới đây và đoạn code thực hiện:



#include (stdio.h)
#include (stdlib.h)
#include (math.h)
#include (alloc.h)

/* STRUCTURES */
typedef struct {int rows; int cols; unsigned char* data;} sImage;


20
/* PROTOTYPES */
long getImageInfo(FILE*, long, int);
void copyImageInfo(FILE* inputFile, FILE* outputFile);
void copyColorTable(FILE* inputFile, FILE* outputFile, int nColors);

int main(int argc, char* argv[])
{
FILE *bmpInput, *bmpOutput;
sImage originalImage;
sImage edgeImage;
unsigned int X, Y;
int I, J;
long SUM;
int nColors;

unsigned long vectorSize;
unsigned long fileSize;
int MASK[5][5];
unsigned char *pChar, someChar;
unsigned int row, col;

someChar = '0'; pChar = &someChar;

/* 5x5 Laplace mask. Ref: Myler Handbook p. 135 */
MASK[0][0] = -1; MASK[0][1] = -1; MASK[0][2] = -1; MASK[0][3] = -1;
MASK[0][4] = -1;
MASK[1][0] = -1; MASK[1][1] = -1; MASK[1][2] = -1; MASK[1][3] = -1;
MASK[1][4] = -1;
MASK[2][0] = -1; MASK[2][1] = -1; MASK[2][2] = 24; MASK[2][3] = -1;
MASK[2][4] = -1;
MASK[3][0] = -1; MASK[3][1] = -1; MASK[3][2] = -1; MASK[3][3] = -1;
MASK[3][4] = -1;
MASK[4][0] = -1; MASK[4][1] = -1; MASK[4][2] = -1; MASK[4][3] = -1;
MASK[4][4] = -1;

if(argc < 2) {
printf("Usage: %s bmpInput.bmp\n", argv[0]);
exit(0);
};
printf("Reading filename %s\n", argv[1]);

/* open files for reading and writing to */
bmpInput = fopen(argv[1], "rb");
bmpOutput = fopen("edgeLap.bmp", "wb");


/* start pointer at beginning of file */
fseek(bmpInput, 0L, SEEK_END);

/* retrieve and print filesize and number of cols and rows */
fileSize = getImageInfo(bmpInput, 2, 4);
originalImage.cols = (int)getImageInfo(bmpInput, 18, 4);
originalImage.rows = (int)getImageInfo(bmpInput, 22, 4);
edgeImage.rows = originalImage.rows;
edgeImage.cols = originalImage.cols;

printf("Width: %d\n", originalImage.cols);
printf("Height: %d\n", originalImage.rows);
printf("File size: %lu\n", fileSize);

21

/* retrieve and print Number of colors */
nColors = (int)getImageInfo(bmpInput, 46, 4);
printf("nColors: %d\n", nColors);

vectorSize = fileSize - (14+40+4*nColors);
printf("vectorSize: %lu\n", vectorSize);
edgeImage.data = farmalloc(vectorSize*sizeof(unsigned char));
if(edgeImage.data == NULL) {
printf("Failed to malloc edgeImage.data\n");
exit(0);
}
printf("%lu bytes malloc'ed for edgeImage.data\n", vectorSize);

originalImage.data = farmalloc(vectorSize*sizeof(unsigned char));

if(originalImage.data == NULL) {
printf("Failed to malloc originalImage.data\n");
exit(0);
}
printf("%lu bytes malloc'ed for originalImage.datt\n", vectorSize);

copyImageInfo(bmpInput, bmpOutput);
copyColorTable(bmpInput, bmpOutput, nColors);
fseek(bmpInput, (14+40+4*nColors), SEEK_SET);
fseek(bmpOutput, (14+40+4*nColors), SEEK_SET);

/* Read input.bmp and store it's raster data into originalImage.data
*/
for(row=0; row<=originalImage.rows-1; row++) {
for(col=0; col<=originalImage.cols-1; col++) {
fread(pChar, sizeof(char), 1, bmpInput);
*(originalImage.data + row*originalImage.cols + col) =
*pChar;
}
}

for(Y=0; Y<=(originalImage.rows-1); Y++) {
for(X=0; X<=(originalImage.cols-1); X++) {
SUM = 0;

/* image boundaries */
if(Y==0 || Y==1 || Y==originalImage.rows-2 ||
Y==originalImage.rows-1)
SUM = 0;
else if(X==0 || X==1 || X==originalImage.cols-2 ||

X==originalImage.cols-1)
SUM = 0;

/* Convolution starts here */
else {
for(I=-2; I<=2; I++) {
for(J=-2; J<=2; J++) {
SUM = SUM + (int)( (*(originalImage.data + X + I +
(Y + J)*originalImage.cols)) *
MASK[I+2][J+2]);

}

22
}
}
if(SUM>255) SUM=255;
if(SUM<0) SUM=0;

*(edgeImage.data + X + Y*originalImage.cols) = 255 -
(unsigned char)(SUM);
fwrite((edgeImage.data + X +
Y*originalImage.cols),sizeof(char),1,bmpOutput);
}
}

printf("See edgeLap.bmp for results\n");
fclose(bmpInput);
fclose(bmpOutput);
farfree(edgeImage.data); /* Finished with edgeImage.data */

farfree(originalImage.data); /* Finished with originalImage.data */
return 0;
}

/* GET IMAGE INFO SUBPROGRAM */
long getImageInfo(FILE* inputFile, long offset, int numberOfChars)
{
unsigned char *ptrC;
long value = 0L;
unsigned char dummy;
int i;

dummy = '0';
ptrC = &dummy;

fseek(inputFile, offset, SEEK_SET);

for(i=1; i<=numberOfChars; i++)
{
fread(ptrC, sizeof(char), 1, inputFile);
/* calculate value based on adding bytes */
value = (long)(value + (*ptrC)*(pow(256, (i-1))));
}
return(value);

} /* end of getImageInfo */

/* COPIES HEADER AND INFO HEADER */
void copyImageInfo(FILE* inputFile, FILE* outputFile)
{

unsigned char *ptrC;
unsigned char dummy;
int i;

dummy = '0';
ptrC = &dummy;

fseek(inputFile, 0L, SEEK_SET);
fseek(outputFile, 0L, SEEK_SET);

for(i=0; i<=50; i++)

23
{
fread(ptrC, sizeof(char), 1, inputFile);
fwrite(ptrC, sizeof(char), 1, outputFile);
}

}

/* COPIES COLOR TABLE */
void copyColorTable(FILE* inputFile, FILE* outputFile, int nColors)
{
unsigned char *ptrC;
unsigned char dummy;
int i;

dummy = '0';
ptrC = &dummy;


fseek(inputFile, 54L, SEEK_SET);
fseek(outputFile, 54L, SEEK_SET);

for(i=0; i<=(4*nColors); i++) /* there are (4*nColors) bytesin color
table */
{
fread(ptrC, sizeof(char), 1, inputFile);
fwrite(ptrC, sizeof(char), 1, outputFile);
}

}

Giải thích rõ hơn và cài đặt lại thuật toán bằng Java:
Phương pháp Sobel
Phương pháp Laplacian
Phương pháp dò biên Canny


Ảnh được làm nổi biên :


24


Việc phát hiện biên thực chất là lấy những điểm ảnh có sự biến thiên về cường độ sáng
hay mức xám. Ta dùng ma trận lọc, thường dùng ma trận Sobel theo hướng x và y hoặc
dùng laplace, dùng laplace thì bị ảnh hưởng bởi nhiễu hơn, sau khi làm nổi biên, ta có thể
tính hướng của gradien theo công thức tan(phi) = Gy/Gx, dựa vào góc này để biết hướng
của gradien, từ đó với mỗi điểm trên biên sẽ được hiện khi 2 điểm lân cận theo hướng
đạo hàm của nó đều nhỏ hơn nó. Phương pháp dò biên canny sử dụng một ma trận nhân

chập của laplace với ma trận gauss để khử ảnh hưởng của nhiễu. Các phương pháp lọc tối
ưu thực hiện theo 3 tiêu chí để làm giảm số điểm cực đại cục bộ để làm mảnh biên, làm
giảm nhiễu-tỉ số giữa tín hiệu và nhiễu là cực đại, và cực tiểu hoá phương sai để không
mất đi đặc trưng quan trọng.


Vấn đề dò và tách biên là một vấn đề phức tạp, ở đây chỉ xin trình bày 2 mặt nạ lọc biên
và dò biên theo phương pháp canny để xác định đường biên:

Phương pháp Sobel:

int GX[][] = new int[3][3];
int GY[][] = new int[3][3];


GX[0][0] = -1; GX[0][1] = 0; GX[0][2] = 1;
GX[1][0] = -2; GX[1][1] = 0; GX[1][2] = 2;
GX[2][0] = -1; GX[2][1] = 0; GX[2][2] = 1;

GY[0][0] = 1; GY[0][1] = 2; GY[0][2] = 1;
GY[1][0] = 0; GY[1][1] = 0; GY[1][2] = 0;
GY[2][0] = -1; GY[2][1] = -2; GY[2][2] = -1;


25
for (int i=0;i<DrawImage.height-1;i++){
for (int j=0;j<DrawImage.width-1;j++){

int sumX=0;
int sumY=0;

int SUM = 0;

/* biên ảnh */
if(i==0 || i==1 || i==DrawImage.height-2 ||
i==DrawImage.height-1)
SUM = 0;
else if(j==0 || j==1 || j==DrawImage.height-2 ||
j==DrawImage.height-1)
SUM = 0;

else {

for(int I=-1; I<=1; I++)
for(int J=-1; J<=1; J++)


{

sumX+=(int)(F[i+I][j+J]*GX[I+1][J+1]);
sumY+=(int)(F[i+I][j+J]*GY[I+1][J+1]);
}


SUM = (int)(Math.sqrt(sumX*sumX + sumY*sumY));

}

if(SUM>255) SUM=255;
if(SUM<0) SUM=0;


DrawImage.pixels[i*DrawImage.width+j]=255-SUM;
//đổi đặc tính ảnh để điểm trắng thành đen và ngược lại cho dễ quan sát

}
}


Phương pháp Laplacian như sau:



int L[][] = new int[5][5];

L[0][0] = -1; L[0][1] = -1; L[0][2] = -1; L[0][3] = -1; L[0][4] = -
1;
L[1][0] = -1; L[1][1] = -1; L[1][2] = -1; L[1][3] = -1; L[1][4] = -
1;
L[2][0] = -1; L[2][1] = -1; L[2][2] = 24; L[2][3] = -1; L[2][4] = -
1;
L[3][0] = -1; L[3][1] = -1; L[3][2] = -1; L[3][3] = -1; L[3][4] = -
1;

×