Created
July 22, 2024 00:31
-
-
Save Voldrix/d3c7b0cd6cd5e8a29a1b295e1bf49d91 to your computer and use it in GitHub Desktop.
Seam Carving with Sobel in C
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
unsigned char* rgb_to_monochrome(unsigned char *rgbImg, int width, int height, int colorspace, int weighted) { //RGB(A) -> Mono | |
unsigned char *monoImg = malloc(width * height); | |
int m = 0, n = 0; | |
register r = width * height; | |
//weighted averaging better matches human perception. Human eyes perceive green more strongly than red, and red more strongly than blue | |
//the standard weights are about .3R + .6G + .1B | |
//the weights here are adusted to make them faster to compute. The difference is minimal | |
if(colorspace == 3) { //RGB | |
if(weighted) //weighted averaging | |
while(r--) | |
monoImg[m++] = (rgbImg[n++] >> 2) + (rgbImg[n++] >> 1) + (rgbImg[n++] >> 2); | |
else //simple average method | |
while(r--) | |
monoImg[m++] = (rgbImg[n++] + rgbImg[n++] + rgbImg[n++]) / 3; | |
} | |
else { //RGBA | |
if(weighted) //weighted averaging | |
while(r--) | |
monoImg[m++] = ((rgbImg[n++] >> 2) + (rgbImg[n++] >> 1) + (rgbImg[n++] >> 2)) * (rgbImg[n++] / 255); | |
else //simple average method | |
while(r--) | |
monoImg[m++] = ((rgbImg[n++] + rgbImg[n++] + rgbImg[n++]) / 3) * (rgbImg[n++] / 255); | |
} | |
return monoImg; | |
} | |
/* monochrome Sobel convolution (x & y) */ | |
unsigned char* sobel(unsigned char *srcImg, int width, int height, int colorspace) { | |
unsigned char (*p)[width * colorspace] = srcImg; //Multidimensional array pointer | |
unsigned char (*gradient)[width] = malloc(height * width); //Final filtered image, single channel | |
int x, y, z, w = width, h = height; | |
//greyscale copy of src img | |
unsigned char (*grayscale)[width] = rgb_to_monochrome(srcImg, width, height, colorspace, UNWEIGHTED_AVG); | |
//start sobel convolution (x & y) | |
//Corners | |
x = (p[0][1] - p[0][0] + p[1][1] - p[1][0]) >> 2; | |
y = (p[1][0] - p[0][0] + p[1][1] - p[0][1]) >> 2; | |
gradient[0][0] = sqrt(x*x + y*y); //Top Left | |
x = (p[0][w-1] - p[0][w-2] + p[1][w-1] - p[1][w-2]) >> 2; | |
y = (p[1][w-1] - p[0][w-1] + p[1][w-2] - p[0][w-2]) >> 2; | |
gradient[0][w-1] = sqrt(x*x + y*y); //Top Right | |
x = (p[h-2][1] - p[h-2][0] + p[h-1][1] - p[h-1][0]) >> 2; | |
y = (p[h-1][0] - p[h-2][0] + p[h-1][1] - p[h-2][1]) >> 2; | |
gradient[h-1][0] = sqrt(x*x + y*y); //Bottom Left | |
x = (p[h-2][w-1] - p[h-2][w-2] + p[h-1][w-1] - p[h-1][w-2]) >> 2; | |
y = (p[h-1][w-1] - p[h-2][w-1] + p[h-1][w-2] - p[h-2][w-2]) >> 2; | |
gradient[h-1][w-1] = sqrt(x*x + y*y); //Bottom Right | |
//Edges | |
for(int j = 1; j < width - 1; j++) { | |
//top row | |
x = grayscale[1][j+1] - grayscale[1][j-1] + 2 * grayscale[0][j+1] - 2 * grayscale[0][j-1]; | |
y = grayscale[1][j-1] + grayscale[1][j+1] - grayscale[0][j-1] - grayscale[0][j+1] + 2 * grayscale[1][j] - 2 * grayscale[0][j]; | |
x /= 6; | |
y >>= 3; | |
z = sqrt(x*x + y*y); | |
gradient[0][j] = (z > 255) ? 255 : z; | |
//bottom row | |
x = grayscale[h-2][j+1] - grayscale[h-2][j-1] + 2 * grayscale[h-1][j+1] - 2 * grayscale[h-1][j-1]; | |
y = grayscale[h-1][j-1] + grayscale[h-1][j+1] - grayscale[h-2][j-1] - grayscale[h-2][j+1] + 2 * grayscale[h-1][j] - 2 * grayscale[h-2][j]; | |
x /= 6; | |
y >>= 3; | |
z = sqrt(x*x + y*y); | |
gradient[h-1][j] = (z > 255) ? 255 : z; | |
} | |
//Rows + Row Edges | |
for(int i = 1; i < height - 1; i++) { | |
//left edge | |
x = grayscale[i-1][1] + grayscale[i+1][1] - grayscale[i-1][0] - grayscale[i+1][0] + 2 * grayscale[i][1] - 2 * grayscale[i][0]; | |
y = grayscale[i+1][1] - grayscale[i-1][1] + 2 * grayscale[i+1][0] - 2 * grayscale[i-1][0]; | |
x >>= 3; | |
y /= 6; | |
z = sqrt(x*x + y*y); | |
gradient[i][0] = (z > 255) ? 255 : z; | |
//right edge | |
x = grayscale[i-1][w-1] + grayscale[i+1][w-1] - grayscale[i-1][w-2] - grayscale[i+1][w-2] + 2 * grayscale[i][w-1] - 2 * grayscale[i][w-2]; | |
y = grayscale[i+1][w-2] - grayscale[i-1][w-2] + 2 * grayscale[i+1][w-1] - 2 * grayscale[i-1][w-1]; | |
x >>= 3; | |
y /= 6; | |
z = sqrt(x*x + y*y); | |
gradient[i][w-1] = (z > 255) ? 255 : z; | |
//rest of the row | |
for(int j = 1; j < width - 1; j++) { | |
x = grayscale[i-1][j+1] + grayscale[i+1][j+1] - grayscale[i-1][j-1] - grayscale[i+1][j-1] + 2 * grayscale[i][j+1] - 2 * grayscale[i][j-1]; | |
y = grayscale[i+1][j-1] + grayscale[i+1][j+1] - grayscale[i-1][j-1] - grayscale[i-1][j+1] + 2 * grayscale[i+1][j] - 2 * grayscale[i-1][j]; | |
x >>= 3; | |
y >>= 3; | |
z = sqrt(x*x + y*y); | |
gradient[i][j] = (z > 255) ? 255 : z; | |
} | |
} | |
free(grayscale); | |
return gradient; | |
} | |
void seam_carving(unsigned char *srcImg, int width, int height, int colorspace, int resizeWidth) { | |
if(resizeWidth < 8) return; | |
unsigned char *writePtr, *readPtr; | |
int x, y, z, *energyPtr; | |
register int r; | |
int *energyMap = malloc(sizeof(int) * width * height); | |
int *seam = malloc(sizeof(int) * height); | |
unsigned char *sobelImg = sobel(srcImg, width, height, colorspace); | |
while(width > resizeWidth) { //main seam removal loop | |
energyPtr = &energyMap[width * (height - 1)]; | |
readPtr = &sobelImg[width * (height - 1)]; | |
r = width; | |
while(r--) //copy bottom row from sobelImg to energy map | |
*energyPtr++ = *readPtr++; | |
for(int i = height - 2; i >= 0; i--) { //build energy map, bottom-up | |
x = width * i; | |
y = x + width; | |
energyMap[x] = sobelImg[x] + ((energyMap[y+1] < energyMap[y]) ? energyMap[y+1] : energyMap[y]); //left edge | |
energyMap[y-1] = sobelImg[y-1] + ((energyMap[y+width-2] < energyMap[y+width-1]) ? energyMap[y+width-2] : energyMap[y+width-1]); //right edge | |
for(int j = width - 2; j > 0; j--) { //rest of energy map | |
z = (energyMap[y+j-1] < energyMap[y+j]) ? energyMap[y+j-1] : energyMap[y+j]; | |
energyMap[x+j] = sobelImg[x+j] + ((energyMap[y+j+1] < z) ? energyMap[y+j+1] : z); | |
} | |
} | |
/* start seam */ | |
r = 0; | |
for(int i = 1; i < width; i++) //find lowest energy starting point in first row | |
r = (energyMap[i] < energyMap[r]) ? i : r; //idx pos | |
y = seam[0] = r; | |
for(int i = 1; i < height; i++) { //trace seam on energy map | |
x = y + width; | |
seam[i] = width; | |
if(x == width * i) //left edge | |
seam[i] += energyMap[x+1] < energyMap[x]; | |
else if(x == width * (i+1) - 1) //right edge | |
seam[i] -= energyMap[x-1] < energyMap[x]; | |
else { //middle (not a side edge) | |
r = (energyMap[x-1] < energyMap[x]) ? energyMap[x-1] : energyMap[x]; //relative advancement | |
if(energyMap[x+1] < r) seam[i] += 1; | |
else seam[i] -= energyMap[x-1] < energyMap[x]; | |
} | |
y += seam[i]; //seam total (for finding the tail) | |
} | |
/* img array compacting */ | |
//filter/gradient img | |
writePtr = readPtr = &sobelImg[seam[0]]; //trim seam from sobel filtered img | |
readPtr++; | |
for(int i = 1; i < height; i++) { //compact array, deleting seam | |
r = seam[i] - 1; | |
memcpy(writePtr, readPtr, r); | |
writePtr += r; | |
readPtr += r + 1; | |
} | |
r = height * width - y - 1; //last half of last line | |
while(r--) //compact the tail | |
*writePtr++ = *readPtr++; | |
/* compact src img */ | |
writePtr = readPtr = &srcImg[seam[0] * colorspace]; //trim seam from src img | |
readPtr += colorspace; | |
for(int i = 1; i < height; i++) { //compact array, deleting seam | |
r = (seam[i] - 1) * colorspace; | |
memcpy(writePtr, readPtr, r); | |
writePtr += r; | |
readPtr += r + colorspace; | |
} | |
r = (height * width - y - 1) * colorspace; //last half of last line | |
while(r--) //compact the tail | |
*writePtr++ = *readPtr++; | |
width -= 1; | |
} //end seam carving loop | |
free(seam); | |
free(energyMap); | |
free(sobelImg); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment