การตรวจจับสีอย่างง่าย (Simple colors detection)

การตรวจจับสี หรือ การตรวจวัดสี ของวัตถุภายในภาพ เป็นคำถามถูกที่ถูกถามเข้ามาบ่อยมาก

และผมต้องบอกเลยว่า การตรวจวัดสี ในสภาพสิ่งแวดล้อมแบบเปิด เช่น กลางแจ้ง หรือ ในห้องทั่วไป ไม่ใช่เรื่องง่ายๆ เลย พูดตามตรงคือ ยากถึงยากมาก เพราะปกติแล้วรูปภาพจะต้องเป็น 4 เหลี่ยมเสมอ เราไม่สามารถเลือกตัดภาพออกมาเป็นรูปอื่นๆ อย่างเช่น รูปวงกลม หรือรูปดาว เพราะว่ารูปภาพดิจิตอล มันก็คือ อาเรย์ของตัวเลข ชุดหนึ่ง และถ้าคุณผู้อ่านจำได้ กฎข้อแรกของการสร้างอาเรย์เลยก็คือ "ทุกแถวต้องมีคอลัภม์เท่ากัน และทุกคอมลัภม์ต้องมีแถวเท่ากัน" ดังนั้นมันจึงไม่สามารถเป็นรูปทรงอื่นได้นอกจาก 4 เหลี่ยม

และเมื่อภาพที่เราตัดมาได้เป็น 4 เหลี่ยม มันก็ต้องมีส่วนของพื้นหลัง ซึ่งเป็นสีอื่นๆ ที่ไม่ใช่สีของวัตถุที่เราสนใจ ถ้าเราเอาสีพวกนั้นมาคำนวณด้วย คำตอบที่ได้ก็ต้องเพี้ยนแน่นอน ดังนั้นจึงต้องหาวิธีการแยกพื้นหลังออกจากวัตถุที่เราสนใจให้ได้ ซึ่งก็ไม่ใช่เรื่องง่ายเท่าใดนัก

แต่ถ้าเป็นการตรวจจับสีของวัตถุที่มีรูปทรงไม่ซับซ้อน และอยู่ในสภาพแวดล้อมแบบปิด คือ ควบคุมแสงตลอดเวลา การตรวจจับสีก็พอจะทำได้ง่ายๆ ซึ่งบทความนี้ ผมจะแสดงวิธีการตรวจจับสีอย่างง่าย ให้ได้ชมกันครับ ซึ่งผู้อ่านสามารถนำวิธีการนี้ ไปปรับใช้กับรูปภาพที่อยู่ในสภาพแวดล้อมแบบปิด ได้ทุกประเภท (เพียงแต่ต้องวัดค่าสีเอง)

ก่อนอื่น เราต้องเตรียมรูปภาพที่จะใช้ทดสอบกันก่อนนะครับ ซึ่งในตัวอย่างนี้ ผมใช้ภาพนี้ ใครที่อยากทดลองรันก็ก็อปรูปนี้ไปใช้เลยนะครับ
ต่อไปเราก็มาเริ่มเขียนโปรแกรมกันเลยครับ

ขั้นตอนที่ 1 อ่านไฟล์ภาพและแสดงผล

ในขั้นตอนนี้ผมใช้คำสั่ง uigetfile เพื่อให้ user ได้เลือกภาพเองตามใจชอบครับ ดังนั้นเวลาก็อปปี้ไปใช้งาน จึงไม่จำเป็นต้องแก้ไขโค้ด

%% 1st step: Select and open test image
[fn,pt] = uigetfile({'*.png','PNG'},'Select image');
if(fn==0)
    return;
end
ffn = fullfile(pt,fn);
pic = imread(ffn);
figure;
imshow(pic);

ขั้นตอนที่ 2 วัดค่าสีในภาพ 

เมื่อเรารันโค้ดในขั้นตอนที่ 1 โปรแกรมก็จะเปิดรูปภาพขึ้นมา ให้เราใช้ Cursor Data ใน figure ไปจิ้มที่รูปตามจุดต่างๆ เพื่อวัดค่าสีนะครับ

อย่างเช่นในรูปตัวอย่างนี้ ค่าที่วัดได้ก็คือ R 196 , G 24 , B 14 ซึ่งค่าพวกนี้ พอซ้อนทับกันตามลำดับ RGB จึงแสดงออกมาเป็นสีแดงอย่างที่ตาเรามองเห็น แต่ก็ไม่ใช่ว่าทุกจุดมันจะมีค่าเท่ากันนะครับ ลองเลื่อนดูหลายๆ จุด เพราะว่าพื้นผิวของลูกบอลแต่ละส่วนก็โดนแสงไม่เท่ากัน ดังนั้นค่าสีจึงไม่เท่ากัน

เมื่อเราจิ้มดูหลายๆ จุด เราก็จะเห็นค่าขอบเขตสีคร่าวๆ ของลูกบอลแต่ละลูก ซึ่งในตัวอย่างนี้ ผมวัดค่าสีของลูกบอลแต่ละลูกได้ดังนี้นะครับ

Red , R 200+ G <80 B <80
Blue, R <80  G <10 B 200+
Yellow R 200+ G 200+ B <50

(วิธีการอ่านนะครับ อย่างเช่น สีแดง มีค่า R มากกว่า 200 ขึ้นไป ค่า G น้อยกว่า 80 ค่า B น้อยกว่า 80)

จากนั้นเราก็เอาข้อมูลพวกนี้ลงไปเขียนไว้ในโค้ดครับ ซึ่งในตอนท้าย เราจะใช้เป็นเงื่อนไขในการพิจารณาสี

%% 2nd step: record color data from pick data cursor
% Red , R 200+ G <80 B <80
% Blue, R <80  G <10 B 200+
% Yellow R 200+ G 200+ B <50

Red = [200 80 80];
Blue = [80 30 200];
Yellow = [200 200 50];

ขั้นตอนที่ 3 หาขอบเขตของวัตถุในภาพ

อย่างที่ผมได้อธิบายไปในตอนต้นนั่นแหละครับ โดยปกติแล้ววัตถุที่เราสนใจ มันไม่ได้ถ่ายมาให้เห็นเต็มภาพหรอก อย่างในตัวอย่างนี้ในภาพทดสอบก็มีลูกบอลอยู่ตั้ง 3 ลูก ดังนั้นเราจึงต้องหาขอบเขตที่แน่นอนของลูกบอลแต่ละลูกก่อน แล้วค่อยตัดภาพเฉพาะส่วนนั้นออกมาวิเคราะห์ทีละภาพ

โดยในการหาขอบเขตของลูกบอลแต่ละลูกนั้น ผมจะใช้วิธีการทาง image processing คือ แปลงภาพสีเป็นขาวดำก่อน แล้วหลังจากนั้นใช้คำสั่ง regionprops เพื่อหาขอบเขตของวัตถุในภาพขาวดำ

%% 3rd step: find object boundary
bw = im2bw(pic,0.9);
nbw = not(bw);      %invert color
bbx = regionprops(nbw,'BoundingBox');
BX = cat(1,bbx(:).BoundingBox);
IX = insertShape(pic,'Rectangle',BX);
figure;
imshow(nbw)
figure;
imshow(IX);

ผลรันที่ได้


ขั้นตอนที่ 4 คำนวณค่าเฉลี่ยสี

เมื่อเราทราบขอบเขตของลูกบอลแต่ละลูกแล้ว ต่อไปเราก็ต้องมาคำนวณค่าเฉลี่ยของสี ในแต่ละเลเยอร์ของภาพ (อย่าลืมนะครับว่าภาพสีมี 3 เลเยอร์ คือ RGB) แต่ก็อย่างที่ผมพูดไปข้างต้นนั่นแหละครับว่า เราไม่สามารถเลือกตัดภาพแค่ส่วนวงกลมของลูกบอลได้ ยังไงก็ต้องตัดมาเป็น 4 เหลี่ยม ดังนั้นผมจึงใช้กรอบ 4 เหลี่ยม แทนกรอบวงกลม เพื่อให้คุณผู้อ่านเห็นว่า เมื่อเราตัดลูกบอลออกมาทีละลูกแล้ว ก็ยังมีส่วนพื้นหลังที่เป็นสีขาว อยู่ตามมุมต่างๆ ทั้ง 4 มุมนะครับ

ซึ่งสีขาวนี้ในทางภาพดิจิตอล มันมีค่าสูงถึง 255 ทั้ง 3 เลเยอร์เลย ดังนั้นหากนำมันมาคำนวณด้วย ผลที่ได้ต้องผิดเพี้ยนแน่นอน ดังนั้น ผมจะแก้ปัญหาโดยการคำนวณแค่ส่วนที่เป็นพื้นที่ของลูกบอลนะครับ โดยการทำให้พื้นหลังมีค่าเป็น 0 หลังจากนั้นดึงข้อมูลสีออกมาทีละเลเยอร์ ซึ่งเราจะได้ข้อมูลเป็นอาเรย์ 2 มิติ จากนั้นแปลงมันให้เป็น 1 มิติ แล้วดึงแค่ตำแหน่งที่มีค่ามากกว่า 0 ออกมาหาค่าเฉลี่ยครับ

เมื่อหาค่าเฉลี่ยของแต่ละเลเยอร์ได้แล้ว เราก็เอาไปเปรียบเทียบกับข้อมูลที่เราวัดได้ในขั้นตอนที่ 2 ซึ่งในการเปรียบเทียบ ก็ใช้คำสั่ง if ธรรมดานี่แหละครับ แค่นั้นเราก็ได้คำตอบออกมาแล้ว

%% 4th step: Calculate average color value
r = size(BX,1);
CLR = cell(1,r);   %temp answer
for m=1:r
    rect = BX(m,:);
    img = imcrop(pic,rect);
    bw1 = imcrop(nbw,rect);
    
    k = reshape(bw1,1,[]);
    R = img(:,:,1);
    G = img(:,:,2);
    B = img(:,:,3);
    
    O = reshape(R,1,[]);
    P = reshape(G,1,[]);
    Q = reshape(B,1,[]);
    
    X = O(k);
    Y = P(k);
    Z = Q(k);
    
    rd = mean(X);   % red
    gn = mean(Y);   % green
    yw = mean(Z);   % blue
    
    colors = 'Undefine';
    if(rd>Red(1) && gn<Red(2) && yw<Red(3))
        colors = 'RED';
    end
    if(rd<Blue(1) && gn<Blue(2) && yw>Blue(3))
        colors = 'BLUE';
    end
    if(rd>Yellow(1) && gn>Yellow(2) && yw<Yellow(3))
        colors = 'Yellow';
    end
    CLR{m} = colors;
end

ขั้นตอนที่ 5 แสดงผล

ในที่สุดก็มาถึงขั้นตอนสุดท้ายแล้วครับ เราก็มาแสดงผลดูว่าโปรแกรมที่เราเขียนมานั้น มันทำงานได้ถูกต้อง และให้ผลลัพธ์ตามที่เราออกแบบไว้หรือไม่ ซึ่งในการแสดงผล ก็ไม่มีอะไรยุ่งยากครับ เราแค่อยากรู้ว่าโปรแกรมเห็นลูกบอลทั้ง 3 ลูกเป็นสีอะไร ดังนั้นเราก็แค่ต้องแสดงตัวอักษรบนลูกบอลทั้ง 3 ลูกก็พอครับ ซึ่งในที่นี้ ผมใช้คำสั่ง insertObjectAnnotation เพื่อเขียนข้อความกำกับ object ในภาพ

%% 5th step: Display answer
RGB = insertObjectAnnotation(pic,'rectangle',BX,CLR,'TextBoxOpacity',0.9,...
      'FontSize',18);
figure;
imshow(RGB);

จากนั้นเมื่อเราลองรันโปรแกรมดู ก็จะเห็นผลรันดังนี้ครับ



จะเห็นว่าโปรแกรมของเราบอกสีของลูกบอลแต่ละลูกได้อย่างถูกต้องแล้วนะครับ ถ้าใครทำแล้วไม่ได้ผลตามนี้ ก็ลองไปวัดค่าสีดูอีกรอบนะครับ วัดหลายๆ จุด จะได้เห็นขอบเขตของสีทั้งหมด เวลาเอามาเขียนเป็นเงื่อนไขจะได้ครอบคลุม


เมื่อเราเอาโค้ดของทุกขั้นตอนมารวมกัน ก็จะได้ดังนี้ครับ

% ----------------------- MATLAB Programing --------------------------%
% Example: Simple color detection method                              %
% Create by: Kritthanit                                               %
% Create date: 07-01-2019                                             %
% Blog: https://loglike.blogspot.com                                  %
% Fanpage: https://www.facebook.com/Matlab-Programing-194695677296190 %
% --------------------------------------------------------------------%
clc;clear;close all;

%% 1st step: Select and open test image
[fn,pt] = uigetfile({'*.png','PNG'},'Select image');
if(fn==0)
    return;
end
ffn = fullfile(pt,fn);
pic = imread(ffn);
figure;
imshow(pic);

%% 2nd step: record color data from pick data cursor
% Red , R 200+ G <80 B <80
% Blue, R <80  G <10 B 200+
% Yellow R 200+ G 200+ B <50

Red = [200 80 80];
Blue = [80 30 200];
Yellow = [200 200 50];

%% 3rd step: find object boundary
bw = im2bw(pic,0.9);
nbw = not(bw);      %invert color
bbx = regionprops(nbw,'BoundingBox');
BX = cat(1,bbx(:).BoundingBox);
IX = insertShape(pic,'Rectangle',BX);
figure;
imshow(nbw)
figure;
imshow(IX);

%% 4th step: Calculate average color value
r = size(BX,1);
CLR = cell(1,r);   %temp answer
for m=1:r
    rect = BX(m,:);
    img = imcrop(pic,rect);
    bw1 = imcrop(nbw,rect);
    
    k = reshape(bw1,1,[]);
    R = img(:,:,1);
    G = img(:,:,2);
    B = img(:,:,3);
    
    O = reshape(R,1,[]);
    P = reshape(G,1,[]);
    Q = reshape(B,1,[]);
    
    X = O(k);
    Y = P(k);
    Z = Q(k);
    
    rd = mean(X);   % red
    gn = mean(Y);   % green
    yw = mean(Z);   % blue
    
    colors = 'Undefine';
    if(rd>Red(1) && gn<Red(2) && yw<Red(3))
        colors = 'RED';
    end
    if(rd<Blue(1) && gn<Blue(2) && yw>Blue(3))
        colors = 'BLUE';
    end
    if(rd>Yellow(1) && gn>Yellow(2) && yw<Yellow(3))
        colors = 'Yellow';
    end
    CLR{m} = colors;
end

%% 5th step: Display answer
RGB = insertObjectAnnotation(pic,'rectangle',BX,CLR,'TextBoxOpacity',0.9,...
      'FontSize',18);
figure;
imshow(RGB);

จะเห็นว่าถึงแม้ตัวอย่างนี้จะเป็นแค่ตัวอย่างแบบง่ายๆ แต่โค้ดยาวไม่น้อยเลยนะครับ และอัลกอริทึมก็ซับซ้อนพอสมควร ดังนั้นหากใครไม่เข้าใจ ก็แสดงว่าคุณยังมีความรู้ด้านภาพดิจิตอล และอาเรย์ไม่เพียงพอ ลองไปศึกษา 2 เรื่องนี้เพิ่มเติมดูนะครับ

สำหรับใครที่คิดว่ามันยาก ผมจะพูดอีกรอบนะครับว่า image processing ไม่ใช่ โปรแกรมที่มือใหม่ ควรจะใช้ฝึกหัดเขียน หากคุณเพิ่งหัดเขียน MATLAB ผมแนะนำให้คุณไปหัดเขียนโปรแกรมคำนวณเกรดให้ได้ก่อนเป็นอันดับแรกเลยครับ แล้วหลังจากนั้นไปหาอ่านเรื่องภาพดิจิตอลมาให้เยอะๆ เพราะถ้าคุณไม่มีความรู้เรื่องภาพดิจิตอล คุณก็จะงงเหมือนเดิม และที่สำคัญภาพดิจิตอล ไม่ได้มีแค่ภาพ RGB นะครับ ยังมีภาพประเภทอื่นๆ เช่น HSV ด้วย ซึ่งวิธีการก็จะแตกต่างกันออกไปอีก หรือแม้แต่ภาพ RGB นั้นก็ยังแบ่งย่อยออกเป็นหลายประเภท ตามที่เราเรียกว่า นามสกุลของภาพนั่นแหละครับ เช่น jpg , png , bmp , gif

ถ้าคุณยังไม่เข้าใจว่านามสกุลพวกนี้มันทำให้ภาพต่างกันยังไง แสดงว่าคุณยังไม่เข้าใจเรื่องภาพดิจิตอลนะครับ ให้ลองกลับไปอ่านดูอีกรอบ

สุดท้าย หวังว่าบทความนี้จะพอเป็นแนวทางให้ศึกษา หรือนำไปประยุกต์ใช้ในงานของคุณได้นะครับ



ความเห็น

โพสต์ยอดนิยมจากบล็อกนี้

การแก้สมการ Differential ด้วย MATLAB

ว่าด้วยเรื่องของ ERROR

การหาค่าเฉลี่ยโดยไม่ต้องเก็บค่า