Esempio n. 1
0
        public static OpenFieldReaderResult FindBoxes(int[] imgData, int row, int col, OpenFieldReaderOptions options)
        {
            // Debug image.
            int[,] debugImg = null;
            if (options.GenerateDebugImage)
            {
                debugImg = new int[row, col];
                for (int y = 0; y < row; y++)
                {
                    for (int x = 0; x < col; x++)
                    {
                        debugImg[y, x] = 0;
                    }
                }
            }

            // We are seaching for pattern!
            // We look for junctions.
            // This will help us make a decision.
            // Junction types: T, L, +.
            // Junctions allow us to find boxes contours.

            int width  = options.JunctionWidth;
            int height = options.JunctionHeight;

            // Cache per line speed up the creation of various cache.
            Dictionary <int, List <Junction> > cacheListJunctionPerLine = new Dictionary <int, List <Junction> >();
            List <Junction> listJunction = new List <Junction>();

            // If there is too much junction near each other, maybe it's just a black spot.
            // We must ignore it to prevent wasting CPU and spend too much time.
            int maxProximity = 10;

            for (int y = 1; y < row - 1; y++)
            {
                List <Junction> listJunctionX    = null;
                int             proximityCounter = 0;

                for (int x = 1; x < col - 1; x++)
                {
                    Junction?junction = GetJunction(imgData, row, col, height, width, y, x);
                    if (junction != null)
                    {
                        if (listJunctionX == null)
                        {
                            listJunctionX = new List <Junction>();
                        }
                        listJunctionX.Add(junction.Value);
                        proximityCounter++;
                    }
                    else
                    {
                        if (listJunctionX != null)
                        {
                            if (proximityCounter < maxProximity)
                            {
                                if (!cacheListJunctionPerLine.ContainsKey(y))
                                {
                                    cacheListJunctionPerLine.Add(y, new List <Junction>());
                                }
                                cacheListJunctionPerLine[y].AddRange(listJunctionX);
                                listJunction.AddRange(listJunctionX);
                                listJunctionX.Clear();
                            }
                            else
                            {
                                listJunctionX.Clear();
                            }
                        }
                        proximityCounter = 0;
                    }
                }

                if (proximityCounter < maxProximity && listJunctionX != null)
                {
                    if (!cacheListJunctionPerLine.ContainsKey(y))
                    {
                        cacheListJunctionPerLine.Add(y, new List <Junction>());
                    }
                    cacheListJunctionPerLine[y].AddRange(listJunctionX);
                    listJunction.AddRange(listJunctionX);
                }
            }

            if (options.Verbose)
            {
                Console.WriteLine("Junction.count: " + listJunction.Count);
            }

            if (listJunction.Count >= options.MaxJunctions)
            {
                // Something wrong happen. Too much junction for now.
                // If we continue, we would spend too much time processing the image.
                // Let's suppose we don't know.
                return(new OpenFieldReaderResult
                {
                    // Too many junctions. The image seem too complex. You may want to increase MaxJunctions
                    ReturnCode = 10
                });
            }

            // Let's check the list of points.

            // Search near same line.

            // TODO: should be parameters. (Can speed up process if you know what you are looking for.)
            int minX       = 15;       // Min estimated cell width (should not be less than 15.)
            int maxX       = 80;       // Max estimated cell width (should not be greater than 85. Most of the time, it can be reduced to 50 or 60.)
            int variationY = 3;        // Variation of y to find next cell. (should be really small for faster result)

            // Prepare cache to speedup searching algo.
            Dictionary <int, Junction[]> cacheNearJunction = new Dictionary <int, Junction[]>();
            Dictionary <int, Junction[]> cachePossibleNextJunctionRight = new Dictionary <int, Junction[]>();
            Dictionary <int, Junction[]> cachePossibleNextJunctionLeft  = new Dictionary <int, Junction[]>();

            foreach (var junction in listJunction)
            {
                var listJunctionNearJunction = new List <Junction>();

                for (int deltaY = -variationY; deltaY <= variationY; deltaY++)
                {
                    if (cacheListJunctionPerLine.ContainsKey(junction.Y - deltaY))
                    {
                        listJunctionNearJunction.AddRange(cacheListJunctionPerLine[junction.Y - deltaY]);
                    }
                }

                var list = listJunctionNearJunction
                           .Where(m =>
                                  Math.Abs(m.X - junction.X) <= maxX
                                  )
                           .ToArray();

                var id = junction.X | junction.Y << 16;

                cacheNearJunction.Add(id, list);

                var possibleNextJunction = list
                                           .Where(m =>
                                                  Math.Abs(m.X - junction.X) >= minX
                                                  )
                                           .ToList();

                cachePossibleNextJunctionLeft.Add(id, possibleNextJunction.Where(m => m.X < junction.X).ToArray());
                cachePossibleNextJunctionRight.Add(id, possibleNextJunction.Where(m => m.X > junction.X).ToArray());
            }

            int numSol = 0;

            List <Line> possibleSol = new List <Line>();


            // We use a dictionary here because we need a fast way to remove entry.
            // We reduce computation and we also merge solutions.
            var elements = listJunction.OrderBy(m => m.Y).ToDictionary(m => m.X | m.Y << 16, m => m);

            int skipSol = 0;

            while (elements.Any())
            {
                var start = elements.First().Value;
                elements.Remove(start.X | start.Y << 16);

                Dictionary <int, List <int> > usedJunctionsForGapX = new Dictionary <int, List <int> >();
                List <Line> listSolutions   = new List <Line>();
                var         junctionsForGap = cacheNearJunction[start.X | start.Y << 16];

                for (int iGap = 0; iGap < junctionsForGap.Length; iGap++)
                {
                    var gap = junctionsForGap[iGap];

                    // Useless because it's already done with: cacheNearJunction.

                    /*
                     * var gapY = Math.Abs(gap.Y - start.Y);
                     * if (gapY > 2)
                     * {
                     *      continue;
                     * }*/

                    var gapX = Math.Abs(gap.X - start.X);
                    if (gapX <= minX || gapX > maxX)
                    {
                        continue;
                    }

                    // We will reduce list of solution by checking if the solution is already found.
                    //if (listSolutions.Any(m => Math.Abs(m.GapX - gapX) < 2 && m.Junctions.Contains(start)))
                    if (usedJunctionsForGapX.ContainsKey(gap.X | gap.Y << 16) &&
                        usedJunctionsForGapX[gap.X | gap.Y << 16].Any(m => Math.Abs(m - gapX) < 10))
                    {
                        skipSol++;
                        continue;
                    }

                    List <Junction> curSolution = new List <Junction>();
                    curSolution.Add(start);

                    int numElementsRight = FindElementsOnDirection(cachePossibleNextJunctionRight, start, gap, gapX, curSolution);
                    int numElementsLeft  = FindElementsOnDirection(cachePossibleNextJunctionLeft, start, gap, -gapX, curSolution);

                    int numElements = numElementsLeft + numElementsRight;

                    if (numElements >= options.MinNumElements)
                    {
                        if (numSol == options.MaxSolutions)
                        {
                            // Something wrong happen. Too much solution for now.
                            // If we continue, we would spend too much time processing the image.
                            // Let's suppose we don't know.
                            return(new OpenFieldReaderResult
                            {
                                // Too much solution. You may want to increase MaxSolutions.
                                ReturnCode = 30
                            });
                        }

                        numSol++;
                        listSolutions.Add(new Line
                        {
                            GapX      = gapX,
                            Junctions = curSolution.ToArray()
                        });
                        foreach (var item in curSolution)
                        {
                            List <int> listGapX;
                            if (!usedJunctionsForGapX.ContainsKey(item.X | item.Y << 16))
                            {
                                listGapX = new List <int>();
                                usedJunctionsForGapX.Add(item.X | item.Y << 16, listGapX);
                            }
                            else
                            {
                                listGapX = usedJunctionsForGapX[item.X | item.Y << 16];
                            }
                            listGapX.Add(gapX);
                        }
                    }
                }

                Line bestSol = listSolutions.OrderByDescending(m => m.Junctions.Count()).FirstOrDefault();

                if (bestSol != null)
                {
                    // Too slow. (faster if we skip removal)
                    // But, we have more solutions.
                    foreach (var item in bestSol.Junctions)
                    {
                        elements.Remove(item.X | item.Y << 16);
                    }

                    possibleSol.Add(bestSol);
                }
            }


            if (options.Verbose)
            {
                Console.WriteLine("Skip solutions counter: " + skipSol);
                Console.WriteLine(numSol + " : Solution found");
                Console.WriteLine(possibleSol.Count + " : Best solution found");
            }

            // Let's merge near junctions. (vertical line)
            // We assign a group id for each clusters.

            Dictionary <int, int> junctionToGroupId = new Dictionary <int, int>();

            int nextGroupId = 1;

            foreach (var curSolution in possibleSol)
            {
                if (curSolution.Junctions.First().GroupId == 0)
                {
                    for (int i = 0; i < curSolution.Junctions.Length; i++)
                    {
                        ref var j = ref curSolution.Junctions[i];
                        j.GapX = curSolution.GapX;
                    }

                    // Not assigned yet.

                    // Find near junction.
                    int groupId = 0;

                    foreach (var item in curSolution.Junctions)
                    {
                        var alreadyClassified = cacheNearJunction[item.X | item.Y << 16]
                                                .Where(m =>
                                                       // Doesn't work with struct.
                                                       //m.GroupId != 0 &&
                                                       Math.Abs(m.X - item.X) <= 5 &&
                                                       Math.Abs(m.Y - item.Y) <= 3
                                                       // Doesn't work with struct.
                                                       //Math.Abs(m.GapX - item.GapX) <= 2
                                                       ).Where(m => junctionToGroupId.ContainsKey(m.X | m.Y << 16));
                        if (alreadyClassified.Any())
                        {
                            Junction junction = alreadyClassified.First();
                            groupId = junctionToGroupId[junction.X | junction.Y << 16];
                            //groupId = alreadyClassified.First().GroupId;
                            break;
                        }
                    }

                    if (groupId == 0)
                    {
                        // Not found.

                        // Create a new group.
                        nextGroupId++;

                        groupId = nextGroupId;
                    }

                    for (int i = 0; i < curSolution.Junctions.Length; i++)
                    {
                        ref var j = ref curSolution.Junctions[i];
                        j.GroupId = groupId;
                        int id = j.X | j.Y << 16;
                        if (!junctionToGroupId.ContainsKey(id))
                        {
                            junctionToGroupId.Add(id, groupId);
                        }
                    }
                }
Esempio n. 2
0
        private static void Run(OpenFieldReaderOptions options)
        {
            try
            {
                using (var image = Image.Load(options.InputFile))
                {
                    try
                    {
                        int row = image.Height;
                        int col = image.Width;

                        int[] imgData = new int[row * col];
                        for (int y = 0; y < row; y++)
                        {
                            for (int x = 0; x < col; x++)
                            {
                                var pixel = image[x, y];
                                var val   = pixel.R | pixel.G | pixel.B;
                                imgData[y + x * row] = val > 122 ? 0 : 255;
                            }
                        }

                        var result = OpenFieldReader.FindBoxes(imgData, row, col, options);

                        if (result.ReturnCode != 0)
                        {
                            if (options.Verbose)
                            {
                                Console.WriteLine("Exit with code: " + result.ReturnCode);
                            }
                            Environment.Exit(result.ReturnCode);
                        }

                        if (options.OutputFile == "std")
                        {
                            // Show result on the console.

                            Console.WriteLine("Boxes: " + result.Boxes.Count);
                            Console.WriteLine();

                            int iBox = 1;
                            foreach (var box in result.Boxes)
                            {
                                Console.WriteLine("Box #" + iBox);

                                foreach (var element in box)
                                {
                                    Console.WriteLine("  Element: " +
                                                      element.TopLeft + "; " +
                                                      element.TopRight + "; " +
                                                      element.BottomRight + "; " +
                                                      element.BottomLeft);
                                }

                                iBox++;
                            }
                            Console.WriteLine("Press any key to continue...");
                            Console.ReadLine();
                        }
                        else
                        {
                            // Write result to output file.
                            var outputPath = options.OutputFile;
                            var json       = JsonSerializer.ToJsonString(result);
                            File.WriteAllText(outputPath, json);
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine("File: " + options.InputFile);
                        Console.WriteLine("Something wrong happen: " + ex.Message + Environment.NewLine + ex.StackTrace);
                        Environment.Exit(3);
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("File: " + options.InputFile);
                Console.WriteLine("Something wrong happen: " + ex.Message + Environment.NewLine + ex.StackTrace);
                Environment.Exit(2);
            }
        }