Từ 1 tới 3 trên tổng số 3 kết quả

Đề tài: Dlib GUI: một thư viện giao diện người dùng

  1. #1
    Ngày gia nhập
    01 2008
    Nơi ở
    Rất đông người
    Bài viết
    726

    Mặc định Dlib GUI: một thư viện giao diện người dùng

    Dlib của Davis E King, nghiên cứu viên bộ quốc phòng Mỹ, là một thư viện phần mềm nguồn mở nổi tiếng, quen thuộc với những người làm trong mảng hệ thống thông tin, dữ liệu lớn, khoa học dữ liệu, học máy, nhận dạng, v.v. Trong Wikipedia, nó đứng trong top 20 thư viện nguồn mở về trí tuệ nhân tạo.

    Nhưng Dlib GUI, thành phần giao diện người dùng đồ hoạ (GUI) của Dlib, thì không nổi tiếng. Thậm chí trong top 50 thư viện nguồn mở về GUI trên Wikipedia nó cũng không có tên. Thế vì sao mình lại thích nó?

    Nói về lập trình GUI đa nền tảng, các doanh nghiệp và các lập trình viên "công nghiệp" ưa dùng Qt, WxWidgets hay Gtk. Vì vẽ đẹp và tính năng phong phú. Còn các nhà khoa học, giảng viên, sinh viên ưa dùng FLTK, FOX hay Ultimate++ hơn. Vì nhỏ gọn, dễ học, dễ dùng hơn.

    Nhỏ gọn không có nghĩa là yếu kém. Tuỳ theo mục đích mà dùng. Chẳng hạn, Qt, FOX và FLTK là 3 thư viện GUI được khuyên dùng khi viết ứng dụng khai thác cơ sở dữ liệu sinh học phân tử của bộ y tế Mỹ.

    Nhỏ gọn, dễ học, dễ dùng có lẽ là những tiêu chí vừa đủ nếu đề tài nghiên cứu, giảng dạy, học tập thuộc về một lĩnh vực khoa học kỹ thuật nào đó. Nhưng ở đây, đề tài là giao diện người dùng, thư viện GUI chính là đối tượng để tìm hiểu, sử dụng, hack (cải biên) và phát triển. Cho nên 3 tiêu chí ấy là chưa đủ. Mà còn phải có code đẹp nữa.

    Code đẹp là code có mẫu thiết kế kinh điển, nhưng thực hiện bằng C++ hiện đại. Nghĩa là dựa trên những kinh nghiệm tinh hoa nhất rút tỉa từ quá khứ, nhưng không mang nặng di sản của quá khứ.

    Nói cụ thể và đơn giản, Dlib GUI thực hiện hành tung bằng interface. Trong một GUI, các phần tử giao tiếp trực quan (widget, còn gọi là control hoặc view ở một số thư viện) có 2 khía cạnh là diện mạo (look) và hành tung (feel). Hành tung là phản ứng với thao tác của người dùng tuỳ theo tình trạng của hệ thống; đó là phần tinh vi, viết dài và khó. Diện mạo đơn giản chỉ là biểu hiện thị giác để người dùng nhìn thấy, cảm nhận hành tung một cách trực quan; đó là phần viết ngắn và dễ. Cả hai khía cạnh này đều cần tuỳ biến, nâng cấp theo sở thích của người lập trình và người sử dụng, nhưng không thường xuyên như nhau (diện mạo tuỳ biến, nâng cấp thường xuyên hơn) và phát triển khá độc lập với nhau. Tác giả Dlib dường như hiểu rất rõ điều này. Trong Dlib GUI, diện mạo là cụ thể, hành tung được trừu tượng hoá hoàn toàn.

    Dlib GUI nhỏ gọn. Ước tính, tách riêng mã nguồn GUI, mã đích tương ứng chỉ có khoảng 170 KB. (Mã đích của Dlib có 2.8 MB. Mã nguồn Dlib có 20 MB, trong đó GUI chỉ chiếm 1.2 MB, tức 6% khối lượng.) Để so sánh, thư viện Qt 4 đã biên dịch ra mã đích có 12 MB. Hơn nữa, việc tách riêng mã nguồn GUI xem ra khá dễ dàng. Mã nguồn GUI lệ thuộc vào một số ít cấu phần khác của Dlib; trong đó, do Dlib được viết bằng C++11, một số cấu phần, như hỗ trợ đa luồng (multi-threading), có thể dễ dàng thay thế bằng bản tương đương sẵn có trong thư viện chuẩn C++ phiên bản mới hơn (14, 17, 20, 23). Thí dụ, dlib::rmutex có thể thay thế bằng std::recursive_mutex, vì chúng có chung nguồn gốc là thư viện Boost.

    Dlib GUI có kiến trúc đơn giản, gồm 2 cấu phần corewidgets. Phần widgets (1 MB) hoàn toàn độc lập với nền tảng, chỉ dựa hoàn toàn trên phần core, cung cấp các widget cho người dùng. Phần core (200 KB) gồm 1 interface (trừu tượng) độc lập với nền tảng và 1 implementation (cụ thể) lệ thuộc nền tảng cho một kiểu cơ bản tên là base window. Kiểu này dùng làm gốc để dẫn xuất mọi kiểu window khác trong phần widgets. Trong core, mã nguồn interface chiếm 30 KB, implementation cho X và Win32 mỗi cái khoảng 85 KB. Core cũng thực thi cơ chế xử lý sự kiện, gồm 2 đối tượng là chu trình sự kiện (event loop) và thủ tục sự kiện (event procedure). Cả 2 đều cùng thuộc về một luồng, gọi là luồng xử lý sự kiện (event handler thread) được tạo và kích hoạt như một phần của quá trình tạo dựng window.

    Tuy chương trình có nhiều window, nhưng chỉ có 1 luồng xử lý sự kiện duy nhất (vậy, chỉ 1 chu trình sự kiện và 1 thủ tục sự kiện duy nhất) mà thôi. Nó được tạo ra khi tạo dựng window đầu tiên. Điều này được đảm bảo kể cả nếu các window được tạo bởi các luồng chạy song song, nghĩa là không thể tiên đoán thứ tự tạo dựng của chúng. Nếu giả sử người dùng tạo Login window trong một luồng con và Main View window trong luồng chính, tức luồng gọi hàm main() thì core vẫn chỉ tạo ra 1 luồng xử lý sự kiện duy nhất và chính luồng này sẽ đứng tên đăng ký cả 2 window với hệ điều hành. Trong con mắt hệ điều hành, chủ nhân của mọi window chính là luồng này chứ không phải là 2 luồng kia. Nó chỉ chấm dứt sau khi đã huỷ đăng ký mọi window với hệ điều hành, như một phần của quá trình tiêu huỷ window.

    Cũng như ở mọi thư viện khác, người dùng không cần nắm vững mọi chi tiết thực thi phần core, chỉ cần biết và tuân thủ "giao kèo" đã "ký kết" với nó thông qua interface của nó: code xử lý sự kiện, tức các hàm kiểu như on_key_press() hay on_mouse_move(), do được gọi trong luồng xử lý sự kiện, đều phải nhanh gọn và không được trao đổi dữ liệu với các luồng khác bằng những cơ chế "tự biên tự diễn" (chẳng hạn, biến toàn cục), để đảm bảo GUI được "trơn, mượt" (phản ứng nhanh, bảo toàn dữ liệu).

    Trước khi viết hướng dẫn build, mình thực nghiệm. Để chắc ăn hơn, mình không chỉ thử build chương trình demo về GUI mà còn build vài thí dụ khác (về trí tuệ nhân tạo). Đính kèm là đầu vào (1 ảnh) và đầu ra (4 ảnh) của thí dụ dnn_face_recognition_ex.cpp.

    - - - Nội dung đã được cập nhật ngày 24-07-2021 lúc 06:21 PM - - -

    Sau đây là vài hướng dẫn để có thể build Dlib và các thí dụ đi kèm với mã nguồn.

    Trước hết, tóm tắt lý thuyết. Trên website (link đã dẫn) tác giả đưa ra 3 cách build, theo thứ tự khuyến cáo.

    a) Dùng Cmake, build các makefile cho bộ biên dịch (BCC, GCC,...) và/hoặc các project cho các IDE (MSVC, Code::Blocks,...). Nhờ các file này, có thể build được thư viện (.LIB) lẫn mọi thí dụ (.EXE) trong 1 lần chạy.

    b) Biên dịch tệp thí dụ (dnn_face_recognition_ex.cpp chẳng hạn) cùng với dlib\all\source.cpp. Trong đó, tệp source.cpp chứa một loạt #include mọi tệp nguồn của Dlib.

    c) Biên dịch tệp thí dụ và liên kết với thư viện Dlib đã build sẵn (ở dạng .LIB hay .DLL).

    Thực tế, thử cách b) thì biên dịch được và chạy được. Nhưng thông số -O3 làm cho chương trình không thể debug. Bỏ -O3 đi thì lập tức gặp lỗi build: linker bị quẩn sau khi tuôn ra một loạt thông báo lỗi ngớ ngẩn. Không build được một thí dụ nào. Mình từ bỏ cách này.

    Một thực tế khác bất ngờ, là cả cách a) cũng không hoàn toàn thành công. Mọi thí dụ đều build được. Nhưng có cái chạy được, cái thì không. Chẳng hạn, bayes_net_gui_ex.cpp chạy được, nhưng dnn_face_recognition_ex.cpp thì không: nó gây lỗi xâm phạm bộ nhớ (segfault).

    Cách build của mình là kết hợp a) với c): dùng Cmake build (tự động) thư viện, rồi liên kết (thủ công) với thí dụ nhờ 1 dòng lệnh hoặc 1 project trong IDE. Để xác định các thông số cần thiết cho việc liên kết, trước khi build thư viện, mình đã thử liên kết với thư viện build sẵn. Nghĩa là thực nghiệm cách c) trước, cách a) + c) sau.

    - - - Nội dung đã được cập nhật ngày 24-07-2021 lúc 09:36 PM - - -

    1. Biên dịch thí dụ với Dlib đã build sẵn bằng GCC (mingw-w64)

    -- Tải gói mã nguồn xuống, giải nén vào 1 thư mục, chẳng hạn, C:\C-lib\dlib-master. (Chú ý, đây là phiên bản đang phát triển, chưa phát hành. Phiên bản mới nhất đã phát hành là v19.22 và tên thư mục tương ứng có thể là C:\C-lib\dlib-19.22.)

    -- Tạo một thư mục, chẳng hạn D:\testDlib.

    -- Trong Code::Blocks, mở một workspace sẵn có hoặc tạo một workspace mới.

    -- Tạo một project tại thư mục D:\testDlib nói trên và mở hộp thoại Build properties của nó.

    -- Ở tab Compiler Settings, tab con Compiler Flags, chọn cho g++ ngôn ngữ ISO C++ 11.

    -- Ở tab Linker Settings, khung Other linker options, viết thêm -ldlib -lgdi32 -lcomctl32 -luser32 -lwinmm -lws2_32 -limm32 -llapack -lblas -lcblas -lopenblas -llibgif.

    -- Đóng hộp thoại.

    -- Chọn target là Release.

    -- Bỏ vào thư mục D:\testDlib tệp thí dụ cần biên dịch, chẳng hạn dnn_face_recognition_ex.cpp từ thư mục con examples trong gói mã nguồn. Thêm tệp này vào project, kích hoạt project và build.


    NOTES

    (1) Code::Blocks đã được cấu hình dùng bộ biên dịch GCC của môi trường MSYS2 -MINGW64.

    (2) Trước khi build nên kiểm tra môi trường, khẳng định các gói dlib, lapack, openblasgiflib đã được cài đặt. (Chú ý, tên gói không nhất thiết trùng với tên thư viện. Chẳng hạn, gói giflib chứa thư viện libgif). Kiểm tra và cài đặt bằng pacman.

    (3) Đối với những thí dụ đơn giản, Other linker options có thể không cần kể ra hết các thư viện như trên. Chẳng hạn, để build gui_api_ex.cpp chỉ cần -ldlib -lgdi32.

    (4) Nhiều thí dụ không thể build được khi chọn target là Debug.

    - - - Nội dung đã được cập nhật ngày 24-07-2021 lúc 10:06 PM - - -

    2. Build Dlib riêng rồi liên kết với thí dụ bằng GCC (mingw-w64)

    -- Chạy MINGW64 shell, chuyển về nơi chứa mã nguồn thí dụ, tạo một thư mục trống đặt tên là build và chuyển về đó:
    Code:
    cd /C/C-lib/dlib-master/examples
    mkdir build
    cd build
    -- Phát lệnh cmake:
    Code:
    cmake -G "MinGW Makefiles" ..
    -- Trong thư mục hiện thời (build) xuất hiện một số file và thư mục con mới. Kiểm tra, khẳng định đã xuất hiện thư mục con dlib_build và 2 tệp makefile lần lượt trong thư mục hiện thời và trong thư mục con dlib_build.

    -- Phát lệnh cmake:
    Code:
    cmake --build dlib_build --config Release
    -- Kiểm tra, khẳng định trong thư mục con dlib_build đã xuất hiện tệp mới tên là libdlib.a. Đó chính là thư viện Dlib đã được biên dịch thành mã đích dùng cho liên kết tĩnh, giống như .LIB đối với các môi trường biên dịch Microsoft hay Borland.

    -- Vẫn dùng lại project như phần 1 trên, bổ sung thông số Build properties như sau. Ở tab Search Directories, tab con Compiler, thêm C:\C-lib\dlib-master; và ở tab con Linker, thêm C:\C-lib\dlib-master\examples\build\dlib-build. Chọn target giống như phần 1.


    NOTES

    (1) Để loại trừ rủi ro đụng độ giữa Dlib đã cài đặt sẵn với Dlib mới build, nên huỷ cài đặt Dlib cũ chậm nhất là trước khi bắt đầu biên dịch thí dụ. Huỷ gói bằng pacman.

    (2) Trước khi build Dlib, nếu muốn, có thể huỷ cài đặt 2 gói lapackopenblas (việc đó sẽ vô hiệu hoá 2 gói cblasblas). Và nếu làm thế, có thể bỏ bớt các thông số Other linker options -llapack -lopenblas -lblas -lcblas khi build thí dụ. Lý do là khi build Dlib, cmake truy vấn môi trường xem có 4 thư viện này hay không, nếu có, nó sẽ tự động chọn phương án build dùng 4 thư viện này. (Cmake sẽ không tự động chọn nếu người dùng đã chỉ định một cách tường minh dùng hay không dùng thư viện ngoài.)

    (3) Cmake không phải là công cụ "chuẩn" trong toolchain đi cùng với MinGW64. Ngoài bản build sẵn có trên website của Cmake, MinGW64 cũng có bản build sẵn, có thể cài đặt bằng pacman.

    (4) Dlib còn cho phép cấu hình build thư viện liên kết động (.DLL) và cài đặt thư viện (tĩnh hay động) mới build vào các môi trường phát triển (MSYS2, MINGW64,...). Ở đây, mình không làm thế, do mục đích là build một "bản thu nhỏ" của thư viện chỉ chứa các cấu phần thông dụng, có thể cần sửa đổi mã nguồn. Vả lại tác giả không khuyến khích build thư viện liên kết động [CmakeLists.txt, thư mục con dlib]. Dlib không cho phép build thư viện động bằng MS Visual Studio.

    (5) Tệp CMakeLists.txt trong thư mục con examples chứa hướng dẫn cấu hình build ứng dụng.
    Attached Thumbnails Attached Thumbnails bald_guy_d.jpg   bald_guy_c.jpg   bald_guy_b.jpg   bald_guy_a.jpg   bald_guys.jpg  

    Đã được chỉnh sửa lần cuối bởi Ada : 27-07-2021 lúc 03:44 AM. Lý do: Loại bỏ thí dụ (không chính xác)
    -...- -.- .. .-.. .-.. - .... . -... . .- ... - .-.-.

  2. #2
    Ngày gia nhập
    01 2008
    Nơi ở
    Rất đông người
    Bài viết
    726

    NOTES

    (1) Sau khi build Dlib (phần 2), thay vì dùng Code::Blocks (phần 1), có thể build 1 thí dụ, chẳng hạn bayes_net_gui_ex.cpp, bằng dòng lệnh
    Code:
    cmake --build . --target bayes_net_gui_ex --config Release
    (2) Tương tự, có thể build tất cả thí dụ bằng dòng lệnh
    Code:
    cmake --build . --config Release
    Song, như đã nói trên, thí dụ build bằng những dòng lệnh như thế có thể bị lỗi, không chạy được.

    - - - Nội dung đã được cập nhật ngày 26-07-2021 lúc 11:59 AM - - -

    Đính kèm là ảnh chụp màn hình của thí dụ bayes_net_gui_ex.cpp.
    Attached Thumbnails Attached Thumbnails bayes_net_gui_ex_1.png  
    -...- -.- .. .-.. .-.. - .... . -... . .- ... - .-.-.

  3. #3
    Ngày gia nhập
    01 2008
    Nơi ở
    Rất đông người
    Bài viết
    726

    Ảnh màn hình cho thấy một nhược điểm là chất lượng đồ hoạ. Dlib GUI thực thi phần core cho nền tảng Win32 dựa trên thư viện Microsoft GDI đã lỗi thời. Một hướng phát triển xác đáng, do đó, là hack phần core, thay thế GDI bằng Direct2D (và DirectWrite).

    Direct2D đòi hỏi Windows 7+. Các hướng tiềm năng khác rộng lớn hơn (hỗ trợ nhiều hệ điều hành hơn) nhưng khó hơn (cần thử nghiệm đồng thời nhiều hệ điều hành) có thể là P0267, Skia, SDLAGGe.

    - - - Nội dung đã được cập nhật ngày 04-08-2021 lúc 10:46 AM - - -

    Đây là mã nguồn thực thi chuẩn của đặc tả P0267 nói trên.
    Đã được chỉnh sửa lần cuối bởi Ada : 04-08-2021 lúc 04:19 PM.
    -...- -.- .. .-.. .-.. - .... . -... . .- ... - .-.-.

Quyền hạn của bạn

  • Bạn không thể gửi đề tài mới
  • Bạn không thể gửi bài trả lời
  • Bạn không thể gửi các đính kèm
  • Bạn không thể chỉnh sửa bài viết của bạn