การทำปุ่ม Browse และปุ่ม Save

การทำปุ่ม Browse และปุ่ม Save นั้นไม่ได้มีเครื่องมือพิเศษใดๆ ในการสร้าง เพียงแค่ใช้ปุ่ม push button ธรรมดาเท่านั้น และทุกโปรแกรม GUI มักจะมีปุ่ม Browse เสมอ เพื่อให้ user เลือกไฟล์หรือเลือกโฟลเดอร์ที่จะประมวลผลได้ เพราะถ้าจะให้ user พิมพ์ชื่อไฟล์ก็คงจะไม่สะดวกเท่าไร


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

1. ให้ user เลือกรูปภาพ หรือ เลือกโฟลเดอร์ที่เก็บภาพ ในกรณีที่ user เลือกภาพเดียวโปรแกรมก็จะแปลงและ save ภาพเดียว แต่ถ้า user เลือกโฟลเดอร์ โปรแกรมก็จะแปลงภาพทั้งหมดในโฟลเดอร์นั้น และ save ภาพทั้งหมด

2. แสดงภาพตัวอย่างที่ user เลือกในหน้า GUI

3. สร้างปุ่ม convert ภาพ เพื่อให้ user ดูตัวอย่างภาพก่อน save ได้

4. สร้างปุ่ม save ภาพ ถ้ามีภาพเดียวจะ save ตามชื่อที่ user ตั้ง แต่ถ้ามีหลายภาพจะเพิ่มตัวเลขลำดับต่อท้ายจากชื่อที่ user ตั้งเอาไว้ อย่างเช่น test001.png


ขั้นตอนที่ 1 ออกแบบหน้า GUI


โปรแกรมนี้ผมออกแบบหน้า GUI ได้ประมาณนี้


*หมาย ตัวหนังสือ tag: xxx ไม่ต้องใส่เข้าไปใน GUI นะครับ ผมเขียนเอาไว้ให้ทุกคนตั้งชื่อ tag ตามนี้เท่านั้น เวลาเขียนโปรแกรมจะได้ไม่ผิดพลาด

อ้อ บางคนอาจจะมีปัญหาว่า ปุ่ม Gray กับ Save มันไปซ่อนอยู่หลัง axes1 ก็ให้คลิกขวาที่ปุ่ม Gray หรือ Save แล้วเลือก Bring to front หรือคลิกขวาที่ axes แล้วเลือก Send to back แต่ผมแนะนำเทคนิคง่ายๆ อีกอย่างคือ อยากให้อะไรอยู่หลัง ก็สร้างอันนั้นก่อน อย่างเช่นผมอยากให้ axes1 อยู่ด้านหลัง ผมก็สร้าง axes1 ก่อน แล้วค่อยมาสร้างปุ่ม Gray กับปุ่ม Save

เมื่อสร้างเสร็จแล้วก็กดปุ่ม Save (ด้านบนซ้ายมือ ไม่ใช่ปุ่ม btSave นะ) เพื่อให้ MATLAB สร้างโค้ดสำหรับ GUI ให้เรา อ้อพอโค้ดสร้างเสร็จไม่ต้องไปสนใจว่าฟังก์ชันไหนอยู่บนอยู่ล่างนะครับ ไม่ได้มีผลกับการเขียนโปรแกรม


ขั้นตอนที่ 2 เริ่มเขียนโปรแกรมควบคุมการทำงานของแต่ละปุ่ม

2.1 ปรับแต่ axes

ถ้าหากเรายังไม่ได้เขียนโค้ดใดๆ และกดรันโปรแกรม เราจะเห็นว่าตรง axes มันจะมีขีดสเกล และตัวเลขตามแกน x และแกน y แบบนี้


ซึ่งมันทำให้โปรแกรมดูไม่สวยงาม ดังนั้นเราจัดการเอาตัวเลขและสเกลพวกนี้ออกก่อน โดยการเขียนโค้ดเข้าไปในฟังก์ชัน OpeningFcn (อยู่อันดับ 2 จากบนสุด) ซึ่งฟังก์ชันนี้จะทำงานทันทีเมื่อโปรแกรมเริ่มรัน โดยเราจะเขียนโค้ดกำกับดังนี้

pic = 255.*ones(100,100,3);
pic = uint8(pic);
image(pic,'Parent',handles.axes1);
axis off

บรรทัดแรกเราใช้คำสั่ง ones สร้างอาเรย์ 3 มิติขึ้นมา โดยมีขนาด 100*100*3 เพราะว่ารูปภาพสีทุกชนิดจะมีโครงสร้างเป็นอาเรย์ 3 มิติเสมอ

บรรทัดที่ 2 เราใช้คำสั่ง uint8 เพื่อแปลงชนิดข้อมูลจาก double เป็น uint8 เพราะรูปภาพสีทุกชนิดจะมีประเภทข้อมูลเป็น uint8 เสมอ

จากนั้นจึีงใช้คำสั่ง image (ไม่ใช้ imshow) แสดงรูปภาพ เพราะ image จะปรับภาพของเราให้เท่ากับ axes เสมอ หลังจากนั้นจึงใช้คำสั่ง axis off เพื่อสเกลและตัวเลขเอาไว้ ไม่ต้องแสดงออกมา

เมื่อเราลองรันโปรแกรมใหม่ เราก็จะได้หน้าตาโปรแกรมแบบนี้


2.2 ปุ่ม btFile

*เขียนเอาไว้ในฟังก์ชัน btFile_Callback

[fn,pt] = uigetfile({'*.png','PNG';'*.jpg','JPG'},'Select image');
if(isequal(fn,0))
    return;
end
ffn = fullfile(pt,fn);
set(handles.tbLoc,'string',ffn);
pic = imread(ffn);
imshow(pic);
set(handles.btFile,'userdata',1);
set(handles.btDir,'userdata',0);
set(handles.axes1,'userdata',pic);



คำสั่ง uigerfile ใช้สำหรับสร้างหน้าไดอะล็อกเลือกไฟล์ โดยกำหนดให้เลือกไฟล์ได้ 2 ประเภทคือ PNG และ JPG และในกรณีที่ผู้ใช้งานไม่ได้เลือกไฟล์ แต่กด cancel ตัวแปร fn จะมีค่าเท่ากับ 0 ดังนั้นเราจึงต้องใช้ if เช็คก่อนว่า fn เท่ากับ 0 หรือไม่ ถ้าเท่ากับ 0 ก็ให้โปรแกรมจบการทำงานเลย ถ้าไม่เท่ากับ 0 ค่อยให้ทำงานต่อไป

คำสั่ง uigetfile จะคืนค่ามาให้เรา 2 ค่านั่นคือ ชื่อไฟล์ เก็บไว้ในตัวแปร fn และที่อยู่ไฟล์ เก็บไว้ในตัวแปร pt ดังนั้นเราจึงต้องใช้คำสั่ง fullfile เพื่อรวมชื่อไฟล์และที่อยู่ไฟล์เข้าด้วยกัน ทำให้กลายเป็นชื่อเต็มของไฟล์นั้นๆ ซึ่งโปรแกรมจะสามารถอ่านไฟล์นั้นได้เสมอไม่ว่าจะเก็บไว้ที่ใดในคอม ถ้าเรากำหนดแค่ชื่อไฟล์อย่างเดียว โปรแกรมจะอ่านได้เฉพาะไฟล์ที่อยู่ในโฟลเดอร์เดียวกันกับโปรแกรมเท่านั้น

จากนั้นก็ใช้คำสั่ง set ไปที่ tbLoc เพื่อแสดงไฟล์ที่ user เลือก จากนั้นก็อ่านรูปภาพและแสดงใน axes1

*เนื่องจากใน GUI ของเรามี axes เดียวจึงไม่จำเป็นต้องกำหนด axes ในการแสดงรูปภาพหรือกราฟ แต่ถ้าหากใน GUI ของเรามีหลาย axes เราจะต้องกำหนด axes ที่จะแสดงภาพก่อนเสมอ ดังนี้

imshow(pic,'Parent',handles.axes1)


*คำสั่ง imshow จะไม่เปลี่ยน ratio ของภาพเวลาแสดงผล ทำให้ภาพไม่ยืดหรือหด

ส่วนคำสั่ง set 'userdata' ใน 3 บรรทัดด้านล่างนั้นทำเพื่อเก็บข้อมูลเข้าไว้ใน property ที่ชื่อว่า userdata ของแต่ละ object อย่างเช่น

set(handles.btFile,'userdata',1);
set(handles.btDir,'userdata',0);

เราทำเอาไว้เพื่อบอกว่า user กดเลือกรูปภาพแบบไหน (เลือกไฟล์เดียว หรือเลือกทั้งโฟลเดอร์) เวลาเราจะแปลงรูปภาพจะได้ทำถูกวิธี

ส่วนคำสั่ง

set(handles.axes1,'userdata',pic);

เป็นการเก็บข้อมูลรูปภาพ pic เอาไว้ใน userdata ของ axes1 เราจะได้ไม่ต้องเสียเวลามาอ่านรูปภาพใหม่ทีหลัง

2.3 ปุ่ม btDir

*เขียนเอาไว้ในฟังก์ชัน btDir_Callback
DRs = uigetdir();
if(isequal(DRs,0))
    return;
end
imgSets = imageSet(DRs);
L = imgSets.Count;
if(L==0)
    return;
end
set(handles.tbLoc,'string',DRs);
pic = read(imgSets,1);
imshow(pic,'Parent',handles.axes1);
set(handles.btFile,'userdata',0);
set(handles.btDir,'userdata',1);
set(handles.axes1,'userdata',pic);


ในตัวอย่างนี้ผมใช้คำสั่ง imageSet เพื่ออ่านรูปภาพทั้งหมดที่อยู่ในโฟลเดอร์ ไม่ว่ารูปภาพนั้นจะมีนามสกุลอะไรก็ตาม และใช้คำสั่ง read(imgSets,1) เพื่อดึงภาพแรกออกมาแสดงผล



2.4 ปุ่ม btTogray

*เขียนเอาไว้ในฟังก์ชัน btTogray_Callback
pic = get(handles.axes1,'userdata');
gry = rgb2gray(pic);
imshow(gry);

จากตัวอย่างนี้เราจะเห็นว่าการวางแผนเขียนโปรแกรมนั้นสำคัญเลยทีเดียว มันจะทำให้เราเขียนโปรแกรมได้ง่ายขึ้นอีกเยอะ ถ้าก่อนหน้านี้เราไม่ใช้คำสั่ง set(handles.axes1,'userdata',pic) เราก็ไม่สามารถ get รูปภาพออกมาจาก axes1 ได้ (จริงๆ มัน get ออกมาจาก property อื่นได้ โดยที่เราไม่ต้อง set เพียงแต่ว่าข้อมูลรูปภาพนั้นอาจมีการผิดเพี้ยนไป ดังนั้นผมจึงเลือก set รูปภาพต้นฉบับเอาไว้ใน userdata ของ axes1 เพื่อความปลอดภัย)


2.5 ปุ่ม btSave


*เขียนเอาไว้ในฟังก์ชัน btSave_Callback
[fn,pt] = uiputfile({'*.png','PNG';'*.jpg','JPG'});
if(isequal(fn,0))
    return;
end

v1 = get(handles.btFile,'userdata');
if(v1>0)
    %User select sigle file
    pic = get(handles.axes1,'userdata');
    gry = rgb2gray(pic);
    ffn = fullfile(pt,fn);
    imwrite(gry,ffn);
else
    %User select folder
    DRs = get(handles.tbLoc,'string');
    imgSets = imageSet(DRs);
    L = imgSets.Count;
    for m=1:L
        pic = read(imgSets,m);
        gry = rgb2gray(pic);
        [filepath,name,ext] = fileparts(fn);
        sname = sprintf('%s%03d%s',name,m,ext);
        ffn = fullfile(pt,sname);
        imwrite(gry,ffn);
    end    
end

ฟังก์ชันนี้เป็นฟังก์ชันสุดท้ายที่เราจะเขียนในโปรแกรมนี้ครับ โดยจะทำหน้าที่แปลงภาพเป็นสีเทาแล้วบันทึกภาพ

คำสั่ง userputfile เป็นคำสั่งสร้างหน้าไดอะล็อกสำหรับบันทึกไฟล์ โดยในที่นี้เรากำหนดให้บันทึกได้แค่ 2 แบบคือนามสกุล *.png กับ *.jpg

แต่ก่อนบันทึกเราต้องเช็คก่อนว่า user เลือกภาพแบบไฟล์เดียวหรือเลือกทั้งโฟลเดอร์ โดยเช็คจาก userdata ของ btFile ถ้ามันมีค่าเป็น 1 แสดงว่า user เลือกไฟล์เดียว แต่ถ้ามันเป็น 0 แสดงว่า user เลือกทั้งโฟลเดอร์

จากนั้นก็ใช้ if-else เพื่อแยกการประมวลผลตามรูปแบบที่ user เลือก

ถ้าหาก user เลือกแค่ไฟล์เดียว กระบวนการก็ไม่ซับซ้อนอะไร แค่แปลงภาพสีเป็นภาพเทาด้วยคำสั่ง rgb2gray แล้วก็บันทึกภาพก็เสร็จ

แต่ถ้าหาก user เลือกทั้งโฟลเดอร์ เราต้องเพิ่มจำนวนตัวเลขลำดับภาพหลังชื่อด้วย ก่อนที่จะบันทึกภาพ จึงทำให้กระบวนการซับซ้อนขึ้นมาเล็กน้อย


เพียงเท่านี้ก็เสร็จเรียบร้อยครับ ลองรันโปรแกรมดูได้เลย



จบบทความ

ความเห็น

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

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

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

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