浅谈OpenCV 粗略计算工件轮廓面积和外接圆直径(Emgu.CV)

news2024/11/26 23:50:03

前言

最近领导在做库房工具管理这块的功能,希望能集成OpenCV 粗略的计算出工具的长度,以方便用户再归还工具的时候,提示用户该放在那种尺寸的盒子里面,这便是这篇文章的由来。

我们的系统是基于.net开发的,所以采用的是 Emgu.CV这个框架来开发相应的功能,首先我们来看看效果吧,如下图。在这里我们的高宽、面积、直径都是计算的像素值,实际情况中我们需要根据自己的相片尺寸和拍照背景板与摄像头的距离,得出比例尺,根据比例尺大概计算出物体的实际面积和长度。

在这里插入图片描述

实现思路

我们业务中不需要太高的精度,所以采用一些简单的调用OpenCV 函数就能实现,注意本文的背景采用的是A4纸白色背景,如果背景不同,二值化的过程中需要你自己调节对应的参数。

  • 对照片进行灰度和二值处理
  • 去除照片中的阴影
  • 描绘物体的轮廓、和外接圆,就可以得出面积和物体长度

代码(主要逻辑代码,关于页面的代码需要自己采用 winform设计)

 public partial class Form1 : Form
 {
     public Form1()
     {
         InitializeComponent();
     }

     string imagePath = string.Empty;
     string result_path = string.Empty;
     /// <summary>
     ///  上传图片
     /// </summary>
     /// <param name="sender"></param>
     /// <param name="e"></param>
     private void button_up_Click(object sender, EventArgs e)
     {
       

         OpenFileDialog openFileDialog = new OpenFileDialog();
         if (openFileDialog.ShowDialog() == DialogResult.OK)
         {
             imagePath = openFileDialog.FileName; //得到文件全路径名
             // 使用Image.FromFile方法加载图片  
             System.Drawing.Image image = System.Drawing.Image.FromFile(imagePath);
             // 设置PictureBox的图片  
             src.Image = image;
             // (可选)设置PictureBox的大小以适应图片  
             src.SizeMode = PictureBoxSizeMode.StretchImage;
            // AreaCalculate(imagePath);
             BinaryTreatment(imagePath);
         }
     }

     private void button_calculate_Click(object sender, EventArgs e)
     {
         AreaCalculate(result_path);
     }

     /// <summary>
     /// 去除图片阴影
     /// </summary>
     /// <param name="path"></param>
     private string RemoveShadow(string path)
     {
         // 加载图像
         Mat image = CvInvoke.Imread(path, ImreadModes.Color);

         // 将图像转换为灰度图像
         Mat grayImage = new Mat();
         CvInvoke.CvtColor(image, grayImage, ColorConversion.Bgr2Gray);

         // 增加对比度和亮度来减少阴影
         double alpha = 1.2; // 对比度增强因子
         int beta = 20; // 亮度增强因子
         grayImage.ConvertTo(grayImage, DepthType.Cv8U, alpha, beta);

         DateTime now = DateTime.Now; // 获取当前本地时间 
         long timestamp = (long)(now - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds;
         string shadow_path = "C:\\Users\\Administrator\\Desktop\\openCV\\" + timestamp.ToString() + "dilated.jpg";
         CvInvoke.Imwrite(shadow_path, grayImage);
         return shadow_path;
     }


     /// <summary>
     /// 灰度和二值处理
     /// </summary>
     /// <param name="file_path"></param>
     private void BinaryTreatment(string file_path)
     {
         string shadow_path = RemoveShadow(file_path);

         // 读取图片文件
         Mat src = CvInvoke.Imread(shadow_path);
         // 创建一个与源图像大小相同的目标图像
         Mat dst = new Mat(src.Size, DepthType.Cv8U, 3);
         // 将目标图像设置为黑色
         dst.SetTo(new MCvScalar(0, 0, 0));

        
         // 显示输入图像
         //CvInvoke.Imshow("input", src);

        // 创建一个灰度图像
         Mat grayImg = new Mat();
         // 将源图像转换为灰度图像
         CvInvoke.CvtColor(src, grayImg, ColorConversion.Bgr2Gray);

         // 对灰度图像进行阈值处理,得到二值化图像
         CvInvoke.Threshold(grayImg, grayImg, 127, 255, ThresholdType.BinaryInv);
         // 创建一个用于膨胀的核,通常使用3x3或5x5的矩形核  
         Mat kernel = CvInvoke.GetStructuringElement(ElementShape.Rectangle, new Size(3, 3), new Point(-1, -1));
         // 对灰度图像进行膨胀处理  
         CvInvoke.Dilate(grayImg, dst, kernel, new Point(-1, -1), 1, new BorderType(), new MCvScalar());
         DateTime now = DateTime.Now; // 获取当前本地时间 
         long timestamp = (long)(now - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds;
         result_path = "C:\\Users\\Administrator\\Desktop\\openCV\\" + timestamp.ToString() + "dilated.jpg";
         CvInvoke.Imwrite(result_path, dst);
         // 使用Image.FromFile方法加载图片  
         System.Drawing.Image image = System.Drawing.Image.FromFile(result_path);
         // 设置PictureBox的图片  
         result.Image = image;
         // (可选)设置PictureBox的大小以适应图片  
         result.SizeMode = PictureBoxSizeMode.StretchImage;
     }

     /// <summary>
     /// 计算面积和长度
     /// </summary>
     /// <param name="read_path"></param>
     private  void AreaCalculate( string  read_path)
     {
        
         // 读取图片  
         Mat src = CvInvoke.Imread(read_path);
         // 克隆源图像,用于绘制结果  
         Mat dst = src.Clone();
         // 创建一个新的Mat对象用于存储灰度图像 
         Mat grayImg = new Mat();     
         // 将源图像转换为灰度图像  
         CvInvoke.CvtColor(src, grayImg, ColorConversion.Bgr2Gray);
         CvInvoke.Imwrite(result_path, grayImg);
         // 初始化轮廓和层次结构向量 
         VectorOfVectorOfPoint contours = new VectorOfVectorOfPoint();

         VectorOfRect hierarchy = new VectorOfRect();

         // 查找轮廓,这里只查找最外层的轮廓  
         CvInvoke.FindContours(grayImg, contours, hierarchy, RetrType.External, ChainApproxMethod.ChainApproxNone);

         double maxArea = 0.0;
         int max_coutours = 0;
         
         for (int i = 0; i < contours.Size; i++)
         {
             //求取面积、周长、多边形逼近
             double length = CvInvoke.ArcLength(contours[i], true); //计算轮廓周长
             double area = CvInvoke.ContourArea(contours[i], false); //计算轮廓面积
             if (area > maxArea)
             {
                 maxArea = area;
                 max_coutours = i;
             }
             VectorOfPoint approxPoly = new VectorOfPoint();
             CvInvoke.ApproxPolyDP(contours[max_coutours], approxPoly, length * 0.001, true); //多变形轮廓拟合  0.001 值越小拟合程度高
             CvInvoke.Polylines(dst, approxPoly, true, new MCvScalar(255, 255, 0), 2); //绘制拟合多边形
         }

         if (!grayImg.IsEmpty)
         {
             Console.WriteLine("图像的宽度是:{0}", grayImg.Cols);
             textBox_width.Text = grayImg.Cols.ToString();

             Console.WriteLine("图像的高度是:{0}", grayImg.Rows);
             textBox_height.Text = grayImg.Rows.ToString();

             Console.WriteLine("图像的面积(总像素数)是:{0}", grayImg.Cols * grayImg.Rows);
             textBox_total.Text = (grayImg.Cols * grayImg.Rows).ToString();
         }
         Console.WriteLine("图像轮廓最大面积:" + maxArea);

         textBox_area.Text = maxArea.ToString();

         CircleF circleF1 = CvInvoke.MinEnclosingCircle(contours[max_coutours]);
         Console.WriteLine("最大半径: " + circleF1.Radius );
         textBox_length.Text = (circleF1.Radius*2 ).ToString();

         DateTime now = DateTime.Now; // 获取当前本地时间 
         long timestamp = (long)(now - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds;
         string c_path = "C:\\Users\\Administrator\\Desktop\\openCV\\" + timestamp.ToString() + "dilated.jpg";
         CvInvoke.Imwrite(c_path, dst);
         // 使用Image.FromFile方法加载图片  
         System.Drawing.Image image2 = System.Drawing.Image.FromFile(c_path);
         // 设置PictureBox的图片  
         pictureBox_contoures.Image = image2;
         // (可选)设置PictureBox的大小以适应图片  
         pictureBox_contoures.SizeMode = PictureBoxSizeMode.StretchImage;
          
     }
 }

其他实列图

在这里插入图片描述
在这里插入图片描述在这里插入图片描述

结语

作为一个新手接触,记录一下学习成果,不喜勿喷。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1631797.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Memory augment is All You Need for image restoration 论文翻译

目录 一.介绍 二.实际工作 A.图像阴影去除 B.图像去雨 C.存储模块的开发 三.网络结构 A.内存扩充 B.损失函数设计 四.实验 A.与最先进方法的比较 B.MemoryNet消融研究 五.结论 CVPR2023 MemoryNet 记忆增强是图像恢复所需要的一切 论文地址https://arxiv.org/abs/…

就业班 第三阶段(nginx) 2401--4.26 day5 nginx5 nginx https部署实战

三、HTTPS 基本原理 1、https 介绍 HTTPS&#xff08;全称&#xff1a;HyperText Transfer Protocol over Secure Socket Layer&#xff09;&#xff0c;其实 HTTPS 并不是一个新鲜协议&#xff0c;Google 很早就开始启用了&#xff0c;初衷是为了保证数据安全。 近些年&…

大型零售企业,适合什么样的企业邮箱大文件解决方案?

大型零售企业通常指的是在全球或特定地区内具有显著市场影响力和知名度的零售商。这些企业不仅在零售业务收入上达到了惊人的规模&#xff0c;而且在全球范围内拥有广泛的销售网络和实体店铺。它们在快速变化的零售行业中持续创新&#xff0c;通过实体店、电商平台等多种渠道吸…

「C++ 内存管理篇 1」C++动态内存分配

目录 〇、C语言的动态内存分配方式 一、C的动态内存分配方式 1. 什么是C的动态内存分配&#xff1f; 2. 为什么需要C的动态内存分配&#xff1f; a. new的优势 b. new的不足 c. delete的优势 d. 总结 3. 怎么使用new和delete? a. 对于内置类型 b. 对于自定义类型 c. 为什么ne…

python学习笔记----循环语句(四)

一、while循环 为什么学习循环语句 循环在程序中同判断一样&#xff0c;也是广泛存在的&#xff0c;是非常多功能实现的基础&#xff1a; 1.1 while循环语法 while 条件表达式:# 循环体# 执行代码这里&#xff0c;“条件表达式”是每次循环开始前都会评估的表达式。如果条件…

【血泪教训】Altium Designer隐藏覆铜层导致PCB电路板未加工隐藏层

Altium Designer隐藏覆铜层导致PCB电路板未加工隐藏层 血泪教训&#xff01;&#xff01;&#xff01; 事情经过是这样的 测试板PCB Layout完成后&#xff0c;隐藏铺铜层&#xff0c;方便check&#xff0c;隐藏操作如下图所示&#xff0c;选择“隐藏所有”或“隐藏选中铺铜”…

Redis基本數據結構 ― String

Redis基本數據結構 ― String 介紹常用命令範例1. 為字串鍵設值/取得字串鍵的值2. 查看字串鍵的過期時間3. 如何為key設置時間?4. 如何刪除指定key?5. 如何增加value的值?6. 獲取value值的長度 介紹 字串鍵是Redis中最基本的鍵值對類型&#xff0c;這種類型的鍵值對會在數據…

【python技术】使用akshare抓取东方财富所有概念板块,并把指定板块概念的成分股保存excel 简单示例

最近有个想法&#xff0c;分析A股某个概念成分股情况进行分析&#xff0c;第一反应是把对应概念板块的成分股爬取下来。说干就干 下面是简单示例 import akshare as ak import pandas as pddef fetch_and_save_concept_stocks(name):# 获取指定股票概念的成分股&#xff0c;并…

机器学习每周挑战——百思买数据

最近由于比赛&#xff0c;断更了好久&#xff0c;从五一开始不会再断更了。这个每周挑战我分析的较为简单&#xff0c;有兴趣的可以将数据集下载下来试着分析一下&#xff0c;又不会的我们可以讨论一下。 这是数据集&#xff1a; import pandas as pd import numpy as np impo…

Leetcode-面试题 02.02. 返回倒数第 k 个节点

目录 题目 图解 代码 面试题 02.02. 返回倒数第 k 个节点 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/kth-node-from-end-of-list-lcci/description/ 题目 实现一种算法&#xff0c;找出单向链表中倒数第 k 个节点。返回该节点的值。 注意&…

保证接口幂等性的多种实现方式(数据库方案)

1. 幂等性的概念 接口幂等性是指在软件工程和Web服务领域中&#xff0c;一个接口&#xff08;通常是HTTP API&#xff09;无论被调用一次还是多次&#xff0c;其对系统产生的副作用应该是相同的&#xff0c;即结果保持一致&#xff0c;不会因为多次请求而有所不同。换句话说&am…

ES练习项目-酒店搜索

目录 1 需求分析2 酒店搜索和分页2.1 请求和响应分析2.2 定义实体类&#xff0c;接收请求参数的JSON对象2.3 编写controller&#xff0c;接收页面的请求2.4 编写业务实现&#xff0c;利用RestHighLevelClient实现搜索、分页 3. 酒店结果过滤3.1 请求和响应分析3.2 修改请求参数…

上位机图像处理和嵌入式模块部署(树莓派4b设置ftp下载)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 作为一个开发板&#xff0c;最好支持ftp下载&#xff0c;这样文件的上传和下载都会比较方便。虽然目前为止&#xff0c;利用mobaxterm和ssh也能实现…

算法学习(5)-图的遍历

目录 什么是深度和广度优先 图的深度优先遍历-城市地图 图的广度优先遍历-最少转机 什么是深度和广度优先 使用深度优先搜索来遍历这个图的过程具体是&#xff1a; 首先从一个未走到过的顶点作为起始顶点&#xff0c; 比如以1号顶点作为起点。沿1号顶点的边去尝试访问其它未…

用Jenkins实现cherry-pick多个未入库的gerrit编译Android固件

背景: 在做Android固件开发的时候,通常我们可以利用gerrit-trigger插件,开发者提交一笔的时候自动触发jenkins编译,如果提交的这一笔的编译依赖其他gerrit才能编译过,我们可以在commit message中加入特殊字段,让jenkins在编译此笔patch的时候同时抓取依赖的gerrit代码下…

selenium在Pycharm中结合python的基本使用、交互、无界面访问

下载 下载与浏览器匹配的浏览器驱动文件&#xff0c;这里一定注意的是&#xff0c;要选择和浏览器版本号相同的驱动程序&#xff0c;否则后面会有很多问题。 &#xff08;1&#xff09;浏览器&#xff08;以google为例&#xff09;版本号的查询&#xff1a; 我这里的版本号是1…

RT-Thread V5.2.0版本尝鲜

文章目录 配置界面的更新新旧内核生成的二进制文件大小差异旧版本V5.1.0内核旧版本V5.2.0内核 配置界面的更新 尝试将手头RT-Thread工程的OS部分源码进行了更新&#xff0c;发现不少新的变化 配置界面变得更醒目&#xff1a; 配置变更保存提醒界面更新&#xff1a; 新旧内核…

Android Widget开发代码示例详细说明

因为AppWidgetProvider扩展自BroadcastReceiver, 所以你不能保证回调函数完成调用后&#xff0c;AppWidgetProvider还在继续运行。 a. AppWidgetProvider 的实现 /*** Copyright(C):教育电子有限公司 * Project Name: NineSync* Filename: SynWidgetProvider.java * Author(S…

【Node.js工程师养成计划】之原生node开发web服务器

一、使用node创建http服务器 var http require(http);// 获取到服务器实例对象 var server http.createServer() server.listen(8080, function() {console.log(http://127.0.0.1:8080); })server.on(request, function(req, res){console.log(request);res.write(6666666688…

VitePress 构建的博客如何部署到 github 平台?

VitePress 构建的博客如何部署到 github 平台&#xff1f; 1. 新建 github 项目 2. 构建 VitePress 项目 2.1. 设置 config 中的 base 由于我们的项目名称为 vite-press-demo&#xff0c;所以我们把 base 设置为 /vite-press-demo/&#xff0c;需注意前后 / export default…