C# 实现电子签名

本项目基于Emgu.CV(C#下OpenCv的封装)开发的,编译器最新版Vs2022,编译环境x86

直接看效果图

1.主页面

2.我们先看手写的方式:

点击确认就到主界面,如下 :

点击自动适配-,再点击生成:

放大看

 

点击保存即可,生成透明电子签名图片。

3.在单色背景下手写名字,导入图片生成

先点击 选择图像 按钮,然后选择图像,点击自动适配,点击Canny算法生成,最后点击生成,如下:

双击右边第一张放大:

选择需要的区域点击保存即可。

4.算法介绍:

4.1自动适配

自动适配指根据灰度值像素选取Canny算子的上下阈值,主要目的是能快速自动适配Canny上下阈值,这里主要介绍以中位数或者平均值,然后以Sigma为临界值选取一个区间,如下:

        /// <summary>
        /// 自动适配
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button4_Click(object sender, EventArgs e)
        {
            if (pictureBox1.Image == null)
            {
                MessageBox.Show("请选择图像");
                return;
            }
            imgShow?.Dispose();
            gray?.Dispose();

            imgShow = new Image<Bgra, byte>(new Bitmap(pictureBox1.Image));
            gray = imgShow.Convert<Gray, byte>();

            double median = radioButton1.Checked ? CalculateMedian(gray) : CalculateAvg(gray);

            double sigma = (float)numericUpDown1.Value;
            double lower = Math.Max(0, (1.0 - sigma) * median);
            double upper = Math.Min(255, (1.0 + sigma) * median);
            trackBar1.Value = (int)lower;
            label1.Text = trackBar1.Value.ToString();
            trackBar2.Value = (int)upper;
            label3.Text = trackBar2.Value.ToString();
        }

 4.2Canny生成

Canny算子边缘检测具体不多介绍,这里目的是为了找到字体边缘,然后在图像上填充,方便我们能确定这个边缘是否能够准确找到,如果大都填充到字体上,说明边缘检测不错,找到这些边缘,然后计算它们的平均R、G、B:

        /// <summary>
        /// Canny生成
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button6_Click(object sender, EventArgs e)
        {
            if (gray == null || imgShow == null)
            {
                MessageBox.Show("请先选择自动适配");
                return;
            }
            // Canny算子边缘检测
            using Image<Gray, byte> edges = gray.Canny(trackBar1.Value, trackBar2.Value);
            using VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint();
            using Mat hier = new Mat();

            CvInvoke.FindContours(edges, contours, hier, RetrType.List, ChainApproxMethod.ChainApproxSimple);
            // 掩码
            using Image<Gray, byte> mask = new Image<Gray, byte>(gray.Size);
            if (contours.Size > 10000)
            {
                MessageBox.Show("边缘太多,会非常耗时,请重先调节参数");
                return;
            }
            // 在imgShow图像上填充边缘边框
            for (int i = 0; i < contours.Size; i++)
            {
                var contour = contours[i];
                CvInvoke.DrawContours(imgShow, contours, i, new MCvScalar(0, 0, 255), 2);
                CvInvoke.DrawContours(mask, contours, i, new MCvScalar(255), thickness: -1);
            }
            this.pictureBox3.Image?.Dispose();
            this.pictureBox3.Image = imgShow.Bitmap;

            using Image<Gray, byte> maskedImage = new Image<Gray, byte>(gray.Size);
            CvInvoke.BitwiseAnd(gray, gray, maskedImage, mask);
            MCvScalar average = CvInvoke.Mean(maskedImage, mask);
            double blue = average.V0;
            double green = average.V1;
            double red = average.V2;
            trackBar3.Value = (int)Math.Round(red, 0);
            trackBar4.Value = (int)Math.Round(green, 0);
            trackBar5.Value = (int)Math.Round(blue, 0);
            label9.Text = trackBar3.Value.ToString();
            label10.Text = trackBar4.Value.ToString();
            label12.Text = trackBar5.Value.ToString();
        }

4.3生成

在生成中,我们会根据R、G、B计算出掩码,然后将掩码中选取的颜色的alpha通道设置为0,即为透明。

        /// <summary>
        /// 生成
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button2_Click(object sender, EventArgs e)
        {
            if (pictureBox1.Image == null)
            {
                MessageBox.Show("请选择图片");
                return;
            }
            img?.Dispose();
            img = new Image<Bgra, byte>(new Bitmap(pictureBox1.Image));

            // 掩码
            using Image<Gray, byte> mask2 = img.InRange(new Bgra(trackBar5.Value, trackBar4.Value, trackBar3.Value, 0), new Bgra(255, 255, 255, 255));
            // 将掩码中选取的颜色的alpha通道设置为0
            img.SetValue(new Bgra(0, 0, 0, 0), mask2);


            this.pictureBox2.Image?.Dispose();
            this.pictureBox2.Image = img.Bitmap;
        }

5.也可以直接调用GrabCut方法,多迭代几次就可以,不过效率低

 

 private void button2_Click(object sender, EventArgs e)
        {
            if(this.pictureBox1.Image==null)
            {
                return;
            }
            float scaleX = (float)this.pictureBox1.Image.Width / pictureBox1.Width;
            float scaleY = (float)this.pictureBox1.Image.Height / pictureBox1.Height;

            Rectangle rect = new Rectangle(
                (int)(Rect.Left * scaleX),
                (int)(Rect.Top * scaleY),
                (int)(Rect.Width * scaleX),
                (int)(Rect.Height * scaleY));
            var rectangle = new OpenCvSharp.Rect(rect.X, rect.Y, rect.Width, rect.Height);

            rect.Intersect(new Rectangle(0, 0, this.pictureBox1.Image.Width, this.pictureBox1.Image.Height));

            using Mat nimage = new Bitmap(this.pictureBox1.Image).ToMat();
            using Mat image = new Mat(nimage, rectangle);
            using Mat convertedImage = new Mat();
            Cv2.CvtColor(image, convertedImage, ColorConversionCodes.BGRA2BGR); // 将图像转换为CV_8UC3类型
           

            using Mat mask = new Mat(convertedImage.Size(), MatType.CV_8UC1, Scalar.Black);
            using Mat bgdModel = new Mat();
            using Mat fgdModel = new Mat();

            var r = new OpenCvSharp.Rect(0, 0, rectangle.Width-10, rectangle.Height-10);
            Cv2.GrabCut(convertedImage, mask, r, bgdModel, fgdModel, 20, GrabCutModes.InitWithRect);
            Cv2.Threshold(mask, mask, 2, 255, ThresholdTypes.Binary);
            using Mat foreground = new Mat();
            Cv2.BitwiseAnd(convertedImage, convertedImage, foreground, mask);


            // 创建一个具有 alpha 通道的新图像
            using Mat result = new Mat();
            Cv2.CvtColor(image, result, ColorConversionCodes.BGRA2BGR);
            Cv2.Rectangle(result, rectangle, new Scalar(0, 0, 0), 1); // 绘制矩形边界
            Cv2.Multiply(result, new Scalar(1, 1, 1, 0), result); // 将背景部分置为透明

            // 将前景部分复制到结果图像中
            image.CopyTo(result, mask);

            pictureBox2.Image?.Dispose();
            pictureBox2.Image = result.ToBitmap();
            image.Dispose();
        }
       

注:如果需要其它源码,请留言邮箱,我看到私发。

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
使用Wacom电子签名需要调用Wacom提供的API,下面是一个示例代码,可以帮助你了解如何使用Wacom电子签名。 ```C++ #include <windows.h> #include <wintab.h> #include <pktdef.h> #include <stdio.h> #define PACKETDATA (PK_X | PK_Y | PK_NORMAL_PRESSURE | PK_BUTTONS | PK_TIME) #define PACKETMODE PK_BUTTONS #define PACKETNAME "WinTab Example" #define WM_PACKETDATA WM_USER+3 HCTX hTab; HWND hWnd; LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { MSG msg; WNDCLASS wc = { 0 }; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; wc.hInstance = hInstance; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wc.lpszClassName = TEXT("WintabSample"); RegisterClass(&wc); hWnd = CreateWindow(wc.lpszClassName, TEXT("Wintab Sample"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 640, 480, NULL, NULL, hInstance, NULL); if (!hWnd) { return FALSE; } if (!LoadWintab()) { MessageBox(hWnd, TEXT("Wintab not available"), TEXT("Error"), MB_OK); return FALSE; } hTab = InitDigitizer(hWnd); ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } CloseDigitizer(hTab); return (int)msg.wParam; } LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { static int pressure = 0, x = 0, y = 0; switch (message) { case WM_PACKETDATA: pressure = (int)PK_NORMAL_PRESSURE; x = GET_X(lParam); y = GET_Y(lParam); printf("Pressure: %d, X: %d, Y: %d\n", pressure, x, y); break; case WM_PAINT: PAINTSTRUCT ps; HDC hdc; hdc = BeginPaint(hWnd, &ps); Ellipse(hdc, x - pressure / 2, y - pressure / 2, x + pressure / 2, y + pressure / 2); EndPaint(hWnd, &ps); break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; } BOOL LoadWintab() { HINSTANCE hinstLib; BOOL bReturn = TRUE; hinstLib = LoadLibrary(TEXT("WINTAB32.DLL")); if (hinstLib == NULL) { bReturn = FALSE; } else { WTINFOA lpWTInfoA; WTINFOB lpWTInfoW; WTPROSPA lpWTProportional; WTPACKET lpPacket; lpWTInfoA = (WTINFOA)GetProcAddress(hinstLib, "WTInfoA"); lpWTInfoW = (WTINFOB)GetProcAddress(hinstLib, "WTInfoW"); lpWTProportional = (WTPROSPA)GetProcAddress(hinstLib, "WTProportional"); if (lpWTInfoA == NULL && lpWTInfoW == NULL && lpWTProportional == NULL) { bReturn = FALSE; } lpPacket = (WTPACKET)GetProcAddress(hinstLib, "WTPacket"); if (lpPacket == NULL) { bReturn = FALSE; } } return bReturn; } HCTX InitDigitizer(HWND hWnd) { AXIS TabletX = { 0 }, TabletY = { 0 }; LOGCONTEXT lcMine = { 0 }; HCTX hCtx = NULL; lcMine.lcName = PACKETNAME; lcMine.lcOptions = CXO_MESSAGES; lcMine.lcPktData = PACKETDATA; lcMine.lcPktMode = PACKETMODE; lcMine.lcMoveMask = PACKETDATA; lcMine.lcBtnUpMask = lcMine.lcBtnDnMask = PK_BUTTONS; lcMine.lcInOrgX = 0; lcMine.lcInOrgY = 0; lcMine.lcInExtX = GetSystemMetrics(SM_CXSCREEN); lcMine.lcInExtY = GetSystemMetrics(SM_CYSCREEN); lcMine.lcOutOrgX = 0; lcMine.lcOutOrgY = 0; lcMine.lcOutExtX = GetSystemMetrics(SM_CXSCREEN); lcMine.lcOutExtY = GetSystemMetrics(SM_CYSCREEN); lcMine.lcSysMode = 0; lcMine.lcSysOrgX = 0; lcMine.lcSysOrgY = 0; lcMine.lcSysExtX = GetSystemMetrics(SM_CXSCREEN); lcMine.lcSysExtY = GetSystemMetrics(SM_CYSCREEN); lcMine.lcSensX = lcMine.lcSensY = 1000; lcMine.lcSysSensX = lcMine.lcSysSensY = 1000; lcMine.lcPenMap = PEN_MAPPER; lcMine.lcDevice = 0; lcMine.lcOutOverlap = 0; lcMine.lcMsgBase = WM_PACKETDATA; lcMine.lcPktRate = 100; lcMine.lcPktSize = sizeof(WTPACKET); if (WTInfo(WTI_DEFSYSCTX, 0, &lcMine) == 0) { lcMine.lcOptions |= CXO_SYSTEM; hCtx = WacomCreateContext(&lcMine); if (hCtx) { WacomSetWindow(hCtx, hWnd); WacomSetMode(hCtx, CXO_MESSAGES, TRUE); WacomOpen(hCtx, TRUE); } } return hCtx; } void CloseDigitizer(HCTX hCtx) { if (hCtx) { WacomClose(hCtx); WacomDestroyContext(hCtx); } } ``` 上面的代码使用了WinTab API来获取Wacom签名板的输入数据,并将签名信息显示在窗口上。如果你需要在自己的程序中使用Wacom电子签名,可以参考上面的代码实现

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值