Imam Ferianto Blogs

sekedar catatan kecil saja

Pada tulisan kali ini ide dasarnya adalah mengambil gambar kemudian ditampilkan pada sebuah panel. Gambar ini nantinya akan diproses kemudian menjadi source pada videochat windows entah nanti mau pake protokol rtmp atau websocket dengan encoding vp8 atau vp9.

Berikut Contoh code untuk menampilkan opencv image pada wxImage

#include <opencv2/opencv.hpp>
#include <iostream>
#include <wx/wxprec.h>
#include <wx/wx.h>
#include <wx/textctrl.h>
#include <wx/bookctrl.h>
#include <wx/toplevel.h>
#include <wx/dcclient.h>
#include <iostream>
#include <wx/dcmemory.h>
#include <wx/log.h>
#include <wx/graphics.h>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>


#ifdef __WXMAC__
#include <ApplicationServices/ApplicationServices.h>
ProcessSerialNumber PSN;
//GetCurrentProcess(&PSN);
//TransformProcessType(&PSN,kProcessTransformToForegroundApplication);
#endif

//#define FormUtama_STYLE wxCAPTION | wxRESIZE_BORDER | wxSYSTEM_MENU | wxRESIZE_BORDER | wxMINIMIZE_BOX | wxMAXIMIZE_BOX | wxCLOSE_BOX
//#define FormUtama_STYLE wxCAPTION | wxRESIZE_BORDER | wxSYSTEM_MENU | wxRESIZE_BORDER | wxMINIMIZE_BOX | wxMAXIMIZE_BOX | wxCLOSE_BOX

// Declare some IDs. These are arbitrary.
const int BOOKCTRL = 100;
const int BUTTON1 = 101;
const int BUTTON2 = 102;
const int LISTBOX1 = 103;
const int TEXTBOX1 = 104;
const int FILE_QUIT = wxID_EXIT;
const int HELP_ABOUT = wxID_ABOUT;
const int ID_WXPANEL3 = 1003;
const int ID_WXPANEL2 = 1002;
const int ID_WXPANEL1 = 1001;

const wxEventTypeTag<wxThreadEvent> wxEVT_THREAD_UPDATE(wxNewEventType());

using namespace cv;
using namespace std;


class MyFrame : public wxFrame , public wxThreadHelper {
public:
 wxPanel* WxPanel1;
 wxBoxSizer *sizer;
 wxStaticBitmap *image;
 MyFrame(const wxString& title,const wxSize& size); //,long style);
 void OnButton1( wxCommandEvent& event );
 void OnButton2( wxCommandEvent& event );
 void OnQuit(wxCommandEvent& event);
 void OnAbout(wxCommandEvent& event);
 void OnThreadUpdate(wxThreadEvent& evt);
 void DoStartALongTask();
protected:
 wxCriticalSection m_cs_image;
 wxImage       m_video_image;
 cv::Mat captured_image;

 virtual wxThread::ExitCode  Entry();
 void Render(wxDC &dc);
 void OnIdle(wxIdleEvent& event);
 void OnClose(wxCloseEvent& event);
 void OnMenuExit(wxCommandEvent& event);
 //void OnCameraControls(wxCommandEvent& event);
 void OnUpdateUI(wxUpdateUIEvent& event);

 DECLARE_EVENT_TABLE();

};

// Attach the event handlers. Put this after MyFrame declaration.
BEGIN_EVENT_TABLE(MyFrame, wxFrame)
 EVT_BUTTON(BUTTON1, MyFrame::OnButton1)
 EVT_BUTTON(BUTTON2, MyFrame::OnButton2)
 EVT_MENU(FILE_QUIT, MyFrame::OnQuit)
 EVT_MENU(HELP_ABOUT, MyFrame::OnAbout)
 EVT_CLOSE(MyFrame::OnClose)
END_EVENT_TABLE();


MyFrame::MyFrame(const wxString& title,const wxSize& size): 
 wxFrame(NULL, wxID_ANY, title, wxDefaultPosition, size) 
{

  //SetIcon(wxICON(sample));
  SetBackgroundColour(wxColour(77,77,77));

  //top menubar
  wxMenu *fileMenu = new wxMenu;
  wxMenu *helpMenu = new wxMenu;
  helpMenu->Append(HELP_ABOUT, _T("&About...\tF1"), _T("Show about dialog"));
  fileMenu->Append(FILE_QUIT, _T("E&xit\tAlt-X"),  _T("Quit this program"));

  wxMenuBar* menuBar = new wxMenuBar();
  menuBar->Append(fileMenu, _T("&File"));
  menuBar->Append(helpMenu, _T("&Help"));  
  
  //bottom statusbar 2 column
  CreateStatusBar(2);
  SetStatusText(_T("So far so good."), 0);
  SetStatusText(_T("Welcome."), 1);
  
  sizer = new wxBoxSizer(wxHORIZONTAL);
  WxPanel1 = new wxPanel(this, ID_WXPANEL1, wxPoint(20, 20), wxSize(280, 281));
  WxPanel1->SetBackgroundColour(wxColour(55,55,55));
  WxPanel1->SetSizer(sizer);
  
  //load initial image to wxbitmap panel
  image = new wxStaticBitmap(WxPanel1, wxID_ANY, 
  wxBitmap("mobil.jpg", wxBITMAP_TYPE_JPEG), wxPoint(0,0), WxPanel1->GetSize()); //wxSize(100, 500));

  //autosize image
  sizer->Add(image, 1, wxEXPAND);
  sizer->SetSizeHints(this);


  wxPanel *WxPanel2 = new wxPanel(this, ID_WXPANEL2, wxPoint(400, 20), wxSize(180, 136));
  WxPanel2->SetBackgroundColour(wxColour(44,44,44));
  wxButton *m_btn_2 = new wxButton(WxPanel2, 0, wxT("Panel 2") , wxPoint(10,10), wxSize(100,30));

  //wxPoint(int x, int y)
  //wxSize(int width, int height)
  wxPanel *WxPanel3 = new wxPanel(this, ID_WXPANEL3, wxPoint(400, 165), wxSize(180, 136));
  WxPanel3->SetBackgroundColour(wxColour(99,99,99));
  wxButton *m_btn_3 = new wxButton(WxPanel3, 0, wxT("Panel 3") , wxPoint(10,10), wxSize(100,30));


  wxPanel *WxPanel4 = new wxPanel(this, ID_WXPANEL3, wxPoint(0, 400), wxSize(600, 50));
  WxPanel4->SetBackgroundColour(wxColour(99,99,00));

  //add button to panel 4
  wxButton *button1=new wxButton(WxPanel4, BUTTON1, _T("Start &1"), wxPoint(10,10), wxSize(100,30) );
  wxButton *button2=new wxButton(WxPanel4, BUTTON2, _T("Stop &2"), wxPoint(130,10), wxSize(100,30) );


  //SetIcon(wxNullIcon);
  SetSize(8,8,600,490);
  Center();

  SetMenuBar(menuBar);
  

  // It is also possible to use event tables, but dynamic binding is simpler.
  Bind(wxEVT_THREAD_UPDATE, &MyFrame::OnThreadUpdate, this);
  
}


void MyFrame::OnQuit(wxCommandEvent& WXUNUSED(event))
{
 Close(true);
}

void MyFrame::OnAbout(wxCommandEvent& WXUNUSED(event))
{
  wxString msg;
  msg.Printf( _T("About.\n")
  _T("Selamat Datang di %s"), wxVERSION_STRING);

  wxMessageBox(msg, _T("About My Program"), wxOK | wxICON_INFORMATION, this);
}


void MyFrame::OnButton1(wxCommandEvent& WXUNUSED(event))
{
  wxMessageBox("Click1", "Click", wxOK | wxICON_INFORMATION, this);
};


void MyFrame::OnButton2(wxCommandEvent& WXUNUSED(event))
{
  wxMessageBox("Click2", "Click", wxOK | wxICON_INFORMATION, this);
}

/*thead handle*/
void MyFrame::OnThreadUpdate(wxThreadEvent& evt)
{
  wxClientDC dc(this);
  this->Render(dc);
}

void MyFrame::Render(wxDC &dc)
{
  if (!dc.IsOk() || !m_video_image.IsOk()) return;

  //cout << captured_image;

  int win_width = this->GetClientSize().GetX();
  int win_height  = this->GetClientSize().GetY();

  wxCriticalSectionLocker lock(m_cs_image);

  int img_width = m_video_image.GetSize().GetX();
  int img_height  = m_video_image.GetSize().GetY();

  int new_img_width = (img_width  * win_height)   / img_height;
  int new_img_height  = (img_height * win_width)    / img_width;
  int width_diff    = (win_width  - new_img_width)  / 2;
  int height_diff   = (win_height - new_img_height) / 2;

  //test image
  //cv::imshow("preview", captured_image);
  //cout << " width : " << img_width << endl;
  //cout << " height : " << img_height << endl;

  


  //draw opencvBitmap to image panel
  if (height_diff >= 0)
  {
    image->SetBitmap(wxBitmap(m_video_image.Scale(win_width, new_img_height)));
    //dc.DrawBitmap(wxBitmap(m_video_image.Scale(win_width, new_img_height)), 0, height_diff);
  } else{
    image->SetBitmap(wxBitmap(m_video_image.Scale(new_img_width, win_height)));
    //dc.DrawBitmap(wxBitmap(m_video_image.Scale(new_img_width, win_height)), width_diff, 0);
  }

  //draw text
  //dc.DrawText(strbuffer, 0, 0); 

}

/*
void MyFrame::OnCameraControls(wxCommandEvent& event)
{
  int id = event.GetId();

  switch (id)
  {
  case ID_MENU_START_CAMERA:
  case ID_TB_START_CAMERA:
  {
    if (CreateThread(wxTHREAD_DETACHED) != wxTHREAD_NO_ERROR)
    {
      wxLogMessage("Error: Could not create the worker thread!");
      return;
    }

    if (GetThread()->Run() != wxTHREAD_NO_ERROR)
    {
      wxLogMessage("Error: Could not run the worker thread!");
      return;
    }
    break;
  }
  case ID_MENU_PAUSE_RESUME:
  case ID_TB_PAUSE_RESUME:
  {
    if (!this->GetThread()) return;
    if (!this->GetThread()->IsPaused())
    {
      this->GetThread()->Pause();
      wxLogMessage("Camera paused");
    } else
    {
      this->GetThread()->Resume();
      wxLogMessage("Camera running");
    }
    break;
  }
  default: break;
  }
}
*/


std::string time_in_HH_MM_SS_MMM()
{
    using namespace std::chrono;

    // get current time
    auto now = system_clock::now();

    // get number of milliseconds for the current second
    // (remainder after division into seconds)
    auto ms = duration_cast<milliseconds>(now.time_since_epoch()) % 1000;

    // convert to std::time_t in order to convert to std::tm (broken time)
    auto timer = system_clock::to_time_t(now);

    // convert to broken time
    std::tm bt = *std::localtime(&timer);

    std::ostringstream oss;

    oss << std::put_time(&bt, "%H:%M:%S"); // HH:MM:SS
    oss << '.' << std::setfill('0') << std::setw(3) << ms.count();

    return oss.str();
}

wxThread::ExitCode MyFrame::Entry()
{
  // VERY IMPORTANT: this function gets executed in the secondary thread context!
  // Do not call any GUI function inside this function; rather use wxQueueEvent():

  wxLogMessage("Initializing camera...");

  // Initialize the OpenCV capture device. This has to be done in the secondary thread context!
  cv::VideoCapture capture_device(0);
  if (!capture_device.isOpened())
  {
    wxLogMessage("Error: Couldn't initialize camera!");
    return (wxThread::ExitCode)0;
  }

  // here we do our long task, periodically calling TestDestroy():
  while (!this->GetThread()->TestDestroy())
  {
    capture_device >> captured_image;
    if (captured_image.empty()) return static_cast<wxThread::ExitCode>(0);

    //draw text image info
    cv::Size sz = captured_image.size();
    int img_height = sz.height;
    int img_width = sz.width;
    char strbuffer[100];
    int retVal=sprintf(strbuffer, "size: %d x %d Time : %s", img_width, img_height, time_in_HH_MM_SS_MMM().c_str());
    cv::putText(captured_image, 
            strbuffer,
            cv::Point(40,40), // Coordinates
            cv::FONT_HERSHEY_COMPLEX_SMALL, // Font
            1.0, // Scale. 2.0 = 2x bigger
            cv::Scalar(0,0,0), // BGR Color
            2, // Line Thickness (Optional)
            LINE_AA); // Anti-alias (Optional)


    //change color
    cv::cvtColor(captured_image, captured_image, COLOR_BGR2RGB);

    {
      wxCriticalSectionLocker lock(m_cs_image);
      m_video_image = wxImage(captured_image.cols, captured_image.rows, captured_image.data, true);
    }

    wxQueueEvent(this, new wxThreadEvent(wxEVT_THREAD_UPDATE));

    this->GetThread()->Sleep(10);
  }

  return static_cast<wxThread::ExitCode>(0);
}


void MyFrame::OnClose(wxCloseEvent& event)
{
    // important: before terminating, we _must_ wait for our joinable
    // thread to end, if it's running; in fact it uses variables of this
    // instance and posts events to *this event handler

  if (!this->GetThread())
  {
    this->Destroy();
    return;
  }

  if (this->GetThread()->Delete() != wxTHREAD_NO_ERROR)
    wxLogMessage("Error: Can't delete the camera thread!");

  while (true)
  {
    if (!this->GetThread()) break;
    this->GetThread()->Sleep(15);
  }

  this->Destroy();
  event.Skip();
  
  /*

    if (GetThread() &&      // DoStartALongTask() may have not been called
        GetThread()->IsRunning())
        GetThread()->Wait();
    Destroy();
  */

}

void MyFrame::OnUpdateUI(wxUpdateUIEvent& event)
{
  event.Skip();
}

void MyFrame::OnIdle(wxIdleEvent& event)
{
  event.Skip();
}

void MyFrame::OnMenuExit(wxCommandEvent& event)
{
  this->Close();
}


void MyFrame::DoStartALongTask()
{
    // we want to start a long task, but we don't want our GUI to block
    // while it's executed, so we use a thread to do it.
    if (CreateThread(wxTHREAD_JOINABLE) != wxTHREAD_NO_ERROR)
    {
        wxLogError("Could not create the worker thread!");
        return;
    }
    // go!
    if (GetThread()->Run() != wxTHREAD_NO_ERROR)
    {
        wxLogError("Could not run the worker thread!");
        return;
    }
}

/*app part*/
class MyApp : public wxApp {
  public: bool OnInit(){

    //wxImage::AddHandler(new wxPNGHandler());
    wxImage::AddHandler(new wxJPEGHandler());


    MyFrame *frame = new MyFrame(_T("OpenCV Camera wxWidgets"),wxSize(600, 400));
    frame->Center(wxBOTH);
    frame->Show();
    SetTopWindow(frame);

    //start camera in wxthread
    frame->DoStartALongTask();

 
    return true;
  }
};

IMPLEMENT_APP(MyApp);

Compile Command:

g++ -ggdb -std=c++11 \
`pkg-config --cflags opencv4` \
frame.cpp -o frame \
`pkg-config --cflags --libs opencv4` \
`wx-config --cxxflags --libs`

Run : ./frame
Result:

Referensi:
https://wiki.wxwidgets.org/Drawing_on_a_panel_with_a_DC