我的应用大体功能是这样的,使用opencv的接口,从摄像头实时读取color数据显示,并完全保存所有视频到硬盘,已经点击按钮之后将当前的数据帧保存为图片存储到硬盘。
第一个错误:
第二个错误:
void FaceCapture::showColor(cv::Mat colorFrame)
{
//colorframeMutex.lock();
m_colorFrame = colorFrame;
if (g_colorWriter.isOpened())
{
g_colorWriter << colorFrame;
}
//colorframeMutex.unlock();
//cv::resize(colorFrame, frameForShow, dsize );
int img_width = colorFrame.cols; // 在这行发生如上错误,上面两个错误都有可能发生。
int img_height = colorFrame.rows;
int channelnum = colorFrame.channels();
if (NULL == g_prgbdatabuff)
{
g_prgbdatabuff = (uchar *)malloc(img_width * img_height * channelnum);
}
uchar* colorData = colorFrame.data;
bool isColorImgTwoDark = true;
int MaxValueCount = 0;
for (int i = 0; i < img_width * img_height; i++)
{
int rgbaOffset = i * channelnum;
g_prgbdatabuff[rgbaOffset] = colorData[rgbaOffset + 2];
g_prgbdatabuff[rgbaOffset + 1] = colorData[rgbaOffset +1];
g_prgbdatabuff[rgbaOffset + 2] = colorData[rgbaOffset];
if ((255 == colorData[rgbaOffset + 2])
&& (255 == colorData[rgbaOffset + 1])
&& (255 == colorData[rgbaOffset]))
{
MaxValueCount++;
isColorImgTwoDark = false;
}
if ((IMG_DARKNESS_THRESHOLD <= colorData[rgbaOffset + 2])
|| (IMG_DARKNESS_THRESHOLD <= colorData[rgbaOffset + 1])
|| (IMG_DARKNESS_THRESHOLD <= colorData[rgbaOffset]))
{
isColorImgTwoDark = false;
}
}
QImage img(g_prgbdatabuff, img_width, img_height, QImage::Format_RGB888);
QPixmap pixmap = QPixmap::fromImage(img);
QPixmap scaredPixmap = pixmap.scaled(ui.label_color->width(), ui.label_color->height());
ui.label_color->setPixmap(scaredPixmap);
if (IMG_COLOR_OVER_EXPOSURE_PIXEL_COUNT <= MaxValueCount)
{
//ui.label_color_quality->setStyleSheet("color: rgb(255,0,0);");
ui.label_color_quality->setText(QString::fromLocal8Bit("Color图像过度曝光"));
}
else if (true == isColorImgTwoDark)
{
//ui.label_color_quality->setStyleSheet("color: rgb(255,0,0);");
ui.label_color_quality->setText(QString::fromLocal8Bit("Color图像过暗"));
}
else
{
//ui.label_color_quality->setStyleSheet("color: rgb(0,0,0);");
ui.label_color_quality->setText(QString::fromLocal8Bit("Color图像正常"));
}
//free(data);
}
遇到这个问题,非常头大,网上的说法也乱七八糟。基本找不到一个可行的解决方案。而且这个问题也不是每一个数据帧都出现,不断的测试,突然什么时候就崩出来。
针对这个问题,第一反应是colorFrame 的Mat结构被破坏了。然后开始找各种关于Mat的信息。首先寻找判断判断Mat是否有效的两个函数:empty(如果Mat的data区域没有数据,或者尚未开辟空间,则返回false。),另一个是continuous(用来判断Mat中的数据是否是连续内存存的);加入如上两个判断之后发现虽然Mat比较大,1280*800的分辨率存储color数据,但是这两个判断基本上也都能通过,最后了解到像Mat结构确实比较特殊,Mat的赋值运算= 不会开辟新的空间,只会增加引用计数,于是找到可以使用clone或者copyto来进行深copy,多次尝试之后依然无法解决问题。另外了解到Mat 作为参数时,及时是直接传值其实也是传指针。搞过来搞过去,还是无法解决问题。
调用上面showColor函数的调用函数是如下的线程函数:
void ColorThread(VideoCapture cap)
{
cv::Mat input_image;
Rect faceRect;
clock_t currentTime;
bool isReadOk;
while (g_running)
{
isReadOk = cap.read(input_image);
if (false == isReadOk)
{
continue;
}
if (input_image.empty())
{
continue;
}
//这里拿到彩色图之后对图像进行人脸检测,检测成功后可以根据一定的条件设置ROI曝光
detectFace(input_image, faceRect);
currentTime = clock();
if (currentTime - LastSetRoiTime > (1 * CLOCKS_PER_SEC))
{
if (faceRect.width > 50)
{
if (!CAEParamSetting::setAEROIArea(faceRect))
{
logUtil.error("WRITER BUFFER ERROR");
}
}
LastSetRoiTime = currentTime;
}
g_io_mutex.lock();
g_w->showColor(input_image);
g_io_mutex.unlock();
}
}
根据上面的函数我又猜想是不是g_io_mutex使用lock不合适,showColor发生异常导致锁无法释放,于是改用lock_guard来包装g_io_mutex。这个方法也不凑效。
于是开始使用懒人定位法,一行一行注释掉看到底是哪一行发生问题。结果把g_w->showColor(input_image);注释掉后没发生问题。于是就盯着这个函数。然后进入这个函数,再一行一行的注释看是否能发生问题。最后发现,把如下保持视频的代码注释掉之后:
if (g_colorWriter.isOpened())
{
g_colorWriter << colorFrame;
}
这个问题很难出现。于是想到是不是得给主线程中操作g_colorWriter的地方加锁,g_colorWriter在应用中也会进行多次open及release。保证互斥访问。加锁之后果然问题解决。
应用中还有一个保存m_colorFrame图片到硬盘的imwrite操作,再刚才的调试过程中是注释掉的,现在放开之后,会再imwrite保存图片的地方也出现上述问题。于是在imwrite的地方加了同一把锁g_io_mutex。在这里加锁之后,问题果然解决。
到此,其实问题的原因已经比较清楚了,这是一个多线程访问共享数据,导致访问数据冲突出现的问题。
但是遇到另外一个问题。界面上的操作变得非常卡,于是想到应该是g_io_mutex锁保护的关键区域太多导致。将不必要的操作放到锁的外面,缩小关键区域之后,界面也不卡了。问题顺利解决。