Bootstrap

【混合编程】C#调用Python脚本进行目标尺寸检测

1415dfe836c0f2dc011a01429215e4bc.png

cad1a52b1aaa26a4afadac1a9cf54c4c.png

C#界面

55c5cf476b56b21db37c8476d7f866fd.png

可执行程序

视频演示

笔记:

一、C#调用Python脚本程序

     用到了控件ConsoleControl。

corePath = "./core.py";//python脚本
corePathExe = "core.exe"; //python生成的可执行文件
private void startBtn_Click(object sender, EventArgs e)
{
    switch (File.Exists(corePathExe))//python可执行文件存在?
    {
        case true://存在
            if (!File.Exists(corePathExe)) return;
            appConsole.StartProcess("cmd", $"/c {corePathExe}");//ConsoleControl 控件 启动python进程: "core.exe"
            break;
        case !false:
            if (!File.Exists(corePath)) return;
            appConsole.StartProcess("cmd", $"/c @python {corePath}");//启动python脚本:"./core.py"
            break;
        default:
            break;
    }


    /*
    ProcessStartInfo startInfo = new ProcessStartInfo();
    startInfo.FileName = "python";
    startInfo.Arguments = corePath;
    startInfo.UseShellExecute = true;


    Process.Start(startInfo);
    */


    menuCtrl.SelectedIndex = 1;//设置Tab控件 活动页面


    if (closeCheck.Checked) this.WindowState = FormWindowState.Minimized;//勾选复选框,最小化窗体
}

二、Python脚本:对象尺寸检测

2.1 core.py

import cv2
import helper
import json


img_f = open('./settings.json') #读取配置
settings = json.load(img_f)     #加载配置参数


webcam = settings['useWebcam']  #是否使用网络摄像头
path = settings['imgFilePath']  #图片路径
cap = cv2.VideoCapture(int(settings['webcamIndex']))#打开摄像头
dashed_gap = int(settings['dashGapScale'])  #虚线 破折号间距
cap.set(10, 160)    #https://blog.csdn.net/qq_43797817/article/details/108096827 10:CV_CAP_PROP_BRIGHTNESS 图像的亮度(仅适用于相机)
resArray = settings['resolution'].split('x')    #分辨率
#print(int(resArray[0]), int(resArray[1]))
cap.set(3, int(resArray[0]))    #3:CV_CAP_PROP_FRAME_WIDTH 视频流中帧的宽度。
cap.set(4, int(resArray[1]))    #4:CV_CAP_PROP_FRAME_HEIGHT 视频流中帧的高度
scale = int(settings['generalScale'])   #比例:图像像素 与 CM 的比例
wP = 210 * scale    #210*3=630
hP = 297 * scale    #297*3=891
windowName = settings['windowName'] #窗口标题


print('Settings loaded.')   #


checkPrintLoop = False  #图像处理轮询未开始


while True:
    if checkPrintLoop == False:
        print('Image process loop started.')
        checkPrintLoop = True
    if webcam:#使用网络摄像头
        success, img = cap.read()   #读取一帧
        imgLast = img   #获取最新的一帧
    else:
        img = cv2.imread(path)   #读取一帧图像


    imgContours, conts = helper.getBorders(img, minArea=50000, filter=4)      #获取四边形边界框
    if len(conts) != 0:     #找到对象
        biggest = conts[0][2]   #拟合的四边形 
        # print(biggest)
        imgWarp = helper.warpImg(img, biggest, wP, hP)  #投影映射并缩放图像    把图像缩放为WP,hP尺寸
        imgContours2, conts2 = helper.getBorders(imgWarp, minArea=2000, filter=4, cThr=[50, 50], draw=False) #在从映射为矩形的图像上搜索矩形边界框  面积大于2000,四边形  
        if len(conts) != 0:  #找到边界四边形
            for obj in conts2: #遍历找到的边界框数组:从大到小排序
                #print(obj[2])
                
                #cv2.polylines(imgContours2, helper.getDashedPoint(obj[2]), True, (0, 140, 255), 2)
                #helper.drawpoly(imgContours2, [obj[2]], (0, 140, 255), 2)


                nPoints = helper.reorder(obj[2])  #重排序四边形角点
                nW = round((helper.findDis(nPoints[0][0]//scale, nPoints[1][0]//scale)/10), 1)  #真实宽度尺寸   地板除(取整除) x // y
                nH = round((helper.findDis(nPoints[0][0]//scale, nPoints[2][0]//scale)/10), 1)  #真实高度


                #objDef1 = (nPoints[0][0][0], nPoints[0][0][1]), (nPoints[1][0][0], nPoints[1][0][1])    #
                #objDef2 = (nPoints[0][0][0], nPoints[0][0][1]), (nPoints[2][0][0], nPoints[2][0][1])    #


                #print(obj[2][0][0], obj[2][1][0])
                #绘制四边形虚线直线    图像,起点,终点,颜色,线宽,默认点类型,破折号间距
                helper.dashLine(imgContours2, obj[2][0][0], obj[2][1][0], (0, 140, 255), 2, 'dotted', dashed_gap)
                helper.dashLine(imgContours2, obj[2][1][0], obj[2][2][0], (0, 140, 255), 2, 'dotted', dashed_gap)
                helper.dashLine(imgContours2, obj[2][2][0], obj[2][3][0], (0, 140, 255), 2, 'dotted', dashed_gap)
                helper.dashLine(imgContours2, obj[2][3][0], obj[2][0][0], (0, 140, 255), 2, 'dotted', dashed_gap)
                
                #helper.dashLine(imgContours2, objDef2[0], objDef2[1], (0, 140, 255), 2)


                #cv2.arrowedLine(imgContours2, objDef1[0], objDef1[1], (255, 0, 255), 3, 8, 0, 0.05)
                #cv2.arrowedLine(imgContours2, objDef2[0], objDef2[1], (255, 0, 255), 3, 8, 0, 0.05)
                
                x, y, w, h = obj[3]  #边界矩形
                #print(x, y, w, h)
                cv2.putText(imgContours2, '{}cm'.format(nW), (x + 30, y - 10), cv2.QT_FONT_NORMAL, 0.5, (0, 0, 0), 1)  #显示  宽度多少cm
                cv2.putText(imgContours2, '{}cm'.format(nH), (x - 70, y + h // 2), cv2.QT_FONT_NORMAL, 0.5,(0, 0, 0), 1) #显示高度 多少cm
        imgLast = imgContours2  #最终要显示的图像:有尺寸信息
        #cv2.imshow('A4', imgContours2)


    img = cv2.resize(img, (0, 0), None, 0.5, 0.5)  #缩放图像  如果dsize被设置为0(None),则按fx与fy与原始图像大小相乘得到输出图像尺寸大小
    cv2.imshow(windowName, imgLast)  #显示
    k =cv2.waitKey(0)  #无限等待按键
    if k == 27: break    # 键盘上Esc键的键值
    if cv2.getWindowProperty(windowName, cv2.WND_PROP_VISIBLE) <= 0:
        cv2.destroyAllWindows()
        break

2.2 helper.py

from operator import index
import cv2
import numpy as np
#图像处理,找到边界框
def getBorders(img,cThr=[100,100],showCanny=False,minArea=1000,filter=0,draw =False):
    imgGray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)  #灰度图
    imgBlur = cv2.GaussianBlur(imgGray,(5,5),1)     #高斯滤波
    imgCanny = cv2.Canny(imgBlur,cThr[0],cThr[1])   #canny边缘检测, cThr:阈值最小、最大值
    kernel = np.ones((5,5))     #卷积核大小
    imgDial = cv2.dilate(imgCanny,kernel,iterations=3) #膨胀操作
    imgThre = cv2.erode(imgDial,kernel,iterations=2)   #腐蚀操作
    if showCanny:cv2.imshow('Canny',imgThre)    #显示边缘检测结果,默认false,不显示
    contours,hiearchy = cv2.findContours(imgThre,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE) #搜索轮廓
    finalCountours = []
    for i in contours:  #遍历搜索结果
        area = cv2.contourArea(i)   #第i+1轮廓面积 https://www.jianshu.com/p/6bde79df3f9d
        if area > minArea:  #大于设定的面积阈值
            peri = cv2.arcLength(i,True)    #计算轮廓的周长
            approx = cv2.approxPolyDP(i,0.02*peri,True) #轮廓的多边形拟合 
            bbox = cv2.boundingRect(approx) #多边形的矩形框  boundingRect、minAreaRect 寻找包裹轮廓的最小正矩形、最小斜矩形 https://www.cnblogs.com/bjxqmy/p/12347355.html
            if filter > 0:  #根据拟合多边形边数过滤  0:不过滤
                if len(approx) == filter:
                    finalCountours.append([len(approx),area,approx,bbox,i]) #添加到最终边界结果数组
            else:
                finalCountours.append([len(approx),area,approx,bbox,i]) #边数,面积,近似多边形,边界框,轮廓
    finalCountours = sorted(finalCountours,key = lambda x:x[1] ,reverse= True) #reverse = True 降序 , reverse = False 升序(默认)  最终边界框数组:面积 排序    
    if draw:
        for con in finalCountours:
            cv2.drawContours(img,con[4],-1,(0,0,255),3) #绘制红色轮廓,
    return img, finalCountours  #返回灰度图(可能带有轮廓),最终轮廓数组
#重排序拟合的四边形的4 的点
def reorder(myPoints):
    #print(myPoints.shape)
    myPointsNew = np.zeros_like(myPoints) #初始化点数组
    myPoints = myPoints.reshape((4,2)) #4个点x,y  
    add = myPoints.sum(1)  #array.sum(axis =1),对array的每一行进行相加
    myPointsNew[0] = myPoints[np.argmin(add)]  #左上角点  https://blog.csdn.net/qq_37591637/article/details/103385174
    myPointsNew[3] = myPoints[np.argmax(add)]  #右下角点
    diff = np.diff(myPoints,axis=1)#y  沿着指定轴计算第N维的离散差值  第一个差异由 out[i]=x[i+1]-a[i] 
    myPointsNew[1]= myPoints[np.argmin(diff)]  #左下角点   
    myPointsNew[2] = myPoints[np.argmax(diff)] #右上角点
    return myPointsNew
#获取排序后索引指定的角点
def getorder(myPoints, index):
    #print(myPoints.shape)
    myPointsNew = np.zeros_like(myPoints)
    myPoints = myPoints.reshape((4,2))
    add = myPoints.sum(1)
    myPointsNew[0] = myPoints[np.argmin(add)]
    myPointsNew[3] = myPoints[np.argmax(add)]
    diff = np.diff(myPoints,axis=1)
    myPointsNew[1]= myPoints[np.argmin(diff)]
    myPointsNew[2] = myPoints[np.argmax(diff)]
    return myPointsNew[index]
#压缩图像,   近似四边形投影映射,截取大部分
def warpImg(img,points,w,h,pad=20):
    # print(points)
    points = reorder(points)    #重排序四个角点
    pts1 = np.float32(points)   #浮点数组:重排序的拟合的图像角点
    pts2 = np.float32([[0,0],[w,0],[0,h],[w,h]])  #变换后的图像顶点
    matrix = cv2.getPerspectiveTransform(pts1,pts2) #获取投影映射(Projective Mapping)  透视变换(Perspective Transformation)矩阵
    imgWarp = cv2.warpPerspective(img,matrix,(w,h)) #投影映射
    imgWarp = imgWarp[pad:imgWarp.shape[0]-pad,pad:imgWarp.shape[1]-pad] #去掉pad边界填充 
    return imgWarp
#计算两点距离
def findDis(pts1,pts2):
    return ((pts2[0]-pts1[0])**2 + (pts2[1]-pts1[1])**2)**0.5
#是否能整除
def checkDivide(num, num2):
    boolDef = (num % num2) == 0
    return (boolDef)
#获取points中指定索引的点
def getPoint(points, _index):
    newPoints = []


    for _point in points:
        newPoints.append(_point)
    
    _lastValue = newPoints[_index]
    return _lastValue
#绘制破折线 直线
def dashLine(img,pt1,pt2,color,thickness=1,style='dotted',gap=20):
    dist =((pt1[0]-pt2[0])**2+(pt1[1]-pt2[1])**2)**.5
    #dist = dist * 3
    pts= []
    for i in  np.arange(0,dist,gap):
        r=i/dist
        x=int((pt1[0]*(1-r)+pt2[0]*r)+.5)
        y=int((pt1[1]*(1-r)+pt2[1]*r)+.5)
        p = (x,y)
        pts.append(p)


    if style=='dotted':
        for p in pts:
            cv2.circle(img,p,thickness,color,-1)
    else:
        s=pts[0]
        e=pts[0]
        i=0
        for p in pts:
            s=e
            e=p
            if i%2==1:
                cv2.line(img,s,e,color,thickness)
            i+=1
#绘制多边形- 
def drawpoly(img,pts,color,thickness=1,style='dotted',):
    s=pts[0]
    e=pts[0]
    pts.append(pts.pop(0))
    i=0
    for p in pts:
        if p==e:continue
        s=e #起点
        if p==p[len(p)-1]:e=pts[0]
        e=p #更新end点
        dashLine(img,s,e,color,thickness,style)

三、json参数C#读写操作

setting.json文件

{"useWebcam":false,"webcamIndex":0,"imgFilePath":"./3.jpg","generalScale":3,"dashGapScale":10,"resolution":"1920x1080","windowName":"Output Window"}

json配置类

using System.Text;
using Newtonsoft.Json;
using System.IO;
using System.Collections.Generic;


namespace ObjectMeasurement
{
    public class CoreSettings
    {
        //属性配置类
        private class CoreProperties//配置类
        {
            public bool useWebcam { get; set; }//使用网络摄像头
            public int webcamIndex { get; set; }//摄像头索引
            public string imgFilePath { get; set; }//图片路径
            public int generalScale { get; set; }//比例
            public int dashGapScale { get; set; }//
            public string resolution { get; set; }//分辨率
            public string windowName { get; set; }//窗口标题
        }
        //私有变量
        private bool a_useWebcam { get; set; }
        private int a_webcamIndex { get; set; }
        private string a_imgFilePath { get; set; }
        private int a_generalScale { get; set; }
        private int a_dashGapScale { get; set; }
        private string a_resolution { get; set; }
        private string a_windowName { get; set; }


        //公开属性
        public bool useWebcam { get; private set; }
        public int webcamIndex { get; private set; }
        public string imgFilePath { get; private set; }
        public int generalScale { get; private set; }
        public int dashGapScale { get; private set; }
        public string resolution { get; private set; }
        public string windowName { get; private set; }


        private string jsonPath { get; set; }//序列化字符串
        //构造函数1:加载序列化字符串
        public CoreSettings(string _jsonPath)
        {
            jsonPath = _jsonPath;
            LoadJson();//加载序列化字符串,反序列化,设置配置
        }
        //构造函数2: 加载参数
        public void Configure(bool _useWebcam, int _webcamIndex,
            string _imgFilePath, int _generalScale,
            int _dashGapScale, string _resolution, string _windowName)
        {
            useWebcam = _useWebcam;
            webcamIndex = _webcamIndex;
            imgFilePath = _imgFilePath;
            generalScale = _generalScale;
            dashGapScale = _dashGapScale;
            resolution = _resolution;
            windowName = _windowName;


            //
            a_useWebcam = _useWebcam;
            a_webcamIndex = _webcamIndex;
            a_imgFilePath = _imgFilePath;
            a_generalScale = _generalScale;
            a_dashGapScale = _dashGapScale;
            a_resolution = _resolution;
            a_windowName = _windowName;
        }
        //加载json字符串
        public void LoadJson()
        {
            string json = File.ReadAllText(jsonPath);
            CoreProperties loadedSettings = JsonConvert.DeserializeObject<CoreProperties>(json);//反序列化
            Configure(loadedSettings.useWebcam, loadedSettings.webcamIndex,
                loadedSettings.imgFilePath, loadedSettings.generalScale, loadedSettings.dashGapScale,
                loadedSettings.resolution, loadedSettings.windowName);//配置
        }
        //保存序列化对象
        public void Save()
        {
            CoreProperties properties = new CoreProperties
            {
                useWebcam = a_useWebcam,
                webcamIndex = a_webcamIndex,
                imgFilePath = a_imgFilePath,
                generalScale = a_generalScale,
                dashGapScale = a_dashGapScale,
                resolution = a_resolution,
                windowName = a_windowName
            };


            string writeJson = JsonConvert.SerializeObject(properties).ToString();//配置类序列化


            /*
            Dictionary<string, string> replacePairs = new Dictionary<string, string>();
            replacePairs.Add("_useWebcam", "useWebcam");
            replacePairs.Add("_webcamIndex", "webcamIndex");
            replacePairs.Add("_imgFilePath", "imgFilePath");
            replacePairs.Add("_generalScale", "generalScale");
            replacePairs.Add("_dashGapScale", "dashGapScale");
            replacePairs.Add("_resolution", "resolution");
            replacePairs.Add("_windowName", "windowName");
            replacePairs.Add("_", "");


            foreach (KeyValuePair<string, string> item in replacePairs)
            {
                string key = item.Key;
                writeJson.Replace(key.ToString(), item.Value.ToString());
            }
            */


            // System.Windows.Forms.MessageBox.Show(JsonConvert.SerializeObject(properties));


            File.WriteAllText(jsonPath, JsonConvert.SerializeObject(properties));//保存序列化的配置
        }
    }
}

json参数加载和保存

//关闭窗口时,读取参数设置配置
        private void SettingsForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            bool _useWebcam = settings_visualSource.SelectedItem.ToString() == "Web Camera";
            int _webcamIndex = Convert.ToInt32(settings_WebcamIndex.Value);
            string _imgFilePath = settings_ImageFile.Text.ToString();
            int _generalScale = Convert.ToInt32(settings_GeneralScale.Value);
            int _dashGapScale = Convert.ToInt32(settings_DashGapScale.Value);
            string _resolution = settings_Resolution.Text.ToString();
            string _windowName = settings_WindowTitle.Text.ToString();


            MainForm.coreSettings.Configure(_useWebcam, _webcamIndex, _imgFilePath, _generalScale, _dashGapScale, _resolution, _windowName);
            MainForm.coreSettings.Save();
        }


        private void SettingsForm_Load(object sender, EventArgs e)
        {
            CoreSettings core = MainForm.coreSettings;
            core.LoadJson();
            if (core.useWebcam)
            {
                settings_visualSource.SelectedIndex = 0;
            }
            else
            {
                settings_visualSource.SelectedIndex = 1;
            }


            settings_WebcamIndex.Value = core.webcamIndex;
            settings_ImageFile.Text = core.imgFilePath;
            settings_GeneralScale.Value = core.generalScale;
            settings_DashGapScale.Value = core.dashGapScale;
            settings_Resolution.Text = core.resolution;
            settings_WindowTitle.Text = core.windowName;
        }

参考:

https://blog.csdn.net/u010636181/article/details/80659700

;