Building a C# App that interfaces with OpenCV using C++ DLL
This video builds on the previous tutorials by adding functionality to our C#/C++ Forms app. We will discuss: Why we are using Windows Forms; using Windows Messages and how our app subscribes to user actions to trigger events; Implementing libraries; and how to configure Visual Studio for OpenCV.
Source code:
Form1.cs
public Form1() { InitializeComponent(); fitPictureBox1(); Cyprus.init(pictureBox1.Width, pictureBox1.Height); updateImage(); } private void fitPictureBox1() { //images can only have a width with interger mulitples of 4. int hPadding = 6; double _picWidth = Math.Floor((panel1.Width - hPadding) / (double)4) * 4; pictureBox1.Width = (int)_picWidth; //show user changes status.Text = "Panel Size: " + panel1.Width + " x " + panel1.Height; status.Text += ", bitMap size: " + _picWidth; status.Text += ", width Difference " + (panel1.Width - pictureBox1.Width - hPadding); } private void groupBox1_Paint(object sender, PaintEventArgs e) { GroupBox box = sender as GroupBox; DrawGroupBox(box, e.Graphics, Color.Lime, Color.Lime); } private void DrawGroupBox(GroupBox box, Graphics g, Color textColor, Color borderColor) { if (box != null) { Brush textBrush = new SolidBrush(textColor); Brush borderBrush = new SolidBrush(borderColor); Pen borderPen = new Pen(borderBrush); SizeF strSize = g.MeasureString(box.Text, box.Font); Rectangle rect = new Rectangle(box.ClientRectangle.X, box.ClientRectangle.Y + (int)(strSize.Height / 2), box.ClientRectangle.Width - 1, box.ClientRectangle.Height - (int)(strSize.Height / 2) - 1); // Clear text and border g.Clear(this.BackColor); // Draw text g.DrawString(box.Text, box.Font, textBrush, box.Padding.Left, 0); // Drawing Border //Left g.DrawLine(borderPen, rect.Location, new Point(rect.X, rect.Y + rect.Height)); //Right g.DrawLine(borderPen, new Point(rect.X + rect.Width, rect.Y), new Point(rect.X + rect.Width, rect.Y + rect.Height)); //Bottom g.DrawLine(borderPen, new Point(rect.X, rect.Y + rect.Height), new Point(rect.X + rect.Width, rect.Y + rect.Height)); //Top1 g.DrawLine(borderPen, new Point(rect.X, rect.Y), new Point(rect.X + box.Padding.Left, rect.Y)); //Top2 g.DrawLine(borderPen, new Point(rect.X + box.Padding.Left + (int)(strSize.Width), rect.Y), new Point(rect.X + rect.Width, rect.Y)); } } private void panel1_Resize(object sender, EventArgs e) { if ((pictureBox1.Width > 0) && (pictureBox1.Height > 0)) { fitPictureBox1(); Cyprus.updateScreenSize(pictureBox1.Width, pictureBox1.Height); updateImage(); } } private void updateImage() { IntPtr _imgPtr = Cyprus.getScreenPtr(); Bitmap bm = new Bitmap( pictureBox1.Width, pictureBox1.Height, pictureBox1.Width * 3, //Stride = 24 bit = 3 bytes per pixel System.Drawing.Imaging.PixelFormat.Format24bppRgb, _imgPtr); pictureBox1.Image = bm; pictureBox1.Refresh(); }
Cyprus.cs
class Cyprus { [DllImport("CyprusDll", CallingConvention = CallingConvention.Cdecl)] public static extern void init(int _screenWidth, int _screenHeight); [DllImport("ChimeraCpp", CallingConvention = CallingConvention.Cdecl)] public static extern void release(); [DllImport("CyprusDll", CallingConvention = CallingConvention.Cdecl)] public static extern void updateScreenSize(int _width, int _height); [DllImport("CyprusDll", CallingConvention = CallingConvention.Cdecl)] public static extern IntPtr getScreenPtr(); }
CyprusDll.cpp
#include "stdafx.h" #include <iostream> #include "alx_system_csDisplay.h" alx::system::csDisplay display; extern "C" __declspec(dllexport) void __cdecl init(int width, int height) { //load spash screen images display.setBackground("bkgImage.jpg"); display.setData("sm.png"); display.setSize(width, height); display.render(); } extern "C" __declspec(dllexport) void __cdecl release() {} extern "C" __declspec(dllexport) void __cdecl updateScreenSize(int width, int height) { display.setSize(width, height); display.render(); } extern "C" __declspec(dllexport) unsigned char* __cdecl getScreenPtr() { return display.getImagePtr(); }
alx_system_csDisplay.h
#pragma once #include "opencv/cv.hpp" namespace alx { namespace system { class csDisplay { int m_height; int m_width; cv::Mat m_background; //background image for screen cv::Mat m_data; //data to show, full size cv::Mat m_rendered; //rendered image that matches screen size public: csDisplay(); ~csDisplay(); void setSize(int width, int height); void setBackground(char* filePath); void setBackground(const char* filePath); void setBackground(unsigned char* dataPtr, int width, int height); void setData(char* filePath); void setData(const char* filePath); void setData(unsigned char* dataPtr, int width, int height); void render(); unsigned char* getImagePtr(); }; } }
alx_system_csDisplay.cpp
#include "stdafx.h" #include <iostream> #include "alx_system_csDisplay.h" namespace alx { namespace system { csDisplay::csDisplay() { //load default images m_background = cv::Mat(100, 100, CV_8UC3, cv::Scalar(0, 25, 0)); //BGR m_data = cv::Mat(100, 100, CV_8UC3, cv::Scalar(0, 51, 0)); //BGR } csDisplay::~csDisplay() {} void csDisplay::setSize(int width, int height) { m_width = width; m_height = height; m_rendered = cv::Mat(m_height, m_width, CV_8UC3); render(); } void csDisplay::setBackground(char* filePath) { try { m_background = cv::imread(filePath, CV_LOAD_IMAGE_COLOR); render(); } catch (...) { std::cout << "ERROR: loading background image..." << std::endl; } } void csDisplay::setBackground(const char* filePath) { try { m_background = cv::imread(filePath, CV_LOAD_IMAGE_COLOR); render(); } catch (...) { std::cout << "ERROR: loading background image..." << std::endl; } } void csDisplay::setBackground(unsigned char* dataPtr, int width, int height) { //creates a Mat header for existing data try { m_background = cv::Mat(height, width, CV_8UC3, dataPtr); render(); } catch (...) { std::cout << "ERROR: creating header for image..." << std::endl; } } void csDisplay::setData(char* filePath) { try { m_data = cv::imread(filePath, CV_LOAD_IMAGE_COLOR); render(); } catch (...) { std::cout << "ERROR: loading image..." << std::endl; } } void csDisplay::setData(const char* filePath) { try { m_data = cv::imread(filePath, CV_LOAD_IMAGE_COLOR); render(); } catch (...) { std::cout << "ERROR: loading image..." << std::endl; } } void csDisplay::setData(unsigned char* dataPtr, int width, int height) { //creates a Mat header for existing data try { m_data = cv::Mat(height, width, CV_8UC3, dataPtr); render(); } catch (...) { std::cout << "ERROR: creating header for image..." << std::endl; } } void csDisplay::render() { //Strategy: // -1. m_rendered should already be the same size as the display // -1. m_data is updated elsewhere so it is most current // 1. place background into m_rendered // 2. resize screenTile to fit into rendered // 3. copy screenTile into m_rendered (using ROI) double _aspectRatioData = m_data.cols / ((double)m_data.rows); double _aspectRatioScreen = m_rendered.cols / ((double)m_rendered.rows); int _tileWidth = 0; int _tileHeight = 0; int _tileOffsetX = 0; int _tileOffsetY = 0; if (_aspectRatioScreen >= _aspectRatioData) { //set heights equal, scale width _tileWidth = (int)(m_height * _aspectRatioData); _tileHeight = m_height; _tileOffsetX = (m_width - _tileWidth) / 2; } else { _tileWidth = m_width; _tileHeight = (int)(m_width / _aspectRatioData); _tileOffsetY = (m_height - _tileHeight) / 2; } cv::Mat _tile = cv::Mat(_tileHeight, _tileWidth, CV_8UC3); cv::resize(m_background, m_rendered, m_rendered.size(), 0, 0, cv::INTER_LINEAR); cv::resize(m_data, _tile, _tile.size(), 0, 0, cv::INTER_LINEAR); _tile.copyTo( m_rendered( cv::Rect( _tileOffsetX, _tileOffsetY, _tile.cols, _tile.rows ) ) ); } unsigned char* csDisplay::getImagePtr() { return m_rendered.data; } } }