并行(多线程)图片边缘检测
代码
ImageStuff.h
#define EDGE 0
#define NOEDGE 255
#define MAXTHREADS 128
struct ImgProp{
int Hpixels;
int Vpixels;
unsigned char HeaderInfo[54];
unsigned long int Hbytes;
};
struct Pixel{
unsigned char R;
unsigned char G;
unsigned char B;
};
struct PrPixel{
unsigned char R;
unsigned char G;
unsigned char B;
unsigned char x; // unused. to make it an even 4B
float BW;
float BW2,BW4,BW5,BW9,BW12,BW15;
float Gauss, Gauss2;
float Theta,Gradient;
};
double** CreateBWCopy(unsigned char** img);
double** CreateBlankDouble();
unsigned char** CreateBlankBMP(unsigned char FILL);
struct PrPixel** PrAMTReadBMP(char*);
struct PrPixel** PrReadBMP(char*);
unsigned char** ReadBMP(char*);
void WriteBMP(unsigned char** , char*);
extern struct ImgProp ip;
extern long NumThreads, PrThreads;
extern int ThreadCtr[];
ImageStuff.c
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include "ImageStuff.h"
double** CreateBlankDouble()
{
int i;
double** img = (double **)malloc(ip.Vpixels * sizeof(double*));
for(i=0; i<ip.Vpixels; i++){
img[i] = (double *)malloc(ip.Hpixels*sizeof(double));
memset((void *)img[i],0,(size_t)ip.Hpixels*sizeof(double));
}
return img;
}
double** CreateBWCopy(unsigned char** img)
{
int i,j,k;
double** imgBW = (double **)malloc(ip.Vpixels * sizeof(double*));
for(i=0; i<ip.Vpixels; i++){
imgBW[i] = (double *)malloc(ip.Hpixels*sizeof(double));
for(j=0; j<ip.Hpixels; j++){
// convert each pixel to B&W = (R+G+B)/3
k=3*j;
imgBW[i][j]=((double)img[i][k]+(double)img[i][k+1]+(double)img[i][k+2])/3.0;
}
}
return imgBW;
}
unsigned char** CreateBlankBMP(unsigned char FILL)
{
int i,j;
unsigned char** img = (unsigned char **)malloc(ip.Vpixels * sizeof(unsigned char*));
for(i=0; i<ip.Vpixels; i++){
img[i] = (unsigned char *)malloc(ip.Hbytes * sizeof(unsigned char));
memset((void *)img[i],FILL,(size_t)ip.Hbytes); // zero out every pixel
}
return img;
}
// This thread function asynchronously pre-calculates a row of pixels.
// It uses the CounterMutex to updated the shared counter-based variables
pthread_mutex_t CounterMutex;
struct PrPixel **PrIm;
int NextRowToProcess, LastRowRead;
int ThreadCtr[MAXTHREADS]; // Counts # rows processed by each thread
void *AMTPreCalcRow(void* ThCtr)
{
unsigned char r, g, b;
int i,j,Last;
float R, G, B, BW, BW2, BW3, BW4, BW5, BW9, BW12, Z=0.0;
do{
// get the next row number safely
pthread_mutex_lock(&CounterMutex);
Last=LastRowRead;
i=NextRowToProcess;
if(Last>=i){
NextRowToProcess++;
j = *((int *)ThCtr);
*((int *)ThCtr) = j+1; // One more row processed by this thread
}
pthread_mutex_unlock(&CounterMutex);
if(Last<i) continue;
if(i>=ip.Vpixels) break;
for(j=0; j<ip.Hpixels; j++){
b=PrIm[i][j].B; B=(float)b;
g=PrIm[i][j].G; G=(float)g;
r=PrIm[i][j].R; R=(float)r;
BW3=R+G+B;
PrIm[i][j].BW = BW = BW3*0.3333333;
PrIm[i][j].BW2 = BW2 = BW+BW;
PrIm[i][j].BW4 = BW4 = BW2+BW2;
PrIm[i][j].BW5 = BW5 = BW4+BW;
PrIm[i][j].BW9 = BW9 = BW5+BW4;
PrIm[i][j].BW12 = BW12 = BW9+BW3;
PrIm[i][j].BW15 = BW12+BW3;
PrIm[i][j].Gauss = PrIm[i][j].Gauss2 = Z;
PrIm[i][j].Theta = PrIm[i][j].Gradient = Z;
}
}while(i<ip.Vpixels);
pthread_exit(NULL);
}
struct PrPixel** PrReadBMP(char* filename)
{
int i,j,k;
unsigned char r, g, b;
float R, G, B, BW, BW2, BW3, BW4, BW5, BW9, BW12, Z=0.0;
unsigned char Buffer[24576];
FILE* f = fopen(filename, "rb");
if(f == NULL){
printf("\n\n%s NOT FOUND\n\n",filename);
exit(1);
}
unsigned char HeaderInfo[54];
fread(HeaderInfo, sizeof(unsigned char), 54, f); // read the 54-byte header
// extract image height and width from header
int width = *(int*)&HeaderInfo[18]; ip.Hpixels = width;
int height = *(int*)&HeaderInfo[22]; ip.Vpixels = height;
int RowBytes = (width*3 + 3) & (~3); ip.Hbytes = RowBytes;
//copy header for re-use
for(i=0; i<54; i++) { ip.HeaderInfo[i] = HeaderInfo[i]; }
printf("\n Input BMP File name: %20s (%u x %u)",filename,ip.Hpixels,ip.Vpixels);
// allocate memory to store the main image
struct PrPixel **PrIm = (struct PrPixel **)malloc(height * sizeof(struct PrPixel *));
for(i=0; i<height; i++) {
PrIm[i] = (struct PrPixel *)malloc(width * sizeof(struct PrPixel));
}
// read the image from disk and pre-calculate the PRImage pixels
for(i = 0; i < height; i++) {
fread(Buffer, sizeof(unsigned char), RowBytes, f);
for(j=0,k=0; j<width; j++, k+=3){
b=PrIm[i][j].B=Buffer[k]; B=(float)b;
g=PrIm[i][j].G=Buffer[k+1]; G=(float)g;
r=PrIm[i][j].R=Buffer[k+2]; R=(float)r;
BW3=R+G+B;
PrIm[i][j].BW = BW = BW3*0.3333333;
PrIm[i][j].BW2 = BW2 = BW+BW;
PrIm[i][j].BW4 = BW4 = BW2+BW2;
PrIm[i][j].BW5 = BW5 = BW4+BW;
PrIm[i][j].BW9 = BW9 = BW5+BW4;
PrIm[i][j].BW12 = BW12 = BW9+BW3;
PrIm[i][j].BW15 = BW12+BW3;
PrIm[i][j].Gauss = PrIm[i][j].Gauss2 = Z;
PrIm[i][j].Theta = PrIm[i][j].Gradient = Z;
}
}
fclose(f);
return PrIm; // return the pointer to the main image
}
unsigned char** ReadBMP(char* filename)
{
int i;
FILE* f = fopen(filename, "rb");
if(f == NULL){
printf("\n\n%s NOT FOUND\n\n",filename);
exit(1);
}
unsigned char HeaderInfo[54];
fread(HeaderInfo, sizeof(unsigned char), 54, f); // read the 54-byte header
// extract image height and width from header
int width = *(int*)&HeaderInfo[18]; ip.Hpixels = width;
int height = *(int*)&HeaderInfo[22]; ip.Vpixels = height;
int RowBytes = (width*3 + 3) & (~3); ip.Hbytes = RowBytes;
//copy header for re-use
for(i=0; i<54; i++) { ip.HeaderInfo[i] = HeaderInfo[i]; }
printf("\n Input BMP File name: %20s (%u x %u)",filename,ip.Hpixels,ip.Vpixels);
// allocate memory to store the main image
unsigned char **Img = (unsigned char **)malloc(height * sizeof(unsigned char*));
for(i=0; i<height; i++) {
Img[i] = (unsigned char *)malloc(RowBytes * sizeof(unsigned char));
}
// read the image from disk
for(i = 0; i < height; i++) {
fread(Img[i], sizeof(unsigned char), RowBytes, f);
}
fclose(f);
return Img; // return the pointer to the main image
}
void WriteBMP(unsigned char** img, char* filename)
{
int i;
FILE* f = fopen(filename, "wb");
if(f == NULL){
printf("\n\nFILE CREATION ERROR: %s\n\n",filename);
exit(1);
}
//write header
for(i=0; i<54; i++) { fputc(ip.HeaderInfo[i],f); }
//write data
for(i=0; i<ip.Vpixels; i++) {
fwrite(img[i], sizeof(unsigned char), ip.Hbytes, f);
}
printf("\n Output BMP File name: %20s (%u x %u)",filename,ip.Hpixels,ip.Vpixels);
fclose(f);
}
imedge.c
#include <pthread.h>
#include <stdint.h>
#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <sys/time.h>
#include "ImageStuff.h"
#define MAXTHREADS 128
#define PI 3.1415926
#define EDGE 0
#define NOEDGE 255
long NumThreads; // Total number of threads working in parallel
int ThParam[MAXTHREADS]; // Thread parameters ...
int ThreshLo,ThreshHi; // "Edge" vs. "No Edge" thresholds
pthread_t ThHandle[MAXTHREADS]; // Thread handles
pthread_attr_t ThAttr; // Pthread attrributes
unsigned char** TheImage; // This is the main image
unsigned char** CopyImage; // This is the copy image (to store edges)
double **BWImage; // B&W of TheImage (each pixel=double)
double **GaussImage; // Gauss filtered version of the B&W image
double **Gradient, **Theta; // gradient and theta for each pixel
struct ImgProp ip;
//Sobel kernels
double Gx[3][3] = { { -1, 0, 1 },
{ -2, 0, 2 },
{ -1, 0, 1 } };
double Gy[3][3] = { { -1, -2, -1 },
{ 0, 0, 0 },
{ 1, 2, 1 } };
double Gauss[5][5] = { { 2, 4, 5, 4, 2 },
{ 4, 9, 12, 9, 4 },
{ 5, 12, 15, 12, 5 },
{ 4, 9, 12, 9, 4 },
{ 2, 4, 5, 4, 2 } };
// Function that takes BWImage and calculates the Gaussian filtered version
// Saves the result in the GaussFilter[][] array
void *GaussianFilter(void* tid)
{
long tn; // My thread number (ID) is stored here
int row,col,i,j;
double G; // temp to calculate the Gaussian filtered version
tn = *((int *) tid); // Calculate my Thread ID
tn *= ip.Vpixels/NumThreads;
for(row=tn; row<tn+ip.Vpixels/NumThreads; row++)
{
if((row<2) || (row>(ip.Vpixels-3))) continue;
col=2;
while(col<=(ip.Hpixels-3)){
G=0.0;
for(i=-2; i<=2; i++){
for(j=-2; j<=2; j++){
G+=BWImage[row+i][col+j]*Gauss[i+2][j+2];
}
}
GaussImage[row][col]=G/159.00D;
col++;
}
}
pthread_exit(NULL);
}
// Function that calculates the Gradient and Theta for each pixel
// Takes the Gauss[][] array and creates the Gradient[][] and Theta[][] arrays
void *Sobel(void* tid)
{
long tn; // My thread number (ID) is stored here
int row,col,i,j;
double GX,GY;
tn = *((int *) tid); // Calculate my Thread ID
tn *= ip.Vpixels/NumThreads;
for(row=tn; row<tn+ip.Vpixels/NumThreads; row++)
{
if((row<1) || (row>(ip.Vpixels-2))) continue;
col=1;
while(col<=(ip.Hpixels-2)){
// calculate Gx and Gy
GX=0.0; GY=0.0;
for(i=-1; i<=1; i++){
for(j=-1; j<=1; j++){
GX+=GaussImage[row+i][col+j]*Gx[i+1][j+1];
GY+=GaussImage[row+i][col+j]*Gy[i+1][j+1];
}
}
Gradient[row][col]=sqrt(GX*GX+GY*GY);
Theta[row][col]=atan(GX/GY)*180.0/PI;
col++;
}
}
pthread_exit(NULL);
}
void *Threshold(void* tid)
{
long tn; // My thread number (ID) is stored here
int row,col;
unsigned char PIXVAL;
double L,H,G,T;
tn = *((int *) tid); // Calculate my Thread ID
tn *= ip.Vpixels/NumThreads;
for(row=tn; row<tn+ip.Vpixels/NumThreads; row++)
{
if((row<1) || (row>(ip.Vpixels-2))) continue;
col=1;
while(col<=(ip.Hpixels-2)){
L=(double)ThreshLo; H=(double)ThreshHi;
G=Gradient[row][col];
PIXVAL=NOEDGE;
if(G<=L){ // no edge
PIXVAL=NOEDGE;
}else if(G>=H){ // edge
PIXVAL=EDGE;
}else{
T=Theta[row][col];
if((T<-67.5) || (T>67.5)){
// Look at left and right
PIXVAL=((Gradient[row][col-1]>H) || (Gradient[row][col+1]>H)) ? EDGE:NOEDGE;
}else if((T>=-22.5) && (T<=22.5)){
// Look at top and bottom
PIXVAL=((Gradient[row-1][col]>H) || (Gradient[row+1][col]>H)) ? EDGE:NOEDGE;
}else if((T>22.5) && (T<=67.5)){
// Look at upper right, lower left
PIXVAL=((Gradient[row-1][col+1]>H) || (Gradient[row+1][col-1]>H)) ? EDGE:NOEDGE;
}else if((T>=-67.5) && (T<-22.5)){
// Look at upper left, lower right
PIXVAL=((Gradient[row-1][col-1]>H) || (Gradient[row+1][col+1]>H)) ? EDGE:NOEDGE;
}
}
CopyImage[row][col*3]=PIXVAL;
CopyImage[row][col*3+1]=PIXVAL;
CopyImage[row][col*3+2]=PIXVAL;
col++;
}
}
pthread_exit(NULL);
}
// returns the time stamps in ms
double GetDoubleTime()
{
struct timeval tnow;
gettimeofday(&tnow, NULL);
return ((double)tnow.tv_sec*1000000.0 + ((double)tnow.tv_usec))/1000.00;
}
double ReportTimeDelta(double PreviousTime, char *Message)
{
double Tnow,TimeDelta;
Tnow=GetDoubleTime();
TimeDelta=Tnow-PreviousTime;
printf("\n.....%-30s ... %7.0f ms\n",Message,TimeDelta);
return Tnow;
}
int main(int argc, char** argv)
{
int a,i,ThErr;
double t1,t2,t3,t4,t5,t6,t7,t8;
switch (argc){
case 3 : NumThreads=1; ThreshLo=50; ThreshHi=100; break;
case 4 : NumThreads=atoi(argv[3]); ThreshLo=50; ThreshHi=100; break;
case 5 : NumThreads=atoi(argv[3]); ThreshLo=atoi(argv[4]); ThreshHi=100; break;
case 6 : NumThreads=atoi(argv[3]); ThreshLo=atoi(argv[4]); ThreshHi=atoi(argv[5]); break;
default: printf("\n\nUsage: imedge infile outfile [Threads] [ThreshLo] [ThreshHi]");
printf("\n\nExample: imedge in.bmp out.bmp 8\n\n");
printf("\n\nExample: imedge in.bmp out.bmp 4 50 150\n\n");
printf("\n\nNothing executed ... Exiting ...\n\n");
exit(EXIT_FAILURE);
}
if((NumThreads<1) || (NumThreads>MAXTHREADS)){
printf("\nNumber of threads must be between 1 and %u... \n",MAXTHREADS);
printf("\n'1' means Pthreads version with a single thread\n");
printf("\n\nNothing executed ... Exiting ...\n\n");
exit(EXIT_FAILURE);
}
if((ThreshLo<0) || (ThreshHi>255) || (ThreshLo>ThreshHi)){
printf("\nInvalid Thresholds: Threshold must be between [0...255] ...\n");
printf("\n\nNothing executed ... Exiting ...\n\n");
exit(EXIT_FAILURE);
}else{
printf("ThresLo=%d ... ThreadHi=%d\n",ThreshLo,ThreshHi);
}
printf("\nExecuting the Pthreads version with %li threads ...\n",NumThreads);
t1 = GetDoubleTime();
TheImage=ReadBMP(argv[1]); printf("\n");
t2 = ReportTimeDelta(t1,"ReadBMP complete"); // Start time without IO
CopyImage = CreateBlankBMP(NOEDGE); // This will store the edges in RGB
BWImage = CreateBWCopy(TheImage);
GaussImage = CreateBlankDouble();
Gradient = CreateBlankDouble();
Theta = CreateBlankDouble();
t3=ReportTimeDelta(t2, "Auxiliary images created");
pthread_attr_init(&ThAttr);
pthread_attr_setdetachstate(&ThAttr, PTHREAD_CREATE_JOINABLE);
for(i=0; i<NumThreads; i++){
ThParam[i] = i;
ThErr = pthread_create(&ThHandle[i], &ThAttr, GaussianFilter, (void *)&ThParam[i]);
if(ThErr != 0){
printf("\nThread Creation Error %d. Exiting abruptly... \n",ThErr);
exit(EXIT_FAILURE);
}
}
for(i=0; i<NumThreads; i++){
pthread_join(ThHandle[i], NULL);
}
t4=ReportTimeDelta(t3, "Gauss Image created");
for(i=0; i<NumThreads; i++){
ThParam[i] = i;
ThErr = pthread_create(&ThHandle[i], &ThAttr, Sobel, (void *)&ThParam[i]);
if(ThErr != 0){
printf("\nThread Creation Error %d. Exiting abruptly... \n",ThErr);
exit(EXIT_FAILURE);
}
}
for(i=0; i<NumThreads; i++){
pthread_join(ThHandle[i], NULL);
}
t5=ReportTimeDelta(t4, "Gradient, Theta calculated");
for(i=0; i<NumThreads; i++){
ThParam[i] = i;
ThErr = pthread_create(&ThHandle[i], &ThAttr, Threshold, (void *)&ThParam[i]);
if(ThErr != 0){
printf("\nThread Creation Error %d. Exiting abruptly... \n",ThErr);
exit(EXIT_FAILURE);
}
}
pthread_attr_destroy(&ThAttr);
for(i=0; i<NumThreads; i++){
pthread_join(ThHandle[i], NULL);
}
t6=ReportTimeDelta(t5, "Thresholding completed");
//merge with header and write to file
WriteBMP(CopyImage, argv[2]); printf("\n");
t7=ReportTimeDelta(t6, "WriteBMP completed");
// free() the allocated area for image and pointers
for(i = 0; i < ip.Vpixels; i++) {
free(TheImage[i]); free(CopyImage[i]); free(BWImage[i]);
free(GaussImage[i]); free(Gradient[i]); free(Theta[i]);
}
free(TheImage); free(CopyImage); free(BWImage);
free(GaussImage); free(Gradient); free(Theta);
t8=ReportTimeDelta(t2, "Program Runtime without IO");
return (EXIT_SUCCESS);
}
编译
gcc imedge.c ImageStuff.c -o imedge -lpthread -lm
运行效果
使用2个线程, T h r e s L o = 50 ThresLo=50 ThresLo=50 , T h r e a d H i = 100 ThreadHi=100 ThreadHi=100 两个阈值 边缘检测图片
./imedge nature_monte.bmp nature_monte_2_50_100.bmp 2 50 100
原图
效果
使用2个线程,
T
h
r
e
s
L
o
=
50
ThresLo=50
ThresLo=50 ,
T
h
r
e
a
d
H
i
=
150
ThreadHi=150
ThreadHi=150 两个阈值 边缘检测图片
./imedge nature_monte.bmp nature_monte_2_50_150.bmp 2 50 150
原图
效果图