接上篇博文中的RGB颜色模型转换为HSI颜色模型,做了消除红眼的算法。
基本的算法描述如下:
代码:
1 #include "opencv_libs.h" 2 #include <highgui.h> 3 #include <cv.h> 4 #include <math.h> 5 6 /* 7 * 描述:基于HSI颜色模型消除红眼 8 * 作者:qdsclove([email protected]) 9 * 时间:22:49 4/18 星期四 2013 10 */ 11 12 /* 鼠标选择的矩形 */ 13 CvRect rect; 14 /* 标记是否在画 */ 15 bool draw = false; 16 /* 确定下来的眼睛矩形 */ 17 CvRect eyeRect; 18 /* 是否选定眼睛区域 */ 19 bool isFinalRect = false; 20 21 IplImage* img; 22 IplImage* temp; 23 IplImage* original; 24 25 // 将HSI颜色空间的三个分量组合起来,便于显示 26 IplImage* catHSImage(CvMat* HSI_H, CvMat* HSI_S, CvMat* HSI_I) 27 { 28 IplImage* HSI_Image = cvCreateImage( cvGetSize( HSI_H ), IPL_DEPTH_8U, 3 ); 29 30 for(int i = 0; i < HSI_Image->height; i++) 31 { 32 for(int j = 0; j < HSI_Image->width; j++) 33 { 34 double d = cvmGet( HSI_H, i, j ); 35 int b = (int)(d * 255/360); 36 d = cvmGet( HSI_S, i, j ); 37 int g = (int)( d * 255 ); 38 d = cvmGet( HSI_I, i, j ); 39 int r = (int)( d * 255 ); 40 41 cvSet2D( HSI_Image, i, j, cvScalar( b, g, r ) ); 42 } 43 } 44 return HSI_Image; 45 } 46 47 // 将HSI颜色模型的数据转换为RGB颜色模型的图像 48 IplImage* HSI2RGBImage(CvMat* HSI_H, CvMat* HSI_S, CvMat* HSI_I) 49 { 50 IplImage * RGB_Image = cvCreateImage(cvGetSize(HSI_H), IPL_DEPTH_8U, 3 ); 51 52 int iB, iG, iR; 53 for(int i = 0; i < RGB_Image->height; i++) 54 { 55 for(int j = 0; j < RGB_Image->width; j++) 56 { 57 // 该点的色度H 58 double dH = cvmGet( HSI_H, i, j ); 59 // 该点的色饱和度S 60 double dS = cvmGet( HSI_S, i, j ); 61 // 该点的亮度 62 double dI = cvmGet( HSI_I, i, j ); 63 64 double dTempB, dTempG, dTempR; 65 // RG扇区 66 if(dH < 120 && dH >= 0) 67 { 68 // 将H转为弧度表示 69 dH = dH * 3.1415926 / 180; 70 dTempB = dI * (1 - dS); 71 dTempR = dI * ( 1 + (dS * cos(dH))/cos(3.1415926/3 - dH) ); 72 dTempG = (3 * dI - (dTempR + dTempB)); 73 } 74 // GB扇区 75 else if(dH < 240 && dH >= 120) 76 { 77 dH -= 120; 78 79 // 将H转为弧度表示 80 dH = dH * 3.1415926 / 180; 81 82 dTempR = dI * (1 - dS); 83 dTempG = dI * (1 + dS * cos(dH)/cos(3.1415926/3 - dH)); 84 dTempB = (3 * dI - (dTempR + dTempG)); 85 } 86 // BR扇区 87 else 88 { 89 dH -= 240; 90 91 // 将H转为弧度表示 92 dH = dH * 3.1415926 / 180; 93 94 dTempG = dI * (1 - dS); 95 dTempB = dI * (1 + (dS * cos(dH))/cos(3.1415926/3 - dH)); 96 dTempR = (3* dI - (dTempG + dTempB)); 97 } 98 99 iB = dTempB * 255; 100 iG = dTempG * 255; 101 iR = dTempR * 255; 102 103 cvSet2D( RGB_Image, i, j, cvScalar( iB, iG, iR ) ); 104 } 105 } 106 return RGB_Image; 107 } 108 109 // 利用HSI颜色空间去除红眼 110 void EraseRedEye(IplImage* img, CvMat* HSI_H, CvMat* HSI_S, CvMat* HSI_I) 111 { 112 // 原始图像数据指针, HSI矩阵数据指针 113 uchar* data; 114 115 // rgb分量 116 byte img_r, img_g, img_b; 117 byte min_rgb; // rgb分量中的最小值 118 // HSI分量 119 float fHue, fSaturation, fIntensity; 120 121 for(int i = 0; i < HSI_H->height; i++) 122 { 123 for(int j = 0; j < HSI_H->width; j++) 124 { 125 data = cvPtr2D(img, i, j, 0); 126 img_b = *data; 127 data++; 128 img_g = *data; 129 data++; 130 img_r = *data; 131 132 // Intensity分量[0, 1] 133 fIntensity = (float)((img_b + img_g + img_r)/3)/255; 134 135 // 得到RGB分量中的最小值 136 float fTemp = img_r < img_g ? img_r : img_g; 137 min_rgb = fTemp < img_b ? fTemp : img_b; 138 // Saturation分量[0, 1] 139 fSaturation = 1 - (float)(3 * min_rgb)/(img_r + img_g + img_b); 140 141 // 计算theta角 142 float numerator = (img_r - img_g + img_r - img_b ) / 2; 143 float denominator = sqrt( 144 pow( (img_r - img_g), 2 ) + (img_r - img_b)*(img_g - img_b) ); 145 146 // 计算Hue分量 147 if(denominator != 0) 148 { 149 float theta = acos( numerator/denominator) * 180/3.14; 150 151 if(img_b <= img_g) 152 { 153 fHue = theta ; 154 } 155 else 156 { 157 fHue = 360 - theta; 158 } 159 } 160 else 161 { 162 fHue = 0; 163 } 164 165 // 赋值 166 cvmSet( HSI_H, i, j, fHue ); 167 168 // 若为红眼像素,进行处理 169 if( (fHue <= 45 || fHue >= 315 ) && fSaturation > 0.3) 170 { 171 // 设置该像素点饱和度为零 172 cvmSet( HSI_S, i, j, 0); 173 } 174 else 175 { 176 cvmSet( HSI_S, i, j, fSaturation); 177 } 178 cvmSet( HSI_I, i, j, fIntensity ); 179 } 180 } 181 } 182 183 void draw_rect(IplImage* img, CvRect rect) 184 { 185 cvRectangle( img, 186 cvPoint( rect.x, rect.y ), 187 cvPoint( rect.x + rect.width, rect.y + rect.height), 188 cvScalar( 0xff, 0x00, 0x00) ); 189 } 190 191 // 鼠标回调函数 192 void my_mouse_callback( int event, int x, int y, int flags, void* param) 193 { 194 IplImage* image = (IplImage*) param; 195 196 switch( event ) 197 { 198 case CV_EVENT_MOUSEMOVE: 199 { 200 if(draw) 201 { 202 rect.width = x - rect.x; 203 rect.height = y - rect.y; 204 } 205 } 206 break; 207 case CV_EVENT_LBUTTONDOWN: 208 { 209 draw = true; 210 rect = cvRect( x, y, 0, 0 ); 211 } 212 break; 213 case CV_EVENT_LBUTTONUP: 214 { 215 draw = false; 216 if(rect.width < 0) 217 { 218 rect.x += rect.width; 219 rect.width *= -1; 220 } 221 if(rect.height < 0) 222 { 223 rect.y += rect.height; 224 rect.height *= -1; 225 } 226 // draw 227 draw_rect(image, rect); 228 229 isFinalRect = true; 230 eyeRect = cvRect( rect.x, rect.y, rect.width, rect.height ); 231 printf("(%d, %d), %d\t%d", rect.x, rect.y, rect.width, rect.height ); 232 } 233 break; 234 // 在右键按下时清除 235 case CV_EVENT_RBUTTONDOWN: 236 cvCopyImage(original, img); 237 printf("clear.\n"); 238 break; 239 } 240 } 241 242 int main() 243 { 244 IplImage* img = cvLoadImage("redeye3.jpg"); 245 246 rect = cvRect( -1, -1, 0, 0); 247 eyeRect = cvRect(-1, -1, 0, 0); 248 249 // 副本 250 temp = cvCreateImage( cvGetSize( img ), img->depth, img->nChannels ); 251 original = cvCreateImage( cvGetSize( img ), img->depth, img->nChannels ); 252 cvCopyImage( img, temp ); 253 cvCopyImage( img, original ); 254 255 cvNamedWindow("去除红眼"); 256 cvSetMouseCallback("去除红眼", my_mouse_callback, (void*)img); 257 258 while(1) 259 { 260 cvCopyImage(img, temp); 261 262 if(draw) 263 { 264 draw_rect( temp , rect ); 265 } 266 267 cvShowImage( "去除红眼", temp); 268 269 if(cvWaitKey(15) == 27) 270 break; 271 272 if(isFinalRect == true) 273 { 274 goto erase; 275 } 276 } 277 // 去除红眼 278 erase: 279 cvCopyImage(original, img); 280 281 282 // 三个HSI空间数据矩阵 283 CvMat* HSI_H = cvCreateMat( eyeRect.height, eyeRect.width, CV_32FC1 ); 284 CvMat* HSI_S = cvCreateMat( eyeRect.height, eyeRect.width, CV_32FC1 ); 285 CvMat* HSI_I = cvCreateMat( eyeRect.height, eyeRect.width, CV_32FC1 ); 286 287 // 设置图像感兴趣区域 288 cvSetImageROI( img, eyeRect ); 289 290 EraseRedEye(img, HSI_H, HSI_S, HSI_I ); 291 292 IplImage* HSI_Image = catHSImage( HSI_H, HSI_S, HSI_I ); 293 IplImage* RGB_Image = HSI2RGBImage( HSI_H, HSI_S, HSI_I ); 294 295 cvShowImage("原始图像", img); 296 cvCopyImage( RGB_Image, img ); 297 298 cvResetImageROI( img ); 299 cvShowImage("红眼擦除后", img ); 300 301 cvWaitKey(0); 302 cvWaitKey(0); 303 304 cvReleaseImage( &img ); 305 cvReleaseImage( &original ); 306 cvReleaseImage( &temp ); 307 cvReleaseImage( &HSI_Image ); 308 cvReleaseImage( &RGB_Image ); 309 cvReleaseMat( &HSI_H ); 310 cvReleaseMat( &HSI_S ); 311 cvReleaseMat( &HSI_I ); 312 313 cvDestroyAllWindows(); 314 315 return 0; 316 }
首先鼠标选择照片的眼睛区域,然后程序实现处理。
效果图:
未来的实现:利用人脸检测自动定位到眼睛区域,不用鼠标选择眼睛区域。