C#界面
可执行程序
视频演示
笔记:
一、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