diff --git a/Grinder/Grinder.pro b/Grinder/Grinder.pro
index 39e27d49b9fe1b5debda114528907f189fb87ebb..87ffa1da5179f6e8351b83007885fa9122623d59 100644
--- a/Grinder/Grinder.pro
+++ b/Grinder/Grinder.pro
@@ -285,7 +285,9 @@ SOURCES += \
     ui/image/tools/EllipseDraftItemTool.cpp \
     ui/image/LayersListItemDelegate.cpp \
     cv/algorithms/FloodFill.cpp \
-    cv/algorithms/CVAlgorithm.cpp
+    cv/algorithms/CVAlgorithm.cpp \
+    engine/processors/EdgesProcessor.cpp \
+    pipeline/blocks/EdgesBlock.cpp
 
 HEADERS += \
 	ui/mainwnd/GrinderWindow.h \
@@ -620,7 +622,9 @@ HEADERS += \
     ui/image/LayersListItemDelegate.h \
     cv/Pixel.impl.h \
     cv/algorithms/FloodFill.h \
-    cv/algorithms/CVAlgorithm.h
+    cv/algorithms/CVAlgorithm.h \
+    engine/processors/EdgesProcessor.h \
+    pipeline/blocks/EdgesBlock.h
 
 FORMS += \
 	ui/mainwnd/GrinderWindow.ui \
diff --git a/Grinder/Version.h b/Grinder/Version.h
index 9e6e54205e8cdb074fe9e7e07f6d6b92e21262f6..9de65af87c707ad032d632c2603afb5f577278ba 100644
--- a/Grinder/Version.h
+++ b/Grinder/Version.h
@@ -10,14 +10,14 @@
 
 #define GRNDR_INFO_TITLE		"Grinder"
 #define GRNDR_INFO_COPYRIGHT	"Copyright (c) WWU Muenster"
-#define GRNDR_INFO_DATE			"13.07.2018"
+#define GRNDR_INFO_DATE			"16.07.2018"
 #define GRNDR_INFO_COMPANY		"WWU Muenster"
 #define GRNDR_INFO_WEBSITE		"http://www.uni-muenster.de"
 
 #define GRNDR_VERSION_MAJOR		0
 #define GRNDR_VERSION_MINOR		6
 #define GRNDR_VERSION_REVISION	0
-#define GRNDR_VERSION_BUILD		219
+#define GRNDR_VERSION_BUILD		220
 
 namespace grndr
 {
diff --git a/Grinder/common/properties/PropertyID.cpp b/Grinder/common/properties/PropertyID.cpp
index f673f02e3825b9db6a87eed208d77ee9e115bde1..99de9b1e9c3223282be367096bd1f0f33b2410b3 100644
--- a/Grinder/common/properties/PropertyID.cpp
+++ b/Grinder/common/properties/PropertyID.cpp
@@ -11,6 +11,8 @@ const char* PropertyID::Default = "Default";
 
 const char* PropertyID::ImageReference = "ImageReference";
 const char* PropertyID::Threshold = "Threshold";
+const char* PropertyID::ThresholdLow = "ThresholdLow";
+const char* PropertyID::ThresholdHigh = "ThresholdHigh";
 const char* PropertyID::TargetValue = "TargetValue";
 const char* PropertyID::Alpha = "Alpha";
 const char* PropertyID::Beta = "Beta";
diff --git a/Grinder/common/properties/PropertyID.h b/Grinder/common/properties/PropertyID.h
index 73535a83090ce8ae52841ae0bdc8498eb607679c..6ef9489267d956a3fc308a5eafb11e1bcf8b579a 100644
--- a/Grinder/common/properties/PropertyID.h
+++ b/Grinder/common/properties/PropertyID.h
@@ -18,6 +18,8 @@ namespace grndr
 
 		static const char* ImageReference;
 		static const char* Threshold;
+		static const char* ThresholdLow;
+		static const char* ThresholdHigh;
 		static const char* TargetValue;
 		static const char* Alpha;
 		static const char* Beta;
diff --git a/Grinder/cv/CVUtils.cpp b/Grinder/cv/CVUtils.cpp
index 69588466feeeda89a65c7a17e3201ceabdff65de..1332b1b4bb9a09adc812673df6665cebed4d0c40 100644
--- a/Grinder/cv/CVUtils.cpp
+++ b/Grinder/cv/CVUtils.cpp
@@ -76,6 +76,7 @@ std::vector<QColor> CVUtils::generateColors(unsigned int count, float saturation
 	{
 		QColor color;
 		color.setHsvF(i * colorStep, saturation, value);
+		colors.push_back(color);
 	}
 
 	return colors;
diff --git a/Grinder/engine/processors/EdgesProcessor.cpp b/Grinder/engine/processors/EdgesProcessor.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f906e332951f15d8bf95e98e12f4517627fff0b7
--- /dev/null
+++ b/Grinder/engine/processors/EdgesProcessor.cpp
@@ -0,0 +1,28 @@
+/******************************************************************************
+ * File: EdgesProcessor.cpp
+ * Date: 16.7.2018
+ *****************************************************************************/
+
+#include "Grinder.h"
+#include "EdgesProcessor.h"
+
+#include <opencv2/imgproc.hpp>
+
+EdgesProcessor::EdgesProcessor(const Block* block) : Processor(block)
+{
+
+}
+
+void EdgesProcessor::execute(EngineExecutionContext& ctx)
+{
+	Processor::execute(ctx);
+
+	if (auto dataBlob = portData(ctx, _block->inPort()))
+	{
+		// Use Canny to detect edges
+		cv::Mat processedImage;
+		cv::Canny(dataBlob->getMatrix(), processedImage, *_block->thresholdLow(), *_block->thresholdHigh(), 3, true);
+
+		ctx.setContextEntry(_block->outPort(), DataBlob{getPortDataDescriptor(_block->outPort()), std::move(processedImage), dataBlob->getColorSpace()});
+	}
+}
diff --git a/Grinder/engine/processors/EdgesProcessor.h b/Grinder/engine/processors/EdgesProcessor.h
new file mode 100644
index 0000000000000000000000000000000000000000..479b69ff0fe73b2d0af988c177aabf257e2c5fdc
--- /dev/null
+++ b/Grinder/engine/processors/EdgesProcessor.h
@@ -0,0 +1,24 @@
+/******************************************************************************
+ * File: EdgesProcessor.h
+ * Date: 16.7.2018
+ *****************************************************************************/
+
+#ifndef EDGESPROCESSOR_H
+#define EDGESPROCESSOR_H
+
+#include "engine/Processor.h"
+#include "pipeline/blocks/EdgesBlock.h"
+
+namespace grndr
+{
+	class EdgesProcessor : public Processor<EdgesBlock>
+	{
+	public:
+		EdgesProcessor(const Block* block);
+
+	public:
+		virtual void execute(EngineExecutionContext& ctx) override;
+	};
+}
+
+#endif
diff --git a/Grinder/pipeline/BlockCatalog.cpp b/Grinder/pipeline/BlockCatalog.cpp
index b9d77e552271db4f5f498b7301bc466428557e6f..f5c77610bb9f9085b6050a26db781b4651d8ac28 100644
--- a/Grinder/pipeline/BlockCatalog.cpp
+++ b/Grinder/pipeline/BlockCatalog.cpp
@@ -24,6 +24,7 @@
 #include "blocks/DilateBlock.h"
 #include "blocks/ErodeBlock.h"
 #include "blocks/ContoursBlock.h"
+#include "blocks/EdgesBlock.h"
 #include "blocks/WatershedBlock.h"
 
 #define REGISTER_BLOCK_TYPE(cls, desc)	registerBlockType(cls::type_value, cls::category_value, [](Pipeline* pipeline, QString name) { return std::make_unique<cls>(pipeline, name); }, desc)
@@ -128,6 +129,7 @@ void BlockCatalog::registerStandardBlocks()
 	REGISTER_BLOCK_TYPE(DistanceTransformBlock, "Generates a map of the distances to the closest zero pixel for each pixel of an image.");
 	REGISTER_BLOCK_TYPE(DilateBlock, "Dilates an image by using a specific structuring element.");
 	REGISTER_BLOCK_TYPE(ErodeBlock, "Erodes an image by using a specific structuring element.");
-	REGISTER_BLOCK_TYPE(ContoursBlock, "Finds and draws contours in binary images.");
+	REGISTER_BLOCK_TYPE(ContoursBlock, "Finds contours in binary images.");
+	REGISTER_BLOCK_TYPE(EdgesBlock, "Finds edges in images.");
 	REGISTER_BLOCK_TYPE(WatershedBlock, "Performs a marker-based segmentation using the Watershed algorithm.");
 }
diff --git a/Grinder/pipeline/BlockCategory.cpp b/Grinder/pipeline/BlockCategory.cpp
index eec8103d0a707bafe193c55e5fc2b57f1d2dc05c..54c7985220643a5ff3cba3cd028033d24a7dbdfb 100644
--- a/Grinder/pipeline/BlockCategory.cpp
+++ b/Grinder/pipeline/BlockCategory.cpp
@@ -16,6 +16,7 @@ const char* BlockCategory::Thresholding = "Thresholding";
 const char* BlockCategory::Blending = "Blending";
 const char* BlockCategory::Filtering = "Filtering";
 const char* BlockCategory::Transformation = "Transformation";
+const char* BlockCategory::Contours = "Contours";
 const char* BlockCategory::Segmentation = "Segmentation";
 
 const char* BlockCategory::Annotation = "Annotation";
diff --git a/Grinder/pipeline/BlockCategory.h b/Grinder/pipeline/BlockCategory.h
index 66fd151214d88a65d93dd7b063900739c26bbb5a..84a95e4ddb343aa4021b1a56e9de9525bf1ca1c5 100644
--- a/Grinder/pipeline/BlockCategory.h
+++ b/Grinder/pipeline/BlockCategory.h
@@ -27,6 +27,7 @@ namespace grndr
 		static const char* Blending;
 		static const char* Filtering;
 		static const char* Transformation;
+		static const char* Contours;
 		static const char* Segmentation;
 
 		static const char* Annotation;
diff --git a/Grinder/pipeline/BlockType.cpp b/Grinder/pipeline/BlockType.cpp
index 359d55ea052d840a23b63fe117c9e3ee68a41ea0..9134da6ca46b4fa13f6009dfbfd8b7e07ae27a82 100644
--- a/Grinder/pipeline/BlockType.cpp
+++ b/Grinder/pipeline/BlockType.cpp
@@ -31,6 +31,8 @@ const char* BlockType::Dilate = "Dilate";
 const char* BlockType::Erode = "Erode";
 
 const char* BlockType::Contours = "Contours";
+const char* BlockType::Edges = "Edges";
+
 const char* BlockType::Watershed = "Watershed";
 
 const char* BlockType::ImageTags = "ImageTags";
diff --git a/Grinder/pipeline/BlockType.h b/Grinder/pipeline/BlockType.h
index 8d9aaddeec2582fdd1626a97d3bda4a199b7f96b..8f069195470b0ecc8bd09f985f4aa96a3bcb3c1e 100644
--- a/Grinder/pipeline/BlockType.h
+++ b/Grinder/pipeline/BlockType.h
@@ -38,6 +38,8 @@ namespace grndr
 		static const char* Erode;
 
 		static const char* Contours;
+		static const char* Edges;
+
 		static const char* Watershed;
 
 		static const char* ImageTags;
diff --git a/Grinder/pipeline/blocks/ContoursBlock.cpp b/Grinder/pipeline/blocks/ContoursBlock.cpp
index f74fc6b8eaa6da7bd083ded0c843d9e3a2f47771..b5aa30f3f0a79a2b9764519ccfcb9214c2f486c1 100644
--- a/Grinder/pipeline/blocks/ContoursBlock.cpp
+++ b/Grinder/pipeline/blocks/ContoursBlock.cpp
@@ -8,7 +8,7 @@
 #include "engine/processors/ContoursProcessor.h"
 
 const BlockType ContoursBlock::type_value = BlockType::Contours;
-const BlockCategory ContoursBlock::category_value = BlockCategory::Segmentation;
+const BlockCategory ContoursBlock::category_value = BlockCategory::Contours;
 
 ContoursBlock::ContoursBlock(Pipeline* pipeline, QString name) : Block(pipeline, type_value, category_value, name)
 {
diff --git a/Grinder/pipeline/blocks/EdgesBlock.cpp b/Grinder/pipeline/blocks/EdgesBlock.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c5cfad2f45dee456ff4aa47a943b12893146a0f9
--- /dev/null
+++ b/Grinder/pipeline/blocks/EdgesBlock.cpp
@@ -0,0 +1,43 @@
+/******************************************************************************
+ * File: EdgesBlock.cpp
+ * Date: 16.7.2018
+ *****************************************************************************/
+
+#include "Grinder.h"
+#include "EdgesBlock.h"
+#include "engine/processors/EdgesProcessor.h"
+
+const BlockType EdgesBlock::type_value = BlockType::Edges;
+const BlockCategory EdgesBlock::category_value = BlockCategory::Contours;
+
+EdgesBlock::EdgesBlock(Pipeline* pipeline, QString name) : Block(pipeline, type_value, category_value, name)
+{
+
+}
+
+std::unique_ptr<ProcessorBase> EdgesBlock::createProcessor() const
+{
+	return std::make_unique<EdgesProcessor>(this);
+}
+
+void EdgesBlock::createProperties()
+{
+	Block::createProperties();
+
+	setPropertyGroup("General");
+
+	_thresholdLow = createProperty<UIntProperty>(PropertyID::ThresholdLow, "Lower threshold.", 100);
+	thresholdLow()->setDescription("Lower threshold for the hysteresis procedure.");
+
+	_thresholdHigh = createProperty<UIntProperty>(PropertyID::ThresholdHigh, "Upper threshold.", 250);
+	thresholdHigh()->setDescription("Upper threshold for the hysteresis procedure.");
+}
+
+void EdgesBlock::createPorts()
+{
+	DataDescriptors inPortDataDescs = {DataDescriptor::imageDescriptor(true), DataDescriptor::imageDescriptor(false)};
+	_inPort = createPort(PortType::ImageIn, Port::Direction::In, inPortDataDescs, "In");
+
+	DataDescriptors outPortDataDescs = {DataDescriptor::imageDescriptor(false)};
+	_outPort = createPort(PortType::ImageOut, Port::Direction::Out, outPortDataDescs, "Out");
+}
diff --git a/Grinder/pipeline/blocks/EdgesBlock.h b/Grinder/pipeline/blocks/EdgesBlock.h
new file mode 100644
index 0000000000000000000000000000000000000000..3b489312133e32c02f8ebff5fa025283e9002108
--- /dev/null
+++ b/Grinder/pipeline/blocks/EdgesBlock.h
@@ -0,0 +1,51 @@
+/******************************************************************************
+ * File: EdgesBlock.h
+ * Date: 16.7.2018
+ *****************************************************************************/
+
+#ifndef EDGESBLOCK_H
+#define EDGESBLOCK_H
+
+#include "pipeline/Block.h"
+
+namespace grndr
+{
+	class EdgesBlock : public Block
+	{
+		Q_OBJECT
+
+	public:
+		static const BlockType type_value;
+		static const BlockCategory category_value;
+
+	public:
+		EdgesBlock(Pipeline* pipeline, QString name = "");
+
+	public:
+		virtual std::unique_ptr<ProcessorBase> createProcessor() const override;
+
+	public:
+		auto thresholdLow() { return dynamic_cast<UIntProperty*>(_thresholdLow.get()); }
+		auto thresholdLow() const { return dynamic_cast<const UIntProperty*>(_thresholdLow.get()); }
+		auto thresholdHigh() { return dynamic_cast<UIntProperty*>(_thresholdHigh.get()); }
+		auto thresholdHigh() const { return dynamic_cast<const UIntProperty*>(_thresholdHigh.get()); }
+
+		Port* inPort() { return _inPort.get(); }
+		const Port* inPort() const { return _inPort.get(); }
+		Port* outPort() { return _outPort.get(); }
+		const Port* outPort() const { return _outPort.get(); }
+
+	protected:
+		virtual void createProperties() override;
+		virtual void createPorts() override;
+
+	private:
+		std::shared_ptr<PropertyBase> _thresholdLow;
+		std::shared_ptr<PropertyBase> _thresholdHigh;
+
+		std::shared_ptr<Port> _inPort;
+		std::shared_ptr<Port> _outPort;			
+	};
+}
+
+#endif
diff --git a/Grinder/ui/graph/GraphStyle.cpp b/Grinder/ui/graph/GraphStyle.cpp
index 8209963fbbbde944fa19e13552f3fbf635ecddbf..6fe3f9faf4aa88c02f1fd191301a61eab2e3afbf 100644
--- a/Grinder/ui/graph/GraphStyle.cpp
+++ b/Grinder/ui/graph/GraphStyle.cpp
@@ -5,27 +5,18 @@
 
 #include "Grinder.h"
 #include "GraphStyle.h"
+#include "cv/CVUtils.h"
 
 QColor GraphStyle::BlockNodeStyle::getBlockCategoryColor(BlockCategory category) const
 {
-	if (category == BlockCategory::Input)
-		return QColor{220, 0, 20};
-	else if (category == BlockCategory::Output)
-		return QColor{100, 0, 140};
-	else if (category == BlockCategory::Conversion)
-		return QColor{0, 90, 150};
-	else if (category == BlockCategory::Filtering)
-		return QColor{10, 40, 90};
-	else if (category == BlockCategory::Thresholding)
-		return QColor{80, 220, 0};
-	else if (category == BlockCategory::Transformation)
-		return QColor{20, 100, 10};
-	else if (category == BlockCategory::Blending)
-		return QColor{40, 180, 200};
-	else if (category == BlockCategory::Annotation)
-		return QColor{240, 200, 60};
-	else if (category == BlockCategory::Segmentation)
-		return QColor{255, 140, 0};
+	static const QStringList categories = {BlockCategory::Input, BlockCategory::Output, BlockCategory::Conversion, BlockCategory::Filtering, BlockCategory::Thresholding, BlockCategory::Transformation,
+										   BlockCategory::Blending, BlockCategory::Annotation, BlockCategory::Contours, BlockCategory::Segmentation};
+	static const auto colors = CVUtils::generateColors(categories.size(), 0.9f, 0.9f);
+
+	auto index = categories.indexOf(category);
+
+	if (index != -1)
+		return colors[index];
 	else
 		return QColor{255, 255, 255};
 }