public void ShouldNotSetTheAttributesWhenOnlyTheComponentsAreSpecified() { using (IMagickImage image = new MagickImage(MagickColors.Red, 1, 1)) { image.ConnectedComponents(4).ToArray(); Assert.IsNull(image.GetArtifact("connected-components:area-threshold")); Assert.IsNull(image.GetArtifact("connected-components:mean-color")); } }
public void ShouldSetMeanColor() { using (IMagickImage image = new MagickImage(MagickColors.Red, 1, 1)) { var settings = new ConnectedComponentsSettings() { MeanColor = true, }; image.ConnectedComponents(settings).ToArray(); Assert.AreEqual("true", image.GetArtifact("connected-components:mean-color")); } }
public void ShouldSetTheAreaThreshold() { using (IMagickImage image = new MagickImage(MagickColors.Red, 1, 1)) { var settings = new ConnectedComponentsSettings() { AreaThreshold = 1.5, }; image.ConnectedComponents(settings).ToArray(); Assert.AreEqual("1.5", image.GetArtifact("connected-components:area-threshold")); } }
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"); }