public void ShouldRemoveMeanColor() { using (var image = new MagickImage()) { var settings = new ConnectedComponentsSettings() { MeanColor = true, }; settings.SetImageArtifacts(image); settings.RemoveImageArtifacts(image); Assert.IsNull(image.GetArtifact("connected-components:mean-color")); } }
public void ShouldRemoveTheAngleThreshold() { using (var image = new MagickImage()) { var settings = new ConnectedComponentsSettings { AngleThreshold = new Threshold(1.5), }; ArtifactsHelper.SetImageArtifacts(image, settings); ArtifactsHelper.RemoveImageArtifacts(image, settings); Assert.Empty(image.ArtifactNames); } }
public void ShouldSetThePerimeterThreshold() { using (var image = new MagickImage()) { var settings = new ConnectedComponentsSettings { PerimeterThreshold = new Threshold(1.5), }; settings.SetImageArtifacts(image); Assert.Single(image.ArtifactNames); Assert.Equal("1.5", image.GetArtifact("connected-components:perimeter-threshold")); } }
public void ShouldSetMeanColor() { using (var image = new MagickImage()) { var settings = new ConnectedComponentsSettings { MeanColor = true, }; settings.SetImageArtifacts(image); Assert.Single(image.ArtifactNames); Assert.Equal("true", image.GetArtifact("connected-components:mean-color")); } }
public void ShouldSetTheMinumunAndMaximumMinorAxisThreshold() { using (var image = new MagickImage()) { var settings = new ConnectedComponentsSettings { MinorAxisThreshold = new Threshold(1.2, 3.4), }; settings.SetImageArtifacts(image); Assert.Single(image.ArtifactNames); Assert.Equal("1.2-3.4", image.GetArtifact("connected-components:minor-axis-threshold")); } }
public void ShouldSetTheMinumunAndMaximumAreaThreshold() { using (var image = new MagickImage()) { var settings = new ConnectedComponentsSettings() { AreaThreshold = new Threshold(1.2, 3.4), }; settings.SetImageArtifacts(image); EnumerableAssert.IsSingle(image.ArtifactNames); Assert.AreEqual("1.2-3.4", image.GetArtifact("connected-components:area-threshold")); } }
public void ShouldRemoveTheEccentricityThreshold() { using (var image = new MagickImage()) { var settings = new ConnectedComponentsSettings() { EccentricityThreshold = new Threshold(1.5), }; settings.SetImageArtifacts(image); settings.RemoveImageArtifacts(image); Assert.IsNull(image.GetArtifact("connected-components:eccentricity-threshold")); } }
public void ShouldSetTheAngleThreshold() { using (var image = new MagickImage()) { var settings = new ConnectedComponentsSettings() { AngleThreshold = new Threshold(1.5), }; settings.SetImageArtifacts(image); EnumerableAssert.IsSingle(image.ArtifactNames); Assert.AreEqual("1.5", image.GetArtifact("connected-components:angle-threshold")); } }
public void ShouldRemoveTheEccentricityThreshold() { using (var image = new MagickImage()) { var settings = new ConnectedComponentsSettings { EccentricityThreshold = new Threshold(1.5), }; settings.SetImageArtifacts(image); settings.RemoveImageArtifacts(image); Assert.Empty(image.ArtifactNames); } }
public void ShouldRemoveTheDiameterThreshold() { using (var image = new MagickImage()) { var settings = new ConnectedComponentsSettings() { DiameterThreshold = new Threshold(1.5), }; settings.SetImageArtifacts(image); settings.RemoveImageArtifacts(image); EnumerableAssert.IsEmpty(image.ArtifactNames); } }
public void ShouldRemoveMeanColor() { using (var image = new MagickImage()) { var settings = new ConnectedComponentsSettings { MeanColor = true, }; settings.SetImageArtifacts(image); settings.RemoveImageArtifacts(image); Assert.Empty(image.ArtifactNames); } }
public void ShouldSetTheMinumunAndMaximumPerimeterThreshold() { using (var image = new MagickImage()) { var settings = new ConnectedComponentsSettings { PerimeterThreshold = new Threshold(1.2, 3.4), }; ArtifactsHelper.SetImageArtifacts(image, settings); Assert.Single(image.ArtifactNames); Assert.Equal("1.2-3.4", image.GetArtifact("connected-components:perimeter-threshold")); } }
public void ShouldSetTheMinorAxisThreshold() { using (var image = new MagickImage()) { var settings = new ConnectedComponentsSettings { MinorAxisThreshold = new Threshold(1.5), }; ArtifactsHelper.SetImageArtifacts(image, settings); Assert.Single(image.ArtifactNames); Assert.Equal("1.5", image.GetArtifact("connected-components:minor-axis-threshold")); } }
public void ShouldNotSetTheAttributesWhenTheyAreNotSpecified() { using (var image = new MagickImage()) { var settings = new ConnectedComponentsSettings(); settings.SetImageArtifacts(image); Assert.IsNull(image.GetArtifact("connected-components:angle-threshold")); Assert.IsNull(image.GetArtifact("connected-components:area-threshold")); Assert.IsNull(image.GetArtifact("connected-components:circularity-threshold")); Assert.IsNull(image.GetArtifact("connected-components:diameter-threshold")); Assert.IsNull(image.GetArtifact("connected-components:eccentricity-threshold")); Assert.IsNull(image.GetArtifact("connected-components:major-axis-threshold")); Assert.IsNull(image.GetArtifact("connected-components:mean-color")); Assert.IsNull(image.GetArtifact("connected-components:minor-axis-threshold")); Assert.IsNull(image.GetArtifact("connected-components:perimeter-threshold")); } }
public void ShouldReturnTheConnectedComponents() { using (var image = new MagickImage(Files.ConnectedComponentsPNG)) { using (var temp = image.Clone()) { temp.Blur(0, 10); temp.Threshold((Percentage)50); var components = temp.ConnectedComponents(4).OrderBy(component => component.X).ToArray(); Assert.Equal(7, components.Length); var color = MagickColors.Black; #if Q8 AssertComponent(image, components[1], 2, 94, 297, 128, 151, 11783, color, 157, 371); AssertComponent(image, components[2], 5, 99, 554, 128, 150, 11772, color, 162, 628); AssertComponent(image, components[3], 4, 267, 432, 89, 139, 11792, color, 310, 501); AssertComponent(image, components[4], 1, 301, 202, 148, 143, 11801, color, 374, 272); AssertComponent(image, components[5], 6, 341, 622, 136, 150, 11793, color, 408, 696); AssertComponent(image, components[6], 3, 434, 411, 88, 139, 11835, color, 477, 480); #else AssertComponent(image, components[1], 2, 94, 297, 128, 151, 11737, color, 157, 371); AssertComponent(image, components[2], 5, 99, 554, 128, 150, 11734, color, 162, 628); AssertComponent(image, components[3], 4, 267, 432, 89, 139, 11749, color, 310, 501); AssertComponent(image, components[4], 1, 301, 202, 148, 143, 11755, color, 374, 272); AssertComponent(image, components[5], 6, 341, 622, 136, 150, 11746, color, 408, 696); AssertComponent(image, components[6], 3, 434, 411, 88, 139, 11793, color, 477, 480); #endif } #if !Q8 using (var temp = image.Clone()) { var settings = new ConnectedComponentsSettings { Connectivity = 4, MeanColor = true, AreaThreshold = new Threshold(400), }; var components = temp.ConnectedComponents(settings).OrderBy(component => component.X).ToArray(); Assert.Equal(12, components.Length); var color = new MagickColor("#010101010101"); AssertComponent(image, components[1], 597, 90, 293, 136, 162, 11624, color, 157, 372); AssertComponent(image, components[2], 3439, 96, 550, 138, 162, 11739, color, 162, 628); AssertComponent(image, components[3], 4122, 103, 604, 4, 2, 4, new MagickColor("#0B0B0B0B0B0B"), 104, 606); AssertComponent(image, components[4], 4157, 107, 612, 3, 1, 4, new MagickColor("#080808080808"), 108, 613); AssertComponent(image, components[5], 4233, 111, 620, 3, 1, 4, new MagickColor("#020202020202"), 112, 621); AssertComponent(image, components[6], 5085, 150, 698, 3, 1, 4, new MagickColor("#424242424242"), 150, 698); AssertComponent(image, components[7], 5132, 152, 702, 3, 1, 4, new MagickColor("#262626262626"), 153, 703); AssertComponent(image, components[8], 2105, 268, 433, 89, 139, 11645, color, 311, 502); AssertComponent(image, components[9], 17, 298, 198, 155, 151, 11622, color, 375, 273); AssertComponent(image, components[10], 4202, 337, 618, 144, 158, 11675, color, 409, 696); AssertComponent(image, components[11], 1703, 435, 412, 87, 138, 11629, color, 478, 481); } #endif } }
public void ShouldReturnTheConnectedComponents() { using (IMagickImage image = new MagickImage(Files.ConnectedComponentsPNG)) { using (IMagickImage temp = image.Clone()) { temp.Blur(0, 10); temp.Threshold((Percentage)50); var components = temp.ConnectedComponents(4).OrderBy(component => component.X).ToArray(); Assert.AreEqual(7, components.Length); var color = MagickColors.Black; #if Q8 AssertComponent(image, components[1], 2, 94, 297, 128, 151, 11783, color, 157, 371); AssertComponent(image, components[2], 5, 99, 554, 128, 150, 11772, color, 162, 628); AssertComponent(image, components[3], 4, 267, 432, 89, 139, 11792, color, 310, 501); AssertComponent(image, components[4], 1, 301, 202, 148, 143, 11801, color, 374, 272); AssertComponent(image, components[5], 6, 341, 622, 136, 150, 11793, color, 408, 696); AssertComponent(image, components[6], 3, 434, 411, 88, 139, 11835, color, 477, 480); #else AssertComponent(image, components[1], 2, 94, 297, 128, 151, 11737, color, 157, 371); AssertComponent(image, components[2], 5, 99, 554, 128, 150, 11734, color, 162, 628); AssertComponent(image, components[3], 4, 267, 432, 89, 139, 11749, color, 310, 501); AssertComponent(image, components[4], 1, 301, 202, 148, 143, 11755, color, 374, 272); AssertComponent(image, components[5], 6, 341, 622, 136, 150, 11746, color, 408, 696); AssertComponent(image, components[6], 3, 434, 411, 88, 139, 11793, color, 477, 480); #endif } #if !Q8 using (IMagickImage temp = image.Clone()) { var settings = new ConnectedComponentsSettings() { Connectivity = 4, MeanColor = true, AreaThreshold = 400, }; var components = temp.ConnectedComponents(settings).OrderBy(component => component.X).ToArray(); Assert.AreEqual(13, components.Length); var color1 = new MagickColor("#010101010101"); var color2 = MagickColors.Black; AssertComponent(image, components[1], 597, 90, 293, 139, 162, 11902, color1, 157, 372); AssertComponent(image, components[2], 3439, 96, 550, 138, 162, 11999, color1, 162, 628); AssertComponent(image, components[3], 4367, 213, 633, 1, 2, 1, color2, 213, 633); AssertComponent(image, components[4], 4412, 215, 637, 3, 1, 1, color2, 215, 637); AssertComponent(image, components[5], 4453, 217, 641, 3, 1, 1, color2, 217, 641); AssertComponent(image, components[6], 4495, 219, 645, 3, 1, 1, color2, 219, 645); AssertComponent(image, components[7], 4538, 221, 647, 3, 1, 1, color2, 221, 649); AssertComponent(image, components[8], 2105, 268, 433, 89, 139, 11808, color1, 311, 502); AssertComponent(image, components[9], 17, 298, 198, 155, 151, 11927, color1, 375, 273); AssertComponent(image, components[10], 4202, 337, 618, 148, 158, 11974, color1, 409, 696); AssertComponent(image, components[11], 314, 410, 247, 2, 1, 2, color2, 410, 247); AssertComponent(image, components[12], 1703, 434, 411, 88, 140, 11763, color1, 477, 480); } #endif } }
public static void Run([BlobTrigger("inbox/{inputFilename}", Connection = "StorageAccountConnString")] Stream inputStream, string inputFilename, [Blob("outbox/out-{inputFilename}", FileAccess.Write, Connection = "StorageAccountConnString")] Stream outputStream, ILogger log) { log.LogInformation($"STEP: trigger fired for file:{inputFilename}, Size: {inputStream.Length} Bytes"); using (MagickImage image = new MagickImage(inputStream)) { log.LogInformation($"STEP: Read image {inputFilename} with {image.BaseWidth} x {image.BaseHeight} in {image.Format.ToString()}"); // Step 0 - does nothing in the sample images I used image.AutoOrient(); // Step 1 - Deskew image.BackgroundColor = MagickColor.FromRgb(0, 0, 0); image.Deskew(new Percentage(1)); // documentation suggests "A threshold of 40% works for most images" but 1% seems to work well IMagickImage rotatedImage = image.Clone(); log.LogInformation($"STEP: Deskewed {inputFilename}"); // Step 2 - Apply threshold to transform in black and white image image.AutoThreshold(AutoThresholdMethod.OTSU); log.LogInformation($"STEP: Applied OTSU to {inputFilename}"); // Step 3 - find the regions (blocs) in the image and group them if large enough IEnumerable <ConnectedComponent> components = null; ConnectedComponentsSettings ccs = new ConnectedComponentsSettings(); ccs.AreaThreshold = 500 * 500.0; // 500x500 -- seems to be pointless, many more regions are returned ccs.Connectivity = 8; components = image.ConnectedComponents(ccs); // if there are multiple blocs, consolidate them in a larger block if (components != null && components.Count() > 0) { log.LogInformation($"STEP: Looked for regions in {inputFilename}, there are {components.Count()}"); // filter out the smaller rectangles, as the AreaThreshold parameter seems not to be working List <ConnectedComponent> biggerComponents = components.Where(cc => cc.Height * cc.Width >= 250000 && cc.Height * cc.Width != image.Width * image.Height) /*.OrderByDescending(i => i.Height * i.Width)*/.ToList(); int topLeftX = biggerComponents[0].X, topLeftY = biggerComponents[0].Y, bottomRightX = biggerComponents[0].Width + topLeftX, bottomRightY = biggerComponents[0].Height + topLeftY; foreach (ConnectedComponent cc in biggerComponents) { #region Debug -- draw the regions on the image //DrawableStrokeColor strokeColor = new DrawableStrokeColor(new MagickColor("yellow")); //DrawableStrokeWidth stokeWidth = new DrawableStrokeWidth(3); //DrawableFillColor fillColor = new DrawableFillColor(new MagickColor(50, 50, 50, 128)); //DrawableRectangle dr = new DrawableRectangle(cc.X, cc.Y, cc.X + cc.Width, cc.Y + cc.Height); //rotatedImage.Draw(dr, strokeColor, stokeWidth, fillColor); #endregion if (cc.X < topLeftX) { topLeftX = cc.X; } if (cc.Y < topLeftY) { topLeftY = cc.Y; } if (cc.X + cc.Width > bottomRightX) { bottomRightX = cc.X + cc.Width; } if (cc.Y + cc.Height > bottomRightY) { bottomRightY = cc.Y + cc.Height; } } #region Debug -- draw the bounding box on the image //DrawableStrokeColor strokeColor2 = new DrawableStrokeColor(new MagickColor("purple")); //DrawableStrokeWidth stokeWidth2 = new DrawableStrokeWidth(3); //DrawableFillColor fillColor2 = new DrawableFillColor(new MagickColor(50, 50, 50, 128)); //DrawableRectangle dr2 = new DrawableRectangle(topLeftX, topLeftY, bottomRightX, bottomRightY); //rotatedImage.Draw(dr2, strokeColor2, stokeWidth2, fillColor2); #endregion // Step 4 - Crop the image MagickGeometry mg = new MagickGeometry(topLeftX, topLeftY, bottomRightX - topLeftX, bottomRightY - topLeftY); rotatedImage.RePage(); // this is needed because otherwise the crop is relative to the page information and sometimes this leads to an incorrect crop rotatedImage.Crop(mg); log.LogInformation($"STEP: Cropped {inputFilename} to fit existing large regions"); } else { log.LogInformation($"STEP: Looked for large regions in {inputFilename}, none were found, skipping crop"); } // Step 5 - Resize the image to 1200px width (todo: move to configuration) int originalWidth = rotatedImage.BaseWidth; int originalHeight = rotatedImage.BaseHeight; rotatedImage.Resize(1200, 0); // make width 1200, height proportional log.LogInformation($"STEP: Resized {inputFilename} from {originalWidth}x{originalHeight} to {image.BaseWidth}x{image.BaseHeight}"); // Step 6 - write out as Jpeg with 70% quality rotatedImage.Format = MagickFormat.Jpeg; rotatedImage.Quality = 70; rotatedImage.Write(outputStream); log.LogInformation($"STEP: Wrote out {inputFilename} as JPEG"); } log.LogInformation($"STEP: Processing of {inputFilename} done"); }