March 24, 2012

Face Detection บน iOS 5

Apple เปิด API ที่ช่วยในการตรวจจับใบหน้าให้มาตั้งแต่ iOS 5.0 ช่วยให้นักพัฒนาสามารถตรวจจับใบหน้าคนในภาพได้ง่ายๆ

คลาสพระเอกของ entry นี้ก็คือ CIDetector ซึ่งเป็นคลาสที่อยู่ใน CoreImage.framework โดยที่คลาสนี้มี method หลักอยู่ตัวเดียวก็คือ (NSArray *)featuresInImage:(CIImage *)image ที่จะคอยรับ CIImage เข้าไปและ return Array ของ CIFaceFeature กลับออกมา ซึ่งขนาดของ Array ก็จะขึ้นอยู่กับจำนวนของใบหน้าคนที่มีการตรวจพบนั่นเอง ซึ่ง CIFaceFeature ก็จะบรรจุตำแหน่งของ ตาซ้าย ตาขวา และปากไว้

เริ่มจากสมมติว่ามี UIImageView อยู่ 1 รูป


__weak IBOutlet UIImageView *_personImageView;


จากนั้นสร้าง object ของ CIDetector ขึ้นมา พร้อมกำหนด option ให้มัน ซึ่ง option จะเป็นการกำหนดความแม่นยำในการหาว่าจะมากหรือน้อย โดยถ้ากำหนดเป็นความแม่นยำมากก็จะใช้เวลาในการตรวจจับใบหน้าคนนานมากขึ้น


NSDictionary *options = [NSDictionary dictionaryWithObject: CIDetectorAccuracyHigh
                                                    forKey: CIDetectorAccuracy];

CIDetector *detector = [CIDetector detectorOfType: CIDetectorTypeFace
                                          context: nil
                                          options: options];


จากโค้ดด้านบนจะสังเกตเห็นตอนสร้าง detector นั้นมีการกำหนด type ให้ด้วย ซึ่งตอนนี้ใน API สามารถกำหนดค่านี้ได้เพียงค่าเดียว (อนาคตอาจกำหนดให้หาส่วนอื่นๆ ได้เพิ่ม?)

เมื่อเราได้ตัว detector มาแล้ว ก็ต้องเตรียม CIImage สำหรับจะส่งเข้าไปให้มันช่วยหาหน้าคนให้หน่อย เนื่องจากในโลกของ CoreImage นั้นมันไม่รู้จัก UIImage ที่เรามักใช้กันนั่นเอง ดังนี้


CIImage *image = [[CIImage allocinitWithImage_personImageView.image];
NSArray *features = [detector featuresInImage: image];


หลังจากได้ features มาแล้ว ก็ลองนำ CIFaceFeature แต่ละตัวใน array มาใช้งาน หรือลองแปะลงบนรูปเพื่อเช็คความถูกต้อง


for(CIFaceFeature *feature in features){
    if(feature.hasLeftEyePosition){
        [self markAtPoint: feature.leftEyePosition];
    }
    
    if(feature.hasRightEyePosition){
        [self markAtPoint: feature.rightEyePosition];
    }

    if(feature.hasMouthPosition){
        [self markAtPoint: feature.mouthPosition];
    }
}


ตอนที่จะ mark ตำแหน่งลงบนรูป ต้องอย่าลืมว่าระบบ coordinate ในโลกของ CoreImage ไม่เหมือนกันกับในโลกของ UIKit ที่คุ้นเคยกันนะ ;)



เท่าที่ทดสอบมาถือว่าใช้งานได้ดีพอสมควร ส่วนความฉลาดในการหาก็น่าจะเป็นแบบเดียวกับที่ใช้ใน iPhone 4S และโปรแกรม iPhoto บน Mac

ที่มาของภาพ: Lenna

การสร้าง Source Path เอง

กรณีที่เรา import ไฟล์ เช่น resource หรือภาพต่างๆ เข้ามาในโปรเจ็คแบบ reference เรามักเจอปัญหาที่ว่าหากวันหนึ่ง เราจำเป็นต้องย้ายที่อยู่ของไฟล์เหล่านั้นไป จนเป็นเหตุให้รายชื่อไฟล์ต่างๆ ใน tree ด้านซ้ายของ Xcode เป็นสีแดง เนื่องจาก Xcode มองหาไฟล์นั้นไม่เจอ จนนักพัฒนาจำเป็นต้องอ้างอิงไฟล์เหล่านั้นใหม่ หลายคนที่ผมเจอมักลากไฟล์เข้ามาในโปรเจ็คใหม่อีกรอบหนึ่ง ซึ่งนั่นทำให้เสียเวลาเป็นอย่างมาก หากในโปรเจ็คมีไฟล์จำนวนมากที่ต้องแก้ไข และที่สำคัญไฟล์ project.pbxproj จะถูกแก้ไข จนอาจทำให้เกิด conflict กับคนอื่นโดยไม่จำเป็น

ในกรณีนี้นี้ เราสามารถแก้ไขตำแหน่งของไฟล์ได้โดยที่ไฟล์ project.pbxproj จะไม่ถูกแก้ไขได้ โดยการให้ไฟล์เหล่านั้น อ้างอิงกับ source path (source path คืออะไร ให้อ่านต่อไปในบทความนี้) ของเราเอง ทำให้ทุกครั้งที่มีการเปลี่ยนตำแหน่งไฟล์ใหม่ เราก็แค่มาแก้ source path ใหม่เท่านั้นเอง โดยจะเป็นการแก้ที่ setting ของตัว Xcode เอง ไม่ได้เกี่ยวกับโปรเจ็คของเราแต่อย่างใดทำให้ไม่มี change เกิดขึ้นกับไฟล์ในโปรเจ็ค

เราสามารถกำหนดการอ้างอิงตำแหน่ง (path) ของไฟล์ โดยสามารถกำหนดเป็น absolute path หรือจะให้อิงกับ path อื่นๆ ก็ได้ และหากดู property ของแต่ละไฟล์ในโปรเจ็ค จะพบว่าแต่ละไฟล์มี Location เป็น Relative to Group ให้อยู่แล้วเนื่องจากค่านี้เป็นค่า Default ของ Xcode



ส่วนค่าอื่นๆ ที่เราสามารถกำหนดให้ได้ ก็จะมีดังนี้



สำหรับความหมายของแต่ละค่านั้นก็พอจะเดาได้คร่าวๆ จากชื่อของมันเอง ส่วนรายละเอียดก็ตามไปอ่านต่อได้ที่ How Files are referenced

ถ้าหากเราต้องการจะให้ไฟล์ของเรา relative กับ path ที่เราต้องการล่ะ จะทำได้ไหม? คำตอบก็คือ ได้ แต่เราต้องไปสร้างสิ่งที่เรียกว่า Source Path ก่อน โดยการเข้าไปใน preference ของ Xcode เลือกแท็บ Locations และ Source Trees จากนั้นให้กดเครื่องหมาย บวก ที่ด้านล่างซ้ายและกำหนดค่าของ Source Path ดังรูปด้านล่าง



โดยที่

  • Setting Name จะเป็นชื่อสำหรับให้ไฟล์ใช้อ้างอิง
  • Display Name จะเป็นชื่อที่จะแสดงใน property panel ของไฟล์
  • Path คือ path directory ของ source path ที่เราจะให้ไฟล์ไป relative ด้วย กรณีนี้เราจะกำหนดค่าให้ Path เป็นที่อยู่ของ resource ที่นำเข้ามาในโปรเจ็คแบบ reference เช่นไฟล์ภาพต่างๆ 

หากตั้งค่าเสร็จแล้ว กลับมาดูที่ property panel ของไฟล์จะพบว่ามี source path ใหม่ที่เราพึ่งสร้างไปปรากฎขึ้นมาแล้วดังรูป





ให้เราเลือกให้ไฟล์ resource มา relative กับ source path ที่เราพึ่งสร้างขึ้นมาใหม่ เท่านี้หากคราวหน้าเรามีการย้ายที่อยู่ของไฟล์ resource ต่างๆ ก็แค่เข้ามาแก้ไข path ของ source path ตัวนี้เท่านั้นเอง โดยที่ไม่กระทบกับไฟล์ project.pbxproj เลยแม้แต่น้อย

อ่านต่อ: Files in Projects

March 22, 2012

เปลี่ยน hex string ให้กลายเป็น NSData จริง

สมมติว่าเรามี NSString ที่เป็นเลขฐาน 16 อยู่แบบนี้

NSString *hexString = @"8A9B1133";

เราจะเห็นว่าถ้าเราแปลง string ชุดนี้ให้เป็น NSData มันจะมีความยาว 4 byte
ส่วนวิธีในการแปลง เราจะใช้วิธีอ่านอักขระใน string เข้ามาเป็นคู่ๆ จากนั้นใช้ฟังก์ชั่น strtol ช่วยในการแปลงอักขระให้กลายเป็นตัว int ก่อน เสร็จแล้วก็ทยอย append ให้กับตัวแปร NSMutableData ตามลำดับ


// ถ้า hex string ที่จะแปลงไม่ครบคู่ก็จะไม่ทำอะไร
if (hexString.length % 2) {
    return nil;
}

NSMutableData *result = [[NSMutableData alloc] init];
unsigned char byte;
char hexChars[3] = {0};
for (int i = 0; i < (hexString.length 2); i++) {
    hexChars[0] = [hexString characterAtIndex:i * 2];
    hexChars[1] = [hexString characterAtIndex:i * 1];
    byte = strtol(hexChars, NULL, 16);
    [result appendBytes: &byte length1]; 
}

NSLog(@"result = %@, length = %d", result, result.length);


ทดลองรันโปรแกรม จะพบผลลัพธ์ออกมาดังนี้


result = <8a9b1133>, length = 4


เท่านี้เราก็มี NSData พร้อมใช้งานแล้ว

March 20, 2012

เปลี่ยน Warning ให้กลายเป็น Error ให้หมด

สิ่งหนึ่งที่ควรทำก็คือ อย่าปล่อยให้โปรแกรมที่เรากำลังพัฒนาอยู่มี warning ค้างคาอยู่ให้ Xcode มันฟ้องเราเวลาเรา build โปรแกรม ก็จริงอยู่ที่แม้จะมี warning ค้างคาเกะกะตา แต่โปรแกรมก็มัก build และรันขึ้นมาได้ด้วยดี (?) ทำให้นักพัฒนาหลายๆ คนมักไม่สนใจ และละเลยปล่อยให้ warning เหล่านั้นค้างอยู่ในโปรเจ็คของเรา

แต่สำหรับนักพัฒนาที่มีวินัย ชอบพัฒนาโปรแกรมอย่างยั่งยืน ไม่ฉาบฉวย ก็คงที่จะอดใจปล่อยให้ warning เหล่านี้มีชีวิตอยู่ในโปรเจ็คต่อไปไม่ได้ และต้องไล่กำจัดให้สิ้นซากให้จงได้

สำหรับการดัดนิสัยที่ไร้วินัยในการเขียนโปรแกรมก็คือไม่อนุญาตให้ build & run โปรเจ็คได้เด็ดขาดหากยังมี warning เหล่านี้อยู่ วิธีทำก็คือให้ไปตั้งค่าได้ใน Build & Setting และ search ด้วยคำว่า treat จากนั้นให้มองหาหัวข้อ Treat Warnings as Erros และกำหนดค่าเป็น YES



เท่านี้จาก warning สีเหลืองๆ ใน Xcode ก็จะกลายเป็น Error สีแดงๆ แล้วล่ะ

March 06, 2012

เรียกโปรแกรมอื่นจากโปรแกรมของเรา ด้วย Custom URL Schemes

เดิมทีนักพัฒนาโปรแกรมบน iOS จะสามารถเขียนโปรแกรมให้ไปเรียกโปรแกรมตัวอื่นๆ ในเครื่องให้เปิดขึ้นมาทำงานได้อยู่แล้ว ผ่าน URL Schemes ต่างๆ โดยที่นักพัฒนาจะรู้จักกันดีอยู่แล้วก็ได้แก่ http, mailto, tel และ sms และพวก http-based ก็ได้แก่ YouTube และ Map

Apple ได้เปิดโอกาสให้เราสามารถกำหนด URL Schemes ให้กับ App ของเราได้เองเช่นกัน โดยมีข้อแม้ว่า  URL Schemes ที่เราจะสร้างขึ้นมาใช้เองนั้นห้ามซ้ำกับของที่ Apple มีอยู่แล้ว แต่ถ้าซ้ำ iOS จะเรียก App ของ Apple ขึ้นมาแทนที่จะเป็นของเรา และในกรณีที่ URL Schemes ของเราไปซ้ำกับ App ของคนอื่นๆ ที่ไม่ใช่ของ Apple เวลาเราเรียกก็จะเงียบ ไม่มีอะไรเกิดขึ้น

วิธีสร้าง URL Schemes ให้กับ App ของเราเอง
ทำได้โดยไปกำหนดค่าในไฟล์ Info.plist โดยสร้าง URL Types จากนั้นกำหนด URL identifier กับ URL Schemes ให้มัน ดังรูปด้านล่าง



โดยที่ URL identifier นั้นควรใช้ reverse domain style ตามธรรมเนียมปฏิบัติ ส่วน URL Schemes นั้น จะเอาไว้ให้ App อื่นๆ อ้างถึงเวลาจะเรียกให้ App ของเราขึ้นมาทำงาน (ในตัวอย่างจะกำหนด URL Schemes เป็น todolist

เมื่อสร้างเสร็จแล้ว ให้ลองเรียก App ของเราจาก App อื่นดู โดยใช้วิธีการเช่นเดียวกับการเรียกใช้ URL Schemes ต่างๆ ของ Apple ได้เลย ดังนี้

NSURL *url = [NSURL URLWithString: @"todolist://"];
[[UIApplication sharedApplication] openURL: url];

หลังจาก App ของเราถูกเรียกขึ้นมาแล้ว จะมี method บางตัวที่อยู่ใน AppDelegate ถูกเรียกขึ้นมาทำงาน ได้แก่


นอกจากเราจะเรียกให้ app ตัวอื่นๆ ทำงานได้แล้ว เรายังสามารถส่ง paramter บางอย่างติดไปกับ url ที่เรียกได้อีกด้วย เช่นเดียวกับ URL Address ที่ใช้กับเว็บไซต์ที่คุ้นเคยกัน แต่ต้องระวังนิดนึงเกี่ยวกับ URL ที่ส่งเข้ามาใน app ของเรา หากสนใจว่าจะจัดการยังไงกับมันดีอ่านต่อที่ ต่อได้ที่นี่

นอกจาก App ของเราที่จะมี URL Schemes แล้ว App คนอื่นๆ ก็มีด้วยเหมือนกันครับ ลอง Google ดูพบว่ามีบางที่ได้รวบรวมไว้ให้เราได้อ่านกันเล่นๆ ด้วย เช่น ที่นี่ ที่รวม App ของ iOS มาแทบทุกตัวเลย ทั้ง Safari, Maps, Phone, SMS, Mail, YouTube, iTunes, App Store และ iBooks (อ่านต่อเกี่ยวกับ format การคุยกับ App เหล่านี้ที่ Apple URL Scheme Reference) นอกจากนั้นก็ยังมี Third Party Application ตัวอื่นๆ อีก แต่ที่เห็นจะเยอะหน่อยก็คือ Facebook นี่แหละ

ส่วนรายละเอียดการสร้าง และวงจรชีวิตของ App ที่เกิดจากการ launch จาก URL โดยละเอียด อ่านได้ที่ Reference ของ Apple ได้เลย


อ่านต่อ

:)

March 03, 2012

อ่านเลข Version และเลข Build

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


NSString *version = [[[NSBundle mainBundle] infoDictionary
                     objectForKey:@"CFBundleShortVersionString"];
    
NSString *build = [[[NSBundle mainBundle] infoDictionary
                   objectForKey:@"CFBundleVersion"];


:)