Finding Correctly Filled Answer Bubbles with OpenCV [closed] - c#

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 6 months ago.
Improve this question
I'm designing an answer sheet scorer and currently only have 1 major issue left to deal with and its the answer bubbles. People can fill these in all sorts of ways Filled Answer Bubbles, I have tried using Cv2.HoughCircles() but it doesn't pick up the weird circles and since you have to specific a radius if its too small or too big it wont pick them up Example of HoughCircles attempt. If I was able to at least get all the circles I could probably use Cv2.CountNonZero() after finding the range of white space to consider an answer good/bad. Does anyone have any suggestions I could give a try? Any help is appreciated.
Here's the portion that makes them.
//Gray Image
Mat GrayImage = new Mat();
Cv2.CvtColor(startingImage, GrayImage, ColorConversionCodes.BGR2GRAY);
//Making clear
Mat thresholdImage = new Mat();
Cv2.Threshold(GrayImage, thresholdImage, 100, 255, ThresholdTypes.BinaryInv);
Mat guassianBlurImage = new Mat();
Cv2.GaussianBlur(thresholdImage, guassianBlurImage, new OpenCvSharp.Size(5, 5), 0);
Mat cannyImage = new Mat();
int lower = (int)Math.Max(0, (1.0 - 0.33) * 126);
int upper = (int)Math.Min(255, (1.0 + 0.33) * 126);
Cv2.Canny(guassianBlurImage, cannyImage, lower, upper);
//Finding the Question circles
Mat copy = guassianBlurImage.Clone();
//Image (gray), type, dp, minDist, param1, param2, minRadius, maxRadius
var circles = Cv2.HoughCircles(copy, HoughModes.Gradient, 1, 10, 1, 25, 13, 18);
//Just so we can see the circles
Foreach (var cir in circles)
{
//Debug.Print(cir.Radius.ToString());
Cv2.Circle(startingImage, (int)cir.Center.X, (int)cir.Center.Y, (int)cir.Radius, Scalar.Green, 4);
}

I cleaned up my adobe template which had the circles. They were spaced wrongly so I fixed that. This then got me better images of each singular bubble using my custom method to tile the image.
Below is how I call my method and a small example of what it produces:
List<Mat> questionMats = new List<Mat>();
utils.TileImage(WarpThresholdImage, 3, 8, false, questionMats);
List<Mat> bubbleMats = new List<Mat>();
int n = 0;
foreach (var mat in questionMats)
{
utils.TileImage(mat, 8, 1, false, bubbleMats, "bubble" + n);
n++;
}
After this I am able to determine the min/max of white pixels using Cv2.CountNonZero() kind of jankly by changing the test image with 3 different versions which have empty bubbles, all filled, and ones that are invalid. I used the following code.
//Sample each bubble get nonzero count find min and max for normal versions of filled and unfilled. Filter based on results
int min = 20000;
int max = 0;
/* These represent the test I did to confirm ranges
* lowerNonZeroUnFilled = 849;
* upperNonZeroUnFilled = 1328;
* lowerNonZeroNormalFilled = 643;
* upperNonZeroNormalFilled = 1261;
* lowerNonZeroBadFilled = 602;
* upperNonZeroBadFilled = 2201;
*/
for (int i = 0; i < bubbleMats.Count(); i++)
{
int total = Cv2.CountNonZero(bubbleMats[i]);
//Empty Spaces
if (total == 0) { }
// 600 is the lowest value a filled circle will be
if(total > 600)
{
if(total < min)
{
min = total;
}
if(max < total)
{
max = total;
}
//Cv2.ImShow("Bubble" + i + "-" + total, bubbleMats[i]);
}
}
So this gets me what I want. This probably has issues but I'm confident I can deal with it later on.

Related

Find points with maximum visibility in a room-as-grid

I have a 2D grid of cells, like so:
I want to find the "centroids," or the places in each room that can see the most other cells. For example, "centroid" 1 can see 500 other cells, "centroid" 2 can see 400 other cells (NOT included in the first 500), and so on (if there's a better name for this let me know).
I'm currently doing this with the code below.
public void SetCentroids(CellWalkableState[,] grid)
{
centroids = new List<((int, int), int)>();
List<(int, int)> cellsCopy = new List<(int, int)>();
for (int i = 0; i < cells.Count; i++)
{
cellsCopy.Add(cells[i]);
}
Debug.Log(DateTime.Now.ToString("o") + " - Setting centroids for room with " + cells.Count + " cells");
var perCellInView = cellsCopy.AsParallel().Select(x => (x, StaticClass.FindInView(x, grid))).ToList();
var force_start = perCellInView.First();
Debug.Log(DateTime.Now.ToString("o") + " - got in view");
var perCellInViewOrdered = perCellInView.AsParallel().OrderByDescending(xxx => xxx.Item2.Count);
var force_start_1 = perCellInViewOrdered.First();
Debug.Log(DateTime.Now.ToString("o") + " - sorted");
List<(int, int)> roomCellsAdded = new List<(int, int)>();
while(roomCellsAdded.Count < (cells.Count*0.9))
{
if(cellsCopy.Count == 0)
{
Debug.LogError("something is wrong here.");
}
var centroid = perCellInViewOrdered.First().x;
var centroidCells = perCellInViewOrdered.First().Item2;
if(centroidCells.Count == 0)
{
Debug.Log("this shouldnt be happening");
break;
}
roomCellsAdded.AddRange(centroidCells);
centroids.Add((centroid, centroidCells.Count));
Debug.Log(DateTime.Now.ToString("o") + " - added centroids, " + roomCellsAdded.Count + " cells in view");
var loopPerCellInView = perCellInView.AsParallel().Where(x => centroids.Select(y => y.Item1).Contains(x.x) == false).Select(x => (x.x, x.Item2.Except(roomCellsAdded).ToList())).ToList();
Debug.Log(DateTime.Now.ToString("o") + " - excluded");
perCellInViewOrdered = loopPerCellInView.AsParallel().OrderByDescending(xxx => xxx.Item2.Count);
Debug.Log(DateTime.Now.ToString("o") + " - resorted");
}
}
public static List<(int, int)> FindInView((int,int) start, CellWalkableState[,] grid)
{
List<(int, int)> visible = new List<(int, int)>() { start };
bool alive = true;
int r = 1;
var length_x = grid.GetLength(0);
var length_y = grid.GetLength(1);
List<(int, int)> searched = new List<(int, int)>();
List<double> angles = new List<double>();
while(alive)
{
//alive = false;
int newR = r;
int count = CountFromR(newR);
var angleInc = 360.0 / count;
var rNexts = Enumerable.Repeat(1, count).ToArray();
for (int i = 0; i < count; i++)
{
var angle = angleInc * i;
if(angles.Contains(angle) == false)
{
angles.Add(angle);
float cos = Mathf.Cos((float)(Mathf.Deg2Rad * angle));
float sin = Mathf.Sin((float)(Mathf.Deg2Rad * angle));
var b = r;
var p = i % (r * 2);
var d = math.sqrt(math.pow(b, 2) + math.pow(p, 2));
var dScaled = d / r;
bool keepGoing = true;
while(keepGoing)
{
var rCur = dScaled * (rNexts[i]);
var loc = (start.Item1 + Mathf.RoundToInt(rCur * cos), start.Item2 + Mathf.RoundToInt(rCur * sin));
if (searched.Contains(loc) == false)
{
searched.Add(loc);
if (loc.Item1 >= 0 && loc.Item1 < length_x && loc.Item2 >= 0 && loc.Item2 < length_y)
{
if (grid[loc.Item1, loc.Item2] == CellWalkableState.Interactive || grid[loc.Item1, loc.Item2] == CellWalkableState.Walkable)
{
visible.Add(loc);
}
else
{
keepGoing = false;
}
}
else
{
keepGoing = false; // invalid, stop
}
}
else
{
if (visible.Contains(loc) == false)
{
keepGoing = false; // can stop, because we can't see past this place
}
}
if(keepGoing)
{
rNexts[i]++;
}
}
}
}
angles = angles.Distinct().ToList();
searched = searched.Distinct().ToList();
visible = visible.Distinct().ToList();
if(rNexts.All(x => x <= r))
{
alive = false;
}
else
{
r = rNexts.Max();
}
}
return visible;
}
static int CountFromR(int r)
{
return 8 * r;
}
The "short" summary of the code above is that each location first determines what cells around itself it can see. That becomes a list of tuples, List<((int,int), List<(int,int)>)>, where the first item is the location and the second is all cells it views. That main list is sorted by the count of the sublist, such that the item with the most cells-it-can-vew is first. That's added as a centroid, and all cells it can view are added to a second ("already handled") list. A modified "main list" is formed, with each sublist now excluding anything in the second list. It loops doing this until 90% of the cells have been added.
Some output:
2021-04-27T15:24:39.8678545-04:00 - Setting centroids for room with 7129 cells
2021-04-27T15:45:26.4418515-04:00 - got in view
2021-04-27T15:45:26.4578551-04:00 - sorted
2021-04-27T15:45:27.3168517-04:00 - added centroids, 4756 cells in view
2021-04-27T15:45:27.9868523-04:00 - excluded
2021-04-27T15:45:27.9868523-04:00 - resorted
2021-04-27T15:45:28.1058514-04:00 - added centroids, 6838 cells in view
2021-04-27T15:45:28.2513513-04:00 - excluded
2021-04-27T15:45:28.2513513-04:00 - resorted
2021-04-27T15:45:28.2523509-04:00 - Setting centroids for room with 20671 cells
This is just too slow for my purposes. Can anyone suggest alternate methods of doing this? For all of the cells essentially the only information one has is whether they're "open" or one can see through them or not (vs something like a wall).
An approximate solution with fixed integer slopes
For a big speedup (from quadratic in the number of rooms to linear), you could decide to check just a few integer slopes at each point. These are equivalence classes of visibility, i.e., if cell x can see cell y along such a line, and cell y can see cell z along the same line, then all 3 cells can see each other. Then you only need to compute each "visibility interval" along a particular line once, rather than per-cell.
You would probably want to check at least horizontal, vertical and both 45-degree diagonal slopes. For horizontal, start at cell (1, 1), and move right until you hit a wall, let's say at (5, 1). Then the cells (1, 1), (2, 1), (3, 1) and (4, 1) can all see each other along this slope, so although you started at (1, 1), there's no need to repeat the computation for the other 3 cells -- just add a copy of this list (or even a pointer to it, which is faster) to the visibility lists for all 4 cells. Keep heading right, and repeat the process as soon as you hit a non-wall. Then begin again on the next row.
Visibility checking for 45-degree diagonals is slightly harder, but not much, and checking for other slopes in which we advance 1 horizontally and some k vertically (or vice versa) is about the same. (Checking for for general slopes, like for every 2 steps right go up 3, is perhaps a bit trickier.)
Provided you use pointers rather than list copies, for a given slope, this approach spends amortised constant time per cell: Although finding the k horizontally-visible neighbours of some cell takes O(k) time, it means no further horizontal processing needs to be done for any of them. So if you check a constant number of slopes (e.g., the four I suggested), this is O(n) time overall to process n cells. In contrast, I think your current approach takes at least O(nq) time, where q is the average number of cells visible to a cell.

C# Creating an Index in a For Loop for an Array [closed]

Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 2 years ago.
Improve this question
I am attempting to do an assignment and I have attempted to try to record the results of two dice rolls by pulling the possibilities I could get from an array. Basically if I rolled a 2, 4, 5, 5, and 2, I'd record I got two 2s, one 4, and two 5s. However, I am trying to figure out the best way to record it without having to resort to list every single variable 2-12. Might someone be able to assist me in learning how to make the shortcut for this from the code I provide? The code is as follows:
using System;
namespace Assignment
{
class Program
{
static void Main(string[] args)
{
//Initialize variable(s).
int diceRollNum = 0;
//Create the array.
int[] DiceResultArray = new int[11];
//Create the random number.
Random diceRoll = new Random();
//Write out Headers.
Console.WriteLine($"Roll\tCount");
//
for (diceRollNum = 0; diceRollNum < 36000; diceRollNum++)
{
//Roll the dice.
int firstDice = diceRoll.Next(1, 6);
int secondDice = diceRoll.Next(1, 6);
//Add the dice sums.
diceRollNum = firstDice + secondDice;
//Record results.
DiceResultArray[diceRollNum] =
}
//
for (int i = 0; i < DiceResultArray.Length; i++)
{
Console.WriteLine($"{i+2}\t{DiceResultArray[i]}");
}
}
}
}
We are looking for specifically what happens under the Record Results comment. If anyone could help explain this to me, that would be wonderful!
Your code has few issues
diceRollNum is updated in the loop and it runs infinitely.
Random.Next(minValue, maxValue) generates a random value excluding the maxValue. Therefore to get a random number between 1 and 6 we should invoke Next() passing 1 and 7 min and max as parameters respectively
Should reduce 1 from diceRollSum (a new variable which stores the sum of dice values) when accessing the array, as the array is indexed from 0-11, not 1-12
using System;
namespace Assignment
{
class Program
{
static void Main( string[] args )
{
//Initialize variable(s).
int diceRollNum = 0;
//Create the array.
int[] DiceResultArray = new int[12];
//As 1 is not a possible value for the sum of dice values, we can instantiate an array with 11 items and reduce 2 from diceRollSum
//A slightly optimized Approach noted by Andrew
//int[] DiceResultArray = new int[11];
//Creates random instance.
Random diceRoll = new Random();
//Write out Headers.
Console.WriteLine( $"Roll\tCount" );
//
for (diceRollNum = 0; diceRollNum < 36000; diceRollNum++)
{
//Roll the dice.
int firstDice = diceRoll.Next( 1, 7 );
int secondDice = diceRoll.Next( 1, 7 );
//Add the dice sums.
int diceRollSum = firstDice + secondDice;
//Record results.
DiceResultArray[diceRollSum - 1]++;
//Slightly Optimized
//DiceResultArray[diceRollSum - 2]++;
}
//
for (int i = 0; i < DiceResultArray.Length; i++)
{
Console.WriteLine( $"{i+1}\t{DiceResultArray[i]}" );
//Slightly Optimized
//Console.WriteLine( $"{i+2}\t{DiceResultArray[i]}" );
}
}
}
}

new vector using .Net System.Numeric fills only half of the vector length

I'm doing a digital filter and using a vector and SIMD instructions to make it faster, but during debugging I noticed that when a new vector was created it only initialized half of the items in the vector, for example when creating a vector with length of 8, only the first 4 items of the vector would have value, the rest would be 0, even with the array that is used to create the vector having 31 items, all different than 0. This is causing the filter use only half of the coefficients and half the data.
The relevant code is below.
var simdLength = Vector<float>.Count;
var leftOver = m_filterSize % simdLength;
for (int i = 0; i < m_filterSize - leftOver; i += simdLength)
{
var filterVector = new Vector<float>(m_filter, i);
var dataVector = new Vector<float>(data, i);
filteredValueVector += filterVector * dataVector;
}
There is some code after to treat the left over but it is not a vector and works fine.
This is a debugger bug:
int capacity = Vector<float>.Count;
float[] testVals = Enumerable.Range(0, capacity).Select(i => (float)i).ToArray();
Vector<float> testV = new(testVals);
float[] returnedVals = Enumerable.Range(0, capacity).Select(i => testV[i]).ToArray();
Interestingly, string.Join always formats your vector to its own choosing:
Vector<int> powersOfTwo = new Vector<int>(Enumerable.Range(0, Vector<int>.Count).Select(i => 1 << i).ToArray());
string powersOfTwoString = string.Join("abc", powersOfTwo);

Character Recognizing using Aforge.net Nural network

I am trying to recognize 0 to 9 digits using Aforge.net . I tried everything but I am still unable to get result please look at my program and why I am unable to recognize digits. Problem may be in number of hidden layers, learning rate or input data , I have tried it by changing number of hidden layers and learning rate. Please suggest ideas.
// opening file
OpenFileDialog open = new OpenFileDialog();
ActivationNetwork enactivation = new ActivationNetwork(new BipolarSigmoidFunction(1), 3886,10, 10);
double[][] input = new double[10][];
double[][] output = new double[10][];
//generating input data using Feature class -- which code is given below
Feature feature = new Feature();
//iterating for all 10 digits.
for (int i = 0; i < 10; i++)
{
open.ShowDialog();
Bitmap bitmap = new Bitmap(open.FileName);
double[] features = feature.features(bitmap);
input[i] = features;
features = feature.features(bitmap);
output[i] = feature.features(bitmap);
}
enactivation.Randomize();
BackPropagationLearning learn = new BackPropagationLearning(enactivation);
//learning
learn.LearningRate = 0.005f;
learn.Momentum = 0.005f;
double errora;
int iteration = 0;
while (true)
{
errora = learn.RunEpoch(input, output);
if (errora < 0.0006)
break;
else if (iteration > 23000)
break;
iteration++;
// Console.WriteLine("error {0} {1} ", errora, iteration);
}
double[] sample;
open.ShowDialog();
Bitmap temp = new Bitmap(open.FileName);
// providing input for computation using feature class
sample = feature.features(temp);
foreach (double daa in enactivation.Compute(sample))
{
Console.WriteLine(daa);
}
Class Feature for providing input for training nural network
class Feature
{
public double[] features(Bitmap bitmap)
{
//feature
double[] feature = new double[bitmap.Width * bitmap.Height];
int featurec = 0;
for (int vert = 0; vert < bitmap.Height; vert++)
{
for (int horizantal = 0; horizantal < bitmap.Width; horizantal++)
{
feature[featurec] = bitmap.GetPixel(horizantal, vert).ToArgb();
if (feature[featurec] < 1)
{
feature[featurec] = -0.5;
}
else
{
feature[featurec] = 0.5;
}
featurec++;
}
}
return feature;
}
}
I haven't used aforge, but re. using backprop neural nets for this problem:
You need something like a 10x10 input grid with each cell in the grid getting 1/100 of the image
You need at least one, possibly 2, hidden layers
The net will train faster with a bias input - meaning a source of a fixed value - for each cell (this lets the cells train faster: Role of Bias in Neural Networks)
I'd never start in bp mode but always run something a statistical annealing first. Bp is for descending inside a local minimum once one is found
Also:
Have you successfully used aforge for other problems?
What happens when you try to train the net?

A way to list to array of bytes pixels values of bricks

Sorry I had no idea how set a topic which could express what help I need.
I have in an array of bytes, values for each pixel from a bitmap. It is a one dimensional array, from left to right. It takes each row and add it to the end of array's index.
I would like to split a bitmap to 225(=15*15) pieces. Each brick has for example dimension 34x34 and the length of array is then 260100(=225*34*34). So as you see now we will need 15 bricks on width and on height.
Few months ago I was using two loops starting from 0 - 14. I wrote own long code to get all that 34x34 bricks. However I didn't used any array which was storing all values.
Now I have a one dimensional array because marshal copy and bitmapdata with bitlocks were the best way to fast copy all pixels' values to array.
But I stand face to face with problem how to get 34 elements then one row lower and another one knowing that on 35 level will be another brick with its own starting value..
PS. edit my post if something is not good.
Few people could say "first make any your test code". I tried that but what I got was just trash and I really don't know how to do that.
This method was used to crop image to smaller images containing bricks. But I don't want store small images of brick. I need values storing in array of bytes.
Under, there is a proof.
private void OCropImage(int ii, int jj, int p, int p2)
{
////We took letter and save value to binnary, then we search in dictionary by value
this.rect = new Rectangle();
this.newBitmap = new Bitmap(this.bitmap);
for (ii = 0; ii < p; ii++)
{
for (jj = 0; jj < p2; jj++)
{
////New bitmap
this.newBitmap = new Bitmap(this.bitmap);
////Set rectangle working area with letters
this.rect = new Rectangle(jj * this.miniszerokosc, ii * this.miniwysokosc, this.miniszerokosc, this.miniwysokosc);
////Cut single rectangle with letter
this.newBitmap = this.newBitmap.Clone(this.rect, this.newBitmap.PixelFormat);
////Add frame to rectangle to delet bad noise
this.OAddFrameToCropImage(this.newBitmap, this.rect.Width, this.rect.Height);
this.frm1.SetIm3 = (System.Drawing.Image)this.newBitmap;
////Create image with letter which constains less background
this.newBitmap = this.newBitmap.Clone(this.GetAreaLetter(this.newBitmap), this.newBitmap.PixelFormat);
////Count pixels in bitmap
this.workingArea = this.GetBinnary(this.newBitmap);
var keysWithMatchingValues = this.alphabetLetters.Where(x => x.Value == this.workingArea).Select(x => x.Key);
foreach (var key in keysWithMatchingValues)
{
this.chesswords += key.ToString();
}
}
this.chesswords += Environment.NewLine;
var ordered = this.alphabetLetters.OrderBy(x => x.Value);
}
}
PS2. sorry for my English, please correct it if it is needed.
If I get you right, then if you have an image like this
p00|p01|p02|...
---+---+-------
p10|p11|p12|...
---+---+-------
p20|p21|p22|...
---+---+---+---
...|...|...|...
Which is stored in an array in left-to-right row scan like this:
p00,p01,...,p0n, p10,p11,...,p1n, p20,p21, ...
If I understand you correctly, what you want to be able to do, is to take a given rectangle (from a certain x and y with a certain width and height) from the image. Here is code to do this, with explanations:
byte[] crop_area (byte[] source_image, int image_width, int image_height,
int start_x, int start_y, int result_width, int result_height)
{
byte[] result = new byte[result_width * result_height];
int endX = x + result_width;
int endY = y + result_height;
int pos = 0;
for (int y = startY; y < endY; y++)
for (int x = startX; x < endX; x++)
{
/* To get to the pixel in the row I (starting from I=1), we need
* to skip I-1 rows. Since our y indexes start from row 0 (not 1),
* then we don't need to subtract 1.
*
* So, the offset of the pixel at (x,y) is:
*
* y * image_width + x
* |-----------------------| |-----------------|
* Skip pixels of y rows Offset inside row
*/
result[pos] = source_image[y * image_width + x];
/* Advance to the next pixel in the result image */
pos++;
}
return result;
}
Then, to take the block in the row I and column J (I,J=0,...,14) do:
crop_area (source_image, image_width, image_height, J*image_width/15, I*image_height/15, image_width/15, image_height/15)

Categories