I would like to use this "Levenshtein" function to assess similarities between two strings (to check if user has committed a spelling mistake).
Since I work on null safe mode, it points out an error with the LIST constructor :
List<List<int>> d = List.generate(sa + 1, (int i) => List(sb + 1));
What can I write to replace List(sb+1)); ?
int levenshtein(String a, String b) {
a = a.toUpperCase();
b = b.toUpperCase();
int sa = a.length;
int sb = b.length;
int i, j, cost, min1, min2, min3;
int levenshtein;
// ignore: deprecated_member_use
List<List<int>> d = List.generate(sa + 1, (int i) => List(sb + 1));
if (a.length == 0) {
levenshtein = b.length;
return (levenshtein);
}
if (b.length == 0) {
levenshtein = a.length;
return (levenshtein);
}
for (i = 0; i <= sa; i++) d[i][0] = i;
for (j = 0; j <= sb; j++) d[0][j] = j;
for (i = 1; i <= a.length; i++)
for (j = 1; j <= b.length; j++) {
if (a[i - 1] == b[j - 1])
cost = 0;
else
cost = 1;
min1 = (d[i - 1][j] + 1);
min2 = (d[i][j - 1] + 1);
min3 = (d[i - 1][j - 1] + cost);
d[i][j] = min(min1, min(min2, min3));
}
levenshtein = d[a.length][b.length];
return (levenshtein);
}
You can use List.generate for the inner list as well.
List<List<int>> d = List.generate(sa + 1, (int i) => List.generate(sb + 1, (int j) => 0));
Also, if they're all going to be initialized to 0 you can just do this too:
List<List<int>> d = List.filled(sa + 1, List.filled(sb + 1, 0));
I'm making an image editing program in c++ using sfml and tried to add image filters using:
int clamp(int value, int min, int max)
{
if (value < min)
return min;
if (value > max)
return max;
return value;
}
void MyImage::applyKernel(std::vector<std::vector<int>> kernel)
{
int index(0), tempx(0), tempy(0);
int wr(0), wg(0), wb(0), wa(0), sum(0);
auto newPixels = new sf::Uint8[this->size_y * this->size_x * 4];
// Calculate the sum of the kernel
for (int i = 0; i < kernel.size(); i++) {
for (int j = 0; j < kernel[i].size(); j++) {
sum += kernel[i][j];
}
}
for (int y = 0; y < this->size_y; y++) {
for (int x = 0; x < this->size_x; x++) {
/*
Calculate weighted sum from kernel
*/
wr = wg = wb = wa = 0;
for (int i = 0; i < kernel.size(); i++) {
for (int j = 0; j < kernel[i].size(); j++) {
/*
Calculates the coordinates of the kernel relative to the pixel we are changing
*/
tempx = x + (j - floor(kernel[i].size() / 2));
tempy = y + (i - floor(kernel.size() / 2));
//std::cout << "kernel=(" << j << ", " << i << "), pixel=(" << x << ", " << y << ") tempPos=(" << tempx << ", " << tempy << ")\n";
/*
This code below should have the effect of mirroring the image in the case the kernel coordinate is out of bounds (along the edge of the image)
*/
tempx = (tempx < 0) ? -1 * tempx : tempx;
tempy = (tempy < 0) ? -1 * tempy : tempy;
tempx = (tempx > this->size_x) ? x - (j - floor(kernel[i].size() / 2)) : tempx;
tempy = (tempy > this->size_y) ? y - (i - floor(kernel.size() / 2)) : tempy;
if (tempx >= 0 && tempx < this->size_x && tempy >= 0 && tempy < this->size_y) {
index = (((tempy * this->size_x) - tempy) + (tempx)) * 4;
wr += kernel[i][j] * this->pixels[index];
wg += kernel[i][j] * this->pixels[index + 1];
wb += kernel[i][j] * this->pixels[index + 2];
wa += kernel[i][j] * this->pixels[index + 3];
}
}
}
if (sum) {
wr /= sum;
wg /= sum;
wb /= sum;
wa /= sum;
}
index = (((y * this->size_x) - y) + (x)) * 4;
newPixels[index] = clamp(wr, 0, 255); // Red
newPixels[index + 1] = clamp(wg, 0, 255); // Green
newPixels[index + 2] = clamp(wb, 0, 255); // Blue
newPixels[index + 3] = clamp(wa, 0, 255); // Alpha
}
}
this->pixels = newPixels;
// Copies the data from our sf::Uint8 array to the image object to be displayed => Removes the overhead of calling setPixel(x,y,color) for every pixel {As a side note setPixel() should always be avoided}|
this->im->create(this->size_x, this->size_y, this->pixels);
}
I was trying to use [-1,-1,-1], [-1,8,-1]. [-1,-1,-1] for edge detection but just ended up with a white image except for some pixels near the bottom. I've tried different images and kernels out but any that add to 0 don't work. For example if I take the edge detection kernel above and change the 8 to a 9, it gives an expected result. Is there something wrong with my idea of how convolution kernels work or is it just a bug in my code?
Thank you.
So, I decided to create a simple Canny edge detector just as exercise before biting harder topics with image processing.
I tried to follow the typical path of Canny:
1. Grayscaling the image
2. Gaussian filter to blur the noise
3. Edge detection - I use both Sobel and Scharr
4. Edge thinning - I used non-maximum suppression in direction depending on gradient direction - vertical, horizontal, 45 diagonal or 135 diagonal
5. Hysteresis
I somehow managed to get it working with Scharr's detection but I have recurring problem with double or multiple edges, espacially with Sobel. I can't really find a set of parameters which will make it work.
My algorithm for Sobel:
void sobel(sf::Image &image, pixldata **garray, float division)
{
int t1 = 0, t2 = 0, t3 = 0, t4 = 0;
sf::Color color;
sf::Image bufor;
bufor.create(image.getSize().x, image.getSize().y, sf::Color::Cyan);
for (int i = 1;i < image.getSize().y - 1;i++)
{
for (int j = 1;j < image.getSize().x - 1;j++)
{
t1 = (- image.getPixel(j - 1, i - 1).r - 2 * image.getPixel(j - 1, i).r - image.getPixel(j - 1, i + 1).r + image.getPixel(j + 1, i - 1).r + 2 * image.getPixel(j + 1, i).r + image.getPixel(j + 1, i + 1).r) / division;
t2 = (- image.getPixel(j - 1, i).r - 2 * image.getPixel(j - 1, i + 1).r - image.getPixel(j, i + 1).r + image.getPixel(j + 1, i).r + 2 * image.getPixel(j + 1, i - 1).r + image.getPixel(j, i - 1).r) / division;
t3 = (- image.getPixel(j - 1, i + 1).r - 2 * image.getPixel(j, i + 1).r - image.getPixel(j + 1, i + 1).r + image.getPixel(j - 1, i - 1).r + 2 * image.getPixel(j, i - 1).r + image.getPixel(j + 1, i - 1).r) / division;
t4 = (- image.getPixel(j, i + 1).r - 2 * image.getPixel(j + 1, i + 1).r - image.getPixel(j + 1, i).r + image.getPixel(j - 1, i).r + 2 * image.getPixel(j - 1, i - 1).r + image.getPixel(j, i - 1).r) / division;
color.r = (abs(t1) + abs(t2) + abs(t3) + abs(t4));
color.g = (abs(t1) + abs(t2) + abs(t3) + abs(t4));
color.b = (abs(t1) + abs(t2) + abs(t3) + abs(t4));
garray[j][i].gx = t1;
garray[j][i].gy = t3;
garray[j][i].gtrue = sqrt(t1*t1 + t2*t2 + t3*t3 + t4*t4);
garray[j][i].gsimpl = sqrt(t1*t1 + t2*t2);
t1 = abs(t1);
t2 = abs(t2);
t3 = abs(t3);
t4 = abs(t4);
if (t1 > t4 && t1 > t3 && t1 > t2)
garray[j][i].fi = 0;
else if (t2 > t4 && t2 > t3 && t2 > t1)
garray[j][i].fi = 45;
else if (t3 > t4 && t3 > t2 && t3 > t1)
garray[j][i].fi = 90;
else if (t4 > t3 && t4 > t2 && t4 > t1)
garray[j][i].fi = 135;
else
garray[j][i].fi = 0;
if (sqrt(t1*t1 + t2*t2 + t3*t3 + t4*t4) < 0)
{
color.r = 0;
color.g = 0;
color.b = 0;
}
else if (sqrt(t1*t1 + t2*t2 + t3*t3 + t4*t4) > 255)
{
color.r = 255;
color.g = 255;
color.b = 255;
}
else
{
color.r = sqrt(t1*t1 + t2*t2 + t3*t3 + t4*t4);
color.g = sqrt(t1*t1 + t2*t2 + t3*t3 + t4*t4);
color.b = sqrt(t1*t1 + t2*t2 + t3*t3 + t4*t4);
}
bufor.setPixel(j, i, color);
}
}
image.copy(bufor, 0, 0);
}
Code for Scharr differs only in multiplying the pixels' values.
t1 = (-3 * image.getPixel(j - 1, i - 1).r - 10 * image.getPixel(j - 1, i).r - 3 * image.getPixel(j - 1, i + 1).r + 3 * image.getPixel(j + 1, i - 1).r + 10 * image.getPixel(j + 1, i).r + 3 * image.getPixel(j + 1, i + 1).r) / division;
t2 = (-3 * image.getPixel(j - 1, i).r - 10 * image.getPixel(j - 1, i + 1).r - 3 * image.getPixel(j, i + 1).r + 3 * image.getPixel(j + 1, i).r + 10 * image.getPixel(j + 1, i - 1).r + 3 * image.getPixel(j, i - 1).r) / division;
t3 = (-3 * image.getPixel(j - 1, i + 1).r - 10 * image.getPixel(j, i + 1).r - 3 * image.getPixel(j + 1, i + 1).r + 3 * image.getPixel(j - 1, i - 1).r + 10 * image.getPixel(j, i - 1).r + 3 * image.getPixel(j + 1, i - 1).r) / division;
t4 = (-3 * image.getPixel(j, i + 1).r - 10 * image.getPixel(j + 1, i + 1).r - 3 * image.getPixel(j + 1, i).r + 3 * image.getPixel(j - 1, i).r + 10 * image.getPixel(j - 1, i - 1).r + 3 * image.getPixel(j, i - 1).r) / division;
Thinning code:
void intelligentThin(sf::Image &image, int radius, pixldata **garray)
{
int xmax = image.getSize().x;
int ymax = image.getSize().y;
bool judgeandjury = true;
for (int i = 0;i < xmax;i++)
{
int leftBound = 0, rightBound = 0, ceilBound = 0, bottomBound = 0;
if (i < radius)
{
leftBound = 0;
rightBound = i + radius;
}
else if (i >= xmax - radius)
{
leftBound = i - radius;
rightBound = xmax - 1;
}
else
{
leftBound = i - radius;
rightBound = i + radius;
}
for (int j = 0;j < ymax;j++)
{
if (j < radius)
{
ceilBound = 0;
bottomBound = j + radius;
}
else if (j >= ymax - radius)
{
ceilBound = j - radius;
bottomBound = ymax - 1;
}
else
{
ceilBound = j - radius;
bottomBound = j + radius;
}
if (garray[i][j].fi == 0)
{
for (int t = leftBound; t <= rightBound; t++)
{
if ((image.getPixel(t, j).r >= image.getPixel(i, j).r) && (t != i))
{
judgeandjury = false;
}
}
}
else if (garray[i][j].fi == 135)
{
for (int l = leftBound, t = ceilBound; (l <= rightBound && t <= bottomBound); l++, t++)
{
if ((image.getPixel(l, t).r >= image.getPixel(i, j).r) && (t != j))
{
judgeandjury = false;
}
}
}
else if (garray[i][j].fi == 90)
{
for (int t = ceilBound; t <= bottomBound; t++)
{
if ((image.getPixel(i, t).r >= image.getPixel(i, j).r) && (t != j))
{
judgeandjury = false;
}
}
}
else if (garray[i][j].fi == 45)
{
for (int l = rightBound, t = ceilBound; (l >= leftBound && t <= bottomBound); l--, t++)
{
if ((image.getPixel(l, t).r >= image.getPixel(i, j).r) && (t != j))
{
judgeandjury = false;
}
}
}
if (judgeandjury == false)
{
image.setPixel(i, j, sf::Color::Black);
}
judgeandjury = true;
}
leftBound = rightBound = 0;
}
}
Hysteresis code:
void hysteresis(sf::Image &image, int radius, int uplevel, int lowlevel)
{
int xmax = image.getSize().x;
int ymax = image.getSize().y;
bool judgeandjury = false;
sf::Image bufor;
bufor.create(image.getSize().x, image.getSize().y, sf::Color::Cyan);
for (int i = 0;i < xmax;i++)
{
int leftBound = 0, rightBound = 0, ceilBound = 0, bottomBound = 0;
if (i < radius)
{
leftBound = 0;
rightBound = i + radius;
}
else if (i >= xmax - radius)
{
leftBound = i - radius;
rightBound = xmax - 1;
}
else
{
leftBound = i - radius;
rightBound = i + radius;
}
for (int j = 0;j < ymax;j++)
{
int currentPoint = image.getPixel(i, j).r;
if (j < radius)
{
ceilBound = 0;
bottomBound = j + radius;
}
else if (j >= ymax - radius)
{
ceilBound = j - radius;
bottomBound = ymax - 1;
}
else
{
ceilBound = j - radius;
bottomBound = j + radius;
}
if (currentPoint > uplevel)
{
judgeandjury = true;
}
else if (currentPoint > lowlevel)
{
for (int t = leftBound; t <= rightBound; t++)
{
for (int l = ceilBound; l <= bottomBound; l++)
{
if (image.getPixel(t, l).r > uplevel)
{
judgeandjury = true;
}
}
}
}
else judgeandjury = false;
if (judgeandjury == true)
{
bufor.setPixel(i, j, sf::Color::White);
}
else
{
bufor.setPixel(i, j, sf::Color::Black);
}
judgeandjury = false;
currentPoint = 0;
}
leftBound = rightBound = 0;
}
image.copy(bufor, 0, 0);
}
The results are quite unsatisfactionary for Sobel:
Thinning the Sobel
Sobel after hysteresis
With Scharr the results are way better:
Thinned Scharr
Scharr after hysteresis
Set of parameters:
#define thinsize 1
#define scharrDivision 1
#define sobelDivision 1
#define hysteresisRadius 1
#define level 40
#define hysteresisUpperLevelSobel 80
#define hysteresisLowerLevelSobel 60
#define hysteresisUpperLevelScharr 200
#define hysteresisLowerLevelScharr 100
As you can see, there is a problem with Sobel, which generate double edges. Scharr also generates some noise but I think it's acceptable. Of course, it always can get better, if someone could give some advice :)
What is the cause of this behaviour? Does it result from my mistakes or poor algorithms or maybe is it just a case of parameters?
EDIT:
posting main()
sf::Image imydz;
imydz.loadFromFile("lena.jpg");
int x = imydz.getSize().x;
int y = imydz.getSize().y;
pixldata **garray = new pixldata *[x];
for (int i = 0;i < x;i++)
{
garray[i] = new pixldata[y];
}
monochrome(imydz);
gauss(imydz, radius, sigma);
//sobel(imydz, garray, sobelDivision);
scharr(imydz, garray, scharrDivision);
intelligentThin(imydz, thinsize, garray);
hysteresis(imydz, hysteresisRadius, hysteresisUpperLevel, hysteresisLowerLevel);
Second edit - repaired suppression:
sf::Image bufor;
bufor.create(image.getSize().x, image.getSize().y, sf::Color::Black);
for (int i = 1;i < xmax - 1;i++)
{
for (int j = 1;j < ymax - 1;j++)
{
if (garray[i][j].fi == 0)
{
if (((image.getPixel(i, j).r >= image.getPixel(i + 1, j).r) && (image.getPixel(i, j).r > image.getPixel(i - 1, j).r)) ||
((image.getPixel(i, j).r > image.getPixel(i + 1, j).r) && (image.getPixel(i, j).r >= image.getPixel(i - 1, j).r)))
{
judgeandjury = true;
}
else judgeandjury = false;
}
...
if (judgeandjury == false)
{
bufor.setPixel(i, j, sf::Color::Black);
}
else bufor.setPixel(i, j, image.getPixel(i, j));
judgeandjury = false;
}
}
image.copy(bufor, 0, 0);
Repaired Scharr on Lena
It seems strange
Another test image - strange results
Before binarization
Ready gears
I haven't read your whole code in detail, there is much too much code there. But obviously your non-maximum suppression code is wrong. Let's look at what it does for one pixel in the middle of the image, where the gradient is close to 0 degrees:
leftBound = i - radius;
rightBound = i + radius;
// ...
for (int t = leftBound; t <= rightBound; t++)
{
if ((image.getPixel(t, j).r >= image.getPixel(i, j).r) && (t != i))
{
judgeandjury = false; // it's not a maximum: suppress
}
}
// ...
if (judgeandjury == false)
{
image.setPixel(i, j, sf::Color::Black);
}
Here, radius is set to 1 by the calling code. Any other value would be bad, so this is OK. I would remove that as a parameter altogether. Now your loop is:
for (int t = i-1; t <= t+1; t++)
if (t != i)
This means that you hit exactly two values of t. So this should of course be replaced with simpler code that does not loop, it will be more readable.
This is what it now does:
if ( (image.getPixel(i-1, j).r >= image.getPixel(i, j).r)
|| (image.getPixel(i+1, j).r >= image.getPixel(i, j).r)) {
judgeandjury = false; // it's not a maximum: suppress
}
So you suppress the pixel if it is not strictly larger than its neighbors. Looking back at the Wikipedia article, it seems that they suggest the same. But in fact, this is not correct, you want the point to be strictly larger than one of the two neighbors, and larger or equal to the other. This prevents the situation where the gradient happens to be equally strong on two neighboring pixels. The actual maximum can fall right in the middle of two pixels, yielding two pixels on this local maximum gradient with exactly the same value. But let's ignore this case for now, it is possible but not all that likely.
Next, you suppress the maximum... in the input image! This means that, when you reach the next pixel on this line, you will compare its value to this value that was just suppressed. Of course it will be larger, even though it was smaller than the original value at that location. That is, non-maxima will look like maxima because you put a neighboring pixel to 0.
So: write the result of the algorithm to an output image:
if (judgeandjury == true)
{
output.setPixel(i, j, image.getPixel(i, j));
}
...which of course you need to allocate, but you already know that.
Your second problem is in the sobel function, where you compute the gradient magnitude. It clips (saturates) the output. By cutting values of the output above 255 to 255, you create very broad lines along the edges of a constant value. The test of the non-maximum suppression is satisfied at the two edges of this line, but not in the middle, where pixels have the same value as both its neighbors.
To solve this, either:
Use a floating-point buffer to store the gradient magnitude. Here you don’t need to worry about data ranges.
Divide the magnitude by some value such that it will never exceed 255. Now you’re quantifying the magnitude rather than clipping it. Quantizing should be fine in this case.
I strongly recommend that you follow (1). I typically use floating-point—values images for everything, and only convert to 8-bit ints for display. This simplified a lot of things!
Ok I have a little problem with my code. This error disp when I try run it.
Unhandled exception at 0x012D4CBF in Dywan.exe: 0xC0000005: Access violation reading location 0x015E2348.
When ROZMIAR=257 or less then code works properly. But i need set higher value like 500. Error stucked at double odcien = dane[x][y] + dane[x + dlboku][y] + dane[x][y + dlboku] + dane[x + dlboku][y + dlboku];
Should i use dynamic arrays to this ?
double dane[ROZMIAR][ROZMIAR];
double wartoscinit = 0.0;
dane[0][0] = dane[0][ROZMIAR - 1] = dane[ROZMIAR - 1][0] = dane[ROZMIAR -1][ROZMIAR - 1] = wartoscinit;`
void comp_Fractal(){
for (int dlboku = ROZMIAR - 1; dlboku >= 2; dlboku /= 2, h /= 2.0) {
int polboku = dlboku / 2;
for (int x = 0; x<ROZMIAR - 1; x += dlboku) {
for (int y = 0; y<ROZMIAR - 1; y += dlboku) {
double odcien = dane[x][y] + dane[x + dlboku][y] + dane[x][y + dlboku] + dane[x + dlboku][y + dlboku];
odcien /= 4.0;
double wzor = (-h) + rand() * (2*h) / RAND_MAX;
dane[x + polboku][y + polboku] = odcien + wzor;
}
}
for (int x = 0; x<ROZMIAR - 1; x += polboku) {
for (int y = (x + polboku) % dlboku; y<ROZMIAR - 1; y += dlboku) {
double odcien =
dane[(x - polboku + ROZMIAR) % ROZMIAR][y] +
dane[(x + polboku) % ROZMIAR][y] +
dane[x][(y + polboku) % ROZMIAR] +
dane[x][(y - polboku + ROZMIAR) % ROZMIAR];
odcien /= 4.0;
double wzor = (-h) + rand() * (2*h) / RAND_MAX;
odcien = odcien + wzor;
dane[x][y] = odcien;
if (x == 0) dane[ROZMIAR - 1][y] = odcien;
if (y == 0) dane[x][ROZMIAR - 1] = odcien;
}
}
}
for (int i = 0; i<ROZMIAR - 1; i++)
for (int j = 0; j<ROZMIAR - 1; j++) {
if (dane[i][j] > maxY)
maxY = dane[i][j];
if (dane[i][j] < minY)
minY = dane[i][j];
}
}
When both dlboku, y and x are something like ROZMIAR - 2, following
dane[x + dlboku][y + dlboku];
will make you to access dane[ROZMIAR + ROZMIAR - 4][ROZMIAR + ROZMIAR - 4] which is out of bounds. You alvays had a problem with your code. It just large values of ROZMIAR which made it access out of allocated memory and crash.
I'm writing myself a Newton Fractal Generator. The images all looked like this:
But I actually would like it to look a bit smoother - sure I've done some research and I ran over http://www.hiddendimension.com/FractalMath/Convergent_Fractals_Main.html and this looks rather correct, except that there are at the edges of the basins some issues..
This is my generation loop:
while (i < 6000 && fabs(z.r) < 10000 && !found){
f = computeFunction(z, params, paramc[0]);
d = computeFunction(z, paramsD, paramc[1]);
iterexp = iterexp + exp(-fabs(z.r) - 0.5 / (fabs(subComplex(zo, z).r)));
zo = z;
z = subComplex(z, divComplex(f, d));
i++;
for (int j = 0; j < paramc[0] - 1; j++){
if (compComplex(z, zeros[j], RESOLUTION)){
resType[x + xRes * y] = j;
result[x + xRes * y] = iterexp;
found = true;
break;
}
}
if (compComplex(z, zo, RESOLUTION/100)){
resType[x + xRes * y] = 12;
break;
}
}
The coloration:
const int xRes = res[0];
const int yRes = res[1];
for (int y = 0; y < fraktal->getHeight(); y++){
for (int x = 0; x < fraktal->getWidth(); x++){
int type, it;
double conDiv;
if (genCL && genCL->err == CL_SUCCESS){
conDiv = genCL->result[x + y * xRes];
type = genCL->typeRes[x + y * xRes];
it = genCL->iterations[x + y * xRes];
} else {
type = 3;
conDiv = runNewton(std::complex<double>((double)((x - (double)(xRes / 2)) / zoom[0]), (double)((y - (double)(yRes / 2)) / zoom[1])), type);
}
if (type < 15){
Color col;
col.setColorHexRGB(colors[type]);
col.setColorHSV(col.getHue(), col.getSaturation(), 1-conDiv);
fraktal->setPixel(x, y, col);
} else {
fraktal->setPixel(x, y, conDiv, conDiv, conDiv, 1);
}
}
}
I appreciate any help to actually smooth this ;-)
Thanks,
- fodinabor