这里讨论了以下几个主题:
- 访问像素的几种方法
- 图像融合
- copyMakeBorder函数
- Sobel、Laplace、Canny边缘提取
- 绘制图像的直方图
- 反向投影
- DFT变换
- DCT变换
- OpenCV内置的XML,YAML文件访问机制
首先打印一些Mat的信息吧~ :)
void ShowImageInfo()
{
cout << image.step << endl
<< image.rows << " * " << image.cols << endl
<< image.size().height << " * " << image.size().width << endl
<< image.elemSize() << endl
<< image.channels() << endl
<< image.total() << endl
<< boolalpha << image.isContinuous() << endl;
}
访问像素的几种方法
- SimplyAccess使用at成员函数来进行像素访问,简单但是不好用,因为速度很慢!
- IteratorAccess适合像素便利,但速度也很慢!
- FastestPointAccess才是真正实用的像素访问方法,尤其是当continuous时把行设置为1,列设置为rows*cols的算法,这样非常高效且好用,应该只使用这一种像素访问方式。
void SimplyAccess()
{
for (int i = 0; i < image.rows; ++i)
{
for (int j = 0; j < image.cols; ++j)
{
if (rand() % 5 == 0)
{
image.at<Vec3b>(i, j) = Vec3b(255, 255, 255);
}
}
}
imshow("img", image);
Mat_<Vec3b> im2 = image;
for (int i = 0; i < image.rows; ++i)
{
for (int j = 0; j < image.cols; ++j)
{
if (rand() % 5 == 0)
{
im2(i, j) = Vec3b(0, 0, 0);
}
}
}
imshow("im2", im2);
waitKey();
}
void IteratorAccess()
{
auto it = image.begin<Vec3b>();
auto iter_end = image.end<Vec3b>();
for (; it != iter_end; ++it)
{
if (rand() % 3 == 0)
{
(*it) = Vec3b(255, 255, 255);
}
}
imshow("image", image);
waitKey();
}
void FastestPointAccess()
{
int div = 64;
int nl = image.rows; // number of lines
int nc = image.cols * image.channels();
// 如果是continuous,就可以把图片视为1*(rows*cols)的单行图片,这种方法太帅了!
// 对于Continuous的图片,后面就只需要进行一次ptr函数取地址就可以了,剩下的全是指针运算,太高效了!
if (image.isContinuous())
{
// then no padded pixels
nc = nc * nl;
nl = 1; // it is now a 1D array
}
// this loop is executed only once
// in case of continuous images
for (int j = 0; j < nl; j++)
{
uchar* data = image.ptr<uchar>(j);
for (int i = 0; i < nc; i++)
{
// process each pixel ---------------------
data[i] = data[i] / div * div + div / 2;
// end of pixel processing ----------------
}
}
imshow("image", image);
waitKey();
}
图像融合
图像融合是指把两幅图像渐近的融合在一起,不用多废话,聪明的你一看就一切明白了。
代码如下:
int main( int argc, char** argv )
{
cv::Mat image = cv::imread("/home/chuanqi/ImageDataset/wy.jpg");
cv::Mat image2 = cv::imread("/home/chuanqi/ImageDataset/111.jpg");
cv::resize(image2, image2, image.size());
cv::Mat result;
cv::addWeighted(image, 0.5, image2, 0.5, 0., result);
imshow("result", result);
waitKey();
return 0;
}
效果如下:
copyMakeBorder
copyMakeBorder是个很常用的函数,用来给图像添加边框添,加边框的操作在很多的图像处理中都需要用到。使用下面的代码来进行copyMakeBorder函数的测试:
int main() {
Mat src = imread("/home/chuanqi/ImageDataset/wy.jpg");
imshow("src", src);
Mat dst1, dst2;
int const k_border_size = 20;
copyMakeBorder(src, dst1,
k_border_size, k_border_size,
k_border_size, k_border_size,
cv::BORDER_CONSTANT, Scalar(0, 0, 255));
copyMakeBorder(src, dst2,
k_border_size, k_border_size,
k_border_size, k_border_size,
cv::BORDER_REPLICATE);
imshow("dst1", dst1);
imshow("dst2", dst2);
waitKey();
return 0;
}
效果如下图所示:
Sobel、Laplace、Canny边缘提取
采用的测试代码如下:
int main() {
Mat src = imread("/home/chuanqi/ImageDataset/wy.jpg");
if( !src.data )
{ return -1; }
imshow("src", src);
char const *window_name = "Sobel Demo - Simple Edge Detector";
namedWindow( window_name, CV_WINDOW_AUTOSIZE );
int scale = 1;
int delta = 0;
int ddepth = CV_16S;
GaussianBlur( src, src, Size(3,3), 0, 0, BORDER_DEFAULT );
/// 转换为灰度图
Mat src_gray;
cvtColor( src, src_gray, CV_RGB2GRAY );
/// 创建 grad_x 和 grad_y 矩阵
Mat grad_x, grad_y;
/// 求 X方向梯度
//Scharr( src_gray, grad_x, ddepth, 1, 0, scale, delta, BORDER_DEFAULT );
Sobel( src_gray, grad_x, ddepth, 1, 0, 3, scale, delta, BORDER_DEFAULT );
convertScaleAbs( grad_x, grad_x );
/// 求Y方向梯度
//Scharr( src_gray, grad_y, ddepth, 0, 1, scale, delta, BORDER_DEFAULT );
Sobel( src_gray, grad_y, ddepth, 0, 1, 3, scale, delta, BORDER_DEFAULT );
convertScaleAbs( grad_y, grad_y );
/// 合并梯度(近似)
Mat grad;
addWeighted( grad_x, 0.5, grad_y, 0.5, 0, grad );
imshow( window_name, grad );
/// 同时计算x,y方向的Sobel
Mat grad_xy;
Sobel(src_gray, grad_xy, ddepth, 1, 1, 3, scale, delta, BORDER_DEFAULT);
convertScaleAbs(grad_xy, grad_xy);
grad_xy *= 10;
imshow("grad_xy", grad_xy);
/// 计算Laplace算子的结果
Mat laplace_dst;
Laplacian(src, laplace_dst, CV_16S, 3);
convertScaleAbs(laplace_dst, laplace_dst);
imshow("laplace_dst", laplace_dst);
/// 计算Canny的结果
Mat canny_dst;
Canny(src, canny_dst, 100, 200);
imshow("canny_dst", canny_dst);
waitKey(0);
return 0;
}
效果如下图所示:
绘制图像的直方图
画一幅3通道的图像的直方图,先split为3个通道,然后对各个通道进行直方图计算,并以不同的颜色画出来
Mat GetHistogramImage(Mat const &src) {
if (src.empty()) {
return Mat();
}
/// 分割成3个单通道图像 ( R, G 和 B )
vector<Mat> rgb_planes;
split( src, rgb_planes );
/// 设定bin数目
int histSize = 255;
/// 设定取值范围 ( R,G,B) )
float range[] = { 0, 255 } ;
const float* histRange = { range };
bool uniform = true; bool accumulate = false;
Mat r_hist, g_hist, b_hist;
/// 计算直方图:
calcHist( &rgb_planes[0], 1, 0, Mat(), r_hist, 1, &histSize, &histRange, uniform, accumulate );
calcHist( &rgb_planes[1], 1, 0, Mat(), g_hist, 1, &histSize, &histRange, uniform, accumulate );
calcHist( &rgb_planes[2], 1, 0, Mat(), b_hist, 1, &histSize, &histRange, uniform, accumulate );
// 创建直方图画布
int hist_w = 255; int hist_h = 255;
int bin_w = cvRound( (double) hist_w/histSize );
Mat histImage( hist_w, hist_h, CV_8UC3, Scalar( 0,0,0) );
/// 将直方图归一化到范围 [ 0, histImage.rows ]
normalize(r_hist, r_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );
normalize(g_hist, g_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );
normalize(b_hist, b_hist, 0, histImage.rows, NORM_MINMAX, -1, Mat() );
/// 在直方图画布上画出直方图
for( int i = 1; i < histSize; i++ ) {
line( histImage, Point( bin_w*(i-1), hist_h - cvRound(r_hist.at<float>(i-1)) ) ,
Point( bin_w*(i), hist_h - cvRound(r_hist.at<float>(i)) ),
Scalar( 0, 0, 255), 2, 8, 0 );
line( histImage, Point( bin_w*(i-1), hist_h - cvRound(g_hist.at<float>(i-1)) ) ,
Point( bin_w*(i), hist_h - cvRound(g_hist.at<float>(i)) ),
Scalar( 0, 255, 0), 2, 8, 0 );
line( histImage, Point( bin_w*(i-1), hist_h - cvRound(b_hist.at<float>(i-1)) ) ,
Point( bin_w*(i), hist_h - cvRound(b_hist.at<float>(i)) ),
Scalar( 255, 0, 0), 2, 8, 0 );
}
return histImage;
}
int main() {
Mat src = imread("/home/chuanqi/ImageDataset/wy.jpg");
/// 显示直方图
namedWindow("calcHist Demo", CV_WINDOW_AUTOSIZE );
imshow("calcHist Demo", GetHistogramImage(src));
waitKey(0);
return 0;
}
效果如下图所示:
反向投影
反向投影其实就是做了一个Look Up Table查找而已,简要描述如下:
1:首先要提供一个目标直方图和一个测试图像;
2:对测试图像中的每一个像素,去目标直方图中查找该像素的值所对应的bin的值;
3:这个值就是最后的结果图像中的灰度值,非常简单易懂!
测试代码如下:
int main( int argc, char** argv )
{
Mat src = imread("/home/chuanqi/ImageDataset/wy.jpg", 0);
/// 计算直方图并归一化
MatND hist;
int histSize = 100;
float hue_range[] = { 0, 180 };
const float* ranges = { hue_range };
calcHist( &src, 1, 0, Mat(), hist, 1, &histSize, &ranges, true, false );
normalize( hist, hist, 0, 255, NORM_MINMAX, -1, Mat() );
/// 计算反向投影
MatND backproj;
calcBackProject( &src, 1, 0, hist, backproj, &ranges, 1, true );
imshow( "BackProj", backproj );
waitKey(0);
return 0;
}
效果图如下:
DFT变换:
首先进行DFT变换,再进行IDFT变换回原来的图像;以及,只取DFT变换结果的前16个系数,就可以大致的表示图像的大体轮廓了。
首先看代码:
int main( int argc, char** argv )
{
struct {
void operator()(Mat &src, Mat& dst){
Mat tmp;
int cx = src.cols/2;
int cy = src.rows/2;
for(int i=0; i<=cx; i+=cx) {
Mat qs(src, Rect(i^cx,0,cx,cy));
Mat qd(dst, Rect(i,cy,cx,cy));
qs.copyTo(tmp);
qd.copyTo(qs);
tmp.copyTo(qd);
}
}
} shift_dft;
cv::Mat src_img = cv::imread("/home/chuanqi/ImageDataset/wy.jpg", 0);
imshow("src", src_img);
Size s_size = src_img.size();
int src_cols = s_size.width;
int src_rows = s_size.height;
Mat Re_img = Mat(s_size, CV_64F);
Mat Im_img = Mat::zeros(s_size, CV_64F);
Mat Complex_img = Mat(s_size, CV_64FC2);
src_img.convertTo(Re_img, CV_64F);
vector<Mat> mv;
mv.push_back(Re_img);
mv.push_back(Im_img);
merge(mv, Complex_img);
Mat zero;
int dft_rows = getOptimalDFTSize(src_rows);
int dft_cols = getOptimalDFTSize(src_cols);
Mat dft_src = Mat::zeros(dft_rows, dft_cols, CV_64FC2);
Mat roi(dft_src, Rect(0, 0, src_cols, src_rows));
Complex_img.copyTo(roi);
Mat dft_dst;
dft(dft_src, dft_dst);
//split(dft_dst.mul(dft_dst), mv);
//sqrt(mv[0]+mv[1], mv[0]);
split(dft_dst, mv);
magnitude(mv[0], mv[1], mv[0]);
log(mv[0]+1, mv[0]); // for ver. 2.1 or later
double dft_coefs[64];
for (int i = 0; i < 8; ++i) {
for (int j = 0; j < 8; ++j) {
dft_coefs[i*8+j]=mv[0].at<double>(i,j);
}
}
shift_dft(mv[0], mv[0]);
Mat mag_img;
normalize(mv[0], mag_img, 0, 1, CV_MINMAX);
imshow("dft", mag_img);
double min, max;
idft(dft_dst, dft_src);
split(dft_src, mv);
minMaxLoc(mv[0], &min, &max);
Mat idft_img1 = Mat(mv[0]*1.0/max, Rect(0, 0, src_cols, src_rows));
imshow("idft1", idft_img1);
cv::Point center(dft_dst.rows / 2, dft_dst.cols / 2);
for (int i = 0; i < dft_dst.rows; ++i) {
for (int j = 0; j < dft_dst.cols; ++j) {
if (i>4 && j>4) {
dft_dst.at<Vec2d>(i, j) = Vec2d(0, 0);
}
}
}
idft(dft_dst, dft_src);
split(dft_src, mv);
minMaxLoc(mv[0], &min, &max);
Mat idft_img2 = Mat(mv[0]*1.0/max, Rect(0, 0, src_cols, src_rows));
imshow("idft2", idft_img2);
waitKey();
return 0;
}
效果如下图所示:
DCT变换
与DFT类似的常用变换还有DCT变换,与DFT变换非常类似,但是更简单一些,我更喜欢用:)
代码如下:
int main( int argc, char** argv )
{
cv::Mat img = cv::imread("/home/chuanqi/ImageDataset/wy.jpg", 0);
imshow("src", img);
cv::Mat src = cv::Mat_<double>(img);
cv::Size even_size(src.cols % 2 == 0 ? src.cols : src.cols - 1, src.rows % 2 == 0 ? src.rows : src.rows - 1);
cv::resize(src, src, even_size);
cv::Mat dst;
cv::dct(src, dst);
cv::imshow("dst", dst);
cv::Mat dsti1;
cv::idct(dst, dsti1);
cv::Mat idst1 = cv::Mat_<uchar>(dsti1);
cv::imshow("idst1", idst1);
for (int i = 0; i < dst.rows; ++i) {
for (int j = 0; j < dst.cols; ++j) {
if (i > dst.rows / 4 || j > dst.cols / 4) {
dst.at<double>(i, j) = 0;
}
}
}
cv::Mat dsti2;
cv::idct(dst, dsti2);
cv::Mat idst2 = cv::Mat_<uchar>(dsti2);
cv::imshow("idst2", idst2);
cv::waitKey();
return 0;
}
效果如下图所示:
OpenCV内置的XML,YAML文件访问机制
OpenCV中内置的XML、YAML文件访问机制非常非常强大啊,我觉得对于一般的应用已经没有必要再去单独使用特别的XML库了,内置的这个API太好用了。
支持sequence和map,感觉风格有点像python。废话不多说,继续看代码:
void XmlYamlTest(string const &path) {
FileStorage fs(path, FileStorage::WRITE);
fs << "iterationNr" << 100;
fs << "strings" << "["; // text - string sequence
fs << "image1.jpg" << "Awesomeness" << "baboon.jpg";
fs << "]"; // close sequence
fs << "Mapping"; // text - mapping
fs << "{" << "One" << 1;
fs << "Two" << 2 << "}";
Mat R(5, 6, CV_8UC1);
fs << "R" << R; // cv::Mat
fs.release(); // explicit close
}
int main(int argc, char* const argv[])
{
XmlYamlTest("/home/chuanqi/1.txt");
XmlYamlTest("/home/chuanqi/1.xml");
cout << "Write Done." << endl;
return 0;
}
生成的两个文件如下所示,可以看出,OpenCV根据后缀名来决定使用XML还是YAML格式,对于XML后缀的就是XML格式,其它的情况都默认使用YAML:
========== 1.txt ==========
%YAML:1.0
iterationNr: 100
strings:
- "image1.jpg"
- Awesomeness
- "baboon.jpg"
Mapping:
One: 1
Two: 2
R: !!opencv-matrix
rows: 5
cols: 6
dt: u
data: [ 255, 255, 255, 255, 0, 0, 0, 0, 184, 195, 138, 1, 0, 0, 0, 0,
88, 227, 138, 1, 0, 0, 0, 0, 0, 0, 255, 66, 0, 0 ]========== 1.xml ==========
<?xml version="1.0"?>
<opencv_storage>
<iterationNr>100</iterationNr>
<strings>
image1.jpg Awesomeness baboon.jpg</strings>
<Mapping>
<One>1</One>
<Two>2</Two></Mapping>
<R type_id="opencv-matrix">
<rows>5</rows>
<cols>6</cols>
<dt>u</dt>
<data>
255 255 255 255 0 0 0 0 184 195 138 1 0 0 0 0 88 227 138 1 0 0 0 0 0
0 255 66 0 0</data></R>
</opencv_storage>