本次的实验在实验八的基础上进行整合,删除冗余的函数和操作按钮,修改不必要的跳转函数,优化处理,完成对视频、图像的操作。
本次的实验平台搭建用QT Create 和Opencv 一同完成。在对图片处理的过程中,只调用QT的库函数对图片的像素的Rgb进行处理,在对视频的处理上,调用Opencv 的库函数操作。在本次的实验分成两部分进行:一、对图片进行处理;二、完成对视频的处理。具体要求如下:
2.1、可行性研究
在实验八的基础上可知,Qt调用C/C++库函数可以完成对图像(QImage)的像素点的处理,所有在接下里的二值化等需求也是可行的,在查阅了相关的资料后得知,opencv可以对视频经行切片处理,获得帧,从而对视频经行转帧播放,同时在对帧即图像可以处理,所有视频的灰度化等是可行的。
2.2、需求分析
对图片的处理是对像素点的处理,根据之前的方法知道,没有另外的需求。对视频的处理需要在QT中加入Opencv的库函数,调用 VideoCapture capture 来对视频进行分析和操作。
3.1、基本界面的搭建
注意点在于:
1.对mainToolBar的引入是不可以直接的,因为在工具库中是没有这个工具的,所以在项目外打开 .ui 文件 在文件中加入下列代码实现对mainToolBar 的添加。
Qt::ToolButtonIconOnly
TopToolBarArea
false
2.在之前实践的基础上,将减少不必要的操作,放入工具栏和两个Dock栏(左侧的是对图片的处理,右侧的是对视频的处理),在中间打破centralwidget布局,放入三个label,两个用来显示图片,在打开图片时隐藏一个(originMovieLabel),打开视频是,隐藏另外两个(originLabel、showLabel)。隐藏和显示的代码如下:
ui->originMovieLabel->setVisible(false);
运行效果如下:
3.2、警告窗口
在本次的作用要求中,对提示警告窗进行了详细的制作,在其中加入图片和图标,和主窗口对应,相关代码和运行效果如下:
代码如下:
QMessageBox *msgBox=new QMessageBox();
msgBox->setWindowTitle(tr("关于本软件对话框"));
msgBox->setIconPixmap(QPixmap(":/img/img/warning.png"));
QIcon icon(":/img/img/need_icon.ico");
msgBox->setInformativeText(tr("没有图片"));
msgBox->setStyleSheet("QLabel{"
"min-width: 200px;"
"min-height: 100px; "
"}");
msgBox->setWindowIcon(icon);
msgBox->show();
3.3、图像处理
在对图像处理中梳理其核心为对其与周边像素的处理,在此出对本软件的部分实现算法做一份介绍,如:灰度处理、均值滤波、二值化、边缘检测、伽马处理。
3.3.1、读入文件
调用QFileDialog读入文件,获得文件的地址信息,打开文件。如果打开失败,弹出提醒弹窗,读入成功,在originLabel中显示。
3.3.2、灰度处理
获得originLabel中的图片,对Qpixmap对象转变为QImage对象,获得图像的像素,获得其RGB的值,然后通过灰度公式对其进行计算,得出最后的RGB值,然后重新赋值给这个像素点,将像素点放回到图像从而得到最后的处理图像,显示在showlabel中。(这里用的是加权的计算方法)运行结果(图4-5)和代码如下:
QImage MainWindow::grayscale1(){
QImage *origin =new QImage();
、/*警告模块,参考上述3.2的代码内容*/
*origin =ui->originLabel->pixmap()->toImage();
QImage *newImage = new QImage(origin->width(), origin->height(), QImage::Format_ARGB32);
for(int y = 0; yheight(); y++){
//获取一行像素的首地址
QRgb * line = (QRgb *)origin->scanLine(y);
for(int x = 0; xwidth(); x++){
//提取像素
//灰度转换的一种方法(R G B 求平均值)
int average = (qRed(line[x])*0.299 + qGreen(line[x])*0.587 + qBlue(line[x])*0.114);
newImage->setPixel(x,y, qRgb(average, average, average));
}
}
return *newImage;}
3.3.3、二值化处理
同上,获得图像的像素点,设置阈值,将图像灰度化处理,比较像素点灰度值与阈值的关系,大于设为黑色,即RGB(0,0,0);反正着设为RGB(255,255,255),组合显示。运行效果(图4-6)和代码如下:
QImage MainWindow::bianry1(QImage origin){
/*警告模块,参考上述3.2的代码内容*/
QImage *newImage=new QImage(origin.width(), origin.height(), QImage::Format_ARGB32);
for(int y = 0; yheight(); y++){
//获取一行像素的首地址
QRgb * line = (QRgb *)origin.scanLine(y);
for(int x = 0; xwidth(); x++){
//提取像素
int rgb_r=qRed(line[x]);
if(rgb_r<=int(num)){
newImage->setPixel(x,y, qRgb(255,255,255));
}else{
newImage->setPixel(x,y, qRgb(0,0,0));
}
}
}
return *newImage;}
3.3.4、均值滤波(阈值可调)
同上,先获得像素点,然后根据所提供的阈值将所在的矩形(其为中心)的所有像素的RGB的值都相加,同时统计加入了几个像素点。计算出其平均值后再赋值,找下一个像素点重复上述的操作,以此达到对均值滤波的效果。此处的注意点在于对边界的判定和对像素点个数的判定。代码运行与截图(4-7)如下:(注意:此处代码有误,bug点应该在对便捷判断的地方,两层循环的里面的if ,自行改正啦,不然运行会出黑边)
QImage MainWindow::average1(){
QImage *origin =new QImage();
/*警告模块,参考上述4.2的代码内容*/
*origin =ui->originLabel->pixmap()->toImage();
QImage *newImage = new QImage(origin->width(), origin->height(), QImage::Format_ARGB32);
qDebug()<height(); y++){
//获取一行像素的首地址
QRgb * line = (QRgb *)origin->scanLine(y);
for(int x = 0; xwidth(); x++){
//1.设法提去周边的数组
int sum=0;double rgb_r=0;double rgb_b=0;double rgb_g=0;
for(int y1=y-int(num/2);y1<(y+int(num/2));y1++){
for(int x1=x-int(num/2);x1<(x+int(num/2));x1++){
if(x1<0||y1<0||x1>x||y1>y) {break;}else{
rgb_r=rgb_r+qRed(line[x1]);
rgb_g=rgb_g+qGreen(line[x1]);
rgb_b=rgb_b+qBlue(line[x1]);
sum++;}
}
}
//2.计算平均值//3.放回数值 newImage->setPixel(x,y,qRgb(int(rgb_r/sum),int(rgb_g/sum),int(rgb_b/sum)));
}
}
//4.打印照片
return *newImage;}
3.3.5、边缘检测算法
本算法的实现采用canny算法,其先对图像进行灰度的处理,在进行高斯平滑算法处理,降低其噪点的存在,再计算梯度强度和方向,对图像非最大值进行抑制,双阀值检测与抑制孤立低阈值点,减少早点的存在,使图片的效果更加清晰,最后得出边缘检测的图。本处的代码较多,不做展示,详情看源码文件。运行截图如下:
相关整合代码如下:提供参照文档
QImage MainWindow::By1(QImage image){
QImage *img =new QImage();
*img=image;
QImage img_gray=image;
// QImage img_guass;
//**********高斯模糊开始
int k=5;//高斯滤波器规模
double kernel[10][10];
generic_guess(kernel,k,1.4);
QImage * t_img=new QImage(img_gray.width()+k-1,img_gray.height()+k-1,QImage::Format_ARGB32);
QImage *img_guass=new QImage(img_gray.width(),img_gray.height(),QImage::Format_ARGB32);
//lefttop
for(int i=0;isetPixel(i,j,img_gray.pixel(0,0));
}
}
//righttop
for(int i=0;isetPixel(i,j,img_gray.pixel(img_gray.width()-1,0));
}
}
//rightbottom
for(int i=img_gray.width()+k/2;iwidth();++i){
for(int j=0;jsetPixel(i,j,img_gray.pixel(img_gray.width()-1,img_gray.height()-1));
}
}
//leftbottom
for(int i=0;iheight();++j){
t_img->setPixel(i,j,img_gray.pixel(0,img_gray.height()-1));
}
}
//top
for(int i=0;isetPixel(i+k/2,j,img_gray.pixel(i,0));
}
}
//right
for(int i=t_img->width()-k/2;iwidth();++i){
for(int j=0;jsetPixel(i,j+k/2,img_gray.pixel(img_gray.width()-1,j));
}
}
//bottom
for(int i=0;iheight()-k/2;jheight();++j){
t_img->setPixel(i+k/2,j,img_gray.pixel(i,img_gray.height()-1));
}
}
//left
for(int i=0;isetPixel(i,j+k/2,img_gray.pixel(0,j));
}
}
for(int i=0;isetPixel(i+k/2,j+k/2,img_gray.pixel(i,j));
}
}
for(int i=k/2;iwidth()-k/2;++i){
for(int j=k/2;jheight()-k/2;++j){
double temp=0;
for(int ti=0;tipixel(i-k/2+ti,j-k/2+tj));
}
}
img_guass->setPixel(i-k/2,j-k/2,qRgb(temp,temp,temp));
}
}
//**************高斯模糊结束、end
//**************计算梯度强度和方向开始
double** gradx=new double*[img->width()];
for(int i=0;iwidth();++i)
gradx[i]=new double[img->height()];
double** grady=new double*[img->width()];
for(int i=0;iwidth();++i)
grady[i]=new double[img->height()];
double** grad=new double*[img->width()];
for(int i=0;iwidth();++i)
grad[i]=new double[img->height()];
double** dir=new double*[img->width()];
for(int i=0;iwidth();++i)
dir[i]=new double[img->height()];
int k1=3;//sobel算子规模
double kernelx[3][3]={-1,0,1,-2,0,2,-1,0,1};
double kernely[3][3]={1,2,1,0,0,0,-1,-2,-1};
QImage *t_img1=new QImage(img_gray.width()+k1-1,img_gray.height()+k1-1,QImage::Format_ARGB32);
//lefttop
for(int i=0;isetPixel(i,j,img_gray.pixel(0,0));
}
}
//righttop
for(int i=0;isetPixel(i,j,img_gray.pixel(img_gray.width()-1,0));
}
}
//rightbottom
for(int i=img_gray.width()+k1/2;iwidth();++i){
for(int j=0;jsetPixel(i,j,img_gray.pixel(img_gray.width()-1,img_gray.height()-1));
}
}
//leftbottom
for(int i=0;iheight();++j){
t_img1->setPixel(i,j,img_gray.pixel(0,img_gray.height()-1));
}
}
//top
for(int i=0;isetPixel(i+k1/2,j,img_gray.pixel(i,0));
}
}
//right
for(int i=t_img1->width()-k1/2;iwidth();++i){
for(int j=0;jsetPixel(i,j+k1/2,img_gray.pixel(img_gray.width()-1,j));
}
}
//bottom
for(int i=0;iheight()-k1/2;jheight();++j){
t_img1->setPixel(i+k1/2,j,img_gray.pixel(i,img_gray.height()-1));
}
}
//left
for(int i=0;isetPixel(i,j+k1/2,img_gray.pixel(0,j));
}
}
for(int i=0;isetPixel(i+k1/2,j+k1/2,img_gray.pixel(i,j));
}
}
for(int i=k1/2;iwidth()-k1/2;++i){
for(int j=k1/2;jheight()-k1/2;++j){
double tempx=0;
double tempy=0;
for(int ti=0;tipixel(i-k1/2+ti,j-k1/2+tj));
tempy+=kernely[ti][tj]*qRed(t_img1->pixel(i-k1/2+ti,j-k1/2+tj));
}
}
gradx[i-k1/2][j-k1/2]=tempx;//error
grady[i-k1/2][j-k1/2]=tempy;
grad[i-k1/2][j-k1/2]=sqrt(pow(tempx,2)+pow(tempy,2));
double theta=atan(tempy/tempx)+90;
if (theta >= 0 && theta < 45)
dir[i-k1/2][j-k1/2] = 2;
else if (theta >= 45 && theta < 90)
dir[i-k1/2][j-k1/2] = 3;
else if (theta >= 90 && theta < 135)
dir[i-k1/2][j-k1/2] = 0;
else
dir[i-k1/2][j-k1/2] = 1;
}
}
bool calcu=true;
//***********************计算梯度强度和方向结束
//***********************非极大值抑制开始
if(!calcu){
QMessageBox::critical(this,u8"提示",u8"未计算边缘强度和方向");
}
QColor c1(255,255,255);
QColor c(0,0,0);
QRgb edge = qRgb(c.red(),c.green(),c.blue());
QRgb notedge = qRgb(c1.red(),c1.green(),c1.blue());
QImage *img_nms=new QImage(img_guass->width(),img_guass->height(),QImage::Format_ARGB32);
double temp=0;
for(int i=0;iwidth();++i){
for(int j=0;jheight();++j){
temp+=grad[i][j];
}
}
temp/=img_nms->width()*img_nms->height();
double highthresh =temp;
double lowthresh=highthresh;
double N,NE,E,SW,W,SE,S,NW;
double grad1=0,grad2=0,tantheta=0;
for(int i=1;iwidth()-1;++i){
for(int j=1;jheight();++j){
N=grad[i][j-1];
NE=grad[i+1][j-1];
E=grad[i+1][j];
SW=grad[i-1][j+1];
W=grad[i-1][j];
SE=grad[i+1][j+1];
S=grad[i][j+1];
NW=grad[i-1][j-1];
if(dir[i][j]==0){
tantheta=abs(grady[i][j]/gradx[i][j]);
grad1=E*(1-tantheta)+NE*tantheta;
grad2=W*(1-tantheta)+SW*tantheta;
}
else if(dir[i][j]==1){
tantheta=abs(gradx[i][j]/grady[i][j]);
grad1=N*(1-tantheta)+NE*tantheta;
grad2=S*(1-tantheta)+SW*tantheta;
}
else if(dir[i][j]==2){
tantheta=abs(gradx[i][j]/grady[i][j]);
grad1=N*(1-tantheta)+NW*tantheta;
grad2=S*(1-tantheta)+SE*tantheta;
}
else if(dir[i][j]==3){
tantheta=abs(grady[i][j]/gradx[i][j]);
grad1=W*(1-tantheta)+NW*tantheta;
grad2=E*(1-tantheta)+SE*tantheta;
}
else{
grad1=highthresh;
grad2=highthresh;
}
if(grad[i][j]>grad1&&grad[i][j]>grad2){
img_nms->setPixel(i,j,edge);//black边缘
// gradcp[i][j]=highthresh;
}
else{
img_nms->setPixel(i,j,notedge);//white
grad[i][j]=0;
}
}
}
//************非极大值抑制结束
//************双阈值检测开始
QImage *img_dt=new QImage(img_nms->width(),img_nms->height(),QImage::Format_ARGB32);
double** gradcp=new double*[img->width()];
for(int i=0;iwidth();++i)
gradcp[i]=new double[img->height()];
for(int i=0;iwidth();++i){
for(int j=0;jheight();++j){
if(grad[i][j]>highthresh){//强边缘
gradcp[i][j]=highthresh;
img_dt->setPixel(i,j,edge);
}
else if(grad[i][j]>lowthresh){//弱边缘
gradcp[i][j]=lowthresh;
img_dt->setPixel(i,j,edge);
}
else{//非边缘
gradcp[i][j]=0;
img_dt->setPixel(i,j,notedge);//抑制
}
}
}
//************双阈值检测结束
//************抑制孤立低阈值点开始
QImage *img_st=new QImage(img_dt->width(),img_dt->height(),QImage::Format_ARGB32);
int frac[8][2]={{1,0},{1,1},{0,1},{-1,1},{-1,0},{-1,-1},{0,-1},{1,-1}};
for(int i=1;iwidth()-1;++i){
for(int j=1;jheight()-1;++j){
if(gradcp[i][j]==highthresh){//强边缘
img_st->setPixel(i,j,edge);
}
else if(gradcp[i][j]==lowthresh){//弱边缘
for(int p=0;p<8;++p){
if(gradcp[i+frac[p][0]][j+frac[p][1]]==highthresh){
img_st->setPixel(i,j,edge);//边缘
break;
}
img_st->setPixel(i,j,notedge);//非边缘
}
}
else//非边缘
img_st->setPixel(i,j,notedge);
}
}
//*******最终结果如下
return *img_st;/*}
return *img;*/
}
如各位所见,代码量很大,而且...不是自己纯原创的
参考文档:边缘检测之Canny算法_Qt实现(C++)
3.3.6、伽马转变
伽马转变是在图像处理中,将漂白或曝光过头或不足的照片经行修正,根据伽马变换的公式与其图像可知:gamma值小于1时,会拉伸图像中灰度级较低的区域,同时会压缩灰度级较高的部分;gamma值大于1时,会拉伸图像中灰度级较高的区域,同时会压缩灰度级较低的部分。获得图像的像素后,将其每一个RGB的值经行一次公式的运行后放回,得到相关的图片。运行截图与代码如下:
QImage MainWindow::gamma1(){
QImage *image=new QImage();
/*警告模块,参考上述3.2的代码内容*/
*image=ui->originLabel->pixmap()->toImage();
double d=num;
QColor color;
int height = image->height();
int width = image->width();
for (int i=0;ipixel(i,j));
double r = color.red();
double g = color.green();
double b = color.blue();
int R = qBound(0,(int)qPow(r,d),255);
int G = qBound(0,(int)qPow(g,d),255);
int B = qBound(0,(int)qPow(b,d),255);
image->setPixel(i,j,qRgb(R,G,B));
}
}
return *image;}
4.3.7、保存图片
获得showlabel中的图片信息,调用QPixmap的save()函数,使得可以保存图片,同时读取系统时间,将其作为命名的标题。保存地址为程序编译的地址。相关代码如下:
void MainWindow::save(){
QImage *image =new QImage();
/*警告模块,参考上述3.2的代码内容*/
*image = ui->showLabel->pixmap()->toImage();
QPixmap p =QPixmap::fromImage(*image);
//文件命名系统
QTime current_time =QTime::currentTime();
int hour = current_time.hour();//当前的小时
int minute = current_time.minute();//当前的分
int second = current_time.second();//当前的秒
QString filename=QString::number(hour)+"_"+QString::number(minute)+"_"+QString::number(second)+"_"+QString::number(num)+".jpg";
//保存图片
p.save(filename,Q_NULLPTR,100);
qDebug()<<"ok";}
3.4、视频处理
在本处的视频处理中,引入opencv作为对视频的处理。将视频读入后,获得其每一帧的图像,然后处理
3.4.1、安装OpenCV
在安装过程中注意其的报错,有可能就因为网络问题出现无法下载相关的库文件,在写函数的时候使无法发现的,但在运行的时候才会发现库文件的确实,最容易出错的为:opencv_ffmpeg_64.dll、ffmpeg_version.cmake、opencv_ffmpeg.dll .
相关的参考资料如下:
1.OpenCV安装教程 基于64位win10系统的QT+opencv环境配置_huadianyue的专栏-CSDN博客_opencv qt win10
2.OpenCV Cmake-Configure报错的解决教程:
CMake opencv时Download: opencv_ffmpeg.dll、ippicv等失败的解决方法_楷尘·极客-CSDN博客_opencv_ffmpeg.dll下载
CMake编译OpenCV4.0时opencv_ffmpeg.dll等下载失败的解决思路总结 - 葫芦娃508 - 博客园
3.4.2、对.opr文件的操作
在安装完openCV后,需要在.opr文件中添加以下的信息(地址为你的配置地址):
INCLUDEPATH +=D:/qt/opencv-4.5.4/opencvlib/install/include
INCLUDEPATH +=D:/qt/opencv-4.5.4/opencvlib/install/include/opencv
INCLUDEPATH +=D:/qt/opencv-4.5.4/opencvlib/install/include/opencv2
LIBS +=D:/qt/opencv-4.5.4/opencvlib/install/x64/mingw/lib/libopencv_*.a
3.4.3、读取视频文件
在函数中调用QFileDialog读入文件,获得文件的地址信息,打开视频文件,调用capture.open(video_path.toStdString());打开视频。用capture.isOpened()判断文件是否正确打开。没有提供相关的警告弹窗。打开视频就让前面显示图片的label暂时消失,并且显示originMovieLabel。计算帧率和视频时间。值得注意的是:在本段代码里需要注意的是,opencv用的是String类型的字符串,但QT用的是QString类型的字符串,其不是一样的,需要进行处理和转置,其中可能出现乱码的可能性。相关代码与截图(图4-10)如下:
void MainWindow::openMovie(){
QString video_path = QFileDialog::getOpenFileName(this,tr("选择视频"),"E:/Qt/qtworks/MainWindow/images",tr("Video (*.WMV *.mp4 *.rmvb *.flv)"));
if(video_path!=nullptr){
//打开视频文件:其实就是建立一个VideoCapture结构
capture.open(video_path.toStdString());
//检测是否正常打开:成功打开时,isOpened返回ture
if (!capture.isOpened()){
/*警告模块,参考上述3.2的代码内容*/
//对label的管理
ui->showLabel->setVisible (false);//让他暂时消失
ui->originLabel->setVisible (false);//让他暂时消失
ui->originMovieLabel->setVisible(true);//视频部分显示出来
//获取整个帧数
long totalFrameNumber = capture.get(CAP_PROP_FRAME_COUNT);
//设置开始帧()
long frameToStart = 0;
capture.set(CAP_PROP_POS_FRAMES, frameToStart);
//获取帧率
double rate = capture.get(CAP_PROP_FPS);
delay = 1000 / rate;
timer.start(delay);
type=0;
//timer.start();
isstart=!isstart;
isMovie=1;
isPhoto=0;
isPhoto_r=0;
}
}
4.4.4、视频播放与视频处理
其原理是调用MAT对象的函数,获得视频的帧,对帧进行操作,然后返回继续显示,在不断的重复中输出处理后的视频图像。本次实验实验对视频的二值化处理、灰度处理、边缘检测处理、马赛克、缩放的处理。
调用了opencv的自带函数threshold(frame, frame,too, 255, THRESH_BINARY);将Mat对象处理返回,然后转化图像显示。相关运行截图(图4-11)如下。
对视频的灰度处理也是调用系统函数cvtColor(frame,frame,CV_BGR2GRAY);,不然其无法平滑播放。相关运行截图如下。
边缘检测调用前面对图像的处理,使其完成边缘检测,但因为要循环的数据过大,使其无法平稳的播放。相关运行截图如下。
与上述的均值有些相似,划定一个大小的矩阵,遍历矩阵中的所有像素,将矩阵最开始的像素的值赋值给所有的点,然后输入。相关截图如下。
马赛克的相关代码
Mat MainWindow::masaike(Mat src){
int width = src.rows; //图片的长度
int height = src.cols; //图片的宽度
if(scale==NULL){scale=10;}
int arr = scale;//试图调节
//i和j代表了矩形区域的左上角的像素坐标
for (int i = 0; i < width; i+=arr) {//int i = width/2.5; i < width/1.5; i+=arr
for (int j = 0; j < height; j+=arr) {//int j = height/2.5; j < height/1.5; j+=arr
//对矩形区域内的每一个像素值进行遍历
for (int k = i; k < arr + i && k < width; k++) {
for (int m = j; m < arr + j && m < height; m++) {
//在这里进行颜色的修改
//at(k, m)[0]=qRed();取出像素点中的rgb三数值,不过这里是对数组的形式
src.at(k, m)[0] = src.at(i, j)[0];//B
src.at(k, m)[1] = src.at(i, j)[1];//G
src.at(k, m)[2] = src.at(i, j)[2];//R
}
}
}
}
return src;
}
最后放上参考文档:使用QT5+Opencv完成简单的图像处理及视频处理软件_每天进步一点点!-CSDN博客_opencv图像处理软件