Commit 92340f80 authored by Ivan Tyagov's avatar Ivan Tyagov

Add optical inspection sensor using opencv and a sample PLC program which uses it.

parent cdd9c8b8
<?xml version='1.0' encoding='utf-8'?>
<BeremizRoot xmlns:xsd="http://www.w3.org/2001/XMLSchema" URI_location="LOCAL://">
<TargetType/>
</BeremizRoot>
<?xml version='1.0' encoding='utf-8'?>
<BaseParams xmlns:xsd="http://www.w3.org/2001/XMLSchema" IEC_Channel="1" Name="opcua_0"/>
<?xml version='1.0' encoding='utf-8'?>
<OPCUAClient xmlns:xsd="http://www.w3.org/2001/XMLSchema" Server_URI="opc.tcp://192.168.0.118:4840">
<AuthType/>
</OPCUAClient>
input,shape,2,int,2,Double,2
output,shape,2,int,2,Double,2
<?xml version='1.0' encoding='utf-8'?>
<BaseParams xmlns:xsd="http://www.w3.org/2001/XMLSchema" IEC_Channel="0" Name="opcua_1"/>
<?xml version='1.0' encoding='utf-8'?>
<OPCUAClient xmlns:xsd="http://www.w3.org/2001/XMLSchema" Server_URI="opc.tcp://192.168.0.135:4840"/>
input,I2C0 / Relay 0,1,str,i2c0.relay0,Int32,0
input,I2C0 / Relay 1,1,str,i2c0.relay1,Int32,1
output,I2C0 / Relay 0,1,str,i2c0.relay0,Int32,0
output,I2C0 / Relay 1,1,str,i2c0.relay1,Int32,1
<?xml version='1.0' encoding='utf-8'?>
<project xmlns:ns1="http://www.plcopen.org/xml/tc6_0201" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.plcopen.org/xml/tc6_0201">
<fileHeader companyName="Unknown" productName="Unnamed" productVersion="1" creationDateTime="2021-05-14T14:33:11"/>
<contentHeader name="Counter (OSIE)" modificationDateTime="2023-07-12T15:09:32">
<coordinateInfo>
<fbd>
<scaling x="0" y="0"/>
</fbd>
<ld>
<scaling x="0" y="0"/>
</ld>
<sfc>
<scaling x="0" y="0"/>
</sfc>
</coordinateInfo>
</contentHeader>
<types>
<dataTypes/>
<pous>
<pou name="plc_prg" pouType="program">
<interface>
<inputVars>
<variable name="Reset">
<type>
<BOOL/>
</type>
</variable>
</inputVars>
<outputVars>
<variable name="Cnt0">
<type>
<INT/>
</type>
</variable>
<variable name="Cnt1">
<type>
<INT/>
</type>
</variable>
</outputVars>
<localVars>
<variable name="CounterST0">
<type>
<derived name="CounterST"/>
</type>
</variable>
</localVars>
</interface>
<body>
<FBD>
<comment localId="1" height="143" width="201">
<position x="566" y="75"/>
<content>
<xhtml:p><![CDATA[Test Ivan: this PLC will switch ON / OFF attached over modbus relays {0..3} of a Lime2 coupler.]]></xhtml:p>
</content>
</comment>
<block localId="2" typeName="CounterST" instanceName="CounterST0" executionOrderId="0" height="109" width="99">
<position x="288" y="192"/>
<inputVariables>
<variable formalParameter="Reset">
<connectionPointIn>
<relPosition x="0" y="42"/>
<connection refLocalId="3">
<position x="288" y="234"/>
<position x="258" y="234"/>
<position x="258" y="218"/>
<position x="231" y="218"/>
</connection>
</connectionPointIn>
</variable>
</inputVariables>
<inOutVariables/>
<outputVariables>
<variable formalParameter="Out1">
<connectionPointOut>
<relPosition x="99" y="42"/>
</connectionPointOut>
</variable>
<variable formalParameter="Out0">
<connectionPointOut>
<relPosition x="99" y="86"/>
</connectionPointOut>
</variable>
</outputVariables>
</block>
<inVariable localId="3" executionOrderId="0" height="25" width="50" negated="false">
<position x="181" y="206"/>
<connectionPointOut>
<relPosition x="50" y="12"/>
</connectionPointOut>
<expression>Reset</expression>
</inVariable>
<outVariable localId="4" executionOrderId="0" height="25" width="42" negated="false">
<position x="433" y="266"/>
<connectionPointIn>
<relPosition x="0" y="12"/>
<connection refLocalId="2" formalParameter="Out0">
<position x="433" y="278"/>
<position x="387" y="278"/>
</connection>
</connectionPointIn>
<expression>Cnt0</expression>
</outVariable>
<outVariable localId="6" executionOrderId="0" height="25" width="42" negated="false">
<position x="435" y="222"/>
<connectionPointIn>
<relPosition x="0" y="12"/>
<connection refLocalId="2" formalParameter="Out1">
<position x="435" y="234"/>
<position x="387" y="234"/>
</connection>
</connectionPointIn>
<expression>Cnt1</expression>
</outVariable>
</FBD>
</body>
</pou>
<pou name="CounterST" pouType="functionBlock">
<interface>
<inputVars>
<variable name="Reset">
<type>
<BOOL/>
</type>
</variable>
</inputVars>
<outputVars>
<variable name="Out1">
<type>
<INT/>
</type>
</variable>
<variable name="Out0">
<type>
<INT/>
</type>
</variable>
</outputVars>
<localVars>
<variable name="Cnt0">
<type>
<INT/>
</type>
</variable>
</localVars>
<externalVars>
<variable name="ResetCounterValue">
<type>
<INT/>
</type>
</variable>
<variable name="OpticalShape">
<type>
<LREAL/>
</type>
</variable>
<variable name="Relay0">
<type>
<DINT/>
</type>
</variable>
<variable name="Relay1">
<type>
<DINT/>
</type>
</variable>
</externalVars>
</interface>
<body>
<ST>
<xhtml:p><![CDATA[(* this will always switch on relay0 which controls conveyor line*)
Relay0 := 1;
IF OpticalShape=2.0 THEN
(* a rectangle was recognized thus switch ON air valve *)
Relay1 := 1;
END_IF;
IF OpticalShape=0.0 OR OpticalShape=1.0 OR OpticalShape=3.0 THEN
(* a circle / triangle or nothing was recognized thus switch OFF air valve *)
Relay1 := 0;
END_IF;
]]></xhtml:p>
</ST>
</body>
</pou>
</pous>
</types>
<instances>
<configurations>
<configuration name="config">
<resource name="resource1">
<task name="task0" priority="0" interval="T#50ms">
<pouInstance name="instance0" typeName="plc_prg"/>
</task>
</resource>
<globalVars>
<variable name="ResetCounterValue">
<type>
<INT/>
</type>
<initialValue>
<simpleValue value="0"/>
</initialValue>
</variable>
<variable name="OpticalShape" address="%IL1.2">
<type>
<LREAL/>
</type>
<documentation>
<xhtml:p><![CDATA[OPC UA optical shape sensor]]></xhtml:p>
</documentation>
</variable>
<variable name="Relay0" address="%QD0.0">
<type>
<DINT/>
</type>
<documentation>
<xhtml:p><![CDATA[relay 0 (drive motor)]]></xhtml:p>
</documentation>
</variable>
<variable name="Relay1" address="%QD0.1">
<type>
<DINT/>
</type>
<documentation>
<xhtml:p><![CDATA[relay 0 (air valve)]]></xhtml:p>
</documentation>
</variable>
</globalVars>
</configuration>
</configurations>
</instances>
</project>
#!/usr/bin/python
"""
Basic (POC) OPC UA shape detection sensor using opencv libraryś countours detection.
Exposes a default OPC UA shape detection profile where:
0.0 - nothing detected
1.0 - triangle
2.0 - rectangle
3.0 - circle
Based on this classification any PLC can integrate this Optical Inspection and implement
additional logic.
"""
import cv2
import numpy as np
import asyncio
import logging
from asyncua import Server
def nothing(x):
# any operation
pass
async def main():
_logger = logging.getLogger(__name__)
# setup our server
server = Server()
await server.init()
server.set_endpoint("opc.tcp://0.0.0.0:4840/freeopcua/server/")
# set up our own namespace, not really necessary but should as spec
uri = "http://examples.freeopcua.github.io"
idx = await server.register_namespace(uri)
# populating our address space
myobj = await server.nodes.objects.add_object(idx, "OpticalInspection")
myvar = await myobj.add_variable(idx, "shape", 0.0)
await myvar.set_writable()
# init camera
cap = cv2.VideoCapture(0)
cv2.namedWindow("Trackbars")
cv2.createTrackbar("L-H", "Trackbars", 0, 180, nothing)
cv2.createTrackbar("L-S", "Trackbars", 0, 255, nothing)
cv2.createTrackbar("L-V", "Trackbars", 3, 255, nothing)
cv2.createTrackbar("U-H", "Trackbars", 39, 180, nothing)
cv2.createTrackbar("U-S", "Trackbars", 155, 255, nothing)
cv2.createTrackbar("U-V", "Trackbars", 148, 255, nothing)
font = cv2.FONT_HERSHEY_COMPLEX
_logger.info("Starting server!")
async with server:
while True:
# XXX: find out why we need to sleep (otherwise OPC UA server stops work)
await asyncio.sleep(0.0001)
# read and process camera
_, frame = cap.read()
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
l_h = cv2.getTrackbarPos("L-H", "Trackbars")
l_s = cv2.getTrackbarPos("L-S", "Trackbars")
l_v = cv2.getTrackbarPos("L-V", "Trackbars")
u_h = cv2.getTrackbarPos("U-H", "Trackbars")
u_s = cv2.getTrackbarPos("U-S", "Trackbars")
u_v = cv2.getTrackbarPos("U-V", "Trackbars")
lower_red = np.array([l_h, l_s, l_v])
upper_red = np.array([u_h, u_s, u_v])
mask = cv2.inRange(hsv, lower_red, upper_red)
kernel = np.ones((5, 5), np.uint8)
mask = cv2.erode(mask, kernel)
# Contours detection
contours, _ = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
for cnt in contours:
area = cv2.contourArea(cnt)
approx = cv2.approxPolyDP(cnt, 0.02*cv2.arcLength(cnt, True), True)
x = approx.ravel()[0]
y = approx.ravel()[1]
if area > 400:
cv2.drawContours(frame, [approx], 0, (0, 0, 0), 5)
if len(approx) == 3:
cv2.putText(frame, "Triangle", (x, y), font, 1, (0, 0, 0))
await myvar.write_value(1.0)
#_logger.info("Triangle")
elif len(approx) == 4:
cv2.putText(frame, "Rectangle", (x, y), font, 1, (0, 0, 0))
await myvar.write_value(2.0)
#_logger.info("Rectangle")
elif 7 < len(approx) < 20:
cv2.putText(frame, "Circle", (x, y), font, 1, (0, 0, 0))
await myvar.write_value(3.0)
#_logger.info("Circle")
else:
await myvar.write_value(0.0)
cv2.imshow("Frame", frame)
cv2.imshow("Mask", mask)
key = cv2.waitKey(1)
if key == 27:
break
if __name__ == "__main__":
logging.basicConfig(level=logging.ERROR)
asyncio.run(main(), debug=True)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment