有个项目需要将某GigE摄像机的图像在win10端进行展示,并可以获取该图像上指定点的灰度值,并将该数值通过USB-RS232串口通信发给某下位机设备。
由于GigE设备图像获取是基于厂商有限的SDK资料,无法做到“显示图像以及获取任意像素的灰度值”这项需求,由于种种原因吧,也不太好在Qtc端软件直接包含该sdk,并直接显示图像(主要还是任务时间紧,没时间仔细研究SDK源码),故我考虑通过QUdpSocket
进行图像数据传输。
实现思路:
(1)首先通过VS2017编译GigE厂商给的sdk,获取到摄像头的图像数组数据,在此处用QUdpsocket
将图像发送给外部端口;
(2)然后在另外一个由Qtc编译的软件中进行图像相关显示、编辑操作,并实现像素点选择、灰度值获取等功能;
(3)最后实现PC端Qtc软件与单片机下位机的通信工作。
用QUdpSocket
做图像通信,经过尝试发现通过下述语句
QUdpSocket udpSocket;
udpSocket.writeDatagram(data, dstip, dstport);
进行图像传输时,受限于udp通信理论限制,data的大小需要小于64kB,所以压缩后的图像数据最好小于此数值,如果需要传输大图像,Client端需要对图像数据进行分包,而Server端需要对图像进行组包,代码较为复杂,故本次实验中没有涉及到。
①国内某中字头厂商的GigE工业摄像机连接DC-5V电源,网线连接PC机的GigE端口;
②PC机通过USB-RS232串口连接89C52单片机开发板。
某中字头国企的GigE相机,其实内部板卡、CMOS等部件全部是外企货,包括软件SDK等,真正的国产就外面的铝合金外壳而已,本文这里隐去摄像头腔体上方的信息,以避免不必要的麻烦;
该软件通过VS2017编译
while ( !PvKbHit() )
{
PvBuffer *lBuffer = NULL;
PvResult lOperationResult;
// Retrieve next buffer
PvResult lResult = aStream->RetrieveBuffer( &lBuffer, &lOperationResult, 1000 );
if ( lResult.IsOK() )
{
if ( lOperationResult.IsOK() )
{
PvPayloadType lType;
//
// We now have a valid buffer. This is where you would typically process the buffer.
// -----------------------------------------------------------------------------------------
// ...
int size = lBuffer->GetAcquiredSize();
cout << "lBuffer->GetAcquiredSize()" << size << endl;
int num = size / 2;
uint8_t *data = new uint8_t[num];
uint16_t *buffer = new uint16_t[num];
memcpy(buffer, lBuffer->GetDataPointer(), size);
for (int i = 0; i < num; i++)
{
data[i] = buffer[i] >> 4;
}
QImage img = QImage((uchar *)data, 2048, 2048, QImage::Format_Grayscale8);
QImage img_scaled = img.scaled(800, 800, Qt::IgnoreAspectRatio);
cout << "img_scaled所占内存大小为: " << img_scaled.sizeInBytes() << " 字节" << endl;
QUdpSocket udpSocket;
QHostAddress dstip = QHostAddress("127.0.0.1");
quint16 dstport = 5555;
udpSocket.bind(dstip, dstport);
QByteArray byte;
QBuffer buff(&byte);
buff.open(QIODevice::WriteOnly);
img_scaled.save(&buff, "JPEG");
QByteArray ss = qCompress(byte, 5);
cout << "发送的ss占的内存大小为: " << ss.size() << " 字节" << endl;
udpSocket.writeDatagram(ss, dstip, dstport);
std::cout << "发送一帧完成!" << std::endl;
lFrameRate->GetValue( lFrameRateVal );
lBandwidth->GetValue( lBandwidthVal );
// If the buffer contains an image, display width and height.
uint32_t lWidth = 0, lHeight = 0;
lType = lBuffer->GetPayloadType();
cout << fixed << setprecision( 1 );
cout << lDoodle[ lDoodleIndex ];
cout << " BlockID: " << uppercase << hex << setfill( '0' ) << setw( 16 ) << lBuffer->GetBlockID();
if ( lType == PvPayloadTypeImage )
{
// Get image specific buffer interface.
PvImage *lImage = lBuffer->GetImage();
// Read width, height.
lWidth = lBuffer->GetImage()->GetWidth();
lHeight = lBuffer->GetImage()->GetHeight();
cout << " W: " << dec << lWidth << " H: " << lHeight;
cout << "第" << i << "帧" << endl;
i++;
}
else
{
cout << " (buffer does not contain image)";
}
cout << " " << lFrameRateVal << " FPS " << ( lBandwidthVal / 1000000.0 ) << " Mb/s \r";
delete data;
delete buffer;
}
else
{
// Non OK operational result
cout << lDoodle[ lDoodleIndex ] << " " << lOperationResult.GetCodeString().GetAscii() << "\r";
}
// Re-queue the buffer in the stream object
aStream->QueueBuffer( lBuffer );
}
else
{
// Retrieve buffer failure
cout << lDoodle[ lDoodleIndex ] << " " << lResult.GetCodeString().GetAscii() << "\r";
}
++lDoodleIndex %= 6;
}
关键语句分析
uint8_t *data = new uint8_t[num];
uint16_t *buffer = new uint16_t[num];
memcpy(buffer, lBuffer->GetDataPointer(), size);
for (int i = 0; i < num; i++)
{
data[i] = buffer[i] >> 4;
}
上面这段,是将从GigE摄像头内获取的uint_16型图像数组数据,转换为uint_8型,由于原始数据的存储方式为:单通道灰度图像12位数据,该12位数据占了一个“双字节”数据的低12位,而QImage显示单通道图像时一般采用了8位的形式,因此将uint_16数据右移4位,并将其低8位直接赋值给uint_8型数据,实现了赋值操作。
memcpy(buffer, lBuffer->GetDataPointer(), size);
通过此句将记录图像的数据搞出来;
QImage img = QImage((uchar *)data, 2048, 2048, QImage::Format_Grayscale8);
通过此句将构造一个QImage类型数据,该QImage通过上面说的uint_8型数组直接构造,后面三个参数分别为图像的宽度、高度、以及其形式,形式这里应该选QImage::Format_Grayscale8
;
QUdpSocket udpSocket;
QHostAddress dstip = QHostAddress("127.0.0.1");
quint16 dstport = 5555;
udpSocket.bind(dstip, dstport);
QByteArray byte;
QBuffer buff(&byte);
buff.open(QIODevice::WriteOnly);
img_scaled.save(&buff, "JPEG");
QByteArray ss = qCompress(byte, 5);
上面这些是做Udp通信相关的代码,端口号和ip地址主要要和Server端代码相互对应。
有些SDK拿到手的时候要注意它的编译平台,是x86还是x64,是debug还是release,本次实验该公司源码是基于x86的release编译的,选debug编译会发现缺少许多dll文件;
在该项目下添加Qt相关库,需要与该平台编译平台一致,本项目需要x86版本下的Qt模块因此需要选择相应的lib、dll文件;
当出现“未加载xx pdb文件”时,需要将Qt相应库的dll文件与编译生成的.exe文件放到同一目录下。
该软件通过Qtcreator ,Qt版本号5.12.12编译
下图为展示图像的软件,通过Qtc制作,左边的图像为从GigE相机获取的图像的展示,右边的两个输入框分别输入所选择像素点的坐标位置,以及该点的灰度值数据。
这里仅展示widget.cpp里一些核心逻辑代码,全部代码请至本文末尾下载链接处下载。
void Widget::video_receive_show()
{
qDebug()<<"in video_receive_show!";
quint64 size = receiver->pendingDatagramSize();
QByteArray buff;
buff.resize(size);
QHostAddress adrr;
quint16 port;
receiver->readDatagram(buff.data(),buff.size(),&adrr,&port);
buff = qUncompress(buff);
QBuffer buffer(&buff);
QImageReader reader(&buffer,"JPEG");//可读入磁盘文件、设备文件中的图像、以及其他图像数据如pixmap和image,相比较更加专业。
//buffer属于设备文件一类,
QImage image = reader.read();//read()方法用来读取设备图像,也可读取视频,读取成功返回QImage*,否则返回NULL
QPoint pix_point = QPoint(ui->textEdit->toPlainText().toInt(), ui->textEdit_2->toPlainText().toInt());
QPoint pix_point_2 = QPoint(ui->textEdit->toPlainText().toInt()+ 5, ui->textEdit_2->toPlainText().toInt() - 5);
QRgb mRgb = image.pixel(pix_point);
QColor mColor = QColor(mRgb);
int red_value = mColor.red();
int bright_scale = int(red_value * 100 / 256);
QPainter p(&image);
QPen pen;
pen.setWidth(3);
pen.setColor(Qt::white);
p.setPen(pen);
p.drawPoint(pix_point);
p.drawText(pix_point_2, QString::fromLocal8Bit("参考点"));
QImage img_re = image.scaled(ui->label->width(), ui->label->height());
ui->label->setPixmap(QPixmap::fromImage(img_re));
ui->label_5->setText(QString::number(red_value));
send_data();
qDebug()<<"bright_scale"<<bright_scale;
// brightness->SetBrightness(bright_scale); //设置屏幕亮度(仅在笔记本上有效)
}
下面这段为从ui
界面中的“显示灰度值的QLabel
”内获取当前像素点的数据,然后将该数据转换为可通过串口传输的QByteArray
类型的数据,然后通过串口传输。
void Widget::send_data()
{
uint8_t data_10 = ui->label_5->text().toInt();
qDebug() << "data_10" << data_10;
QString s = QString::number(data_10, 16);
if(s.length() == 1)
{
s = '0' + s;
}
qDebug() << "s" <<s;
QByteArray ba = QString2Hex(s);
qDebug() << "ba" <<ba;
m_serialPort->write(ba);
}
这里有个Brightness
类可用于c++代码调节笔记本屏幕的亮度情况,本软件有个功能“根据特定像素点的灰度值调节当前屏幕的亮暗程度”,可以通过这个Brightness实例来实现,相关代码也是当的网上的。
要修改屏幕亮度,必须将该软件放置于笔记本的桌面上进行显示,无法修改外接显示屏的亮度。
单片机软件相关说明见我此篇博客https://blog.csdn.net/wang_chao118/article/details/129384041?spm=1001.2014.3001.5502
本项目下所有代码请至以下连接下载。
https://download.csdn.net/download/wang_chao118/87547805?spm=1001.2014.3001.5503